:root{
  /* ==========================================================================
     v2.0 色温统一（2026-11-03）
     改版前顶栏是米橙暖色、内容区是冷灰蓝 —— 一个界面两套色温，
     这是"看着别扭但说不出哪儿"最直接的原因。现在全站转暖：
     底色从 #f6f7f9（冷灰）换成 #faf8f5（暖白），边框从冷灰换成米灰，
     主色从钢蓝 var(--primary) 换成陶土 #b8501f。

     为什么主色选 #b8501f：它和白字的对比是 4.99:1（过 AA），
     而顶栏现成的 #e89a6b 只有 2.3:1 —— 那个只能做装饰，不能做按钮底色。
     ========================================================================== */
  --bg:#faf8f5;          /* 页面底：暖白，不是灰白 */
  --bg-2:#f5f0ea;        /* 次级底：表头、内嵌块、hover */
  --panel:#ffffff;
  --line:#ece4db;        /* 轻边框 */
  --line-2:#ddd2c6;      /* 强边框：输入框、需要看清的边界 */
  --line-soft:#f2e9e0;   /* 表格行分隔：比 --line 再淡一档 */
  --text:#2c2521;        /* 暖黑，比 var(--text) 柔 */
  --muted:#7a6c60;       /* 暖灰，在 --bg 上 4.77:1 过 AA */

  --primary:#b8501f;     /* 陶土橙（白字 4.99:1） */
  --primary-d:#9a4219;   /* hover / 深字 */
  --primary-soft:#fbf1e9;
  --danger:#b4372a; --danger-soft:#fdf1ef;
  --warn:#a8630c;   --warn-soft:#fdf6e8;
  --ok:#2f7a52;     --ok-soft:#eef7f1;
  --radius:10px;--radius-sm:8px;--radius-lg:14px;

  /* ==========================================================================
     P0 设计令牌：字体 6 档 + 间距 7 档（DESIGN_SYSTEM.md 第二节）
     改版前这里散着 16 种字号、22 种间距 —— 同一级的文字权重不一致，
     卡片内边距各不相同，页面拼不齐。收敛成两套刻度后，
     「同级信息看起来同级」才成立。
     硬下限：--fs-micro = 11px。再小在 Windows 125% 缩放下会糊成灰块。
     ========================================================================== */
  --fs-display:24px;   /* 全站最大：KPI 数字、统计卡 */
  --fs-xl:20px;        /* 强调数字：工资条实发合计、绩效最终分、登录页标题 */
  --fs-h1:18px;        /* 页面标题 */
  --fs-h2:15px;        /* 卡片标题 */
  --fs-body:13px;      /* 正文、表格 —— 全站基准 */
  --fs-sm:12px;        /* 辅助说明、标签 */
  --fs-micro:11px;     /* 角标（下限，不可再小） */

  --sp-1:4px; --sp-2:8px; --sp-3:12px; --sp-4:16px; --sp-5:20px;
  --sp-6:24px; --sp-8:32px; --sp-12:48px;

  /* 顶栏药丸滑块（Lucian #039）：--x / --w 由 syncNavPill() 按实测像素实时写入。
     这里兜底两个用处：首帧还没测量时不塌成 0 宽；令牌守卫要求 var() 有定义。
     --i 是项目既有的逐行延迟约定（列表行、下拉行共用），兜底 0 与原 fallback 等价。 */
  --x:0px; --w:80px; --i:0;
  /* 顶栏搜索框的左边距（v1.9.4 改为贴着菜单栏、向右展开）。
     由 nsOpen() 实测写入；这里兜底一个值，令牌守卫才认它有定义。 */
  --ns-l:160px;

  /* 缓动 --ease-out / --ease-spring 定义在文件后段「动效令牌」一节，
     不要在这里再写一份：两份值不同（.34,1.56 vs .34,1.4），后定义者生效，
     容易让人以为是这里在起作用。

     顶栏暖色（用户要求：纯黑太硬，要轻松温暖）。
     只作用于顶栏 + 下拉，不动全站 --text / --line —— 改全局会让表格、表单一起变色。
     --warm-dim 是未选中项的文字色：#7d6555 在 --warm-soft 上约 4.9:1，够 AA。 */
  --warm-1:#f7bd93; --warm-2:#e89a6b; --warm-ink:#4a2c17;
  --warm-soft:#fdf2e9; --warm-line:#f1e2d6; --warm-dim:#7d6555;
  /* 搜索结果行的图标底色：由 JS 内联写成 style="--c:…"（按分组轮换）。
     这里给 :root 兜底，令牌守卫才认它有定义；内联值会覆盖。 */
  --c:linear-gradient(140deg,#f8c9a6,#efae82);
}
*{box-sizing:border-box}
/* font 简写里不能塞 var()：整条声明要到 computed-value time 才解析，
   斜杠后的字族部分会和 font-size 的解析打架，浏览器直接丢弃。
   所以拆成 font-size + line-height 两行写。 */
body{margin:0;background:var(--bg);color:var(--text);
  font-size:var(--fs-body);line-height:1.6;
  font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif}
a{color:var(--primary);text-decoration:none}

/* 登录 / 门禁页：第一印象。
   原来是纯渐变 + 居中文字 —— 长段落居中读起来费劲（每行都要重新找起点），
   而且 360px 宽把「到飞书工作台找到「六道CRM」点开即自动登录」挤成三行。
   现在：加宽到 420、文字左对齐、背景叠两层暖色光晕把整块白卡"托"起来。 */
.login-wrap{display:flex;align-items:center;justify-content:center;height:100vh;
  background:
    radial-gradient(110% 80% at 88% -12%,rgba(232,154,107,.20),transparent 58%),
    radial-gradient(80% 70% at -8% 108%,rgba(184,80,31,.07),transparent 60%),
    var(--bg)}
.login-card{background:var(--panel);border:1px solid var(--line);border-radius:16px;
  padding:var(--sp-8);width:420px;max-width:calc(100vw - 32px);
  box-shadow:0 1px 2px rgba(62,44,32,.04),0 24px 48px -34px rgba(62,44,32,.4)}
.login-card h1{margin:0 0 var(--sp-1);font-size:var(--fs-xl);letter-spacing:-.3px}
.login-card p{margin:0 0 var(--sp-6);color:var(--muted);font-size:var(--fs-body)}
.login-logo{display:flex;align-items:center;gap:var(--sp-2);font-size:var(--fs-xl);
  font-weight:700;margin-bottom:var(--sp-1);letter-spacing:-.2px}
.login-logo .logo-mark{width:28px;height:28px;font-size:var(--fs-h2);border-radius:9px}

/* ---------- 布局：顶部横向导航（工作台）+ 左侧纵栏（管理后台） ---------- */
.app-shell{display:flex;flex-direction:column;min-height:100vh}
.app-body{flex:1;display:flex;min-height:0}
.main{flex:1;min-width:0;display:flex;flex-direction:column}
/* ---------- 版式骨架（v2.0） ----------
   原来 .content 是 padding 16/24 + 无最大宽度：1440 宽下表格直接贴到窗口边缘，
   1920 宽下再拉长 —— 内容永远比"舒适阅读宽度"宽一截，眼睛要横着扫。
   现在加最大宽度 + 左右 32px，宽屏时居中留白，窄屏靠 media query 收回。 */
.content{padding:var(--sp-5) var(--sp-8) var(--sp-12);overflow:auto;flex:1;
  max-width:1720px;width:100%;margin:0 auto}
/* 页面标题：从一个 15px 的加粗字，改成有上下呼吸的页头。
   底边线让"页头"和"内容"分开 —— 原来标题和第一张卡之间只有 16px，
   看起来像卡片的标题跑到了外面。 */
.page-head{display:flex;align-items:baseline;gap:var(--sp-3);margin-bottom:var(--sp-5);
  padding-bottom:var(--sp-3);border-bottom:1px solid var(--line)}
.page-head b{font-size:var(--fs-h1);font-weight:650;letter-spacing:-.3px}

/* ---- 顶栏 ---- */
.navbar{height:56px;background:#fffdfb;border-bottom:1px solid var(--warm-line);display:flex;align-items:center;
  justify-content:space-between;padding:0 var(--sp-4);position:sticky;top:0;z-index:30}
.navbar-left,.navbar-right{display:flex;align-items:center;gap:var(--sp-2);min-width:0}
.navbar-right{gap:var(--sp-3)}
.logo{display:flex;align-items:center;gap:var(--sp-2);margin-right:var(--sp-4);flex-shrink:0}
.logo-mark{width:26px;height:26px;border-radius:7px;background:var(--primary);color:#fff;font-weight:700;
  display:flex;align-items:center;justify-content:center;font-size:var(--fs-body)}
.logo-text{font-weight:700;font-size:var(--fs-h2);letter-spacing:.3px}
.console-title{font-size:var(--fs-h2);font-weight:600;margin-left:var(--sp-1)}
.console-title .crumb{font-size:var(--fs-body);font-weight:400;color:var(--muted);margin-left:var(--sp-2)}

/* 顶部主导航 = Lucian UX/UI #039「Sliding pill tabs」的横向版。
   原来是一排各自 independent 的小方块（hover 变灰、选中蓝底），
   改成一整颗胶囊容器 + 一枚深色滑块：换页时滑块带弹簧曲线滑过去，
   「我现在在哪一块」从文字变色变成一件看得见的位移。
   ⚠ 滑块是 absolute + transform，容器**不能**改成 overflow:hidden ——
     窄屏 .nav-main 要横向滚动，hidden 会把下拉和滑块一起裁掉。 */
.nav-main{position:relative;display:flex;align-items:center;gap:0;min-width:0;overflow:visible;
  padding:var(--sp-1);border-radius:999px;background:var(--warm-soft);box-shadow:0 0 0 1px var(--warm-line)}
.nav-pill{position:absolute;left:0;top:var(--sp-1);bottom:var(--sp-1);width:var(--w,80px);
  border-radius:999px;background:linear-gradient(135deg,var(--warm-1),var(--warm-2));
  transform:translateX(var(--x,var(--sp-1)));
  box-shadow:0 10px 20px -12px rgba(180,101,47,.65),inset 0 1px 0 rgba(255,255,255,.55);
  pointer-events:none;
  transition:transform .5s var(--ease-spring),width .5s var(--ease-spring)}
.nav-item{display:inline-flex;align-items:center;gap:var(--sp-1);padding:var(--sp-2) var(--sp-3);border-radius:6px;
  color:var(--text);font-size:var(--fs-body);white-space:nowrap;cursor:pointer}
.nav-item:hover{background:var(--bg)}
.nav-item.active{color:var(--primary);font-weight:600;background:var(--primary-soft)}
.nav-item.ghost{border:1px solid var(--line);padding:var(--sp-1) var(--sp-3);font-size:var(--fs-body)}
.nav-item .caret{font-size:var(--fs-micro);color:var(--muted)}
/* 以下四条只作用于顶栏这一层（> 直系），下拉菜单里的 .nav-item 仍保持原来的方块 hover */
.nav-main>.nav-item,.nav-main>.dd>.dd-toggle{position:relative;z-index:1;
  padding:var(--sp-1) var(--sp-3);border-radius:999px;background:none;
  color:var(--warm-dim);font-weight:600;transition:color .35s var(--ease-out)}
.nav-main>.nav-item:hover,.nav-main>.dd>.dd-toggle:hover{background:none;color:var(--warm-ink)}
.nav-main>.nav-item.active,.nav-main>.dd>.dd-toggle.active{background:none;color:var(--warm-ink);font-weight:600}
.nav-main>.nav-item.active .caret,.nav-main>.dd>.dd-toggle.active .caret{color:var(--warm-ink);opacity:.85}
.badge{background:#ef4444;color:#fff;font-size:var(--fs-sm);line-height:1;border-radius:20px;
  padding:var(--sp-1) var(--sp-2);min-width:18px;text-align:center}

/* ---------- 顶栏全局搜索 = Lucian UX/UI #027「Expanding top-bar search」 ----------
   原元素：点放大镜 → 输入框从 44px 横向拉长 → 下方浮层展开建议、命中字母高亮、上下键选择。
   移植时保留结构、宽度伸展曲线（.55s 弹簧）与浮层的 delay 分层，
   只把数据换成本系统的功能模块。
   ⚠ v1.9.4：放大镜从顶栏最右挪到**菜单栏右边**（用户："想搜要横跨整个屏幕"）。
     所以锚边整体从左改写：按钮/输入框贴 left，展开时向右长，浮层也从左侧展开。
     别只改 JS 不改这几行 —— 那样输入框会从屏幕中间往外长，浮层还会跑到视口外。
   ⚠ 展开时把导航淡出（同 #027 让 logo/nav 让位），否则浮层和菜单抢注意力。 */
.nav-search{position:relative;width:34px;height:34px;flex:none;z-index:46;margin-left:var(--sp-2)}
.ns-go{position:absolute;left:0;top:0;width:34px;height:34px;border:1px solid var(--warm-line);
  border-radius:10px;background:var(--warm-soft);color:var(--warm-ink);display:grid;place-items:center;
  padding:0;cursor:pointer;z-index:2;
  transition:background-color .25s var(--ease-out),opacity .2s var(--ease-out)}
.ns-go:hover{background:#fbe5d3}
.ns-go:focus-visible{outline:2px solid var(--warm-2);outline-offset:2px}
.ns-go svg,.ns-x svg{width:17px;height:17px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round}
.nav-search.is-open .ns-go{opacity:0;pointer-events:none}
.ns-field{position:absolute;left:0;top:50%;transform:translateY(-50%);
  width:34px;height:34px;display:flex;align-items:center;border-radius:10px;background:#fff;
  box-shadow:0 0 0 1px var(--warm-line);
  transition:width .55s var(--ease-spring),box-shadow .3s var(--ease-out)}
/* --ns-l 由 nsOpen() 写入（按钮当前的左边距），把展开宽度夹在视口内 */
.nav-search.is-open .ns-field{width:min(320px,calc(100vw - var(--ns-l,160px) - var(--sp-4)));
  box-shadow:0 0 0 2px var(--warm-2),0 12px 26px -18px rgba(150,90,50,.55)}
.ns-in{flex:1;min-width:0;height:100%;border:0;outline:0;background:none;
  padding:0 var(--sp-8) 0 var(--sp-3);
  font-size:var(--fs-body);color:var(--text);opacity:0;transition:opacity .3s var(--ease-out) .12s}
.ns-in::placeholder{color:var(--muted)}
/* 输入框不能再吃全站的蓝色焦点环（:focus-visible 的 primary 描边 + input:focus 的蓝光晕）：
   外框已经用 --warm-2 的 2px 描边表示「正在输入」，再叠一层蓝框就花了。 */
.ns-in:focus,.ns-in:focus-visible{outline:none;box-shadow:none}
.nav-search.is-open .ns-in{opacity:1}
.ns-x{position:absolute;right:2px;top:2px;width:30px;height:30px;border:0;padding:0;border-radius:8px;
  background:var(--warm-soft);color:var(--warm-ink);display:grid;place-items:center;cursor:pointer;
  opacity:0;transform:scale(.6) rotate(-90deg);
  transition:opacity .25s var(--ease-out),transform .5s var(--ease-spring)}
.nav-search.is-open .ns-x{opacity:1;transform:none}
.ns-pop{position:absolute;left:0;top:calc(100% + var(--sp-3));width:340px;
  max-width:calc(100vw - var(--ns-l,160px) - var(--sp-4));padding:var(--sp-3) var(--sp-2) var(--sp-2);
  border-radius:14px;background:#fffdfb;
  box-shadow:0 0 0 1px var(--warm-line),0 30px 60px -30px rgba(120,80,50,.45);
  transform-origin:0 0;opacity:0;transform:translateY(-10px) scale(.96);
  pointer-events:none;visibility:hidden;
  transition:opacity .25s var(--ease-out),transform .45s var(--ease-spring),visibility 0s .45s}
.nav-search.is-open .ns-pop{opacity:1;transform:none;pointer-events:auto;visibility:visible;
  transition:opacity .3s var(--ease-out) .1s,transform .55s var(--ease-spring) .1s}
.ns-sec{margin:0 var(--sp-2) var(--sp-2);font-size:var(--fs-sm);font-weight:700;
  letter-spacing:.04em;color:var(--muted)}
.ns-recent{display:flex;gap:var(--sp-1);flex-wrap:wrap;margin:0 var(--sp-1) var(--sp-3)}
.ns-recent button{height:28px;padding:0 var(--sp-3);border:0;border-radius:999px;
  background:var(--warm-soft);font-size:var(--fs-sm);font-weight:600;color:var(--warm-ink);cursor:pointer}
.ns-recent button:hover{background:#fbe5d3}
.ns-list{margin:0;padding:0;list-style:none;max-height:300px;overflow:auto}
.ns-opt{display:flex;align-items:center;gap:var(--sp-3);padding:var(--sp-1) var(--sp-2);
  border-radius:10px;cursor:pointer;opacity:0;transform:translateY(6px);
  transition:background-color .2s var(--ease-out),opacity .3s var(--ease-out),
             transform .45s var(--ease-spring)}
.nav-search.is-open .ns-opt{opacity:1;transform:none}
.ns-opt:hover{background:var(--warm-soft)}
.ns-opt[aria-selected="true"]{background:var(--warm-soft)}
.ns-opt i{width:26px;height:26px;flex:none;border-radius:8px;background:var(--c,var(--warm-1))}
.ns-opt b{display:block;font-size:var(--fs-body);font-weight:600;color:var(--text)}
.ns-opt b mark{background:none;color:#b4652f;font-weight:800}
.ns-opt small{display:block;font-size:var(--fs-sm);color:var(--muted)}
.ns-empty{padding:var(--sp-6) var(--sp-2);text-align:center;font-size:var(--fs-body);color:var(--muted)}
/* 搜索展开时让位（#027 的 logo/nav 淡出）：导航与右侧按钮淡到几乎不可见但保留占位 */
.navbar.searching .nav-main{opacity:.16;pointer-events:none;
  transition:opacity .25s var(--ease-out)}
.navbar.searching .navbar-right>:not(.nav-search){opacity:0;pointer-events:none;
  transition:opacity .2s var(--ease-out)}

/* 下拉：展开/收起由 JS 统一驱动（55ms 进、160ms 出），不再用 :hover。
   原因：:hover 与「点击钉住」的 .open 会叠加 —— 一个菜单被点击钉住后，
   再 hover 下一项就是两个菜单同时展开（2026-11-02 用户截图的那个 bug）。
   只留 JS 一个真相源后，移开必收、也不可能同时开两个。
   ★ 菜单与按钮之间有 6px 视觉间距，鼠标向下移动会穿过空隙：
     给菜单挂一层透明伪元素「悬停桥」把空隙填满，热区连续，视觉留白不变。 */
.dd{position:relative}
.dd-menu{display:none;position:absolute;top:calc(100% + 6px);left:0;min-width:180px;background:#fffdfb;
  border:1px solid var(--warm-line);border-radius:10px;box-shadow:0 14px 34px -14px rgba(120,80,50,.28);
  padding:var(--sp-2);z-index:40;transform-origin:top left}
.dd-menu::before{content:"";position:absolute;left:0;right:0;top:-12px;height:12px}   /* ← 悬停桥 */
/* ⚠ fill-mode 用 backwards 不用 both：both 会在结束后钉住 transform:none，
   日后谁给 .dd-menu 加一点 hover 位移都会被压掉（navRowIn 踩过这个坑）。
   15 个入场也一并从手写的 cubic-bezier(.2,.8,.3,1) 改成 var(--ease-out) ——
   那条手写曲线和令牌不是同一个值（y1 .8 vs 1），等于全站并存两套 ease-out。 */
.dd-menu.show{display:block;animation:ddIn .15s var(--ease-out) backwards}
@keyframes ddIn{from{opacity:0;transform:translateY(-6px) scale(.98)}to{opacity:1;transform:none}}
.dd-menu .nav-item{display:flex;width:100%;border-radius:8px;padding:var(--sp-2) var(--sp-3)}
.dd-menu .nav-item:hover{background:var(--warm-soft);color:var(--warm-ink)}
.dd-menu .nav-item.active{background:var(--warm-soft);color:var(--warm-ink);font-weight:600}
/* 下拉子项逐行入场 —— Lucian #039 的 stagger。
   只做「展开那一下」，不常驻循环：看板类页面是每天盯的，常驻动画会晃眼。
   ⚠ 用 backwards 而不是 both：both 会把 transform:none 钉死在动画结束后，
     压掉 .nav-item:active 的 scale(.97) 按压反馈（这个坑之前踩过）。
   ⚠ v1.9.5：原来是 .42s + var(--ease-spring)。两处都不对 ——
     ① 420ms 超了 UI 的 300ms 预算，菜单每天点几十次，慢就是钝；
     ② 弹簧回弹是"有物理感"的语气，用在财务系统每天点的菜单上像在玩。
     改成 .22s ease-out + 32ms 递进：6 项走完 380ms，第 7 项起同时到（--i 上限 5）。
     别把 delay 也去掉 —— 递进本身是这个动效唯一的信息量（"从上面长出来"）。 */
.dd-menu>.nav-item{animation:navRowIn .22s var(--ease-out) backwards;
  animation-delay:calc(var(--i,0) * 32ms)}
@keyframes navRowIn{from{opacity:0;transform:translateX(-10px)}to{opacity:1;transform:none}}
/* reduced-motion 必须连 animation-delay 一起清：
   只把 duration 归零的话，延迟期元素还停在 opacity:0，菜单会先空一片再突然出现。 */
@media (prefers-reduced-motion:reduce){
  .dd-menu>.nav-item{animation:none!important;animation-delay:0s!important}
}
.dd-menu.right{left:auto;right:0;min-width:220px;transform-origin:top right}
.dd-head{padding:var(--sp-2) var(--sp-3) var(--sp-3);border-bottom:1px solid var(--line);margin-bottom:var(--sp-2)}
.dd-head .muted{margin-top:var(--sp-1)}
/* 鼠标已经进入菜单 / 按钮时，保持展开（JS 加分，避免临界抖动） */
.dd.open>.dd-menu{display:block;animation:ddIn .15s var(--ease-out) backwards}
.dd-menu.open{display:block;animation:ddIn .15s var(--ease-out) backwards}  /* 头像菜单：点击展开 */

.avatar-menu{position:relative}
.avatar{width:32px;height:32px;border-radius:50%;background:#7c3aed;color:#fff;font-weight:600;
  display:flex;align-items:center;justify-content:center;cursor:pointer;user-select:none;font-size:var(--fs-body)}
/* 顶栏的「管理后台 / 进入工作台」切换：原来和导航药丸**同色同形**（橙描边 + 橙字），
   一进页面顶栏就有两个橙色胶囊，谁"当前选中"就说不清了。
   改成中性描边 —— 顶栏只保留一个橙色高亮（当前所在导航项）。 */
button.pill{border:1px solid var(--line-2);color:var(--text);background:var(--panel);
  border-radius:20px;height:30px;padding:0 var(--sp-3);font-size:var(--fs-sm)}
button.pill:hover{background:var(--bg);border-color:var(--line-2);color:var(--text)}

/* ---- 管理后台左侧纵栏 ---- */
.admin-side{width:186px;background:#fff;border-right:1px solid var(--line);padding:var(--sp-3) 0;flex-shrink:0;overflow:auto}
.side-brand{padding:var(--sp-1) var(--sp-4) var(--sp-3);font-weight:600;font-size:var(--fs-body);border-bottom:1px solid var(--line);margin-bottom:var(--sp-2)}
.side-group{padding:var(--sp-3) var(--sp-4) var(--sp-1);font-size:var(--fs-sm);color:var(--muted);letter-spacing:.5px}
.side-item{display:block;padding:var(--sp-2) var(--sp-4);color:var(--text);font-size:var(--fs-body)}
.side-item:hover{background:var(--bg)}
.side-item.active{background:var(--primary-soft);color:var(--primary);font-weight:600}

/* ---- 开关卡片 / 步骤条 ---- */
.sw-list{display:flex;flex-direction:column;gap:var(--sp-2);margin-top:var(--sp-3)}
.sw{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-3);
  border:1px solid var(--line);border-radius:8px;padding:var(--sp-3) var(--sp-4);background:var(--bg)}
.sw.on{border-color:#f0d5c3;background:#f8fbff}
.sw-right{display:flex;align-items:center;gap:var(--sp-3);flex-shrink:0}
.steps{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:var(--sp-3)}
.step{display:flex;gap:var(--sp-3);border:1px solid var(--line);border-radius:8px;padding:var(--sp-3) var(--sp-4);background:var(--bg)}
.step .n{width:22px;height:22px;flex-shrink:0;border-radius:50%;background:var(--primary-soft);
  color:var(--primary);font-weight:600;font-size:var(--fs-sm);display:flex;align-items:center;justify-content:center}

/* ---- 折叠区（高级排障） ---- */
details.adv{background:#fff;border:1px solid var(--line);border-radius:var(--radius);margin-bottom:var(--sp-4)}
details.adv>summary{padding:var(--sp-3) var(--sp-4);cursor:pointer;font-weight:600;font-size:var(--fs-body);list-style:none;
  display:flex;align-items:center;gap:var(--sp-2)}
details.adv>summary::-webkit-details-marker{display:none}
details.adv>summary::before{content:"▸";color:var(--muted);font-size:var(--fs-sm)}
details.adv[open]>summary::before{content:"▾"}
details.adv[open]>summary{border-bottom:1px solid var(--line)}
.adv-body{padding:var(--sp-4) var(--sp-4)}
.adv-body h3{margin:var(--sp-4) 0 var(--sp-2);font-size:var(--fs-body);color:var(--muted);font-weight:600}
.adv-body h3:first-child{margin-top:0}

/* ---------- 组件 ---------- */
/* ---------- 组件（v2.0 重做） ----------
   这一层改的不是"颜色好不好看"，是三个结构性问题：

   ① 卡片 = 白底 + 1px 边框 + 阴影，三层同时上 → 一页十张卡就是十圈框线。
      现在边框保留（数据密集页需要边界），但阴影压到几乎看不见（.03 透明度的
      2px 内阴影），让"白"本身承担分层，不再靠画框。
   ② 表格表头原来是灰底 + 600 字重 + 13px，和正文同一个视觉层级 →
      表头在"抢"数据。现在去掉底色，改成 11px + 0.06em 字距的"标签感"表头，
      眼睛先扫到数据、需要时才去找列名。
   ③ 按钮 hover 原来是"边框和文字同时变主色"—— 一页十几个按钮同时闪橙边，
      比不 hover 还吵。现在 hover 只把底色压深一档，主按钮才用实色。
   ========================================================================== */
.card{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius-lg);
  padding:var(--sp-5);margin-bottom:var(--sp-4);
  box-shadow:0 1px 2px rgba(62,44,32,.035)}
.card h2{margin:0 0 var(--sp-4);font-size:var(--fs-h2);font-weight:650;color:var(--text);
  display:flex;align-items:center;gap:var(--sp-2);letter-spacing:-.1px}
.card h3{margin:var(--sp-4) 0 var(--sp-2);font-size:var(--fs-sm);color:var(--muted);font-weight:600}
.row{display:flex;gap:var(--sp-3);flex-wrap:wrap;align-items:center}
/* 横排表单（`<div class="row"><div><label>…</label><input></div>…<button></div>）：
   原来每个字段宽度由内容撑开 —— 付款主体 158px、金额 85px、事由 220px，
   一排下来右端参差不齐；`align-items:center` 又让按钮浮在中间，
   底线和输入框差 4px，说不清哪儿别扭但就是歪。
   现在：字段等宽（150~300 之间伸缩）、按钮单独贴底。
   ⚠ 只对有 label 的行生效（`:has`）—— `.row` 也用来放纯按钮组和说明文字，
     给那些行改对齐反而会歪。浏览器不支持 `:has` 时整条规则被忽略，退化成原样。 */
.row:has(>div>label){align-items:flex-end}
.row>div:has(>label){flex:1 1 150px;min-width:132px;max-width:300px}
.row>div:has(>label)>input,
.row>div:has(>label)>select,
.row>div:has(>label)>textarea{width:100%!important;max-width:none}
/* ↑ 必须 !important：历史代码里给单个字段写了 style="width:120px"（全项目 26 处），
   内联样式优先于任何选择器。等宽栅格一旦落定，格子宽度就该由栅格决定，
   而不是由"这个字段当年觉得多宽合适"决定 —— 否则 6 个格子等宽、
   里面控件一个 120 一个 220，看起来还是参差。 */
.row>button{align-self:flex-end;flex:none}
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:var(--sp-3)}
.stat{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius-lg);
  padding:var(--sp-3) var(--sp-4);display:flex;flex-direction:column;justify-content:center;
  gap:var(--sp-1);min-height:74px;min-width:0}
.stat .n{font-size:var(--fs-xl);font-weight:650;letter-spacing:-.5px;line-height:1.25;
  font-variant-numeric:tabular-nums}
.stat .l{font-size:var(--fs-sm);color:var(--muted);line-height:1.4}

/* 表头不加底色：底色会把"表头条"变成第二个视觉块，和上方卡片标题打架。
   靠字距 + 小字号 + 一条稍深的底边线把它压到背景里。 */
table{width:100%;border-collapse:collapse;font-size:var(--fs-body)}
th,td{padding:var(--sp-2) var(--sp-3);border-bottom:1px solid var(--line-soft);text-align:left;vertical-align:middle}
th{background:transparent;color:var(--muted);font-weight:600;font-size:var(--fs-micro);
  letter-spacing:.06em;white-space:nowrap;border-bottom:1px solid var(--line)}
tr:hover td{background:var(--bg)}

button{font:inherit;font-size:var(--fs-body);height:32px;padding:0 var(--sp-3);
  border:1px solid var(--line-2);background:var(--panel);color:var(--text);
  border-radius:var(--radius-sm);cursor:pointer;
  display:inline-flex;align-items:center;justify-content:center;gap:var(--sp-2);
  transition:background-color .16s var(--ease-out),border-color .16s var(--ease-out),
             color .16s var(--ease-out),box-shadow .16s var(--ease-out)}
button:hover{background:var(--bg);border-color:var(--line-2);color:var(--text)}
button.primary{background:var(--primary);border-color:var(--primary);color:#fff;
  box-shadow:0 1px 2px rgba(184,80,31,.22)}
button.primary:hover{background:var(--primary-d);border-color:var(--primary-d);color:#fff}
button.danger{color:var(--danger);border-color:#f2d2cc}
button.danger:hover{background:var(--danger-soft);color:var(--danger)}
button.sm{height:28px;padding:0 var(--sp-2);font-size:var(--fs-sm)}
button:disabled{opacity:.45;cursor:not-allowed}

/* 输入框统一 34px：原来按钮和输入框一个 32 一个 30，并排时底线差 2px ——
   单据页那排"字段 + 按钮"就是歪的，说不清哪儿别扭但就是歪。 */
input,select,textarea{font:inherit;font-size:var(--fs-body);height:34px;padding:0 var(--sp-2);
  border:1px solid var(--line-2);border-radius:var(--radius-sm);background:var(--panel);
  color:var(--text);outline:none;max-width:100%;
  transition:border-color .16s var(--ease-out),box-shadow .16s var(--ease-out)}
textarea{height:auto;min-height:72px;padding:var(--sp-2);line-height:1.6}
input:focus,select:focus,textarea:focus{border-color:var(--primary);
  box-shadow:0 0 0 3px rgba(184,80,31,.12)}
label{font-size:var(--fs-sm);color:var(--muted);display:block;margin-bottom:var(--sp-1);font-weight:500}

/* 标签：药丸(20px 圆角)改成小方角 + 语义圆点。
   一屏里"审批通过/已锁定/已通过"三个药丸并排时，药丸的形状本身就在抢镜；
   改成"● 文字"之后，颜色只负责区分状态，形状不再参与。 */
.tag{display:inline-flex;align-items:center;gap:var(--sp-1);padding:0 var(--sp-2);border-radius:6px;
  font-size:var(--fs-micro);font-weight:550;line-height:1.7;white-space:nowrap;
  background:var(--bg-2);color:var(--muted)}
.tag::before{content:"";width:5px;height:5px;border-radius:50%;background:currentColor;
  opacity:.8;flex:none}
.tag.ok{background:var(--ok-soft);color:var(--ok)}
.tag.warn{background:var(--warn-soft);color:var(--warn)}
.tag.err{background:var(--danger-soft);color:var(--danger)}
.tag.info{background:var(--primary-soft);color:var(--primary-d)}
.muted{color:var(--muted);font-size:var(--fs-sm)}
/* 空态：一行灰字 + 一大片白，读起来像"页面坏了"。
   加一个中性的空盒图标做锚点，让空白变成"这里本来就空"。
   图标用 data-uri 内联，不引外部请求（离线/内网也能显示）。 */
.empty{padding:var(--sp-12) var(--sp-6);text-align:center;color:var(--muted);font-size:var(--fs-body)}
.empty::before{
  content:"";display:block;width:46px;height:46px;margin:0 auto var(--sp-3);
  border-radius:14px;background:var(--bg-2) center/22px no-repeat
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23a8968a' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 8l8-4 8 4v8l-8 4-8-4z'/%3E%3Cpath d='M4 8l8 4 8-4M12 12v8'/%3E%3C/svg%3E")
}
.error{background:var(--danger-soft);color:var(--danger);padding:var(--sp-2) var(--sp-3);
  border-radius:var(--radius-sm);margin-bottom:var(--sp-3)}
.notice{background:var(--primary-soft);color:var(--primary-d);padding:var(--sp-2) var(--sp-3);
  border-radius:var(--radius-sm);margin-bottom:var(--sp-3);font-size:var(--fs-body)}

/* 链路预览 */
.chain{display:flex;align-items:center;gap:var(--sp-2);flex-wrap:wrap;padding:var(--sp-3) var(--sp-3);background:var(--bg-2);
  border:1px dashed var(--line);border-radius:6px;margin:var(--sp-3) 0}
.chain .n{background:#fff;border:1px solid var(--line);border-radius:6px;padding:var(--sp-1) var(--sp-3);font-size:var(--fs-sm)}
.chain .n.approve{border-color:#f0d5c3;background:var(--primary-soft);color:var(--primary-d)}
.chain .n.cc{border-color:#e9d5ff;background:#faf5ff;color:#7e22ce}
.chain .arw{color:var(--muted)}

/* 弹层 */
.mask{position:fixed;inset:0;background:rgba(62,44,32,.35);display:flex;align-items:center;
  justify-content:center;padding:var(--sp-6);z-index:50}
.modal{background:#fff;border-radius:10px;width:760px;max-width:100%;max-height:86vh;overflow:auto;padding:var(--sp-6)}
.modal h2{margin:0 0 var(--sp-4);font-size:var(--fs-h2)}
.modal .foot{display:flex;justify-content:flex-end;gap:var(--sp-2);margin-top:var(--sp-4);padding-top:var(--sp-4);border-top:1px solid var(--line)}
.field{margin-bottom:var(--sp-3)}
.two{display:grid;grid-template-columns:1fr 1fr;gap:var(--sp-3)}

/* ---------- 首页看板 ---------- */
.kpis{display:grid;grid-template-columns:repeat(auto-fill,minmax(190px,1fr));gap:var(--sp-3);margin-bottom:var(--sp-4)}
.kpi{background:#fff;border:1px solid var(--line);border-radius:var(--radius);padding:var(--sp-4) var(--sp-4);
  border-left:3px solid var(--primary)}
.kpi.warn{border-left-color:var(--warn)}
.kpi-l{font-size:var(--fs-sm);color:var(--muted)}
.kpi-n{font-size:var(--fs-display);font-weight:600;margin:var(--sp-1) 0 var(--sp-1);line-height:1.25}
.kpi-n small{font-size:var(--fs-sm);font-weight:400;color:var(--muted);margin-left:var(--sp-1)}
.kpi-h{font-size:var(--fs-sm);color:var(--muted);line-height:1.4}

.grid2{display:grid;grid-template-columns:1fr 1fr;gap:var(--sp-4)}
@media (max-width:1100px){.grid2{grid-template-columns:1fr}}

.card h2 .muted{margin-left:var(--sp-2);font-weight:400;font-size:var(--fs-sm)}
table .r,th.r,td.r{text-align:right}
/* 业务存量：9 个带边框的小方块 → 一条连续的指标条。
   原因：auto-fill 网格在 1440 宽下每行放 7 个，9 个条目就是 7+2 ——
   第二行右边永远空 5 格，这是首页最大一块"像没做完"的地方。
   改成 flex + 竖线分隔，条目数不是列数的整数倍时也不会露洞。 */
.dash .grid{display:flex;flex-wrap:wrap;align-items:stretch}
.stock{display:block;flex:0 0 auto;min-width:122px;
  background:none;border:0;border-left:1px solid var(--line);border-radius:0;
  padding:var(--sp-1) var(--sp-5);color:inherit}
.stock:first-child{border-left:0;padding-left:0}
.stock:hover{background:var(--bg);border-radius:var(--radius-sm);color:inherit}
.stock .n{font-size:var(--fs-xl);font-weight:650;letter-spacing:-.4px;line-height:1.35;
  font-variant-numeric:tabular-nums}
.stock .l{font-size:var(--fs-sm);color:var(--muted);line-height:1.4}
.neg{color:var(--danger)}

/* 回款情况三层表 */
tr.grp td{background:var(--bg-2)}
tr.sub td.pad{padding-left:var(--sp-6);color:var(--muted);font-size:var(--fs-sm)}
tr.leaf td.pad2{padding-left:var(--sp-12);font-size:var(--fs-sm)}
tr.leaf td{font-size:var(--fs-sm)}

/* 条形图 */
.bar-row{display:grid;grid-template-columns:110px 1fr 150px;gap:var(--sp-3);align-items:center;margin-bottom:var(--sp-2)}
.bar-lab{font-size:var(--fs-sm);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.bar-track{background:var(--bg-2);border-radius:4px;height:14px;overflow:hidden}
.bar-fill{height:100%;background:var(--primary);border-radius:4px}
.bar-val{font-size:var(--fs-sm);text-align:right}
.bar-val .muted{margin-left:var(--sp-2)}

/* 额度进度条：已用/总额比数字直观；超额度转红，≥80% 转橙 */
.pg-row{margin-bottom:var(--sp-3)}
.pg-head{display:flex;align-items:baseline;gap:var(--sp-2);font-size:var(--fs-sm);margin-bottom:var(--sp-1)}
.pg-head .muted{margin-left:auto}
.pg-val{font-variant-numeric:tabular-nums;font-weight:600}
.pg-val.warn{color:#b45309}
.pg-val.over{color:var(--danger)}
.pg-track{background:var(--bg-2);border-radius:6px;height:12px;overflow:hidden}
.pg-fill{height:100%;background:var(--primary);border-radius:6px;transition:width .4s ease}
.pg-fill.warn{background:#f59e0b}
.pg-fill.over{background:var(--danger)}
.pg-sub{font-size:var(--fs-sm);color:var(--muted);margin-top:var(--sp-1)}

.trend{display:flex;align-items:flex-end;gap:var(--sp-3);height:170px;padding:var(--sp-2) 0}
.trend-col{flex:1;display:flex;flex-direction:column;align-items:center;height:100%}
.trend-bars{flex:1;display:flex;align-items:flex-end;gap:var(--sp-1);width:100%;justify-content:center}
.trend-bar{width:16px;border-radius:3px 3px 0 0;min-height:2px}
.trend-bar.c-in{background:var(--primary)}
.trend-bar.c-cost{background:#f59e0b}
.trend-x{font-size:var(--fs-sm);color:var(--muted);margin-top:var(--sp-1)}
.legend{display:flex;gap:var(--sp-4);font-size:var(--fs-sm);color:var(--muted);margin-top:var(--sp-2)}
.legend i{display:inline-block;width:10px;height:10px;border-radius:2px;margin-right:var(--sp-1)}
.legend i.c-in{background:var(--primary)}
.legend i.c-cost{background:#f59e0b}

/* ---------- 通用列表 ---------- */
.lbar{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-3);
  background:#fff;border:1px solid var(--line);border-radius:var(--radius);padding:var(--sp-3) var(--sp-3);margin-bottom:var(--sp-3);
  flex-wrap:wrap}
.lbar-l,.lbar-r{display:flex;align-items:center;gap:var(--sp-2);flex-wrap:wrap}
/* 数据范围提示（v2.2.0）：只给「本部门 / 仅本人」的人显示，全量的人看不到这一枚。
   用暖色底而不是灰色 —— 它是"解释"，不是"状态"，别做成告警的样子。
   cursor:help 是唯一能让人知道"可以停一下看解释"的线索（title 没有原生指示器）。 */
.scope-chip{display:inline-flex;align-items:center;height:auto;flex:none;
  padding:var(--sp-1) var(--sp-2);border-radius:999px;cursor:help;
  font-size:var(--fs-micro);line-height:1.4;white-space:nowrap;
  color:var(--primary-d);background:var(--primary-soft);
  border:1px solid var(--primary)}
/* 列表内搜索（23 个列表共用同一枚）：圆角 + 放大镜 + 清空 + `/` 键位提示。
   原来是一个浏览器原生方框 + 一个「搜索」按钮，和顶栏那颗放大镜完全是两套语言；
   现在两处同一个视觉（圆角、暖色描边、同样的图标）。
   ⚠ .lq 必须把全站的 input 边框/焦点光晕全部压掉（!important）：
     第 279/479/1349 行三处全局规则都会给它加描边和蓝色光晕，
     叠在 .lsearch 自己的 focus-within 暖色外框上就是双框、还是蓝的。 */
.lsearch{position:relative;display:flex;align-items:center;height:34px;
  border:1px solid var(--line);border-radius:20px;background:#fff;
  padding:0 var(--sp-2) 0 var(--sp-3);min-width:0;
  transition:border-color .25s var(--ease-out),box-shadow .25s var(--ease-out)}
.lsearch:hover{border-color:var(--warm-2)}
.lsearch:focus-within{border-color:var(--warm-2);box-shadow:0 0 0 3px rgba(232,154,107,.18)}
.ls-ico{width:15px;height:15px;flex:none;fill:none;stroke:var(--muted);stroke-width:2;
  stroke-linecap:round;margin-right:var(--sp-2)}
.lsearch:focus-within .ls-ico{stroke:var(--warm-ink)}
.lq{width:220px;height:28px;min-height:0!important;border:0!important;outline:0!important;
  background:none!important;box-shadow:none!important;padding:0!important;
  font-size:var(--fs-body);color:var(--text);min-width:0}
.ls-x{display:none;width:22px;height:22px;flex:none;border:0;border-radius:50%;padding:0;
  background:var(--warm-soft);color:var(--warm-ink);font-size:var(--fs-sm);line-height:1;
  cursor:pointer;place-items:center}
.ls-x:hover{background:#fbe5d3}
.lsearch.has-q .ls-x{display:grid}
.ls-kbd{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;
  flex:none;border:1px solid var(--line);border-radius:5px;font-size:var(--fs-micro);
  color:var(--muted);background:var(--bg);font-family:inherit;line-height:1}
.lsearch.has-q .ls-kbd{display:none}

/* 空结果（Lucian #092）：说清"卡在哪"，并给清筛选/清关键词的出口 */
.lempty{display:flex;flex-direction:column;align-items:center;text-align:center;
  padding:var(--sp-8) var(--sp-6);gap:var(--sp-2)}
.lempty b{font-size:var(--fs-h2)}
.lempty p{margin:0;max-width:460px;line-height:1.8;font-size:var(--fs-body)}
.lem-act{display:flex;gap:var(--sp-2);flex-wrap:wrap;justify-content:center;margin-top:var(--sp-3)}
.lem-ico{width:44px;height:44px;color:var(--warm-2);opacity:.85}
.lem-ico svg{width:100%;height:100%;fill:none;stroke:currentColor;stroke-width:2.2;stroke-linecap:round}
/* 分段控件：原来选中项是**实心主色块**，和旁边的「新建」按钮抢同一档视觉权重 ——
   一进列表页眼睛先落在"表格/看板"上，而不是该点的「新建」。
   改成 iOS 式凹槽 + 白色浮片：选中靠"浮起来"表示，不靠颜色。 */
.seg{display:inline-flex;gap:var(--sp-1);padding:var(--sp-1);border:0;border-radius:9px;
  background:var(--bg-2);overflow:visible}
.seg-i{display:flex;align-items:center;height:26px;padding:0 var(--sp-3);
  font-size:var(--fs-sm);color:var(--muted);cursor:pointer;border-radius:6px}
.seg-i.on{background:var(--panel);color:var(--text);font-weight:600;
  box-shadow:0 1px 2px rgba(62,44,32,.14)}
.chips{display:flex;gap:var(--sp-2);flex-wrap:wrap}
.chip{padding:var(--sp-1) var(--sp-3);border:1px solid var(--line);border-radius:20px;font-size:var(--fs-sm);
  color:var(--muted);cursor:pointer;white-space:nowrap}
.chip.on{border-color:var(--primary);color:var(--primary);background:var(--primary-soft)}

.lfpan{background:#fff;border:1px solid var(--line);border-radius:var(--radius);
  padding:var(--sp-4) var(--sp-4);margin-bottom:var(--sp-3)}
.lfgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(210px,1fr));gap:var(--sp-3)}
.lfgrid .field{margin-bottom:0}

table.ltable{margin:0}
/* 表内链接去色：原来 15 行里 15 个主色字，整片表格"满眼都是可点的东西"，
   反而看不出哪儿该点。数据表里主键列本来就是深色字，hover 才提示可交互 ——
   全站 `a{color:primary}` 是给正文里的引用链接用的，不该管到表格。 */
.ltable td a{color:var(--text);font-weight:500}
.ltable td a:hover{color:var(--primary);text-decoration:underline}
.ltable tbody tr:hover td a{color:var(--primary-d)}
/* 「展开剩余」按钮只在窄屏折卡片时才有意义，宽屏一律不显示 */
.ltable td.lmore-btn{display:none}
.ltable th,.ltable td{white-space:nowrap}
.ltable td{max-width:260px;overflow:hidden;text-overflow:ellipsis}
.ltotals{margin-top:var(--sp-3);background:#fff;border:1px solid var(--line);border-radius:var(--radius);
  padding:var(--sp-3) var(--sp-4);overflow:auto}
.ltotals table{width:auto}
.ltotals th,.ltotals td{border:none;padding:var(--sp-1) var(--sp-4) var(--sp-1) 0;white-space:nowrap}

.lpager{display:flex;align-items:center;gap:var(--sp-3);margin-top:var(--sp-3);flex-wrap:wrap}

.lboard{display:flex;gap:var(--sp-3);overflow:auto;padding-bottom:var(--sp-2)}
.lane{flex:0 0 260px;background:#fff;border:1px solid var(--line);border-radius:var(--radius);
  display:flex;flex-direction:column;max-height:520px}
.lane-h{padding:var(--sp-3) var(--sp-3);border-bottom:1px solid var(--line);display:flex;
  justify-content:space-between;align-items:baseline;gap:var(--sp-2)}
.lane-h .muted{font-size:var(--fs-sm);white-space:nowrap}
.lane-c{padding:var(--sp-2) var(--sp-3);border-bottom:1px solid var(--bg-2)}
.lane-c:hover{background:var(--bg)}
.lane-t{font-size:var(--fs-body)}
.lane-a{font-size:var(--fs-sm);color:var(--muted);margin-top:var(--sp-1)}

button.ghost-pill{border-color:var(--line);color:var(--muted);border-radius:20px;padding:var(--sp-1) var(--sp-4);font-size:var(--fs-body)}
button.ghost-pill:hover{border-color:var(--line);color:var(--muted);background:var(--bg)}

/* ==========================================================================
   交互动效与细节打磨（纯 CSS，无依赖）
   ========================================================================== */

/* ---- 1. 按压反馈 + 涟漪 ---- */
button,.pill,.chip,.seg-i,.nav-item,.side-item,.stock,.kpi,.stat,.lane-c{
  transition:background-color .16s ease,border-color .16s ease,color .16s ease,
             box-shadow .18s ease,transform .12s var(--ease-out)}
button:active:not(:disabled){transform:translateY(1px) scale(.985)}
button.pill:active,.chip:active,.seg-i:active{transform:scale(.96)}
.nav-item:active,.side-item:active{transform:scale(.97)}
.rp{position:relative;overflow:hidden}
.rp>.ripple{position:absolute;border-radius:50%;transform:scale(0);pointer-events:none;
  background:currentColor;opacity:.22;animation:rip .55s var(--ease-out) forwards}
@keyframes rip{to{transform:scale(2.6);opacity:0}}

/* ---- 2. 卡片 / 数据块悬浮 ----
   ⚠ 有**位移**的悬浮必须门禁在 (hover:hover) and (pointer:fine) 里：
     触屏点一下会先触发 hover 态，卡片跳 2px 再弹回，看着像"点歪了"。
     只加阴影的不门禁（没有位移，不会造成点歪的错觉）。 */
.card{transition:box-shadow .2s ease,border-color .2s ease}
.card:hover{box-shadow:0 4px 16px rgba(62,44,32,.06)}
.kpi,.stat{transition:box-shadow .2s ease,transform .16s var(--ease-out),border-color .2s ease}
.lane-c{cursor:default}
@media (hover:hover) and (pointer:fine){
  .kpi:hover,.stat:hover{transform:translateY(-2px);box-shadow:0 6px 20px rgba(62,44,32,.09)}
  .stock:hover{transform:translateY(-2px);box-shadow:0 6px 20px rgba(184,80,31,.12)}
  .lane-c:hover{transform:translateX(2px)}
}
tr{transition:background-color .14s ease}
tr:hover td{background:var(--bg)}
.ltable tbody tr.rowlink{cursor:pointer}
.ltable tbody tr.rowlink:hover td{background:var(--primary-soft)}

/* ---- 3. 弹窗入场 ----
   ⚠ 这里原来有两套弹层动画并存：本节的 .mask{maskIn} / .modal{modalIn}
     和后段「交互质感层」的 .mask{fadeIn} / .mask>.modal{popIn}。
     后段选择器更具体（.mask>.modal = 0-2-0 打败 .modal = 0-1-0），
     所以本节的 maskIn 从来没生效过、modalIn 只剩 .login-card 一个用户。
     删掉死规则，弹层只留一套：遮罩淡入 + 内容上浮（后段 popIn）。
     modalIn 这个 keyframe 要留着给登录卡用 —— 它不在 .mask 里。 */
@keyframes modalIn{from{opacity:0;transform:translateY(12px) scale(.985)}to{opacity:1;transform:none}}

/* ---- 4. 视图切换 / 骨架屏 ----
   路由切换是几十次/天的动作，原来 .2s + 上移 4px 属于"每次翻页都晃一下"。
   位移去掉，只留 .15s 透明度 —— 保住"不是硬切"，去掉"在动"。 */
#vbody{animation:viewIn .15s var(--ease-out) backwards}
@keyframes viewIn{from{opacity:0}to{opacity:1}}

/* ---- 5. 骨架屏 ---- */
.skel{padding:var(--sp-1) 0}
.skel-row{height:14px;border-radius:6px;margin-bottom:var(--sp-3);
  background:linear-gradient(90deg,var(--bg-2) 25%,var(--bg-2) 37%,var(--bg-2) 63%);
  background-size:400% 100%;animation:shimmer 1.3s ease-in-out infinite}
.skel-row.w60{width:60%} .skel-row.w40{width:40%} .skel-row.w80{width:80%}
.skel-cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(190px,1fr));gap:var(--sp-3);margin-bottom:var(--sp-4)}
.skel-card{height:76px;border-radius:var(--radius);
  background:linear-gradient(90deg,var(--bg-2) 25%,var(--bg-2) 37%,var(--bg-2) 63%);
  background-size:400% 100%;animation:shimmer 1.3s ease-in-out infinite}
@keyframes shimmer{0%{background-position:100% 0}100%{background-position:0 0}}

/* ---- 6. 表格：表头吸顶 + 数字等宽 ---- */
.ltable thead th{position:sticky;top:0;z-index:2;background:var(--panel)}
.ltable td.r,.ltable th.r{font-variant-numeric:tabular-nums}
.ltotals td,.ltotals th{font-variant-numeric:tabular-nums}
.kpi-n,.stat .n,.stock .n{font-variant-numeric:tabular-nums;letter-spacing:-.2px}

/* ---- 7. 输入框聚焦柔和过渡 ---- */
input,select,textarea{transition:border-color .16s ease,box-shadow .16s ease,background-color .16s ease}
input:hover,select:hover,textarea:hover{border-color:var(--line-2)}
input:focus,select:focus,textarea:focus{border-color:var(--primary);box-shadow:0 0 0 3px rgba(184,80,31,.12)}

/* ---- 8. 键盘可达性（只对键盘生效，鼠标点击不显示） ---- */
:focus-visible{outline:2px solid var(--primary);outline-offset:2px;border-radius:6px}
a.nav-item:focus-visible,.side-item:focus-visible{outline-offset:-2px}

/* ---- 9. 滚动条（长表格更清爽） ---- */
*{scrollbar-width:thin;scrollbar-color:var(--line-2) transparent}
::-webkit-scrollbar{width:10px;height:10px}
::-webkit-scrollbar-track{background:transparent}
::-webkit-scrollbar-thumb{background:#d8cabd;border-radius:8px;border:3px solid transparent;
  background-clip:content-box}
::-webkit-scrollbar-thumb:hover{background:#b9c2cf;background-clip:content-box}

/* ---- 10. 顶栏当前项的指示条 ---- */
.nav-main>.nav-item.active,.nav-main>.dd>.dd-toggle.active{position:relative}
.nav-main>.dd>.dd-toggle .caret{transition:transform .18s ease}
.nav-main>.dd.open>.dd-toggle .caret{transform:rotate(180deg)}
.dd:hover>.dd-toggle .caret,.dd.open>.dd-toggle .caret{transform:rotate(180deg)}

/* ---- 11. 徽标轻微脉冲（有待办时看得出"活的"） ---- */
/* 筛选计数小圆标：从 scale(.6) 起相当于"无中生有"，现实里没有东西是这么出现的。
   .9 起 + 弹一下的弹簧，"多了一个"这件事被说清楚就够了。 */
.badge{animation:pop .22s var(--ease-spring) backwards}
@keyframes pop{from{transform:scale(.9);opacity:0}to{transform:none;opacity:1}}

/* ---- 12. 尊重系统"减少动态效果"设置 ---- */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.001ms!important;transition-duration:.001ms!important}
}

/* ---- 13. 页头与登录页细修 ---- */
.page-head{align-items:center;padding-bottom:var(--sp-3);border-bottom:1px solid var(--line);margin-bottom:var(--sp-4)}
.page-head b{font-size:var(--fs-h1);letter-spacing:.2px}
.login-card{animation:modalIn .3s var(--ease-out) both}
.login-logo .logo-mark{box-shadow:0 4px 12px rgba(184,80,31,.28)}
.seg-i{transition:background-color .16s ease,color .16s ease}
.seg-i:hover:not(.on){background:var(--bg-2);color:var(--text)}
.chip:hover:not(.on){border-color:var(--line-2);color:var(--text)}
button:hover:not(:disabled){box-shadow:0 1px 4px rgba(62,44,32,.06)}
button.primary:hover:not(:disabled){box-shadow:0 3px 12px rgba(184,80,31,.3)}
.stock:active,.kpi:active,.stat:active{transform:translateY(0) scale(.995)}

/* ==========================================================================
   15. 详情抽屉：一张单据 + 它挂着的所有关联对象
   PC 从右侧滑出占半屏 —— 左边列表还在，看完一单能接着翻下一单，不用跳页；
   手机从底部上滑占满（见窄屏块）。关联对象做成一排 tab，点关联行是"下钻"不是跳转，
   面包屑能逐级退回。统一用 .2,.8,.3,1 这条缓动，进退都不生硬。
   ========================================================================== */
.mask-drawer{justify-content:flex-end;align-items:stretch;padding:0;
  background:rgba(62,44,32,.3);backdrop-filter:blur(2px);-webkit-backdrop-filter:blur(2px)}
.mask-drawer .drawer{width:min(780px,76vw);height:100%;background:#fff;display:flex;
  flex-direction:column;min-width:0;box-shadow:-18px 0 50px rgba(62,44,32,.2);
  animation:dwIn .26s var(--ease-out) both}
@keyframes dwIn{from{opacity:.35;transform:translateX(30px)}to{opacity:1;transform:none}}
/* 切 tab / 下钻时只是"换内容"，不重放进场动画，否则每点一下整块滑一次，晃得难受 */
.mask-swap{animation:none!important}
.mask-swap .drawer{animation:none!important}

.dw-head{display:flex;align-items:flex-start;gap:var(--sp-3);padding:var(--sp-4) var(--sp-4) var(--sp-3);
  border-bottom:1px solid var(--line);flex-shrink:0}
.dw-ic{width:30px;height:30px;flex-shrink:0;padding:0;border-radius:8px;border:1px solid var(--line);
  background:#fff;color:var(--muted);font-size:var(--fs-body);line-height:1;display:flex;
  align-items:center;justify-content:center;cursor:pointer}
.dw-ic:hover{border-color:var(--primary);color:var(--primary)}
.dw-htitle{flex:1;min-width:0}
.dw-t1{font-size:var(--fs-h2);font-weight:600;display:flex;align-items:center;gap:var(--sp-2);flex-wrap:wrap}
.dw-t2{font-size:var(--fs-sm);color:var(--muted);margin-top:var(--sp-1)}
.dw-crumb{font-size:var(--fs-sm);color:var(--muted);margin-top:var(--sp-1)}

.dw-tabs{flex-shrink:0;border-bottom:1px solid var(--line);position:relative}
.dw-tabs-in{display:flex;gap:var(--sp-1);overflow-x:auto;padding:0 var(--sp-3);scrollbar-width:none}
.dw-tabs-in::-webkit-scrollbar{display:none}
.dw-tab{display:inline-flex;align-items:center;gap:var(--sp-1);padding:var(--sp-2) var(--sp-3);font-size:var(--fs-body);
  color:var(--muted);white-space:nowrap;border-bottom:2px solid transparent;
  transition:color .16s ease,border-color .16s ease,background-color .16s ease}
.dw-tab:hover{color:var(--text);background:var(--bg)}
.dw-tab.on{color:var(--primary);font-weight:600;border-bottom-color:var(--primary)}
.dw-tab .n{font-size:var(--fs-sm);background:#eef2f7;color:var(--muted);border-radius:20px;
  padding:var(--sp-1) var(--sp-2);min-width:18px;text-align:center;
  transition:background-color .16s ease,color .16s ease}
.dw-tab.on .n{background:var(--primary-soft);color:var(--primary)}
.dw-tab .n.zero{opacity:.45}

.dw-body{flex:1;overflow:auto;padding:var(--sp-3) var(--sp-4) var(--sp-4);-webkit-overflow-scrolling:touch}
.dw-body .empty{padding:var(--sp-8) 0;text-align:center;color:var(--muted);font-size:var(--fs-body)}

/* 概况：右对齐标签的两列字段表 */
.dw-fields{width:100%;font-size:var(--fs-body)}
.dw-fields th{width:118px;background:transparent;color:var(--muted);font-weight:500;
  padding:var(--sp-2) var(--sp-3) var(--sp-2) 0;border-bottom:1px solid var(--bg-2);vertical-align:top;text-align:left}
.dw-fields td{padding:var(--sp-2) 0;border-bottom:1px solid var(--bg-2);word-break:break-word}
/* 详情里就地编辑的多选：整格都是可点的，右侧留一点余量免得贴着边 */
.dw-multi{padding-right:var(--sp-1)}

/* 关联对象小表格：整行可点 = 下钻到那条单据 */
.dw-tblwrap{overflow:auto;border:1px solid var(--line);border-radius:10px}
.dw-tbl{width:100%;font-size:var(--fs-body)}
.dw-tbl th{background:var(--bg-2);padding:var(--sp-2) var(--sp-3);font-weight:600;white-space:nowrap}
.dw-tbl td{padding:var(--sp-2) var(--sp-3);border-bottom:1px solid var(--bg-2);white-space:nowrap;
  font-variant-numeric:tabular-nums}
.dw-tbl tbody tr{cursor:pointer;transition:background-color .14s ease}
.dw-tbl tbody tr:hover{background:var(--primary-soft)}
.dw-tbl tbody tr:hover .dw-first{color:var(--primary)}
.dw-tbl tbody tr:last-child td{border-bottom:none}
.dw-first{font-weight:600}
.dw-tot{display:flex;gap:var(--sp-4);flex-wrap:wrap;font-size:var(--fs-sm);color:var(--muted);padding:var(--sp-2) var(--sp-1) 0}
.dw-tot b{color:var(--text);font-variant-numeric:tabular-nums}
.dw-more{font-size:var(--fs-sm);padding-top:var(--sp-2)}

.dw-skel{display:flex;flex-direction:column;gap:var(--sp-3);padding:var(--sp-2) 0}
.dw-skel i{height:14px;border-radius:6px;
  background:linear-gradient(90deg,var(--bg-2),#e9edf3,var(--bg-2));
  background-size:400% 100%;animation:shimmer 1.3s ease-in-out infinite}
.dw-skel i:nth-child(2){width:78%}
.dw-skel i:nth-child(3){width:88%}
.dw-skel i:nth-child(4){width:58%}

.dw-foot{flex-shrink:0;display:flex;align-items:center;gap:var(--sp-2);padding:var(--sp-3) var(--sp-4);
  border-top:1px solid var(--line);background:var(--bg);
  padding-bottom:calc(10px + env(safe-area-inset-bottom))}
.dw-sp{flex:1}

/* ---- 16. 列表：翻页/筛选后的内容轻淡入 ----
   ⚠ v1.9.5 删掉了这里原本的「行悬浮左侧指示条」：整条规则挂的是 `.rowlink`，
     而这个 class **在 ui.js / app.js 里从来没被加上过** —— 从写下来那天就是死的，
     谁也没见过那根 3px 的蓝条。而且它是用名为 `barIn` 的关键帧写的，
     和后段提示条的 `@keyframes barIn` **同名不同义**（后者覆盖前者）。
     处理：直接删，不补 class —— 行本来就不能点（只有行内链接能点），
     给一行加"选中感"的蓝条会让用户以为点哪儿都能进详情。

   ⚠⚠ 关于 `#lmain{animation:softIn}`：**写了很久，但只在整页 render 时播过一次。**
     CSS 动画挂在元素自己身上，**换它的 innerHTML 不会让动画重播** ——
     lrefresh() 和 lquick() 都只是 `#lmain.innerHTML = …`，
     所以"翻页/筛选后内容轻淡入"这句注释描述的效果，之前**一次都没出现过**。
     它唯一真正生效的场合是首屏：而那时 `#vbody` 也在淡入（viewIn），
     两层嵌套 opacity 相乘 → 0.55 × 0.55 ≈ 0.30，首屏反而发灰。
   现在分叉成三件事：
     · lrefresh()（翻页 / 筛选 / 清空关键词 / 保存后）→ 新建一层 .lmain-soft，
       新节点必然播动画，"刷新后轻淡入"这才第一次真的出现；
     · 首屏 → 不挂（只让 #vbody 淡一层，不再两层相乘）；
     · lquick()（边打字边搜）→ 不挂（搜索是 100+/天的动作，列表不该跟着透明度动）。
   ⚠ 别改回把 animation 挂 #lmain —— 那样刷新时又什么都不播了。 */
.lmain-soft{animation:softIn .18s var(--ease-out) backwards}
@keyframes softIn{from{opacity:.55}to{opacity:1}}

/* ==========================================================================
   15. 细节统一：控件同高 · 下拉多选 · 用户表 · 审批节点表 · 批量换人
   "错位"几乎都不是玄学，就三件事：控件高度不一、列宽被内容随便撑、
   行内元素按基线对齐。这一节把它们逐条钉死。
   ========================================================================== */

/* ---- 控件基准：所有输入类控件同高，横着排的时候底边自然齐 ---- */
input:not([type=checkbox]):not([type=radio]),select{min-height:34px;line-height:1.35}
input::placeholder{color:var(--muted)}
select{cursor:pointer}

/* ---- 表格通用：表头更轻，操作列不挤成团 ---- */
th{font-size:var(--fs-sm);letter-spacing:.2px}
.r,th.r,td.r{text-align:right}
.td-acts{display:inline-flex;gap:var(--sp-2);align-items:center;justify-content:flex-end;white-space:nowrap}

/* ---- 用户与角色：一键保存条 + 待保存标记 ----
   用户诉求：一次性改几个账号，按一次保存，不要一行行点。
   改动过的行左侧描边 + 姓名旁一个圆点，改了什么一眼能看到。 */
.u-savebar{display:flex;align-items:center;gap:var(--sp-3);flex-wrap:wrap;
  padding:var(--sp-3) var(--sp-4);margin:var(--sp-3) 0;
  background:var(--primary-soft);border:1px solid #f2dbc9;border-radius:10px;
  color:var(--text)}
.u-savebar b{color:var(--primary)}
.u-savebar button{margin-left:auto}
.u-savebar button+button{margin-left:0}
table.utable tr.u-dirty{background:var(--warn-soft);box-shadow:inset 3px 0 0 var(--warn)}
.u-dot{color:var(--warn);margin-left:var(--sp-2);font-size:var(--fs-sm)}
/* 窄屏：保存条按钮占满一行，别挤成一小条 */
@media(max-width:768px){
  .u-savebar button{margin-left:0;flex:1 1 auto;min-height:44px}
  table.utable tr.u-dirty{box-shadow:inset 3px 0 0 var(--warn)}
}
.tbl-wrap{overflow-x:auto}
table.mini{font-size:var(--fs-body);border:1px solid var(--line);border-radius:8px;overflow:hidden}
table.mini th{background:var(--bg-2)}
table.mini td,table.mini th{padding:var(--sp-2) var(--sp-3)}
table.mini tr:last-child td{border-bottom:none}

/* ---- 卡片标题：加一条细线，一屏多卡时能一眼分块 ---- */
.card>h2{padding-bottom:var(--sp-3);border-bottom:1px solid var(--line)}

/* ---- 下拉多选：用户角色、流程「指定成员」共用 ----
   原生 <select multiple> 是列表框（撑高表格 + 看不见选了什么），这里换成
   按钮显示已选标签、点开是复选面板。悬停桥同 .dd-menu，避免鼠标穿缝时闪没。 */
.ms{position:relative;display:inline-block;min-width:132px;max-width:100%}
/* ⚠ 必须显式 height:auto：全站 button 是 height:32px，这里只写 min-height
   会被那个 32 锁住 —— 选中两个角色时标签换行、溢出到下一行，
   看起来像"角色标签跑到别人那一格里去了"（v2.1 从截图里抓出来的）。 */
.ms-btn{display:flex;align-items:center;gap:var(--sp-1);flex-wrap:wrap;width:100%;
  height:auto;min-height:34px;
  padding:var(--sp-1) var(--sp-2);background:#fff;border:1px solid var(--line);border-radius:6px;
  color:var(--text);text-align:left;cursor:pointer;font-size:var(--fs-body);cursor:pointer;
  transition:border-color .16s ease,box-shadow .16s ease}
.ms-btn:hover{border-color:var(--primary);color:var(--text)}
.ms.open>.ms-btn{border-color:var(--primary);box-shadow:0 0 0 3px rgba(184,80,31,.12)}
.ms-ph{color:var(--muted);flex:1;min-width:52px}
.ms-tag{background:var(--primary-soft);color:var(--primary-d);border-radius:20px;
  padding:var(--sp-1) var(--sp-2);font-size:var(--fs-sm);white-space:nowrap}
.ms-more{color:var(--muted);font-size:var(--fs-sm)}
.ms-caret{margin-left:auto;color:var(--muted);font-size:var(--fs-micro);
  transition:transform .18s var(--ease-out)}
.ms.open .ms-caret{transform:rotate(180deg)}
.ms-pop{display:none;position:absolute;top:calc(100% + 6px);left:0;z-index:45;
  min-width:100%;max-height:280px;overflow:auto;background:#fff;border:1px solid var(--line);
  border-radius:10px;box-shadow:0 12px 32px rgba(62,44,32,.14);padding:var(--sp-2);
  transform-origin:top left}
/* ⚠ 面板不能只有 min-width:100% —— 触发按钮在表格里只有一百多像素宽，
   面板跟着那么窄，「未分配角色」「清空」都会被折成两行（2026-11-08 用户截图）。
   给一个绝对下限：面板宽度取 max(触发器宽度, 200px)。 */
.ms{--ms-min:200px}
.ms-pop{min-width:max(100%, var(--ms-min))}
.ms-pop::before{content:"";position:absolute;left:0;right:0;top:-8px;height:8px}
.ms.open>.ms-pop{display:block;animation:ddIn .15s var(--ease-out) both}
.ms.up>.ms-pop{top:auto;bottom:calc(100% + 6px);transform-origin:bottom left}
.ms.up>.ms-pop::before{top:auto;bottom:-8px}
.ms-head{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-2);
  white-space:nowrap;flex-wrap:nowrap;
  padding:var(--sp-1) var(--sp-2) var(--sp-2);border-bottom:1px solid var(--line);margin-bottom:var(--sp-1)}
.ms-head a{font-size:var(--fs-sm)}
.ms-opt{display:flex;align-items:center;gap:var(--sp-2);padding:var(--sp-2) var(--sp-2);border-radius:6px;
  cursor:pointer;font-size:var(--fs-body);color:var(--text);margin:0;transition:background-color .14s ease}
.ms-opt:hover{background:var(--bg)}
.ms-opt input{margin:0;flex-shrink:0;accent-color:var(--primary)}
.ms-opt input:checked+.ms-tx{font-weight:600;color:var(--primary)}
.ms-opt .ms-tx{white-space:nowrap}

/* ---- 输入即搜：订单 / 明细 / 商家这类会涨到上千条的字典 ----
   原生 <select> 把几百条全铺出来，用户翻不动也记不住编号（实际反馈：
   "订单太多了 他们可能选不过来"）。这里做成「输入框 + 候选面板」，
   边打边搜，候选项带副标题（商家 · 业务类型 · 月份），按商家名也能搜到。 */
.cmb{position:relative}
.cmb-in{width:100%}
.cmb-pop{display:none;position:absolute;top:calc(100% + 4px);left:0;right:0;z-index:60;
  max-height:264px;overflow:auto;background:#fff;border:1px solid var(--line);
  border-radius:10px;box-shadow:0 12px 32px rgba(62,44,32,.14);padding:var(--sp-1)}
.cmb-pop.on{display:block;animation:ddIn .14s var(--ease-out) both}
.cmb-opt{padding:var(--sp-2) var(--sp-2);border-radius:6px;cursor:pointer}
.cmb-opt:hover{background:var(--bg)}
.cmb-nm{font-size:var(--fs-body);color:var(--text);line-height:1.4}
.cmb-sub{font-size:var(--fs-sm);color:var(--muted);margin-top:var(--sp-1);line-height:1.4}
.cmb-empty{padding:var(--sp-3);color:var(--muted);font-size:var(--fs-body);text-align:center}

/* ---- 表单字段的说明：圆形角标 + 悬停气泡 ----
   以前每格下面压一段中文说明，一屏二十格就是二十段，扫一遍全在说明里，
   真正要填的格子反而被淹掉。现在只在标签旁挂一个圆形「i」，指向它才弹出来，
   键盘 Tab 到也能弹 —— 说明一句没少，只是换了个位置。 */
.f-hint{font-size:var(--fs-sm);color:var(--muted);margin-top:var(--sp-1);line-height:1.45}
.f-req{color:#c2410c;font-weight:700;margin-left:var(--sp-1)}
/* 单位留在格子上：「元」「0.06 = 6%」是动手之前就得看见的，不能也塞进气泡 */
.f-unit{font-weight:400;color:var(--muted);font-size:var(--fs-sm);margin-left:var(--sp-1)}
.f-tip{display:inline-flex;align-items:center;justify-content:center;
  width:14px;height:14px;margin-left:var(--sp-1);border-radius:50%;
  background:#f6e3d6;color:var(--primary-d);font-size:var(--fs-micro);font-weight:700;font-style:normal;
  line-height:1;cursor:help;flex:none;border:1px solid #f2dbc9;
  vertical-align:middle;outline:none;user-select:none}
.f-tip::before{content:"i"}
.f-tip:hover,.f-tip:focus{background:var(--primary-d);color:#fff;border-color:var(--primary-d)}
/* 气泡挂在 body 上（不是 ::after）：弹层里有滚动容器，伪元素会被裁掉也贴不准边 */
#tip-pop{position:fixed;z-index:9999;max-width:340px;padding:var(--sp-2) var(--sp-3);
  background:var(--text);color:var(--bg-2);border-radius:8px;font-size:var(--fs-body);line-height:1.6;
  box-shadow:0 10px 28px rgba(62,44,32,.28);pointer-events:none;
  opacity:0;transform:translateY(3px);transition:opacity .12s ease,transform .12s ease;
  white-space:normal;word-break:break-word;text-align:left}
#tip-pop.on{opacity:1;transform:none}
.foot-tip .f-tip{margin-left:0}
/* 自动带不出来的时候，当场说清楚为什么、去哪儿补 —— 只留空格子等于让人瞎填 */
.f-warn{display:flex;align-items:flex-start;gap:var(--sp-2);margin-top:var(--sp-1);font-size:var(--fs-sm);
  color:#c2410c;background:var(--warn-soft);border:1px solid #f4e4c2;
  border-radius:7px;padding:var(--sp-1) var(--sp-2);line-height:1.45}
.fw-ic{flex:none;display:inline-flex;align-items:center;justify-content:center;
  width:14px;height:14px;border-radius:50%;background:var(--warn);color:#fff;
  font-size:var(--fs-micro);font-weight:700;line-height:1;margin-top:var(--sp-1)}
.from-tip{background:var(--primary-soft);color:var(--primary-d);border-radius:8px;
  padding:var(--sp-2) var(--sp-3);font-size:var(--fs-body);margin-bottom:var(--sp-3)}

/* ---- 人事总览 / 人效：KPI 与操作区分开排 ----
   之前 KPI 和「月份 + 切换月份 + 导入考勤 + 上传绩效」全塞进一个 .row（flex），
   一共 9 个元素抢同一行，窄屏一换行就出现「按钮压着 select」的错位。
   改成两段：左边一排 KPI，右边一撮操作；窄屏时整体竖排，
   操作区自己贴右，不会插进 KPI 中间。 */
.hr-toolbar{display:flex;gap:var(--sp-4);flex-wrap:wrap;align-items:flex-end;
  justify-content:space-between;margin-top:var(--sp-3)}
.hr-kpis{display:flex;gap:var(--sp-3);flex-wrap:wrap;flex:1 1 auto}
/* 统计卡等宽：原来 flex 里不设 flex-grow，卡宽由内容决定 ——
   「40 在职人数」比「¥99,140.00 应发合计」窄 9px，五个并排参差不齐。
   数字长度本来就不该影响卡片宽度。 */
.hr-kpis>.stat{flex:1 1 0;min-width:118px}
.hr-actions{display:flex;gap:var(--sp-2);align-items:center;flex:0 0 auto;flex-wrap:wrap}
.hr-mlabel{font-size:var(--fs-body);color:var(--text)}
.hr-month{width:132px;height:34px;padding:0 var(--sp-2);border:1px solid var(--line);
  border-radius:8px;background:#fff;font-size:var(--fs-body);color:var(--text)}
.hr-actions button{height:34px}
@media(max-width:1100px){
  .hr-toolbar{flex-direction:column;align-items:stretch}
  .hr-actions{justify-content:flex-end}
}

/* 图表卡片：SVG 自适应宽度，别让图撑破卡片 */
.chart-card svg{display:block;width:100%;height:auto;overflow:visible}
.chart-card h2{display:flex;align-items:center;gap:var(--sp-2)}

/* 「自定义…」的补充输入框 */
.f-custom{margin-top:var(--sp-2)}

/* ---- 表单分段 / 多选 / 附件 / 金额拆算 ----
   一屏二十个平铺格子没人看得懂（"名称和角色都需要有备注写清楚代表什么"）。
   按「基本信息 / 开票信息 / 附件」分段之后，员工知道哪一段该填什么。
   多选、附件、只读编号这几个新控件也统一在这里。 */
.fsec{grid-column:1 / -1;display:grid;grid-template-columns:1fr 1fr;gap:var(--sp-3);
  border:1px solid var(--line);border-radius:10px;
  padding:var(--sp-3) var(--sp-3);margin-bottom:var(--sp-3);background:#fbfcff}
.fsec .field{margin-bottom:0}
.fsec-t{grid-column:1 / -1;font-size:var(--fs-body);font-weight:600;color:var(--primary-d);
  margin-bottom:var(--sp-1);padding-bottom:var(--sp-2);border-bottom:1px dashed var(--line)}
/* ---- 多选（参与人 / 商家角色）----
   候选曾经全部平铺成一排胶囊：人一多就把表单撑出去大半屏，找人靠眼睛扫。
   现在是「已选标签 + 点开筛选」：输入框始终留在标签行末尾，
   点它弹候选、边打边筛，点候选即勾。 */
.f-multi{position:relative}
.ms-box{display:flex;flex-wrap:wrap;align-items:center;gap:var(--sp-1);width:100%;
  min-height:36px;padding:var(--sp-1) var(--sp-2);background:#fff;cursor:text;
  border:1px solid var(--line);border-radius:9px}
.ms-box:hover{border-color:#f2dbc9}
.ms-box:focus-within{border-color:var(--primary);
  box-shadow:0 0 0 3px rgba(184,80,31,.10)}
.ms-tags{display:flex;flex-wrap:wrap;gap:var(--sp-1);align-items:center}
.ms-ph{color:var(--muted);font-size:var(--fs-body)}
.ms-tag{display:inline-flex;align-items:center;gap:var(--sp-1);background:var(--primary-soft);
  border:1px solid #f2dbc9;color:var(--primary-d);font-size:var(--fs-body);font-weight:600;
  border-radius:999px;padding:var(--sp-1) var(--sp-1) var(--sp-1) var(--sp-3);line-height:1.4}
.ms-x{color:#6366f1;text-decoration:none;font-size:var(--fs-body);line-height:1;
  padding:0 var(--sp-1);border-radius:50%;font-weight:700}
.ms-x:hover{background:#f6e3d6;color:var(--primary-d)}
.ms-q{flex:1 1 110px;min-width:90px;border:0;outline:none;background:transparent;
  padding:var(--sp-1) var(--sp-1);font-size:var(--fs-body)}
.ms-q::placeholder{color:var(--muted)}
.ms-drop{display:none;position:absolute;top:calc(100% + 4px);left:0;right:0;z-index:60;
  max-height:264px;overflow:auto;background:#fff;border:1px solid var(--line);
  border-radius:10px;box-shadow:0 12px 32px rgba(62,44,32,.14);padding:var(--sp-1)}
.ms-drop.on{display:block;animation:ddIn .14s var(--ease-out) both}
.ms-o{display:flex;align-items:center;gap:var(--sp-2);padding:var(--sp-2) var(--sp-2);border-radius:6px;
  cursor:pointer;font-size:var(--fs-body);line-height:1.4}
.ms-o:hover{background:var(--bg)}
.ms-o.on{background:var(--primary-soft);color:var(--primary-d);font-weight:600}
.ms-o input{margin:0;accent-color:var(--primary)}
input.ro{background:var(--bg-2);color:var(--muted);cursor:not-allowed}
.f-file{border:1px dashed var(--line);border-radius:9px;padding:var(--sp-2) var(--sp-3);background:#fff}
.ff-list:not(:empty){margin-bottom:var(--sp-2)}
.ff-item{display:flex;align-items:center;gap:var(--sp-2);font-size:var(--fs-body);padding:var(--sp-1) 0;line-height:1.45}
.ff-item a{color:var(--primary-d);text-decoration:none;overflow:hidden;text-overflow:ellipsis;
  white-space:nowrap;min-width:0}
.ff-item a:hover{text-decoration:underline}
.ff-sz{color:var(--muted);font-size:var(--fs-sm);flex:none}
.ff-del{color:var(--danger);flex:none;padding:0 var(--sp-1);font-size:var(--fs-body);line-height:1;text-decoration:none}
.ff-loading{color:var(--muted)}
.ff-err{color:var(--danger)}
.ff-btn{font-size:var(--fs-body);padding:var(--sp-1) var(--sp-3)}
.f-calc{margin-top:var(--sp-1);font-size:var(--fs-sm);color:var(--primary-d);background:var(--primary-soft);
  border-radius:7px;padding:var(--sp-1) var(--sp-2);line-height:1.55}
.f-calc:empty{display:none}
.fc-line b{font-weight:600}
.fc-dim{color:#4b5563;font-size:var(--fs-sm)}
.fc-wait{color:#c2410c;font-size:var(--fs-sm)}
.fc-bad{color:var(--danger);font-size:var(--fs-sm);margin-top:var(--sp-1);font-weight:600}
/* 代收代付自动算出来的含税金额：置灰表示"不用你填"，但仍可选中复制 */
input[readonly]:not(.ro){background:var(--bg-2);color:var(--muted);cursor:not-allowed}
.foot-tip{font-size:var(--fs-sm);color:var(--muted);margin-right:auto;align-self:center;line-height:1.4}

/* ---- 详情抽屉里的「从当前单据发起」工具条 ----
   业务上是串行推进的（订单 → 明细 → 收款计划），入口就摆在上级单据里，
   省掉"回列表页新建、再自己去找那张订单挂上"这一段。 */
.dw-tblbar{display:flex;align-items:center;gap:var(--sp-3);padding:0 0 var(--sp-3)}
.dw-tblbar .muted{font-size:var(--fs-sm)}
.empty-act{margin-top:var(--sp-3)}

/* ---- 用户与角色表：列宽有主次 ----
   实测（Edge 无头量 getBoundingClientRect）：th 与 td 的 padding 都是 10px，
   表头文字和格子里的控件左边缘本来就严丝合缝，所以「错位」不在这里。
   真正难看的是列宽配错了：角色列给了 214px，而里面最宽的控件只有 132px
   （.ms 的 min-width），白白空掉 80 多像素；旁边「飞书身份」只有 86px 挤着。
   按内容重配一遍，把余量还给窄列。 */
table.utable th:nth-child(1){width:108px}
table.utable th:nth-child(2){width:112px}
table.utable th:nth-child(3){width:110px}
table.utable th:nth-child(4){width:158px}
table.utable th:nth-child(5){width:118px}
table.utable th:nth-child(6){width:118px}
table.utable th:nth-child(7){width:94px}
table.utable th:nth-child(8){width:110px}
table.utable th:nth-child(9){width:132px}
table.utable td{vertical-align:top}
table.utable td.u-name{vertical-align:middle}
table.utable select{width:100%}
select.usel-on{color:var(--ok);border-color:#bbf7d0;background:var(--ok-soft)}
select.usel-off{color:var(--muted);background:var(--bg-2)}

/* ---- 审批节点表：列宽定死 + 「审批人」格内横排 ----
   之前审批人那一格是「下拉 130px + 参数控件」两个行内元素，
   窄一点就各自换行，整行高度忽高忽低，看着就是错位。

   v0.5.5 又量了一次：默认弹层只有 760px（.modal 的默认宽度），
   7 列可编辑控件平均只能分到 96px —— 「生效条件」输入框实测只有 87px，
   而 placeholder「留空=总是生效」要 107px，直接被切掉一个字，
   截图里就是「留空=总是执」。所以这个弹层单独放宽到 1040px（modal-wide），
   再按内容重配列宽。 */
table.node-tbl{table-layout:fixed}
table.node-tbl col.c-i{width:40px}
table.node-tbl col.c-t{width:90px}
table.node-tbl col.c-n{width:200px}
table.node-tbl col.c-w{width:250px}
table.node-tbl col.c-m{width:100px}
table.node-tbl col.c-c{width:190px}
table.node-tbl col.c-a{width:124px}
table.node-tbl td{vertical-align:middle}
table.node-tbl td>input,table.node-tbl td>select{width:100%;min-width:0}
.nctl{display:flex;align-items:center;gap:var(--sp-2);flex-wrap:wrap;min-width:0}
/* 类型下拉固定宽度：让它跟着右边那格的内容伸缩的话，
   「指定角色」行和「自动解析」行的下拉会一宽一窄，同一列看起来就是参差不齐。 */
.nctl>select{flex:0 0 116px;width:auto}
.nctl>.ncfg{flex:1 1 128px;min-width:0;display:block}
.nctl>.ncfg>select,.nctl>.ncfg>input{width:100%}
.nctl>.ncfg>.ms{width:100%;min-width:0}
.n-idx{color:var(--muted);font-variant-numeric:tabular-nums}

/* ---- 业务流：状态定义表 + 流转矩阵 ----
   同一类毛病：6 列全是勾选框的表格交给自动列宽排，勾选框被摊在很宽的格子里，
   左一个右一个，看着像没对齐。列宽定死、勾选列居中，一眼就能看出哪格有勾。
   流转矩阵列数随状态数增长，套 .tbl-wrap 让它自己横滑，不撑破弹层。 */
table.bf-tbl{table-layout:fixed}
table.bf-tbl col.c-i{width:42px}
table.bf-tbl col.c-s{width:auto}
table.bf-tbl col.c-b{width:76px}
table.bf-tbl col.c-e{width:150px}
table.bf-tbl col.c-l{width:136px}
table.bf-tbl col.c-a{width:64px}
table.bf-tbl td,table.bf-tbl th{vertical-align:middle}
.bf-tbl .bc,.bm-tbl .bc{text-align:center}
.bf-tbl .bc input[type=checkbox],.bm-tbl .bc input[type=checkbox]{
  margin:0;width:15px;height:15px;accent-color:var(--primary);vertical-align:middle}
.bm-tbl{font-size:var(--fs-body)}
.bm-tbl th{background:var(--bg-2);white-space:nowrap;padding:var(--sp-2) var(--sp-3)}
.bm-tbl td{padding:var(--sp-2) var(--sp-3)}
/* 表头跟格子里的勾选框对齐：首列还是左对齐（那是行标题） */
.bm-tbl thead th{text-align:center}
.bm-tbl thead th:first-child,.bm-tbl tbody th{text-align:left}

/* ---- 批量换人：标签、下拉、按钮落在同一条基线上 ---- */
.row.form-inline{align-items:flex-end;gap:var(--sp-3)}
.form-inline .fi{min-width:172px;flex:0 0 auto}
.form-inline .fi select{width:100%}
.form-inline .arw2{color:var(--muted);font-size:var(--fs-body);padding-bottom:var(--sp-2)}
.form-inline .fi-sp{flex:1 1 12px}
.form-inline .fi-btns{display:flex;gap:var(--sp-2)}

/* 确认类弹层：窄一点，读起来不像一张长表格 */
.mask.modal-narrow .modal{width:560px}

/* 宽表类弹层：审批流节点表 7 列可编辑控件，760px 装不下（见 node-tbl 注释） */
.mask.modal-wide .modal{width:1040px}

/* ==========================================================================
   帐号权限配置页
   左边一列角色（点了就换右边内容），右边按菜单分组摆模块勾选框。
   分组内用自适应网格而不是固定列数：分组宽度差异很大
   （「常用」2 个、「财务管理」11 个），固定列会让长组换行很难看。
   ========================================================================== */
.perm-roles{display:flex;flex-wrap:wrap;gap:var(--sp-2)}
/* ⚠ 必须显式 height:auto：全站 button 是 height:32px，而这张卡是两行内容
   （岗位名 + 人数/范围）。只写 padding 不写 height，卡片被钉死在 32px，
   两行字直接溢出到卡片边框外，第二排还压在卡片底边上 —— 2026-11-08 用户截图报的就是这个。
   同类坑在 .ms-btn 上踩过一次（角色标签溢出单元格）。 */
.perm-role{display:flex;flex-direction:column;align-items:flex-start;gap:var(--sp-1);
  height:auto;min-height:56px;justify-content:center;
  padding:var(--sp-2) var(--sp-4);border:1px solid var(--line);border-radius:10px;background:#fff;
  cursor:pointer;text-align:left;min-width:132px}
.perm-role:hover{border-color:#f2dbc9}
.perm-role.active{border-color:var(--primary);background:var(--primary-soft)}
.perm-role .muted{font-size:var(--fs-sm)}

/* 数据范围三档：横向三张卡，选中态用主色描边。
   不用下拉框 —— 三个选项的含义差别很大（谁看得见谁的数），
   藏进下拉里，管理员会懒得更认真地看说明。 */
.scope-pick{display:grid;gap:var(--sp-2);grid-template-columns:repeat(auto-fit,minmax(210px,1fr))}
.scope-i{display:flex;flex-direction:column;align-items:flex-start;gap:var(--sp-1);
  height:auto;min-height:64px;justify-content:center;text-align:left;
  padding:var(--sp-2) var(--sp-4);border:1px solid var(--line);border-radius:10px;
  background:#fff;cursor:pointer}
.scope-i:hover{border-color:#f2dbc9;background:#fff}
.scope-i.on{border-color:var(--primary);background:var(--primary-soft)}
.scope-i.on b{color:var(--primary-d)}
.scope-i .muted{font-size:var(--fs-sm);white-space:normal;line-height:1.5}

.perm-bar{display:flex;align-items:center;gap:var(--sp-3);flex-wrap:wrap;margin-bottom:var(--sp-3)}
.perm-all{display:flex;align-items:center;gap:var(--sp-2);cursor:pointer}
.perm-all input{width:15px;height:15px;margin:0}

.perm-group{margin-bottom:var(--sp-4);padding-bottom:var(--sp-3);border-bottom:1px solid var(--line)}
.perm-group:last-child{border-bottom:none;margin-bottom:0;padding-bottom:0}
.perm-group-h{display:flex;align-items:baseline;gap:var(--sp-3);margin-bottom:var(--sp-2)}
.perm-group-h .muted{display:flex;gap:var(--sp-3);font-size:var(--fs-sm)}
.perm-group-h a{color:var(--primary);cursor:pointer}

.perm-grid{display:grid;gap:var(--sp-2);
  grid-template-columns:repeat(auto-fill,minmax(158px,1fr))}
.perm-box{display:flex;align-items:center;gap:var(--sp-2);padding:var(--sp-2) var(--sp-3);
  border:1px solid var(--line);border-radius:8px;cursor:pointer;font-size:var(--fs-body);
  user-select:none}
.perm-box:hover{background:var(--bg)}
.perm-box.on{border-color:#a5b4fc;background:var(--primary-soft)}
.perm-box input{width:15px;height:15px;margin:0;flex:0 0 auto}
.perm-box span:first-of-type{flex:1 1 auto;min-width:0}
.perm-box .tag{flex:0 0 auto}

/* ==========================================================================
   14. 自动适配：同一套页面，窄屏切成手机版、宽屏保持电脑版
   断点 768px 是"单手能握"的分界；再往下 560px 只调密度不改结构。
   不需要写两套页面，也不需要 UA 判断——宽度变了版面就跟着变。
   ========================================================================== */
@media (max-width:768px){
  html{-webkit-text-size-adjust:100%}

  /* ---- 顶栏：品牌收窄 + 主导航横向滑动 ---- */
  .navbar{height:52px;padding:0 var(--sp-3);gap:var(--sp-2)}
  .logo{margin-right:var(--sp-1)}
  .logo-text{display:none}
  .navbar-left{flex:1 1 auto;min-width:0}
  .navbar-right{gap:var(--sp-2);flex-shrink:0}
  .console-title{font-size:var(--fs-body)}
  .console-title .crumb{display:none}
  /* 窄屏顶栏放不下全部菜单：让它横向滑，而不是把文字挤成一坨或换行顶破高度 */
  .nav-main{flex:1 1 auto;min-width:0;overflow-x:auto;overflow-y:visible;
    scrollbar-width:none;-webkit-overflow-scrolling:touch}
  .nav-main::-webkit-scrollbar{display:none}
  .nav-item{padding:var(--sp-2) var(--sp-2);font-size:var(--fs-body)}
  /* 药丸版顶栏项在窄屏收窄左右内边距；这里必须写成 > 直系选择器，
     否则打不过上面 .nav-main>.nav-item 的优先级，窄屏会按桌面尺寸撑宽 */
  .nav-main>.nav-item,.nav-main>.dd>.dd-toggle{padding:var(--sp-1) var(--sp-2)}
  .nav-item.ghost{padding:var(--sp-2) var(--sp-2)}
  /* 「管理后台」按钮挪进头像菜单，顶栏只留待办 + 头像 */
  .navbar-right .pill[data-act="switch-mode"],
  .navbar-right .pill[data-act="admin-denied"]{display:none}
  /* 下拉菜单：父级 .nav-main 是横向滚动容器，absolute 会被裁掉 → 改用 fixed 贴视口 */
  .nav-main .dd-menu{position:fixed;top:54px;left:8px;right:8px;width:auto;min-width:0;
    max-height:66vh;overflow-y:auto;-webkit-overflow-scrolling:touch;
    box-shadow:0 18px 44px rgba(62,44,32,.2)}
  .avatar-menu .dd-menu.right{position:fixed;top:54px;right:8px;left:auto;
    min-width:200px;max-width:calc(100vw - 16px)}

  /* ---- 主体 ---- */
  .app-body{flex-direction:column}
  .content{padding:var(--sp-3) var(--sp-3) calc(26px + env(safe-area-inset-bottom))}
  .page-head{padding-bottom:var(--sp-2);margin-bottom:var(--sp-3)}
  .page-head b{font-size:var(--fs-h2)}
  .card{padding:var(--sp-3) var(--sp-3);margin-bottom:var(--sp-3)}
  .card h2{font-size:var(--fs-body)}

  /* ---- 管理后台左栏：竖列改成横向滑动的标签条 ---- */
  .admin-side{width:100%;flex:0 0 auto;border-right:none;border-bottom:1px solid var(--line);
    display:flex;overflow-x:auto;padding:var(--sp-2) var(--sp-2);gap:var(--sp-1);scrollbar-width:none}
  .admin-side::-webkit-scrollbar{display:none}
  .side-brand,.side-group{display:none}
  /* 窄屏是横向标签条：高亮条应该在下边（border-bottom），
     而伪元素是贴左边的竖条 —— 必须关掉，否则宽窄屏两套高亮打架 */
  .side-item{padding:var(--sp-2) var(--sp-3);border-bottom:2px solid transparent;
    border-radius:6px;white-space:nowrap;font-size:var(--fs-body)}
  .side-item::before{display:none}
  .side-item:hover::before{display:none}
  .side-item.active::before{display:none}
  .side-item.active{border-bottom-color:var(--primary)}

  /* ---- 宽表折成卡片：每个 td 用 data-l 把列名画在值前面 ---- */
  .ltwrap{background:transparent;border:none;padding:0;margin-bottom:0;box-shadow:none}
  .ltwrap:hover{box-shadow:none}
  .ltable,.ltable tbody{display:block;width:100%}
  .ltable thead{display:none}
  .ltable tr{display:flex;flex-wrap:wrap;gap:var(--sp-1) 0;position:relative;
    border:1px solid var(--line);border-radius:10px;margin-bottom:var(--sp-3);padding:var(--sp-2) var(--sp-3) var(--sp-2);background:#fff}
  .ltable td{display:flex;align-items:baseline;gap:var(--sp-3);width:50%;border:none;padding:var(--sp-1) 0;
    font-size:var(--fs-body);white-space:normal;max-width:none;overflow:visible;text-overflow:clip}
  .ltable td::before{content:attr(data-l);flex:0 0 auto;min-width:64px;
    color:var(--muted);font-size:var(--fs-sm)}
  /* 勾选框挪到卡片右上角；第 1 个数据列当卡片标题 */
  .ltable td.lpick{position:absolute;top:6px;right:8px;width:auto;padding:0}
  .ltable td.lpick::before{display:none}
  .ltable td.ltitle{width:100%;font-weight:600;font-size:var(--fs-body);padding:0 var(--sp-6) var(--sp-2) 0}
  .ltable td.ltitle::before{display:none}
  /* 列多的对象（订单明细这类十几二十列）：先给 6 个字段，其余收起来自己点开 */
  .ltable td.lmore-td{display:none}
  .ltable tr.lmore-on td.lmore-td{display:flex}
  .ltable td.lmore-btn{display:flex;width:100%;justify-content:center;padding:var(--sp-1) 0 0}
  .ltable td.lmore-btn::before{display:none}
  .ltable td.lmore-btn a{color:var(--primary);font-size:var(--fs-sm);
    padding:var(--sp-1) var(--sp-3);border:1px solid var(--line);border-radius:20px}
  .ltable tr.lmore-on td.lmore-btn a::after{content:"（收起）"}
  .ltable td.r{text-align:left}

  /* 其余普通表格：不折卡片，改成左右可滑，至少不撑破页面。
     注意排除 .tbl-wrap 里的表——那些表由外层容器负责横滑，这里再改一次 display
     会把 table-layout 打散，列宽失效反而被挤成一字一竖。 */
  .content table:not(.ltable):not(.tbl-wrap table),.modal>table:not(.ltable):not(.tbl-wrap table){
    display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}
  /* 后台的表（审批权限矩阵、变更日志）：给个下限，宁可横滑也不把「40.00 ~ 50,000.00」
     折成两行、把「能否」拆成两个字。 */
  .tbl-wrap>table{min-width:660px}
  th,td{white-space:normal}
  .ltotals{padding:var(--sp-3) var(--sp-3)}

  /* ---- 弹层：手机上做成全屏，底部按钮常驻 ---- */
  .mask{padding:0;align-items:stretch}
  .modal{width:100%;max-width:100%;height:100%;max-height:100%;border-radius:0;
    padding:0 var(--sp-4);overflow:auto;-webkit-overflow-scrolling:touch}
  .modal h2{position:sticky;top:0;background:#fff;margin:0 calc(-1 * var(--sp-4)) var(--sp-3);padding:var(--sp-3) var(--sp-4);
    font-size:var(--fs-h2);z-index:3;border-bottom:1px solid var(--line)}
  .modal .foot{position:sticky;bottom:0;background:#fff;margin:var(--sp-4) calc(-1 * var(--sp-4)) 0;
    padding:var(--sp-3) var(--sp-4) calc(10px + env(safe-area-inset-bottom));
    border-top:1px solid var(--line);gap:var(--sp-3)}
  .modal .foot button{flex:1;min-height:40px}

  /* ---- 详情抽屉：手机改成从底部上滑，占 92vh（上面留一条缝看得见列表） ---- */
  .mask-drawer{align-items:flex-end;background:rgba(62,44,32,.42)}
  .mask-drawer .drawer{width:100%;height:92vh;border-radius:16px 16px 0 0;
    box-shadow:0 -16px 44px rgba(62,44,32,.26);
    animation:dwUp .28s var(--ease-out) both}
  @keyframes dwUp{from{opacity:.5;transform:translateY(28px)}to{opacity:1;transform:none}}
  .dw-head{padding:var(--sp-3) var(--sp-4) var(--sp-2);gap:var(--sp-2)}
  .dw-t1{font-size:var(--fs-body)}
  .dw-tabs-in{padding:0 var(--sp-2)}
  .dw-tab{padding:var(--sp-3) var(--sp-3)}
  .dw-body{padding:var(--sp-3) var(--sp-4) var(--sp-4)}
  .dw-fields th{width:96px;font-size:var(--fs-sm)}
  .dw-tbl td,.dw-tbl th{padding:var(--sp-2) var(--sp-2)}
  .dw-foot{padding:var(--sp-2) var(--sp-4) calc(9px + env(safe-area-inset-bottom));gap:var(--sp-2);flex-wrap:wrap}
  .dw-foot button{min-height:36px}
  .dw-foot .dw-sp{display:none}

  /* ---- 表单与控件：指尖尺寸 + 16px 防 iOS 聚焦自动放大 ---- */
  .two,.fsec{grid-template-columns:1fr}
  .grid,.lfgrid,.kpis,.steps,.skel-cards{grid-template-columns:repeat(auto-fill,minmax(148px,1fr))}
  .grid2{grid-template-columns:1fr}
  input,select,textarea{font-size:16px;padding:var(--sp-2) var(--sp-3)}
  button{min-height:36px}
  button.sm{min-height:30px}
  .seg-i{padding:var(--sp-2) var(--sp-3)}
  .chip{padding:var(--sp-2) var(--sp-3)}
  .kpi,.stat{padding:var(--sp-3) var(--sp-4)}
  .kpi-n{font-size:var(--fs-xl)}

  /* ---- 列表工具条 ---- */
  .lbar{padding:var(--sp-2) var(--sp-3);gap:var(--sp-2)}
  .lbar-l,.lbar-r{width:100%;gap:var(--sp-2)}
  .lsearch{flex:1 1 100%;order:-1}     /* 窄屏搜索独占一行，永远是第一个（手指先够到） */
  .lq{width:100%}
  .ls-kbd{display:none}                 /* 手机没有键盘，「/」提示没意义 */
  .lfpan{padding:var(--sp-3) var(--sp-3)}
  .lpager{gap:var(--sp-2);font-size:var(--fs-sm)}
  .lpager button{min-height:34px}

  /* ---- 下拉多选 ---- */
  .ms{min-width:116px}
  .ms-pop{max-height:52vh;min-width:176px}
  /* 节点表在手机上必然是宽表：整体横滑，别把控件压成一条线。
     列宽合计约 994px（见 node-tbl 的 col 定义），min-width 跟着它走，
     .modal>table 的 overflow-x:auto 才有正确的可滑范围。
     同时把 modal-wide 的 1040px 压回 100%——否则窄屏会被那条高优先级规则撑爆。 */
  table.node-tbl{min-width:1000px}
  .mask.modal-wide .modal{width:100%}

  /* ---- 用户与角色：手机上折成卡片 ----
     这页是 9 列表单（每格一个下拉/按钮），横着一排塞进 400 多像素，
     中文没有词间空格，浏览器只能一字一竖地断行 —— 「飞书身份」会竖成四行，
     登录名被切成两截，部门/上级/来源三列窄得只剩一条线。
     业务列表早就是卡片模式了（.ltable），这里用同一套思路：
     每格靠 data-l 把列名画在值前面，姓名当卡片标题，保存/删除沉到底部。 */
  table.utable,table.utable tbody{display:block;width:100%}
  table.utable thead{display:none}
  table.utable tr{display:block;border:1px solid var(--line);border-radius:10px;
    padding:var(--sp-3) var(--sp-3) var(--sp-2);margin-bottom:var(--sp-3);background:#fff}
  table.utable td{display:flex;align-items:center;gap:var(--sp-3);width:100%;max-width:none;
    border:none;padding:var(--sp-1) 0;text-align:left;white-space:normal;vertical-align:middle}
  table.utable td::before{content:attr(data-l);flex:0 0 66px;color:var(--muted);
    font-size:var(--fs-sm);line-height:1.5}
  table.utable td.u-name{display:block;font-size:var(--fs-h2);padding:0 0 var(--sp-2);
    border-bottom:1px solid var(--bg-2);margin-bottom:var(--sp-1)}
  table.utable td.u-name::before{display:none}
  table.utable td.u-name .muted{display:inline;margin-left:var(--sp-2);font-size:var(--fs-sm)}
  table.utable td.u-ops{display:block;padding:var(--sp-2) 0 0;border-top:1px solid var(--bg-2);margin-top:var(--sp-1)}
  table.utable td.u-ops::before{display:none}
  table.utable td.u-ops .td-acts{display:flex;justify-content:flex-end;gap:var(--sp-2)}
  table.utable td.u-ops .td-acts button{flex:0 0 auto;min-height:36px;padding:0 var(--sp-4)}
  table.utable td>.ms{flex:1;min-width:0}
  table.utable td>select{flex:1;width:auto;min-width:0}

  /* ---- 窄屏可点性修复（实测 390×844 下的真问题）----
     ① 触控目标全部不足 44px：保存/删除按钮只有 36 高、
        角色触发器 38 高、三个下拉 41 高 —— 手指点不准的直接原因。
        一律抬到 44（Apple HIG / Material 的可点下限）。
     ② 角色多选的浮层是绝对定位 + max-height:280px，
        在卡片布局里会盖住下面的「部门 / 直属上级 / 状态」三个控件，
        点它们命中的是浮层里的 .ms-opt —— 肉眼完全看不出为什么没反应。
        窄屏改成原生 select：移动端有系统级选择器，
        不依赖浮层定位，也就没有遮挡问题。 */
  table.utable td.u-ops .td-acts button{min-height:44px;padding:0 var(--sp-4);
    font-size:var(--fs-body)}
  table.utable td>select{min-height:44px}
  /* 角色多选：窄屏隐藏浮层版，改用原生 select（见 ui.js 的窄屏分支） */
  table.utable td>.ms{display:none}
  table.utable td>.ms-native{display:flex;align-items:center;gap:var(--sp-3);
    width:100%}
  table.utable td>.ms-native>span{flex:0 0 66px;color:var(--muted);
    font-size:var(--fs-sm);line-height:1.5}
  table.utable td>.ms-native>select{flex:1;width:auto;min-width:0;min-height:44px}

  /* ---- 批量换人：窄屏改成两行，按钮占满 ---- */
  .row.form-inline{gap:var(--sp-3)}
  .form-inline .fi{min-width:0;flex:1 1 140px}
  .form-inline .arw2{display:none}
  .form-inline .fi-sp{display:none}
  .form-inline .fi-btns{flex:1 1 100%}
  .form-inline .fi-btns button{flex:1;min-height:38px}

  /* ---- 帐号权限配置：手机上角色横滑、模块两列 ---- */
  .perm-roles{flex-wrap:nowrap;overflow-x:auto;scrollbar-width:none;
    -webkit-overflow-scrolling:touch}
  .perm-roles::-webkit-scrollbar{display:none}
  .perm-role{flex:0 0 auto;min-width:118px;padding:var(--sp-2) var(--sp-3)}
  .perm-grid{grid-template-columns:repeat(2,minmax(0,1fr))}
  .perm-box{font-size:var(--fs-body);padding:var(--sp-2) var(--sp-2)}
  .perm-bar{gap:var(--sp-2)}
  .perm-bar .primary{flex:1 1 100%;min-height:38px}

  /* ---- 图表 ---- */
  .bar-row{grid-template-columns:78px 1fr;gap:var(--sp-2)}
  .bar-val{grid-column:1/-1;text-align:left}
  .trend{height:130px;gap:var(--sp-2)}
  .trend-bar{width:11px}
  .lane{flex:0 0 86%;max-height:none}
  .chain{padding:var(--sp-2) var(--sp-3)}
}

/* 更窄的屏：卡片字段改成一列，金额标签不再挤成两截 */
@media (max-width:560px){
  .ltable td{width:100%}
  .kpis,.grid,.lfgrid{grid-template-columns:repeat(2,minmax(0,1fr))}
  .kpi-n{font-size:var(--fs-h1)}
  .content{padding:var(--sp-3) var(--sp-3) calc(26px + env(safe-area-inset-bottom))}
}

/* 平板 / 小笔记本：只放慢节奏，不改结构 */
@media (min-width:769px) and (max-width:1120px){
  .content{padding:var(--sp-4) var(--sp-4) var(--sp-6)}
  .admin-side{width:170px}
  .nav-item{padding:var(--sp-2) var(--sp-2)}
}

/* 手动钉住电脑版（个别手机浏览器视口宽度报不准时的兜底） */
html.force-desktop .app-shell{min-width:1180px}
html.force-desktop .content{overflow-x:visible}

/* ==================================================================
   交互质感层
   ------------------------------------------------------------------
   前面各组件的 :hover 是逐处补的，节奏不统一 —— 有的 0.14s 有的 0.2s，
   缓动各写各的，鼠标划过去时快慢不一，整页看着"反应不太一样"。
   这里把时长/缓动/层级提成设计令牌，各组件统一引用；
   再补上原先漏掉反馈的几类元素（标签、侧栏项、勾选框、提示条、骨架屏）。

   节奏约定（快的给即时反馈，慢的给状态切换，别混用）：
     --dur-1  0.12s  悬停/按下这类"我碰到了"
     --dur-2  0.18s  高亮/展开这类"状态变了"
     --dur-3  0.28s  弹层/抽屉这类"内容换了一层"
   缓动只用两种：
     --ease-out  进场/位移（快起慢收，干净）
     --ease-spring 按钮按下（回弹一下，有物理感）
   ================================================================== */
:root{
  --dur-1:.12s; --dur-2:.18s; --dur-3:.28s;
  --ease-out:cubic-bezier(.23,1,.32,1);
  --ease-spring:cubic-bezier(.34,1.4,.5,1);
  /* 层级：底层面板浮起时压住底，弹层再压一层 */
  --sh-1:0 1px 2px rgba(62,44,32,.05);
  --sh-2:0 4px 16px rgba(62,44,32,.08);
  --sh-3:0 12px 40px rgba(62,44,32,.14);
}

/* 尊重系统的"减少动态效果"设置 —— 前庭敏感的用户不该被动画晃到
   ⚠ v1.9.5 补上两个 delay 的归零。只把 duration 压到 .01ms 是不够的：
     凡是用 backwards 填充的入场动画，在 **delay 期间**元素都停在 from 状态（opacity:0）。
     duration 归零、delay 不归零 → 元素先空着等完 delay 再"啪"地出现，
     比原来还难看。实测受影响的有：
       .dash>:not(.bento)    delay 180~410ms（看板整块空 0.4 秒）
       .dash .grid>.stock    delay 180+i*42ms
       .pg-fill/.bar-fill    delay 140~150+i*48~60ms
       .trend-bar            delay 150+col*60ms
       .spark-area           delay 500ms（迷你趋势图空半秒才出来）
     .dd-menu>.nav-item 早先单独清过一次（见文件前段），这里统一兜住。 */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{
    animation-duration:.01ms !important; animation-iteration-count:1 !important;
    animation-delay:0s !important;
    transition-duration:.01ms !important; transition-delay:0s !important;
    scroll-behavior:auto !important;
  }
}

/* ---------- 标签：状态色块也要有反馈，不然后台表格一排标签全是死的 ---------- */
.tag{
  transition:background-color var(--dur-1) var(--ease-out),
             color var(--dur-1) var(--ease-out),
             box-shadow var(--dur-1) var(--ease-out),
             transform var(--dur-1) var(--ease-spring);
}
tr:hover .tag{box-shadow:0 1px 3px rgba(62,44,32,.10)}

/* ---------- 侧栏项：后台每页都点，左侧高亮要给足反馈 ----------
   高亮条改用 ::before 而不是 border-left：窄屏媒体查询里为了横向排布
   把 border-left 清成了 border-bottom，两套高亮会打架（宽屏两条、窄屏一条）。
   统一成伪元素，宽窄屏表现一致，也省掉一处覆写。 */
.side-item{
  position:relative;
  transition:background-color var(--dur-1) var(--ease-out),
             color var(--dur-1) var(--ease-out),
             transform var(--dur-1) var(--ease-out);
}
.side-item::before{
  content:''; position:absolute; left:0; top:50%; width:3px; height:0;
  background:var(--primary); border-radius:0 3px 3px 0;
  transform:translateY(-50%);
  transition:height var(--dur-2) var(--ease-out);
}
.side-item:hover::before{height:34%; opacity:.45}
.side-item.active::before{height:60%}

/* ---------- 表格行：悬停整行抬亮，但只有行内链接是手型 ----------
   ⚠ 别写 .rowlink —— ui.js 里根本没这个 class，整条规则是死的。
     列表页可点的是行内的 <a data-act="l-open">，整行不可点。
     所以光标只给链接，整行给背景高亮，两者分开写。 */
.ltable tbody tr td{
  transition:background-color var(--dur-1) var(--ease-out),
             box-shadow var(--dur-1) var(--ease-out);
}
.ltable tbody tr:hover td{background:var(--primary-soft)}
.ltable tbody tr:active td{background:#dbeafe}
.ltable tbody tr:has(> td > a[data-act='l-open']){cursor:default}
.ltable a[data-act='l-open'],
.lboard a[data-act='l-open']{cursor:pointer}
.ltable a[data-act='l-open']{
  transition:color var(--dur-1) var(--ease-out);
}
.ltable a[data-act='l-open']:hover{color:var(--primary); text-decoration:underline}
/* 抽屉里的行（dw-row）是整行可点的 */
tr[data-act='dw-row']{cursor:pointer}
tr[data-act='dw-row']:hover td{background:var(--primary-soft)}

/* ---------- 勾选框 / 多选下拉：手点的控件必须"按下去" ---------- */
.ms-box,.ms-box:hover{
  transition:background-color var(--dur-1) var(--ease-out),
             border-color var(--dur-1) var(--ease-out),
             transform var(--dur-1) var(--ease-spring);
}
.ms-box:active{transform:scale(.88)}
.ms-box.on{box-shadow:0 0 0 3px rgba(184,80,31,.13)}

/* ---------- 提示条：出现时轻轻淡入，不要"啪"地顶出来 ---------- */
.notice,.error,.warn{
  animation:barIn var(--dur-3) var(--ease-out) both;
  border-left-width:3px;
}
@keyframes barIn{
  from{opacity:0; transform:translateY(-4px)}
  to{opacity:1; transform:none}
}

/* ---------- 骨架屏：加一点微光扫过，比灰块"活着" ---------- */
.skel-card,.skel-row{
  background:linear-gradient(90deg,var(--bg-2) 25%,var(--bg-2) 37%,var(--bg-2) 63%);
  background-size:400% 100%;
  animation:shimmer 1.5s ease-in-out infinite;
}
/* ⚠ 这里原本还有第二份 @keyframes shimmer（100% 50% → 0 50%）。
   和本文件前段那份同名，只是纵向标称位置不同（background-size 是 400% 100%，
   纵向只有一层，50% 和 0 实际上是同一个画面）。CSS 后定义者胜，
   所以一直生效的是这一份、前段那份是死的 —— 两份留着只会让下一个人
   以为改前段就能改骨架屏。删掉这一份，只留前段一处。 */

/* ---------- 空态：给出呼吸感，不要灰得像报错 ---------- */
.empty{
  padding:var(--sp-8) var(--sp-4); color:var(--muted);
  animation:fadeIn var(--dur-3) var(--ease-out) both;
}
@keyframes fadeIn{from{opacity:0}to{opacity:1}}

/* ---------- 弹层：遮罩淡入 + 内容轻微上浮 ---------- */
.mask{animation:fadeIn var(--dur-2) var(--ease-out) both}
.mask>.modal,.mask>.drawer{animation:popIn var(--dur-3) var(--ease-out) both}
@keyframes popIn{
  from{opacity:0; transform:translateY(10px) scale(.985)}
  to{opacity:1; transform:none}
}

/* ---------- 分段切换器：三态按钮，已选项要有"实体感" ---------- */
.seg{position:relative}
.seg-i{
  transition:background-color var(--dur-2) var(--ease-out),
             color var(--dur-2) var(--ease-out),
             box-shadow var(--dur-2) var(--ease-out),
             transform var(--dur-1) var(--ease-spring);
}
.seg-i.on{box-shadow:var(--sh-1)}

/* ---------- 统计卡：数字滚动期间预留合成层，避免边框抖动 ---------- */
.kpi,.stat{will-change:transform}

/* ---------- 全局：任何可点的东西按下去都有回弹 ---------- */
button,.chip,.seg-i,.dd-toggle,.close,.x{
  transition:transform var(--dur-1) var(--ease-spring),
             background-color var(--dur-1) var(--ease-out),
             border-color var(--dur-1) var(--ease-out),
             color var(--dur-1) var(--ease-out),
             box-shadow var(--dur-1) var(--ease-out);
}

/* ---------- 输入区：聚焦时整块浮起一点，视线不用找 ---------- */
.lfgrid .field,.form-inline>div{
  transition:transform var(--dur-2) var(--ease-out);
}
input:focus,select:focus,textarea:focus{
  transition:border-color var(--dur-1) var(--ease-out),
             box-shadow var(--dur-2) var(--ease-out);
}

/* ---------- 顶部导航下拉：箭头跟着转，展开方向一目了然 ---------- */
.caret{transition:transform var(--dur-2) var(--ease-out); display:inline-block}
.dd:hover>.dd-toggle .caret,.dd.open>.dd-toggle .caret{transform:rotate(180deg)}

/* ---------- v0.9.0 订单业务状态按钮组（需求1） ----------
   配色沿用 :root 里的语义变量（ok/danger/warn/primary），
   与截图里"彩色标签 + 可点下一个状态"的观感一致。
   ⚠ 这里必须真写出来 —— ltable 渲染用了 .st / .state-go，
     没有 CSS 时它们就是普通文本，既没有颜色也没有可点的样子，
     而接口与列都在、页面也正常打开 —— 属于最难发现的那类"看着好好的"。 */
.ltable td.ltitle-state{white-space:nowrap}
.st{display:inline-block;padding:var(--sp-1) var(--sp-2);border-radius:20px;font-size:var(--fs-sm);
  font-weight:600;border:1px solid transparent;line-height:1.5}
/*---- 状态色：7 档语义调（与 ui.js 的 TONES 一一对应）----
   ⚠ 类名是**语义**（neutral/info/progress/warn/ok/err/unknown），
     不是状态词本身。状态词由 ui.js 的 statusTone 查表翻译成 tone。
     以前这里只有 gray/blue/cyan/orange/green/red 六个英文类，
     而调用方把中文状态词当类名传进来（st-待发放）—— 匹配不到，
     工资批次页的状态标签从来没上过色。 */
.st-neutral{background:var(--bg-2);color:var(--text);border-color:var(--line)}
.st-info{background:var(--primary-soft);color:var(--primary-d);border-color:#f2dbc9}
.st-progress{background:#eef6f2;color:#2b6e63;border-color:#cfe8dd}
.st-warn{background:var(--warn-soft);color:#b45309;border-color:#f4e4c2}
.st-ok{background:var(--ok-soft);color:var(--ok);border-color:#c9e5d5}
.st-err{background:var(--danger-soft);color:var(--danger);border-color:#f2d2cc}
/*⚠ unknown 不是灰色。灰色与"没样式"长得一样，值班的人分不出来，
   于是"这词还没配色调"会一直没人管。用琥珀+虚线描边把它显式喊出来。 */
.st-unknown{background:var(--warn-soft);color:#92400e;border:1px dashed #f59e0b}

/* 旧类名保留：订单状态机的 STATE_COLOR 返回的是这些值，
   直接换掉会让 30 条biz_flow_state 配置和后端常量一起失效。 */
.st-gray{background:var(--bg-2);color:var(--text);border-color:var(--line)}
.st-blue{background:var(--primary-soft);color:var(--primary-d);border-color:#f2dbc9}
.st-cyan{background:#eef6f2;color:#2b6e63;border-color:#cfe8dd}
.st-orange{background:var(--warn-soft);color:#b45309;border-color:#f4e4c2}
.st-green{background:var(--ok-soft);color:var(--ok);border-color:#c9e5d5}
.st-red{background:var(--danger-soft);color:var(--danger);border-color:#f2d2cc}

/* ---- .tag 与 .st 共用同一套色调 ----
   原来 .tag 用 ok/warn/err/info、.st 用 gray/blue/…，
   颜色几乎重复但类名不通用 —— 两套体系各写一遍，
   改色要改两处，漏一处就是"同类状态颜色不一致"。
   现在 .tag 的 tone 类与 .st 共用定义（stTag / lfmt 都走 statusTone）。 */
.tag-neutral{background:var(--bg-2);color:var(--text)}
.tag-info{background:var(--primary-soft);color:var(--primary)}
.tag-progress{background:#eef6f2;color:#2b6e63}
.tag-warn{background:var(--warn-soft);color:var(--warn)}
.tag-ok{background:var(--ok-soft);color:var(--ok)}
.tag-err{background:var(--danger-soft);color:var(--danger)}
.tag-unknown{background:var(--warn-soft);color:#92400e;box-shadow:inset 0 0 0 1px #f59e0b}

.state-next{display:inline-flex;align-items:center;gap:var(--sp-1);margin-left:var(--sp-2);
  font-size:var(--fs-sm);color:var(--muted)}
.state-go{padding:var(--sp-1) var(--sp-2);border-radius:12px;border:1px dashed var(--line);
  background:#fff;color:var(--primary);cursor:pointer;
  transition:border-color var(--dur-1,160ms) var(--ease-out, ease),
             background-color var(--dur-1,160ms) var(--ease-out, ease),
             color var(--dur-1,160ms) var(--ease-out, ease)}
.state-go:hover{border-color:var(--primary);background:var(--primary-soft);
  border-style:solid}
.state-go:active{transform:translateY(1px)}
.state-end{font-size:var(--fs-sm);opacity:.7}

/* 绩效双向打分（需求2）：自评 / 领导分 / 最终分三列并排看 */
.perf-3col{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--sp-2);margin:var(--sp-3) 0}
.perf-box{border:1px solid var(--line);border-radius:var(--radius);padding:var(--sp-3) var(--sp-3);background:#fff}
.perf-box .lb{font-size:var(--fs-sm);color:var(--muted);margin-bottom:var(--sp-1)}
.perf-box .vl{font-size:var(--fs-xl);font-weight:700}
.perf-box.self{border-left:3px solid var(--primary)}
.perf-box.mgr{border-left:3px solid var(--warn)}
.perf-box.final{border-left:3px solid var(--ok)}
.perf-box.void{opacity:.6;border-style:dashed}
.perf-box .vl.void{color:var(--danger);text-decoration:line-through}

/* 工资条（需求3）：实发放大，扣项用红，认同的项用绿 */
.slip-row{display:flex;justify-content:space-between;align-items:baseline;
  padding:var(--sp-2) var(--sp-1);border-bottom:1px dashed var(--line)}
.slip-row.strong{font-weight:700}
.slip-row.net{margin-top:var(--sp-2);padding-top:var(--sp-3);border-top:2px solid var(--text);
  border-bottom:none;font-size:var(--fs-h1)}
.slip-row .k{color:var(--muted)}
.slip-row.sub .k{padding-left:var(--sp-4);font-size:var(--fs-body)}
.slip-row.sub .v{font-size:var(--fs-body)}
.slip-net{font-size:var(--fs-xl);color:var(--ok)}
.slip-deduct{color:var(--danger)}

/* ==================================================================
   v1.9.0 · 便当盒看板 + 入场动效
   ------------------------------------------------------------------
   目标两条：首页要「有布局」而不是一排便签；操作要有物理感而不是瞬变。
   三条硬约束，破一条就会退化：
   ① 尺寸仍走令牌。字号沿用 6 档，本次只新增 --fs-hero 一档且**仅主卡数字**
      使用；间距只用 --sp-*，不另造刻度。
   ② 动效必须有参照：入场一次性、悬停即时、数据变化才动。
      **不做常驻循环动画** —— 看板是每天盯的，常驻动效三分钟就晃眼。
   ③ prefers-reduced-motion 下全部归零，且必须连 animation-delay 一起清。
      只清 duration 时，元素会在 delay 期间停在不透明度 0 —— 用户看到的是一段
      空白闪烁，比不动更难用。
   ================================================================== */
:root{
  --radius-lg:14px;   /* 便当盒瓷片；比 --radius(8px) 圆一档，大块面才压得住 */
  --fs-hero:40px;     /* 主卡数字。全站仅此一处，勿外溢到其它文案 */
  --kpi-bar:6px;      /* 语义竖条与文字的间距 */
  --spark-h:58px;     /* 主卡趋势线高度；同时决定底部预留，两者必须同源 */
  /* 错峰下标由 app.js 内联写到每个元素上（style="--i:3"）。
     这里给出兜底值：CSS 里任何 var() 都不该没有定义 ——
     否则哪天内联没写上去，整条 animation-delay 声明会失效并被静默丢弃。 */
  --i:0;
  --col:0;
}

/* ---------- 1. 便当盒网格：12 列，按语义跨列 ----------
   跨度由 app.js 的 BENTO_SPAN 按 **key** 决定，不按数组下标 ——
   后端增减指标时，剩下的会自动回流（grid-auto-flow:dense），不会错位。 */
.bento{
  display:grid;
  grid-template-columns:repeat(12,minmax(0,1fr));
  grid-auto-flow:dense;
  gap:var(--sp-3);
  margin-bottom:var(--sp-4);
}
.bento>.kpi{
  grid-column:span 3;          /* 默认 1/4 宽 */
  margin:0;min-width:0;
}
.bento>.kpi.s3{grid-column:span 3}
.bento>.kpi.s4{grid-column:span 4}
.bento>.kpi.hero{grid-column:span 6;grid-row:span 2}

/* ---------- 2. 瓷片本体 ----------
   旧的 .kpi 是「1px 边框 + 左侧 3px 主色粗边」，十个并排像十张便签纸。
   改成：统一的细边框 + 左上一条 3px 竖条做语义色，左侧内缩 6px 让竖条
   与文字之间留白 —— 同样的信息量，读起来像"卡片"而不是"贴纸"。 */
.bento>.kpi{
  position:relative;overflow:hidden;
  padding:var(--sp-4) var(--sp-4) var(--sp-4) calc(var(--sp-4) + var(--kpi-bar));
  border:1px solid var(--line);
  border-left:1px solid var(--line);   /* ← 覆盖 .kpi 的 3px 粗边，语义色改由 ::before 承担 */
  border-radius:var(--radius-lg);
  background:linear-gradient(180deg,#fff 0%,#fdfaf7 100%);
  box-shadow:0 1px 2px rgba(62,44,32,.04);
  transition:transform .35s var(--ease-spring),
             box-shadow .3s var(--ease-out),
             border-color .3s var(--ease-out);
  animation:riseIn .46s var(--ease-out) backwards;
  animation-delay:calc(var(--i,0) * 46ms);
}
/* 语义竖条只在标签那一行，不拉通整张卡 ——
   拉到底的 3px 竖条在 200px 高的主卡上像一条"页签"，
   而且十张卡并排时形成十条长短不一的竖线，比没有还乱。 */
.bento>.kpi::before{
  content:"";position:absolute;left:0;top:var(--sp-5);
  width:3px;height:22px;border-radius:0 3px 3px 0;background:var(--primary);opacity:.9;
}
.bento>.kpi.warn::before{background:var(--warn)}

/* 悬停：抬起 3px + 主色投影。曲线用 --ease-spring（同 #214），
   过冲一点点才像"被碰到"，线性缓动是移动不是响应。
   ⚠ 位移必须门禁：触屏 tap 也会进 :hover，瓷片先抬 3px 再弹回，看着像点歪了。
     :active 不门禁 —— 按下是真实动作，触屏给按压反馈是对的。 */
@media (hover:hover) and (pointer:fine){
  .bento>.kpi:hover{
    transform:translateY(-3px);
    border-color:#efd7c4;
    border-color:color-mix(in srgb,var(--primary) 32%,var(--line));
    box-shadow:0 18px 30px -20px rgba(184,80,31,.6),
               0 2px 6px rgba(62,44,32,.05);
  }
}
.bento>.kpi:active{transform:translateY(-1px) scale(.997)}

/* ---------- 3. 主卡（本月含税金额）：一块有"场景"的瓷片 ----------
   便当盒的视觉重心必须只有一个。这里用三重处理拉开层级：
   更大的数字、更浅的蓝底、右下角一条自绘的趋势线。 */
.bento>.kpi.hero{
  display:flex;flex-direction:column;
  justify-content:flex-start;         /* v2.0：改为靠上 —— 数字贴顶、趋势线贴底，
                                             中间留白是"呼吸"而不是"没加载完" */
  padding-top:var(--sp-5);
  padding-bottom:var(--sp-4);
  border-color:#f2e0d1;
  background:
    radial-gradient(120% 90% at 100% 0%,rgba(184,80,31,.10),transparent 58%),
    linear-gradient(160deg,#fffdfb 0%,#fdf3ea 68%,#f9e9db 100%);
}
.bento>.kpi.hero.has-spark{padding-bottom:var(--spark-h)}   /* 预留高度与曲线同源，改一处即可 */
.bento>.kpi.hero .kpi-l{color:var(--text);font-weight:600}
.bento>.kpi.hero .kpi-n{
  font-size:var(--fs-hero);margin:var(--sp-2) 0 0;
  letter-spacing:-.8px;
}
.bento>.kpi.hero .kpi-h{margin-top:var(--sp-2)}
.bento>.kpi.hero:hover{
  box-shadow:0 22px 38px -22px rgba(184,80,31,.7),
             0 2px 6px rgba(62,44,32,.05);
}

/* 趋势线：进场时从左往右"画"出来（dasharray 由 drawSpark() 量出真实长度后写入）。
   ⚠ 这里**故意不写** stroke-dasharray 的初值 —— 脚本没跑时线就是完整可见的，
     写成 dashoffset:100% 之类的会让"脚本失败"变成"图没了"。 */
.kpi-spark{position:absolute;left:0;right:0;bottom:0;width:100%;height:var(--spark-h);
  display:block;pointer-events:none}
.spark-area{stroke:none;animation:sparkFade .9s var(--ease-out) .5s backwards}
.spark-line{
  fill:none;stroke:var(--primary);stroke-width:2;
  stroke-linecap:round;stroke-linejoin:round;
  vector-effect:non-scaling-stroke;      /* 横向拉满但不把线拉粗 */
}
@keyframes sparkFade{from{opacity:0}to{opacity:1}}

/* ---------- 4. 入场：同一层的东西一起起，不同层错开 ----------
   fill-mode 必须用 backwards 而不是 both：
   both 会在动画结束后继续钉住 transform:none，把后面的 :hover 位移压掉，
   表现是"卡片动效很漂亮但鼠标放上去没反应"——线上排查过这个。 */
@keyframes riseIn{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}}
.dash>:not(.bento){
  animation:riseIn .46s var(--ease-out) backwards;
  animation-delay:180ms;      /* 等瓷片铺完再起，否则两拨同时动，看着乱 */
}
.dash>:nth-child(2){animation-delay:180ms}
.dash>:nth-child(3){animation-delay:228ms}
.dash>:nth-child(4){animation-delay:276ms}
.dash>:nth-child(5){animation-delay:324ms}
.dash>:nth-child(6){animation-delay:372ms}
.dash>:nth-child(n+7){animation-delay:410ms}

/* 业务存量瓷片：逐个浮起，像被依次摆上桌 */
.dash .grid>.stock{animation:riseIn .44s var(--ease-out) backwards;
  animation-delay:calc(180ms + var(--i,0) * 42ms);
  /* 与上方便当盒同一套圆角和悬浮曲线 —— 上下两块若圆角不同（8px vs 14px），
     眼睛会立刻察觉"这是两拨人做的"。 */
  border-radius:var(--radius-lg);
  background:linear-gradient(180deg,#fff 0%,#fcfdfe 100%);
  transition:transform .3s var(--ease-spring),
             box-shadow .26s var(--ease-out),
             border-color .26s var(--ease-out);
}
/* 位移门禁：触屏点一下也会进 :hover，瓷片会先跳 2px 再弹回 —— 看着像点歪了。 */
@media (hover:hover) and (pointer:fine){
  .dash .grid>.stock:hover{transform:translateY(-2px);box-shadow:0 12px 22px -18px rgba(184,80,31,.6)}
}
.dash .grid{animation:none}   /* 容器不动，只让子项动，避免两层透明度相乘发灰 */

/* ---------- 5. 数据生长：条形/进度/柱状从零长出来 ----------
   只在这里动 transform，不改 width/height 的初值 ——
   数值仍是后端算的，动画只是"把已有的长度演示一遍"。 */
@keyframes growX{from{transform:scaleX(.02)}to{transform:scaleX(1)}}
@keyframes growY{from{transform:scaleY(.03)}to{transform:scaleY(1)}}

.pg-fill{transform-origin:left center;animation:growX .78s var(--ease-out) backwards;
  animation-delay:calc(140ms + var(--i,0) * 60ms)}
.bar-fill{transform-origin:left center;animation:growX .78s var(--ease-out) backwards;
  animation-delay:calc(150ms + var(--i,0) * 48ms)}
.trend-bar{transform-origin:50% 100%;animation:growY .72s var(--ease-out) backwards;
  animation-delay:calc(150ms + var(--col,0) * 60ms)}
.trend-bar.c-cost{animation-delay:calc(210ms + var(--col,0) * 60ms)}

/* ---------- 6. 看板卡片：圆角对齐瓷片，标题加一道主色短杠 ---------- */
.dash .card{
  border-radius:var(--radius-lg);
  box-shadow:0 1px 2px rgba(62,44,32,.035);
  transition:box-shadow .28s var(--ease-out),border-color .28s var(--ease-out);
}
.dash .card:hover{
  border-color:#d8e1f0;
  box-shadow:0 12px 28px -20px rgba(62,44,32,.4);
}
.dash .card>h2::before{
  content:"";flex:none;width:3px;height:14px;border-radius:2px;
  background:var(--primary);opacity:.5;
}

/* ---------- 7. 窄屏：两列，主卡独占一行 ---------- */
@media (max-width:900px){
  :root{--spark-h:44px}                        /* 曲线变矮，底部预留跟着一起变 —— 只改一处 */
  .bento{grid-template-columns:repeat(2,minmax(0,1fr))}
  .bento>.kpi,.bento>.kpi.s3{grid-column:span 1}
  .bento>.kpi.s4{grid-column:span 2}          /* 单/双数指标排满一行，不留洞 */
  .bento>.kpi.hero{grid-column:span 2;grid-row:span 1}
  .bento>.kpi.hero .kpi-n{font-size:var(--fs-display)}
}
@media (max-width:420px){
  .bento{gap:var(--sp-2)}
  .bento>.kpi{padding:var(--sp-3) var(--sp-3) var(--sp-3) calc(var(--sp-3) + var(--kpi-bar))}
  .bento>.kpi.hero .kpi-n{font-size:var(--fs-xl)}
}

/* ---------- 8. 减弱动效：全部归零，含 delay ---------- */
@media (prefers-reduced-motion:reduce){
  .bento>.kpi,.dash>:not(.bento),.dash .grid>.stock,
  .pg-fill,.bar-fill,.trend-bar{
    animation:none!important;transition:none!important;
    opacity:1!important;transform:none!important;
  }
  .spark-line{animation:none!important;stroke-dashoffset:0!important}
  .spark-area{animation:none!important;opacity:1!important}
}

/* ==========================================================================
   v1.9.1 · P0 可用性修正（承接 2026-11-01 UX 审计的第一批）
   --------------------------------------------------------------------------
   这五条都不是审美问题，是「看着能用、真用就废」的承载能力问题：
   金额对不齐扫不出大小、状态色看不清、长表单按钮滚出视野、表头不吸顶、
   复选框按不准。只动样式与渲染 class，不动结构、不动业务。
   ========================================================================== */

/* ---------- 1. 状态色降到 4.5:1 以上（WCAG AA 正文标准） ----------
   原值 warn var(--warn) = 3.07:1、ok var(--ok) = 3.58:1、danger var(--danger) = 4.41:1，
   都达不到正文 4.5 的门槛 —— 在浅色 soft 底上更糟（如 .tag.warn）。
   只改令牌、不改用法：全站引用这三处的颜色（标签/按钮/负值/脏行标记）一起变深。 */
:root{
  --warn:#b45309;      /* 4.72:1 on #fff（原 var(--warn) 3.07） */
  --ok:var(--ok);        /* 5.39:1        （原 var(--ok) 3.58） */
  --danger:var(--danger);    /* 6.47:1        （原 var(--danger) 4.41） */
}

/* ---------- 2. 弹层：按钮不许被内容顶出屏幕 ----------
   长表单（比如员工档案）内容比弹层高，保存/取消会滚到看不见的地方，
   用户以为"这个弹窗没按钮"。给 foot 吸底，内容再长按钮永远在。 */
.modal{display:flex;flex-direction:column}
.modal .foot{
  position:sticky;bottom:0;z-index:1;
  background:#fff;margin-top:var(--sp-4);
}

/* ---------- 3. 表头吸顶：先让它真的能滚 ----------
   thead th 早就写了 position:sticky，但列表容器 .ltwrap 是
   `overflow:auto` + 高度自适应 —— 它成了"永远滚不动"的滚动容器，
   sticky 就钉在它身上，页面往下滚时表头跟着跑掉（实测滚 373px 后 top=-165）。
   给它一个可视高度上限，它才真的滚，sticky 才有意义。
   ⚠ 窄屏要把上限撤掉：那时 .ltwrap 退成透明容器，每行自己是一张卡片。 */
:root{--tbl-offset:300px}   /* 顶栏 + 页头 + 工具条 + 分页器 ≈ 300px */
.card.ltwrap{max-height:calc(100vh - var(--tbl-offset))}
/* 表格容器是 padding:0（滚动区不该有内边距，否则横向滚动时左右会"跳"）——
   代价是首尾单元格贴边：勾选框距卡片左缘只有 9px，看起来像"表格掉出去了"。
   用首尾单元格自己的内边距补回来，滚动时也不会漏出白边。 */
.card.ltwrap>table>thead>tr>th:first-child,
.card.ltwrap>table>tbody>tr>td:first-child{padding-left:var(--sp-4)}
.card.ltwrap>table>thead>tr>th:last-child,
.card.ltwrap>table>tbody>tr>td:last-child{padding-right:var(--sp-4)}
@media (max-width:900px){.card.ltwrap{max-height:none}}

/* ==========================================================================
   v2.1 冻结列 + 行末 ⋯ 菜单
   为什么：员工档案 22 列、表宽 1946px。操作列在最右边 —— 要改一个人，
   得先横拖到底找到按钮；拖走之后又不知道自己在改谁。
   现在：勾选列 + 首个数据列钉在左侧，⋯ 钉在右侧，中间随你滚。

   ⚠ 冻结偏移必须是**确定数**：勾选列如果只写 width:38px，复选框会把内容盒
     撑到 40，首列 left 就漏出 2px 的缝 —— 横向滚动时下面的字从缝里透出来。
     所以勾选列 padding 归零 + 内容居中，宽度统一走 --lcol-w。

   ⚠ 阴影只在"真的还有内容"时才出现（.is-x / .is-xend 由 JS 按 scrollLeft 加），
     常驻阴影会让表格看着像两层纸叠在一起。
   ========================================================================== */
:root{--lcol-w:38px}
@media (pointer:coarse){:root{--lcol-w:44px}}

/* 勾选框格：padding 归零。⚠ 特异性要压过上面「首尾单元格补内边距」那条
   （.card.ltwrap>table>thead>tr>th:first-child），否则表头会多出 16px 左内边距，
   冻结列的分界线跟着歪。 */
.card.ltwrap>table>thead>tr>th.lpick-h{padding:0;width:38px}
.card.ltwrap>table>thead>tr>th.lops-h{padding:0}
/* ⚠ 这里的宽度**不引用 --lcol-w**：那个变量是"实测后写回来"的，
   如果宽度也吃它，量到 40 就写 40、宽度再变、再量 —— 会来回抖。
   宽度定死，变量只管 offset，两者各管一件事。 */
.ltable td.lpick{padding:0;text-align:center;width:38px}
.ltable td.lops{padding:0;text-align:center;width:46px}

.ltable th.lpick-h{position:sticky;left:0;z-index:5;background:var(--panel)}
.ltable td.lpick{position:sticky;left:0;z-index:4;background:var(--panel)}
.ltable th.ltitle-h{position:sticky;left:var(--lcol-w);z-index:5;background:var(--panel)}
.ltable td.ltitle,
.ltable td.ltitle-state{position:sticky;left:var(--lcol-w);z-index:4;background:var(--panel)}
.ltable th.lops-h{position:sticky;right:0;z-index:5;background:var(--panel)}
.ltable td.lops{position:sticky;right:0;z-index:4;background:var(--panel)}

/* 冻结列跟着行一起变色：上面这些 td 设了不透明底色，会压掉 tr:hover 的 bg。
   ⚠ 颜色必须和 `.ltable tbody tr:hover td` 的底色**同一个变量**（--primary-soft）。
   曾经写成 --bg：行暖、冻结格白，横滚后左边永远一条白带子，像表格断了一块。 */
.ltable tbody tr:hover td.lpick,
.ltable tbody tr:hover td.ltitle,
.ltable tbody tr:hover td.ltitle-state,
.ltable tbody tr:hover td.lops{background-color:var(--primary-soft)}
/* ⚠ 用 background-color 不用 background —— 后者会把边界渐变（background-image）一并抹掉。 */

/* 冻结边界必须看得出"下面还有内容"。
   ⚠⚠ 别用外层 box-shadow：`border-collapse:collapse` 的 td 上 Chrome **一个像素都不画**
   （实测外阴影 0% / inset 50% / 背景渐变 31%）。computed 值照常返回，
   所以只看 getComputedStyle 会以为画上了 —— 判据必须取样像素。
   这里用背景渐变：从透明渐到暗，最后 1px 是 --line-2 分界线。
   被切半截的字靠这条边读成"分层"，否则看起来像渲染坏了。 */
.ltwrap.is-x .ltable td.ltitle,
.ltwrap.is-x .ltable td.ltitle-state,
.ltwrap.is-x .ltable th.ltitle-h{
  background-image:linear-gradient(to right,
    rgba(44,37,33,0) calc(100% - 14px),
    rgba(44,37,33,.20) calc(100% - 1px),
    var(--line-2) 100%)}
.ltwrap.is-xend .ltable td.lops,
.ltwrap.is-xend .ltable th.lops-h{
  background-image:linear-gradient(to left,
    rgba(44,37,33,0) calc(100% - 14px),
    rgba(44,37,33,.16) calc(100% - 1px),
    var(--line-2) 100%)}

/* ⋯ 按钮：默认只是一个淡灰的点，hover 才有底 —— 每行一个实心按钮会把
   表格变成"一排按钮"，而它的绝大多数时间是不用的。 */
.lops-b{width:26px;height:26px;padding:0;border:none;background:transparent;
  color:var(--muted);font-size:var(--fs-h2);line-height:1;letter-spacing:0;border-radius:6px}
.lops-b:hover{background:var(--bg-2);color:var(--text);border:none}
.lops-b[aria-expanded="true"]{background:var(--primary-soft);color:var(--primary)}

/* 行内菜单：fixed 挂在 body 上（表格容器 overflow:auto，留在行里会被裁掉） */
.rmenu{position:fixed;z-index:90;min-width:140px;padding:var(--sp-1);
  background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);
  box-shadow:0 14px 32px -18px rgba(62,44,32,.55),0 2px 6px rgba(62,44,32,.06);
  animation:rmIn .12s var(--ease-out) backwards}
@keyframes rmIn{from{opacity:0;transform:translateY(-4px) scale(.98)}}
.rm-i{display:flex;width:100%;height:30px;padding:0 var(--sp-3);border:none;background:transparent;
  border-radius:6px;font-size:var(--fs-body);color:var(--text);justify-content:flex-start}
.rm-i:hover{background:var(--bg-2);border:none}
.rm-danger{color:var(--danger)}
.rm-danger:hover{background:var(--danger-soft);color:var(--danger)}
.rm-sep{height:1px;margin:var(--sp-1) var(--sp-2);background:var(--line-soft)}

/* 窄屏：表格本来就被拆成一张张卡片（见 768px 段），sticky 只会让卡片互相盖住 */
@media (max-width:768px){
  .ltable th.lpick-h,.ltable th.ltitle-h,.ltable th.lops-h{display:none}
  .ltable td.lpick,.ltable td.ltitle,.ltable td.ltitle-state{position:static;box-shadow:none}
  .ltable td.lops{position:absolute;top:4px;right:4px;width:auto;z-index:2;box-shadow:none}
  .ltable td.lpick{right:44px}
}


/* ---------- 4. 金额列右对齐 ----------
   class 由 ui.js 按后端列的 type 打（money/rate/percent/num）；
   .r 与 tabular-nums 的定义早就有（第 201/348 行），只是一直没人给列打上。
   这里补一层保险：万一某处漏了 class，数字仍按等宽排（不影响文本列）。 */
.ltable td.r,.ltable th.r{text-align:right}

/* ---------- 5. 复选框热区 ----------
   13×13 的勾选框在触屏上基本点不准，且误点到行会触发"打开详情"，
   于是"想勾选却打开了单据"。放大到 16（触屏 20），并把整格做成点击区。 */
.ltable td.lpick{width:38px}
.ltable input[type=checkbox]{
  width:16px;height:16px;cursor:pointer;accent-color:var(--primary);
}
@media (pointer:coarse){
  .ltable input[type=checkbox]{width:20px;height:20px}
  .ltable td.lpick{width:44px}
}

/* ---------- 6. 减弱动效：以上新增的吸附/过渡也要归零 ---------- */
@media (prefers-reduced-motion:reduce){
  .modal .foot{transition:none!important}
}

/* ==========================================================================
   7. 轻提示（Lucian UX/UI #070「Stacking toasts」）
   ==========================================================================
   为什么挪到右下角：操作反馈原来写进页面顶部的横幅（S.notice），
   而横幅只在整页 render() 时重画 —— 而大多数操作走的是 lrefresh()（只换表格），
   于是"已保存"经常根本不出现，用户只能靠数据变没变去猜。
   现在：立刻出现、自动走、可连发、悬停暂停、同一句话叠成一条并计数（×2）。
   ⚠ 这里的入场不再用 animation，所以早先那条"fill-mode 用 backwards 不用 both"的
     提醒在 toast 上已经失效（改用 transition + @starting-style，见下）。
     那条提醒在 .dd-menu / .nav-item 上仍然有效，别一起删了。 */
.toast-stack{position:fixed;right:var(--sp-4);bottom:var(--sp-4);z-index:90;
  display:flex;flex-direction:column;gap:var(--sp-2);align-items:flex-end;
  pointer-events:none;max-width:calc(100vw - var(--sp-8))}
/* ⚠ v1.9.5：入场/退场从 keyframes 改成 CSS transition + @starting-style。
   ⚠⚠ 说清楚动机，别夸大：**没有**"退场时跳到全亮"这回事 ——
     `@keyframes toastOut` 只写了 `to{}`，隐式起点就是当前值，看起来本来就是顺的
     （我一度这么以为，用合成点击测了才发现测不出来，实测最高 opacity 0.865 没有跳）。
   真实的病只有一条，窗口很窄但会发生：
     · 提示正在退场（加了 .out，260ms 内），**同一句话又触发一次**时，
       JS 会 `classList.remove('out')` 把退场撤回 → animation-name 从 toastOut 换回 toastIn
       → 关键帧带 `from{opacity:0;translateY(14px)}`，**从零重播**，提示"又滑进来一次"。
   换成 transition 后退出/进入是同一个属性的两个值，撤回就是从当前值往回走，
   不存在"重播"这个概念。另外还顺手拆掉了 animation 与 :hover 的 box-shadow 抢属性问题。
   ⚠ 退场必须走和入场同一个方向（都朝下），从侧面或上方消失会让人找不到它去哪了。
   ⚠ JS 在加 .out 之后 260ms 摘节点 —— 这里的退场时长不能超过 260ms，否则会被切断。
   ⚠ 入场依赖 @starting-style（Chrome 117+）。旧 WebView 只是没有入场动画，提示照常出现。 */
.toast{position:relative;pointer-events:auto;display:flex;align-items:flex-start;gap:var(--sp-3);
  width:340px;max-width:100%;padding:var(--sp-3) var(--sp-3);
  border-radius:12px;background:#fffdfb;
  box-shadow:0 0 0 1px var(--warm-line),0 22px 44px -24px rgba(90,60,40,.45);
  overflow:hidden;opacity:1;transform:none;
  transition:opacity .26s var(--ease-out),
             transform .34s var(--ease-spring),
             box-shadow .2s var(--ease-out)}
/* @starting-style：元素第一次参与渲染时的"起跑姿势"，浏览器据此起一段 transition。
   不需要 JS 翻类，也不会出现先用错误位置闪一帧的问题。
   不支持的浏览器（Chrome<117 / 老 WebView）只是没有入场动画，提示照常出现 —— 可接受的降级。 */
@starting-style{
  .toast{opacity:0;transform:translateY(14px) scale(.96)}
}
.toast.out{opacity:0;transform:translateY(16px) scale(.98);pointer-events:none;
  transition:opacity .2s var(--ease-out),transform .2s var(--ease-out)}
.toast:hover{box-shadow:0 0 0 1px var(--warm-2),0 26px 50px -24px rgba(90,60,40,.5)}
.toast-ico{width:22px;height:22px;flex:none;border-radius:50%;display:grid;place-items:center;
  background:var(--warm-soft);color:var(--warm-ink);
  font-size:var(--fs-sm);font-weight:700;line-height:1}
.toast-ok .toast-ico{background:var(--ok-soft);color:var(--ok)}
.toast-ok .toast-ico::after{content:"✓"}
.toast-warn .toast-ico{background:var(--warn-soft);color:var(--warn)}
.toast-warn .toast-ico::after{content:"!"}
.toast-err .toast-ico{background:var(--danger-soft);color:var(--danger)}
.toast-err .toast-ico::after{content:"!"}
.toast-body{flex:1;min-width:0;font-size:var(--fs-body);line-height:1.7;color:var(--text);
  word-break:break-word}
.toast-n{margin-left:var(--sp-2);font-size:var(--fs-sm);color:var(--muted);font-weight:600}
.toast-x{flex:none;width:22px;height:22px;border:0;border-radius:6px;background:none;padding:0;
  color:var(--muted);font-size:var(--fs-sm);cursor:pointer;line-height:1}
.toast-x:hover{background:var(--warm-soft);color:var(--warm-ink)}
.toast-bar{position:absolute;left:0;bottom:0;height:2px;width:100%;
  background:linear-gradient(90deg,var(--warm-1),var(--warm-2));transform-origin:0 50%}
@keyframes toastBar{from{transform:scaleX(1)}to{transform:scaleX(0)}}
.toast-sticky .toast-bar{display:none}
/* 减弱动效：去掉"从下方滑上来"的位移（前庭敏感的人最怕这个），
   但提示本身照常出现、照常能读 —— 减少动态不等于零反馈。 */
@media (prefers-reduced-motion:reduce){
  @starting-style{ .toast{transform:none} }
  .toast,.toast.out{transform:none!important}
  .toast-bar{animation:none!important;display:none}
}
@media (max-width:768px){
  .toast-stack{left:var(--sp-3);right:var(--sp-3);bottom:var(--sp-3);align-items:stretch}
  .toast{width:auto}
}

/* ---------- 8. 列设置弹层（Lucian #144 Column order editor） ---------- */
.cols-modal{width:min(560px,calc(100vw - var(--sp-8)))}
.colrows{list-style:none;margin:var(--sp-2) 0 0;padding:0;overflow:auto;
  max-height:min(58vh,440px);border:1px solid var(--line);border-radius:10px;background:#fff}
.colrow{display:flex;align-items:center;gap:var(--sp-2);padding:var(--sp-2) var(--sp-3);
  border-bottom:1px solid var(--line);background:#fff;cursor:grab}
.colrow:last-child{border-bottom:0}
.colrow:hover{background:var(--warm-soft)}
.colrow.dragging{opacity:.45;background:var(--warm-soft)}
.col-grip{color:var(--muted);font-size:var(--fs-sm);letter-spacing:-2px;user-select:none}
.col-name{flex:1;display:flex;align-items:center;gap:var(--sp-2);min-width:0;
  font-size:var(--fs-body);cursor:pointer}
.col-name input{width:15px;height:15px;margin:0;flex:none;accent-color:var(--warm-2)}
.col-key{font-size:var(--fs-micro);color:var(--muted);background:var(--bg);
  border-radius:4px;padding:0 var(--sp-1);white-space:nowrap}
.col-mv{display:flex;gap:var(--sp-1);flex:none}
.col-mv button{height:26px;min-height:0;line-height:1;padding:0 var(--sp-2)}
@media (pointer:coarse){ .col-mv button{height:34px;padding:0 var(--sp-3)} }

/* 下拉里的分隔线：把"流程页"（工资批次与确认）和上面那批"数据列表"分开 */
.dd-sep{height:1px;margin:var(--sp-2) var(--sp-2);background:var(--warm-line)}

/* 窄屏：搜索浮层改成贴顶整宽（和下拉菜单同一套做法）。
   绝对定位的浮层会从放大镜右边长出去，把整页撑出横向滚动条
   （2026-11-02 实测 390px 多出 67px，页面能左右晃）。fixed 不参与文档宽度计算。 */
@media (max-width:768px){
  .ns-pop{position:fixed;left:var(--sp-2);right:var(--sp-2);top:58px;
    width:auto;max-width:none}
}
