/* ==========================================================================
   项目全生命周期管理平台 · 样式
   浅色 · 玻璃拟态 · 克制动效
   ========================================================================== */

:root{
  --bg:#eef1f6;
  --ink:#10141b;
  --ink2:#4d5665;
  --ink3:#8b95a3;
  --line:rgba(16,20,27,.075);
  --line-2:rgba(16,20,27,.12);

  --accent:#3b5bdb;
  --accent-2:#5b7cfa;
  --accent-soft:rgba(59,91,219,.10);

  --ok:#0f7b4f;
  --warn:#b06800;
  --bad:#c0342b;
  --info:#1f5fd0;

  --glass:rgba(255,255,255,.64);
  --glass-2:rgba(255,255,255,.82);
  --glass-brd:rgba(255,255,255,.9);
  --blur:18px;

  --r-sm:9px;
  --r-md:14px;
  --r-lg:20px;

  --shadow-sm:0 1px 2px rgba(16,20,27,.05);
  --shadow:0 10px 30px rgba(16,20,27,.07), 0 2px 6px rgba(16,20,27,.04);
  --shadow-lg:0 24px 60px rgba(16,20,27,.12);

  --mono:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,"Liberation Mono",monospace;
  --sans:-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Hiragino Sans GB","Microsoft YaHei",sans-serif;
  /* 账号表冻结列的不透明底色（见「v1.39.0 账号列表冻结列」一节）。
     玻璃面板是半透明的，冻结列必须自己垫一层实色，否则横向滚动时后面的列会透上来。 */
  --u-stick:#fbfcfe;

  --side-w:236px;
  --side-w-min:66px;
  --top-h:64px;

  /* 五段配色（v1.8.0）：默认「商务蓝灰」—— 同一色相递进明度，有层次但不吵。
     具体值由 body[data-theme="…"] 覆盖，见文件末尾「配色主题」一节。 */
  --s1:#2b4f7d;
  --s2:#3b6a92;
  --s3:#4a7f9e;
  --s4:#5b8ea6;
  --s5:#6f9aad;
  /* 段头染色浓度：段色混白多少。深色主题要更大的比例才看得出层次 */
  --seg-tint:13%;
  --seg-tint-2:6.5%;
}

*{box-sizing:border-box}
html,body{margin:0;padding:0;height:100%}
body{
  background:var(--bg);
  color:var(--ink);
  font:14px/1.55 var(--sans);
  -webkit-font-smoothing:antialiased;
  font-variant-numeric:tabular-nums;
  overflow-x:hidden;
}
a{color:inherit;text-decoration:none}
button,input,select,textarea{font:inherit;color:inherit}
code{font-family:var(--mono);font-size:.92em;background:rgba(16,20,27,.06);padding:1px 5px;border-radius:4px}
.muted{color:var(--ink3)}
[hidden]{display:none !important}

/* ============================ 背景光斑 ============================ */
.aurora{position:fixed;inset:0;overflow:hidden;pointer-events:none;z-index:0}
.blob{position:absolute;border-radius:50%;filter:blur(90px);opacity:.55}
.b1{width:620px;height:620px;left:-160px;top:-180px;background:radial-gradient(circle,#b7c6ff 0%,rgba(183,198,255,0) 68%)}
.b2{width:560px;height:560px;right:-120px;top:8%;background:radial-gradient(circle,#c9e6ff 0%,rgba(201,230,255,0) 68%)}
.b3{width:700px;height:700px;left:34%;bottom:-320px;background:radial-gradient(circle,#d8d2ff 0%,rgba(216,210,255,0) 70%)}

/* ============================ 玻璃 ============================ */
.glass{
  position:relative;
  background:var(--glass);
  -webkit-backdrop-filter:blur(var(--blur)) saturate(170%);
  backdrop-filter:blur(var(--blur)) saturate(170%);
  border:1px solid var(--glass-brd);
  box-shadow:var(--shadow);
}
.glass::before{
  content:"";position:absolute;inset:0;border-radius:inherit;pointer-events:none;
  background:linear-gradient(160deg,rgba(255,255,255,.85),rgba(255,255,255,0) 42%);
  opacity:.7;
}

/* ============================ 按钮 ============================ */
.btn{
  position:relative;overflow:hidden;
  appearance:none;border:1px solid var(--line-2);background:rgba(255,255,255,.9);
  color:var(--ink2);font-size:13px;font-weight:560;
  padding:8px 15px;border-radius:var(--r-sm);cursor:pointer;
  transition:transform .12s,background .16s,border-color .16s,color .16s,box-shadow .16s;
  white-space:nowrap;
}
.btn:hover{background:#fff;color:var(--ink);border-color:rgba(16,20,27,.18);box-shadow:var(--shadow-sm)}
.btn:active{transform:translateY(.5px) scale(.995)}
.btn:disabled{opacity:.45;cursor:not-allowed;transform:none}
.btn.primary{
  background:linear-gradient(180deg,var(--accent-2),var(--accent));
  border-color:transparent;color:#fff;box-shadow:0 6px 18px rgba(59,91,219,.28);
}
.btn.primary:hover{filter:brightness(1.05)}
.btn.ghost{background:transparent;border-color:transparent;color:var(--ink3)}
.btn.ghost:hover{background:rgba(255,255,255,.8);border-color:var(--line-2);color:var(--ink2)}
.btn.danger{color:var(--bad);border-color:rgba(192,52,43,.25)}
.btn.danger:hover{background:rgba(192,52,43,.06);border-color:rgba(192,52,43,.4)}
.btn.block{width:100%;justify-content:center;padding:11px 15px;font-size:14px}

.icon-btn{
  appearance:none;border:none;background:transparent;color:var(--ink3);
  width:32px;height:32px;border-radius:8px;display:grid;place-items:center;
  cursor:pointer;transition:background .15s,color .15s;
}
.icon-btn svg{width:18px;height:18px}
.icon-btn:hover{background:rgba(16,20,27,.06);color:var(--ink)}

/* 涟漪 */
.ripple{
  position:absolute;border-radius:50%;transform:scale(0);pointer-events:none;
  background:radial-gradient(circle,rgba(59,91,219,.28),rgba(59,91,219,0) 70%);
  animation:ripple .62s cubic-bezier(.22,1,.36,1) forwards;
}
@keyframes ripple{to{transform:scale(2.6);opacity:0}}

/* ============================ 登录 / 改密 ============================ */
.auth{
  position:relative;z-index:1;min-height:100vh;
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:18px;padding:32px 20px;
}
.auth-card{
  width:100%;max-width:420px;border-radius:24px;padding:32px 30px 26px;
  box-shadow:var(--shadow-lg);
  animation:rise .5s cubic-bezier(.22,1,.36,1);
}
@keyframes rise{from{opacity:0;transform:translateY(14px)}to{opacity:1;transform:none}}

.auth-brand{display:flex;gap:13px;align-items:flex-start;margin-bottom:26px;position:relative}
.mark{
  width:44px;height:44px;flex:none;border-radius:13px;display:grid;place-items:center;
  background:linear-gradient(150deg,var(--accent-2),var(--accent));
  color:#fff;box-shadow:0 8px 20px rgba(59,91,219,.3);
}
.mark svg{width:24px;height:24px}
.mark.sm{width:34px;height:34px;border-radius:10px}
.mark.sm svg{width:19px;height:19px}
.mark.warn{background:linear-gradient(150deg,#f0a83c,#d98200);box-shadow:0 8px 20px rgba(217,130,0,.3)}
.auth-brand h1{margin:0 0 3px;font-size:17px;font-weight:650;letter-spacing:-.01em}
.auth-brand p{margin:0;font-size:12.5px;color:var(--ink3);line-height:1.45}

.auth-form{display:flex;flex-direction:column;gap:13px;position:relative}
.field{display:flex;flex-direction:column;gap:6px}
.field>span{font-size:12px;font-weight:560;color:var(--ink2);letter-spacing:.02em}
.field input{
  width:100%;border:1px solid var(--line-2);background:rgba(255,255,255,.86);
  border-radius:11px;padding:11px 13px;font-size:14px;outline:none;
  transition:border-color .16s,box-shadow .16s,background .16s;
}
.field input::placeholder{color:#b6bec9}
.field input:focus{border-color:var(--accent);background:#fff;box-shadow:0 0 0 4px var(--accent-soft)}

.auth-err{
  min-height:0;font-size:12.5px;color:var(--bad);line-height:1.55;
  background:rgba(192,52,43,.07);border:1px solid rgba(192,52,43,.16);
  border-radius:9px;padding:8px 11px;display:none;
}
.auth-err.show{display:block;animation:shake .3s}
@keyframes shake{0%,100%{transform:translateX(0)}25%{transform:translateX(-4px)}75%{transform:translateX(4px)}}

.auth-tip{margin:2px 0 0;font-size:11.5px;color:var(--ink3);text-align:center;line-height:1.6}
.auth-foot{font-size:11.5px;color:var(--ink3);position:relative;text-align:center}

/* 记住账号密码（v1.21.0）：一行勾选，右侧一枚「仅本机」小胶囊 */
.chk-row{
  display:flex;align-items:center;gap:8px;cursor:pointer;user-select:none;
  font-size:12.5px;color:var(--ink2);margin:-2px 0 0;
}
.chk-row input{
  width:15px;height:15px;flex:none;margin:0;accent-color:var(--accent);cursor:pointer;
}
.chk-row span{font-weight:520}
.chk-row em{
  font-style:normal;font-size:10.5px;color:var(--ink3);letter-spacing:.02em;
  border:1px solid var(--line-2);border-radius:999px;padding:1px 7px;
  background:color-mix(in srgb,var(--accent) 6%,transparent);
}

.pwd-dots{display:flex;gap:8px;justify-content:center;margin:2px 0 2px}
.pwd-dots i{width:9px;height:9px;border-radius:50%;background:rgba(16,20,27,.12);transition:background .2s,transform .2s}
.pwd-dots i.on{background:var(--accent);transform:scale(1.12)}

/* ============================ 应用骨架 ============================ */
.app{position:relative;z-index:1;min-height:100vh}

.sidebar{
  position:fixed;left:14px;top:14px;bottom:14px;z-index:50;
  width:var(--side-w-min);border-radius:var(--r-lg);
  display:flex;flex-direction:column;
  overflow:hidden;
  transition:width .3s cubic-bezier(.22,1,.36,1),box-shadow .3s;
}
.sidebar:hover,.sidebar.pinned{width:var(--side-w);box-shadow:var(--shadow-lg)}
.sidebar.pinned{box-shadow:var(--shadow-lg)}

.side-head{
  display:flex;align-items:center;gap:11px;padding:15px 15px 10px;
  flex:none;position:relative;z-index:1;
}
.side-title{
  font-size:14px;font-weight:650;white-space:nowrap;
  opacity:0;transition:opacity .18s .05s;
}
.sidebar:hover .side-title,.sidebar.pinned .side-title{opacity:1}
.pin{
  margin-left:auto;appearance:none;border:none;background:transparent;color:var(--ink3);
  width:26px;height:26px;border-radius:7px;cursor:pointer;display:grid;place-items:center;
  opacity:0;transition:opacity .18s,background .15s;
}
.pin svg{width:15px;height:15px}
.sidebar:hover .pin,.sidebar.pinned .pin{opacity:1}
.pin:hover{background:rgba(16,20,27,.06);color:var(--ink)}
.sidebar.pinned .pin{color:var(--accent)}

.side-nav{
  display:flex;flex-direction:column;gap:3px;padding:6px 10px;flex:1;
  position:relative;z-index:1;overflow-y:auto;overflow-x:hidden;
}
.nav-item{
  position:relative;overflow:hidden;
  display:flex;align-items:center;gap:12px;
  padding:10px 11px;border-radius:11px;cursor:pointer;
  color:var(--ink2);transition:background .16s,color .16s;
  white-space:nowrap;
}
.nav-item .ico{flex:none;width:20px;height:20px;display:grid;place-items:center}
.nav-item svg{width:19px;height:19px;opacity:.85}
.nav-item .txt{
  font-size:13.5px;font-weight:540;opacity:0;transition:opacity .18s;
}
.sidebar:hover .nav-item .txt,.sidebar.pinned .nav-item .txt{opacity:1}
.nav-item:hover{background:rgba(16,20,27,.05);color:var(--ink)}
.nav-item.active{
  background:linear-gradient(100deg,rgba(59,91,219,.14),rgba(59,91,219,.06));
  color:var(--accent);
}
.nav-item.active svg{opacity:1}
.nav-item.active::after{
  content:"";position:absolute;left:0;top:50%;transform:translateY(-50%);
  width:3px;height:18px;border-radius:0 3px 3px 0;background:var(--accent);
}
.badge{
  margin-left:auto;font-style:normal;font-size:11px;font-weight:600;
  padding:1px 6px;border-radius:999px;background:rgba(16,20,27,.07);color:var(--ink3);
  opacity:0;transition:opacity .18s;
}
.sidebar:hover .badge,.sidebar.pinned .badge{opacity:1}

.side-foot{padding:10px 14px 14px;position:relative;z-index:1;flex:none}
.ver-line{
  display:flex;align-items:center;gap:7px;font-family:var(--mono);
  font-size:10.5px;color:var(--ink3);white-space:nowrap;
  opacity:0;transition:opacity .18s;
}
.sidebar:hover .ver-line,.sidebar.pinned .ver-line{opacity:1}
.dot-sep{width:3px;height:3px;border-radius:50%;background:currentColor;opacity:.5}

/* v1.27.0：侧栏底部「数据存哪儿」的小标。
   云同步 = 绿灯常态；退回本地 / 掉线 = 琥珀，鼠标悬停有原因。
   刻意不用红色：本地存储不等于出错（离线开发就是本地），红点会让人以为数据坏了。 */
.cloud-tag{display:inline-flex;align-items:center;gap:5px}
.cloud-tag .led{
  width:6px;height:6px;border-radius:50%;flex:none;
  background:var(--ok);box-shadow:0 0 0 2px rgba(15,123,79,.14);
}
.cloud-tag.off{color:var(--warn)}
.cloud-tag.off .led{background:var(--warn);box-shadow:0 0 0 2px rgba(176,104,0,.16)}

/* 主区 */
.main{
  margin-left:94px;
  padding:14px 18px 28px 8px;
  min-height:100vh;
  transition:margin-left .3s cubic-bezier(.22,1,.36,1);
}
body.pinned .main{margin-left:264px}

.topbar{
  display:flex;flex-direction:column;align-items:stretch;gap:0;
  height:auto;padding:0;border-radius:var(--r-lg);
  margin-bottom:16px;position:sticky;top:14px;z-index:30;
}
/* v1.16.1：滚动时顶栏「漏底」修复。
   原因：.glass 的背景是 rgba(255,255,255,.64)，只挡住 64%；
   而 backdrop-filter 在 sticky + 祖先有动画/变换的环境里会失效
   （截图里「逾期 / 进行中 / 已闭环」那行文字是清晰可见的，说明没被模糊）。
   于是内容滚到顶栏底下时会透上来，看着像顶栏背景破了。
   两道处理：
     ① 顶栏自身提到 97% 白，不依赖 backdrop-filter 也能挡住；
     ② ::after 填掉 sticky 上方那 14px 缝隙（那块原来完全透明）。 */
/* v1.48.0（峰哥）：深蓝渐变顶栏从 Dashboard 推广到全部界面
   （项目管理 / ECN / 系统设置）—— 参考「项目计划数据看板」的深蓝头。
   原来是 97% 白玻璃；text 反白规则见文件尾部的顶栏配色区。
   v1.50.1（峰哥）：最顶部要纯色，不要渐变色 —— 取原渐变较亮一端的 #283593，
   白字对比度不变，四个界面同一条规则。 */
.topbar.glass{
  background:#283593;
  box-shadow:0 8px 24px rgba(26,35,126,.30);
}
/* v1.50.2（峰哥：「为什么左边的颜色这么淡」）：.glass::before 会给所有玻璃
   卡片叠一层「左上白 85% → 右下透明」的装饰高光。原来顶栏是深蓝渐变时看不出来，
   改纯色后左半边被这层白洗淡，看着像还是渐变。顶栏关掉这层高光（其它玻璃卡片保留）。 */
.topbar.glass::before{content:none}
.topbar::after{
  content:"";position:absolute;z-index:-1;pointer-events:none;
  left:0;right:0;top:-24px;height:24px;
  background:var(--bg);
}
/* v1.9.3：顶栏拆两行 —— 第一行标题/同步状态/账号，第二行放「项目进度」的筛选与操作。
   第二行只在项目进度视图出现（body[data-view="projects"]），
   这样其它页面顶栏高度不变，进度页又省掉一整个 toolbar 的高度。 */
.topbar-main{display:flex;align-items:center;gap:14px;height:var(--top-h);padding:0 18px;flex:none}
/* v1.43.11：进度工具栏压成一行（峰哥：不要折两行）。
   nowrap + 横向滚动兜底 —— 再窄也只占一行，极端窄屏时右端可轻微滑动；
   滚动条藏掉（8px 规矩不适用于「藏」），配合下方控件收窄，常规宽度根本滚不动。 */
.topbar-bar{display:none;align-items:center;gap:10px;flex-wrap:nowrap;overflow-x:auto;overscroll-behavior-x:contain;scrollbar-width:none;padding:8px 14px 10px;border-top:1px solid var(--line)}
.topbar-bar .tb-left,.topbar-bar .tb-right{flex-wrap:nowrap;flex:none}
body[data-view="projects"] .topbar-bar{display:flex}
.topbar-bar .search{width:210px}
.topbar-bar .sel{height:30px}
.topbar-bar .seg-chips{margin-right:4px}
.crumb{position:relative;z-index:1;min-width:0}
.crumb h2{margin:0;font-size:16.5px;font-weight:650;letter-spacing:-.01em}
.crumb p{margin:1px 0 0;font-size:11.5px;color:var(--ink3);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.top-right{margin-left:auto;display:flex;align-items:center;gap:12px;position:relative;z-index:1}

.pill{
  display:inline-flex;align-items:center;gap:6px;font-size:11.5px;color:var(--ink3);
  background:rgba(255,255,255,.72);border:1px solid var(--line);border-radius:999px;
  padding:4px 10px;
}
.led{width:6px;height:6px;border-radius:50%;background:var(--ok);box-shadow:0 0 0 3px rgba(15,123,79,.12)}
.pill.busy .led{background:var(--warn);box-shadow:0 0 0 3px rgba(176,104,0,.12);animation:pulse 1.2s infinite}
.pill.err .led{background:var(--bad);box-shadow:0 0 0 3px rgba(192,52,43,.12)}
/* v1.28.0：云端有新数据、但用户正在打字/开着弹窗不能自动刷新时亮这个。
   做成可点的：整颗药丸就是刷新按钮，比在角落塞一个「点击更新」更容易被发现。 */
.pill.remote-hint{
  cursor:pointer;color:var(--accent);
  background:rgba(59,91,219,.10);border-color:rgba(59,91,219,.28);
}
.pill.remote-hint .led{
  background:var(--accent);box-shadow:0 0 0 3px rgba(59,91,219,.16);
  animation:pulse 1.2s infinite;
}
.pill.remote-hint:hover{background:rgba(59,91,219,.16)}
@keyframes pulse{50%{opacity:.35}}

.user-chip{display:flex;align-items:center;gap:9px}
.avatar{
  width:30px;height:30px;border-radius:9px;display:grid;place-items:center;
  background:linear-gradient(150deg,#e7ecff,#d6dfff);color:var(--accent);
  font-size:12.5px;font-weight:650;
}
.u-meta{display:flex;flex-direction:column;line-height:1.25}
.u-meta b{font-size:12.5px;font-weight:600}
.u-meta small{font-size:10.5px;color:var(--ink3)}

.viewport{position:relative}
.view{animation:fade .28s ease}
@keyframes fade{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}

/* ============================ KPI ============================ */
/* v1.14.0：容器统一走 12 栏细分栅格。
   原来 4 栏 / 2 栏时卡片只有「半宽 / 全宽」两档，横向等于调不动；
   切 12 栏后每档 1/12，同一套 gap 下 3 栏 = 原 4 栏的一格、6 栏 = 原半宽，
   默认布局逐像素不变，但卡片能 1~12 栏任意停。 */
.kpi-row{
  display:grid;grid-template-columns:repeat(12,minmax(0,1fr));
  gap:14px;margin-bottom:14px;
}
.kpi-row>.kpi{grid-column:span 3}
.kpi{
  border-radius:var(--r-md);padding:16px 17px 14px;position:relative;overflow:hidden;
}
.kpi .k-l{font-size:11.5px;font-weight:600;color:var(--ink3);letter-spacing:.03em;display:flex;align-items:center;gap:6px}
.kpi .k-v{font-size:28px;font-weight:680;letter-spacing:-.02em;margin:7px 0 2px;line-height:1.1}
.kpi .k-v small{font-size:14px;font-weight:600;opacity:.62;margin-left:2px}
.kpi .k-s{font-size:11.5px;color:var(--ink3)}
.kpi .k-s b{color:var(--ink2);font-weight:600}
.kpi .spark{position:absolute;right:14px;bottom:12px;width:64px;height:26px;opacity:.5}
.kpi.empty .k-v{color:var(--ink3);font-weight:600;font-size:17px}

/* ============================ 面板 ============================ */
.panel{border-radius:var(--r-lg);padding:16px 18px 14px;margin-bottom:14px}
.panel-head{
  display:flex;align-items:flex-start;justify-content:space-between;gap:12px;
  margin-bottom:10px;position:relative;z-index:1;
}
.panel-head h3{margin:0;font-size:14px;font-weight:650;letter-spacing:-.01em}
.panel-head p{margin:2px 0 0;font-size:11.5px;color:var(--ink3)}
.tag{
  font-size:10.5px;font-weight:600;padding:3px 8px;border-radius:999px;
  background:rgba(16,20,27,.05);color:var(--ink3);white-space:nowrap;flex:none;
}
.tag.real{background:rgba(15,123,79,.1);color:var(--ok)}
.tag.live{background:rgba(31,95,208,.1);color:var(--info)}
.tag.demo{background:rgba(176,104,0,.12);color:var(--warn)}

.grid-2{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:14px}
.grid-2>.panel{grid-column:span 6}
.chart-box{height:330px;position:relative;z-index:1;display:flex;flex-direction:column}
.chart-box.short{height:264px}
/* v1.46.0：svg 用 flex 吃满「盒高 − 图例高」，viewBox 比例由 vbH() 按容器实宽实高算，
   两边一致图就正好铺满 —— 原来 svg height:100% + 图例排后面，svg 等比缩放后
   底部空一块（峰哥截图里红框那片白）。 */
.chart-box>.chart-legend{flex:none}

.stat-inline{display:flex;gap:22px;margin:0 0 4px;position:relative;z-index:1}
.stat-inline .si{display:flex;flex-direction:column;gap:1px}
.stat-inline .si-l{font-size:10.5px;color:var(--ink3)}
.stat-inline .si-v{font-size:17px;font-weight:660;letter-spacing:-.01em}
.stat-inline .si-v.na{color:var(--ink3);font-size:13px;font-weight:560}

/* ============================ 图表（SVG） ============================ */
.chart-svg{width:100%;flex:1;min-height:0;display:block;overflow:visible}
/* v1.47.2（峰哥：「打开后一直在重画，一直在不断拉长」）：
   svg 的 viewBox 宽高比会参与 flex 容器的内容尺寸贡献 —— chart-box flex:auto 时
   容器高跟着 svg 内容高走，重画改 viewBox → 容器变高 → 观察器再重画，死循环。
   断根：svg 包进 .chart-plot 绝对定位填充 —— svg 对布局的贡献归零，
   viewBox / fitChartSvg 的校准从此纯视觉，物理上不可能再回流布局。 */
.chart-plot{flex:1;min-height:0;position:relative}
.chart-plot>.chart-svg{position:absolute;inset:0;width:100%;height:100%;flex:none}
.c-grid line{stroke:rgba(16,20,27,.07);stroke-width:1}
/* v1.38.0 清爽档网格：参考稿用的是 #f0f0f0 —— 比原来的 .07 还淡一档，
   但仍然可见；再淡下去就失去「对齐 extrapolation」的作用了。 */
.c-grid.ch-clean line{stroke:#f0f0f0;stroke-width:1}
.c-val.lite{font-size:9.5px}
/* 面积色（只有清爽档才会画出来）：托住线条，不抢数值 */
.c-area{stroke:none;opacity:.10;pointer-events:none}
.c-axis text{fill:var(--ink3);font-size:10.5px;font-family:var(--sans)}
.c-line{fill:none;stroke-width:2.2;stroke-linecap:round;stroke-linejoin:round}
.c-dot{stroke:#fff;stroke-width:2}
.c-bar{transform-box:fill-box;transform-origin:center bottom;animation:grow .55s cubic-bezier(.22,1,.36,1) both}
@keyframes grow{from{transform:scaleY(0)}to{transform:scaleY(1)}}
@keyframes dash{to{stroke-dashoffset:0}}
.c-bar-lbl{fill:var(--ink3);font-size:10px;text-anchor:middle;font-family:var(--sans)}
/* v1.50.1：坑位窄于人名步进时的拥挤档（app.js 侧加 .tight） */
.c-bar-lbl.tight{font-size:8.5px}
/* v1.50.1（峰哥：「不要压字」）：数值/百分比标签统一带白描边 ——
   压到柱身、折线或相邻标签残影上时仍保持可读 */
.c-val{fill:var(--ink2);font-size:10.5px;font-weight:600;text-anchor:middle;font-family:var(--sans);
  paint-order:stroke;stroke:#fff;stroke-width:2.4px;stroke-linejoin:round}

.empty-chart{
  height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:8px;color:var(--ink3);text-align:center;position:relative;z-index:1;
}
.empty-chart svg{width:34px;height:34px;opacity:.4}
.empty-chart b{font-size:13px;color:var(--ink2);font-weight:600}
.empty-chart p{margin:0;font-size:11.5px;max-width:270px;line-height:1.6}

/* ============================ 工具栏 ============================ */
.toolbar{
  display:flex;align-items:center;gap:10px;flex-wrap:wrap;
  padding:10px 12px;border-radius:var(--r-md);margin-bottom:12px;
  position:relative;
}
.tb-left,.tb-right{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.tb-right{margin-left:auto}
.search{
  display:flex;align-items:center;gap:7px;
  background:rgba(255,255,255,.86);border:1px solid var(--line-2);
  border-radius:9px;padding:0 10px;height:34px;
  transition:border-color .16s,box-shadow .16s;
}
.search:focus-within{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft)}
.search svg{width:15px;height:15px;color:var(--ink3);flex:none}
.search input{border:none;background:transparent;outline:none;width:190px;font-size:12.5px}
.sel{
  appearance:none;border:1px solid var(--line-2);background:rgba(255,255,255,.86);
  border-radius:9px;padding:0 26px 0 10px;height:34px;font-size:12.5px;cursor:pointer;
  background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%238b95a3' stroke-width='1.6' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat:no-repeat;background-position:right 9px center;
  outline:none;
}
.sel:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft)}

/* 列设置弹层 */
.col-pop{
  position:absolute;right:18px;top:auto;z-index:60;width:230px;
  border-radius:var(--r-md);padding:10px;margin-top:-4px;
  box-shadow:var(--shadow-lg);
}
.pop-head{font-size:11.5px;font-weight:600;color:var(--ink3);padding:2px 6px 8px;position:relative;z-index:1}
.pop-body{display:flex;flex-direction:column;gap:1px;max-height:320px;overflow:auto;position:relative;z-index:1}
.pop-body label{
  display:flex;align-items:center;gap:8px;padding:6px 8px;border-radius:7px;
  font-size:12.5px;cursor:pointer;transition:background .14s;
}
.pop-body label:hover{background:rgba(16,20,27,.05)}
.pop-body input{accent-color:var(--accent)}

/* ============================ 数据表 ============================ */
/* ⚠ v1.8.0 性能修复：表格区域一律不用 backdrop-filter。
   实测（2016 行 / 4.5 万单元格）：横向滚动 20fps → 去掉玻璃模糊 54fps，涨 2.7 倍。
   原因：backdrop-filter 的元素每帧都要重新对被它盖住的背景做一次高斯模糊，
   而表格每滚一像素背景就变一次 —— 等于每帧把整块卡片重新模糊一遍。
   更糟的是表头：`.prog-table thead th` 有 34 列 × 3 行 = 100 多个 th，
   每个 th 各自 blur(10px)，而且它们还是 sticky 的，滚动时全部重算。
   现在表头改实色底（视觉上和半透明白几乎看不出区别，因为卡片底色本来就浅），
   玻璃质感保留在侧边栏 / 顶栏 / 登录卡片这些不滚动的地方。 */
.table-panel{
  padding:0;overflow:hidden;
  -webkit-backdrop-filter:none;backdrop-filter:none;
  background:rgba(255,255,255,.9);
  /* 单独成一绘制块：横向滚动时只重画表格自己，不去碰外面的玻璃卡片。
     实测 2016 行时 +6fps（39→45），14 行时无影响。 */
  contain:paint;
}
.tablewrap{overflow:auto;max-height:calc(100vh - 300px)}
.data-table{width:100%;border-collapse:separate;border-spacing:0;min-width:1040px}
.data-table thead th{
  position:sticky;top:0;z-index:2;
  background:#f9fafc;
  text-align:left;font-weight:600;color:var(--ink3);
  font-size:10.5px;letter-spacing:.06em;text-transform:uppercase;
  padding:10px 11px;border-bottom:1px solid var(--line);white-space:nowrap;
}
.data-table tbody td{
  padding:6px 11px;border-bottom:1px solid rgba(16,20,27,.05);
  font-size:12.5px;vertical-align:middle;
}
.data-table tbody tr:hover td{background:rgba(59,91,219,.035)}
.data-table tbody tr:last-child td{border-bottom:none}
.data-table td[contenteditable="true"]{outline:none;border-radius:6px}
.data-table td[contenteditable="true"]:focus{
  background:#fff;box-shadow:inset 0 0 0 2px var(--accent);
}
.data-table .num{text-align:right;font-family:var(--mono);font-size:12px}
.data-table .mono{font-family:var(--mono);font-size:12px;color:var(--ink2)}
.data-table .nm{font-weight:560}
.data-table td[data-key="progress"]{white-space:pre-wrap;line-height:1.5;color:var(--ink2);font-size:12px}
.data-table td[contenteditable="true"]:empty::before{
  content:attr(data-ph);color:#c2c9d4;pointer-events:none;
}

.st{
  display:inline-flex;align-items:center;gap:5px;font-size:11.5px;font-weight:580;
  padding:3px 9px;border-radius:999px;white-space:nowrap;
}
.st::before{content:"";width:5px;height:5px;border-radius:50%;background:currentColor;opacity:.8}
.st.s-量产中{background:rgba(15,123,79,.1);color:#0f7b4f}
.st.s-试制中{background:rgba(31,95,208,.1);color:#1f5fd0}
.st.s-待投标{background:rgba(176,104,0,.11);color:#9a5f00}
.st.s-NA{background:rgba(16,20,27,.06);color:#8b95a3}
.st.s-PO{background:rgba(31,95,208,.1);color:#1f5fd0}
.st.s-PR{background:rgba(122,60,208,.1);color:#6b33c4}
.st.s-已生产{background:rgba(15,123,79,.1);color:#0f7b4f}
.st.s-架设导入,.st.s-方案设计\&DFM{background:rgba(0,140,160,.1);color:#067a8a}
.st.s-立项评估{background:rgba(176,104,0,.11);color:#9a5f00}
.st.s-闲置设备{background:rgba(16,20,27,.06);color:#8b95a3}

.lvl{font-size:10.5px;font-weight:680;padding:1px 6px;border-radius:5px}
.lvl.A{background:rgba(192,52,43,.1);color:#c0342b}
.lvl.B{background:rgba(59,91,219,.1);color:#3b5bdb}

.table-foot{
  display:flex;align-items:center;justify-content:space-between;gap:12px;
  padding:9px 14px;border-top:1px solid var(--line);flex-wrap:wrap;
  font-size:11.5px;color:var(--ink2);background:rgba(250,251,253,.7);
}

/* ---- 表尾左侧：计数 + 下单汇总（v1.19.0）----
   峰哥要的「已下单台数 / 未下单台数 / 已下单金额 / 未下单金额」做成四个小胶囊，
   直接跟在「显示 6/6 台 · 数量合计 16」后面。
   margin-right:auto 把右侧的复制提示 / 冻结按钮 / 图例整体推到最右 ——
   原来 space-between 会在五个元素之间均分空白，看着散。 */
.foot-left{display:flex;align-items:center;gap:9px;flex-wrap:wrap;margin-right:auto;min-width:0}
.foot-left #progCount{white-space:nowrap}
.foot-sums{display:inline-flex;align-items:center;gap:5px;flex-wrap:wrap}
.fs{
  display:inline-flex;align-items:baseline;gap:3px;
  padding:2px 8px;border-radius:999px;cursor:default;white-space:nowrap;
  font-size:11px;color:var(--ink3);background:rgba(16,20,27,.045);
}
.fs b{font-size:11.5px;font-weight:650;color:var(--ink);font-variant-numeric:tabular-nums}
.fs em{font-style:normal;font-size:10px;opacity:.72;margin-left:1px}
/* 已下单 = 蓝（--info）、未下单 = 琥珀（--warn）：
   走变量而不是写死色值，峰哥换配色主题时这两个胶囊跟着走。 */
.fs.paid{background:color-mix(in srgb,var(--info) 9%,#fff);color:color-mix(in srgb,var(--info) 60%,var(--ink3))}
.fs.paid b{color:var(--info)}
.fs.unpaid{background:color-mix(in srgb,var(--warn) 11%,#fff);color:color-mix(in srgb,var(--warn) 64%,var(--ink3))}
.fs.unpaid b{color:var(--warn)}
.fs.other{background:rgba(16,20,27,.06);color:var(--ink3)}
.fs.other b{color:var(--ink2)}
@media (max-width:1440px){
  /* 窄屏（含峰哥截图那种 1080 宽的窗口）：计数 + 4 个胶囊自己占一行，
     第二行右边排「复制粘贴提示 / 冻结按钮 / 图例」。
     算过宽度：count≈200 + 四个胶囊≈450 + 提示≈280 + 按钮≈110 + 图例≈250，
     加上 padding 已经 1300 出头 —— 1440 以下不拆行就必然把图例挤成半截。
     选择器带 #view-projects：ECN 页也有 .table-foot，别把它的「计数 + 说明」推到右边。 */
  #view-projects .foot-left{flex:1 0 100%;margin-right:0}
  #view-projects .table-foot{justify-content:flex-end}
}

.mini-table{width:100%;border-collapse:collapse;font-size:12.5px}
.mini-table th{text-align:left;font-size:10.5px;color:var(--ink3);font-weight:600;padding:6px 8px;border-bottom:1px solid var(--line)}
.mini-table td{padding:6px 8px;border-bottom:1px solid rgba(16,20,27,.05)}
.mini-table tr:last-child td{border-bottom:none}

/* ============================ 提示条 ============================ */
.notice{
  display:flex;gap:11px;align-items:flex-start;
  border-radius:var(--r-md);padding:13px 16px;margin-bottom:12px;
  border-left:3px solid var(--warn);
}
.notice svg{width:19px;height:19px;color:var(--warn);flex:none;margin-top:1px}
.notice b{font-size:12.5px;display:block;margin-bottom:2px}
.notice p{margin:0;font-size:11.5px;color:var(--ink3);line-height:1.6}

/* ============================ 设置 ============================ */
/* 设置页栅格（v1.22.0 改瀑布流）
   以前是纯 grid 两列：一行里两张卡片按最高的那张对齐，矮的下面就是一整片空白 ——
   峰哥截图里那块「上面那么大空间、下面的小卡片拖不上去」就是这个。
   现在由 settingsMasonry() 给每张卡片算 grid-row-end:span N（行高 8px），
   网格会自动把后面的卡片填进空隙里；没跑 JS 前保持 auto 行高，版面不会崩。 */
.settings-grid{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:14px;align-items:start}
.settings-grid.masonry{grid-auto-rows:8px}
.settings-grid>.panel{grid-column:span 6}
.settings-grid .panel{margin-bottom:0}
.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:11px;position:relative;z-index:1}
.fld{display:flex;flex-direction:column;gap:5px}
.fld.wide{grid-column:1/-1}
.fld>span{font-size:11px;font-weight:560;color:var(--ink3)}
.fld input,.fld select{
  border:1px solid var(--line-2);background:rgba(255,255,255,.86);
  border-radius:8px;padding:7px 10px;font-size:12.5px;outline:none;width:100%;
  transition:border-color .16s,box-shadow .16s;
}
.fld input:focus,.fld select:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft)}
.fld input[type="range"]{padding:0;height:26px;accent-color:var(--accent)}
/* v1.29.0：新增 ECN 列 · 「人员名单」的来源勾选（研发 / 组装 / 电气 / 调试 / 采购 / 业务）。
   .fld input 那条 width:100% 会把 checkbox 也撑满，这里必须显式收回来。 */
.nc-src{display:flex;flex-wrap:wrap;gap:6px}
.nc-src label{
  display:inline-flex;align-items:center;gap:5px;font-size:11.5px;color:var(--ink2);
  border:1px solid var(--line-2);background:rgba(255,255,255,.8);
  border-radius:7px;padding:4px 9px;cursor:pointer;transition:border-color .16s,background .16s;
}
.nc-src label:hover{border-color:var(--accent);background:#fff}
.nc-src input[type="checkbox"]{width:auto;margin:0;padding:0;accent-color:var(--accent);flex:none}

.btn-row{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:8px;position:relative;z-index:1}
.hint{font-size:11px;color:var(--ink3);line-height:1.6;margin:0;position:relative;z-index:1}

.save-bar{
  display:flex;align-items:center;justify-content:flex-end;gap:14px;
  padding:10px 2px 18px;margin-top:2px;
}

/* ============================ 更新条 / Toast ============================ */
.updatebar{
  position:fixed;left:50%;bottom:22px;transform:translate(-50%,150%);
  background:#10141b;color:#fff;border-radius:999px;
  padding:9px 11px 9px 18px;display:flex;align-items:center;gap:14px;
  font-size:12.5px;box-shadow:var(--shadow-lg);z-index:90;
  transition:transform .32s cubic-bezier(.22,1,.36,1);
}
.updatebar.show{transform:translate(-50%,0)}
.updatebar button{
  appearance:none;border:none;background:#fff;color:#10141b;
  font-size:12px;font-weight:600;padding:5px 12px;border-radius:999px;cursor:pointer;
}
.toast{
  position:fixed;left:50%;top:18px;transform:translate(-50%,-180%);
  background:#10141b;color:#fff;font-size:12.5px;padding:9px 16px;
  border-radius:9px;box-shadow:var(--shadow-lg);z-index:95;
  transition:transform .28s cubic-bezier(.22,1,.36,1);max-width:86vw;
}
.toast.show{transform:translate(-50%,0)}

/* ============================================================
   项目进度表：3 层表头 + 计划/实际 双行 + 粘性列
   ============================================================ */
/* 进度表的高度由 JS 的 fitProgHeight() 精确量出来写内联样式（顶栏结构一变，
   CSS 里任何 calc(100vh - N) 的估值都会错）。这里给一个保底上限，
   防止 JS 还没跑（首屏、脚本报错）时表格把页面撑爆。 */
.prog-wrap{overflow:auto;max-height:calc(100vh - 150px)}

.prog-table{
  border-collapse:separate;border-spacing:0;
  /* v1.43.10：table-layout:fixed —— 列宽由 colgroup 精确控制（管理员可拖宽、
     松手锁定全员生效），内容放不下截断出 …。总宽由 renderProg 写内联 width。 */
  table-layout:fixed;
  width:max-content;min-width:100%;font-size:12px;
}
.prog-table th,.prog-table td{box-sizing:border-box}
/* v1.43.10：内容超宽一律截断出「…」，完整内容看悬浮 title。
   设备名称（.nm-clamp 两行截断）和备注（.rm-clamp + ⋯ 面板）有自己的截断设计，
   各自声明了 white-space:normal / 结构化截断，不受这条影响。 */
.prog-table tbody td{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* v1.50.3（峰哥）：项目代码列内容自动换行 —— 长代码不再被省略号截断 */
.prog-table tbody td.mono{white-space:normal;word-break:break-all;line-height:1.45}
/* v1.43.10：列宽拖手 —— 管理员按住表头右缘横向拖，松手自动锁定 */
.prog-table thead th .col-grip{
  position:absolute;top:0;right:0;width:8px;height:100%;
  cursor:col-resize;user-select:none;z-index:6;
}
.prog-table thead th .col-grip::after{
  content:'';position:absolute;right:2px;top:24%;bottom:24%;width:2.5px;border-radius:2px;
  background:transparent;transition:background .12s;
}
.prog-table thead th:hover .col-grip::after{background:rgba(59,91,219,.3)}
.prog-table thead th .col-grip:hover::after{background:var(--accent)}

/* ---- 表头三层 ----
   ⚠ 表头底色必须是**完全不透明**的实色（#rrggbb），不许用 rgba / 不许叠 backdrop-filter：
   表头是 sticky 的，纵向滚动时它停在表体上方，一旦底色带 alpha，
   下面滚过的「计划 / 实际 / 日期」就会透上来，看起来像表头里有脏字。
   z-index 统一抬到 20 以上（表体冻结列只有 3–5），任何情况下都盖得住。 */
.prog-table thead th{
  position:sticky;z-index:20;
  background:#f8fafd;
  font-weight:600;color:var(--ink3);font-size:10.5px;letter-spacing:.05em;
  text-align:left;padding:0 7px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
  border-bottom:1px solid var(--line);
}
/* 表头文字对齐：左对齐（默认）/ 居中 / 右对齐，系统设置 → 界面 → 表头对齐 */
body[data-ha="center"] .prog-table thead th{text-align:center}
body[data-ha="center"] .prog-table thead th .sico{margin-left:2px}
body[data-ha="right"]  .prog-table thead th{text-align:right}
.prog-table .h-band th{top:0;height:26px;z-index:20;
  background:var(--band-bg,#f0f4fa);color:var(--ink2);
  font-size:9.5px;letter-spacing:.1em;text-transform:uppercase}
.prog-table .h-owner th{top:26px;height:27px;z-index:20;
  background:var(--own-bg,#f6f8fc);font-size:10px;letter-spacing:.03em;color:var(--ink3)}
.prog-table .h-col th{top:53px;height:36px;z-index:20;color:var(--ink3);background:#f8fafd}

.prog-table .h-owner th span{opacity:.72;margin-right:4px}
.prog-table .h-owner th b{color:var(--ink);font-weight:640;font-size:11px}

.prog-table .h-band .band{font-weight:650;color:var(--ink2)}
/* 业务信息带区（v1.8.0 起跟主题走中性色，v1.7.0 的绿色是又多出来的一种"花"） */
.prog-table .h-band .money{
  color:var(--ink2);
  background:var(--band-bg,#f0f4fa);
}

.seg-b{border-left:1.5px solid rgba(16,20,27,.1)}
/* 段内可并行的段（默认只有采购段）：带区标题后加一枚小标签，免得看不出来为什么这段能乱序填 */
.prog-table .h-band .par-pin{
  display:inline-block;margin-left:5px;padding:0 4px;border-radius:4px;
  font-style:normal;font-size:9px;font-weight:600;line-height:14px;
  letter-spacing:.02em;vertical-align:1px;
  background:rgba(31,95,208,.12);color:#1f5fd0;
}

.prog-table .srt{cursor:pointer;user-select:none}
/* v1.43.10：hover 只变色、绝不许碰背景 —— .srt 就是 sticky 表头 th 本身，
   这里一旦给半透明背景（原来写着 rgba(59,91,219,.06)），权重比 thead th 的
   不透明底色高，鼠标一放上去表头就变透明，底下的行内容全透上来
   （峰哥截图：项目代码表头里透出「四工位双热源风冷测试机9A2356」）。 */
.prog-table .srt:hover{color:var(--accent)}
.sico{font-style:normal;opacity:.3;margin-left:3px;font-size:9px}
.sico.on{opacity:1;color:var(--accent)}

.prog-table .dri-h{text-align:center;font-size:9.5px;letter-spacing:.08em;color:var(--ink3);opacity:.85}
.prog-table .money-col{text-align:right;color:#0f7b4f}

/* ---- 冻结列：core 恒钉 / peel 自然滑走 / tail 钉在 core 右边界 ----
   v1.8.0 重做定位模型。原来（v1.4–v1.7）是「滚动时用 JS 往容器写一个 --peel，
   所有让位列取 left:calc(--Cn - --peel)」—— 滚动一次要重新计算 1.8 万个 sticky 单元格的
   left，这是横向滚动掉帧到个位数帧率的真正原因（实测 2016 行 / 4.5 万 td 时只有 6fps）。

   现在改用 sticky 的**原生语义**，滚动时一个变量都不用改：
     core 0–2   left:var(--Cn)               常量 → 永远钉住
     peel 3–7   不加 sticky                  → 随滚动自然左移，滑到 core 底下被盖住
                                               （视觉上就是"逐步隐藏"，z-index 天然更低）
     tail 8–12  left:calc(--Cn − --sec)      每列各自的常量 → 自然位置 > 该值时跟着走，
                                               一到位就钉住，且五列间距恒等于原列宽
   （v1.9.0 一度把 tail 五列都写成 left:var(--C3)，结果滚过让位区间后五列叠成一张。
     v1.9.1 恢复成「每列自己的偏移 − 让位总量」，--sec 由 syncFrozen() 写一次。）
   这套写法下，滚动完全是合成器工作，主线程零重排 —— 与 --peel 时代视觉等价，但快一个数量级。
   理论依据：sticky 的 left 是「最小间距约束」而不是「定位坐标」，
   所以「先跟着滚、到位置再钉住」本来就是它的默认行为，不需要动态算差值。 */
.prog-table th.fz,.prog-table td.fz{position:sticky}
/* ⚠ 选择器必须带 .fz（v1.9.1 修复）。
   peel 列（数量…PM）带 data-fz 但**不是** sticky，而 tbody td 是 position:relative，
   left 对 relative 同样生效 —— 之前写成 [data-fz="7"]{left:var(--C3)}，
   结果 PM 整列被凭空平移了 236px，表体 PM 和表头 PM 完全对不上。 */
.prog-table .fz[data-fz="0"]{left:var(--C0,0px)}
.prog-table .fz[data-fz="1"]{left:var(--C1,0px)}
.prog-table .fz[data-fz="2"]{left:var(--C2,0px)}
/* tail 9–13（阶段…类型）：每列各自「自身累计左偏移 − 让位总量 --sec」。
   --sec = C9 − C3，即 peel 六列的总宽。用同一个 --C3 会让五列全部钉在一点、
   叠成一张（v1.8.1 修好过，v1.9.0 加 PM 时又退回去了）。
   用 C_n − sec 之后，相邻两列的间距恒等于原列宽，任何滚动位置都不重叠；
   并且 s < sec 时自然位置 > left，sticky 根本不介入 —— 依然是「先跟着滚、到位再钉住」，
   整个滚动过程不写任何变量，主线程零重排。
   v1.43.7：v1.43.6 在 peel 段加了「下单时间」，尾段整体后移一位（8–12 → 9–13），
   当时漏了补 [data-fz="13"] 规则 —— 「类型」列没有 left，往右拖就跟着滚走了。 */
.prog-table .fz[data-fz="8"]{left:calc(var(--C8,0px) - var(--sec,0px))}
.prog-table .fz[data-fz="9"]{left:calc(var(--C9,0px) - var(--sec,0px))}
.prog-table .fz[data-fz="10"]{left:calc(var(--C10,0px) - var(--sec,0px))}
.prog-table .fz[data-fz="11"]{left:calc(var(--C11,0px) - var(--sec,0px))}
.prog-table .fz[data-fz="12"]{left:calc(var(--C12,0px) - var(--sec,0px))}
.prog-table .fz[data-fz="13"]{left:calc(var(--C13,0px) - var(--sec,0px))}
/* v1.55.0：异常数量一拆二，tail 最后一列（类型）从 13 挪到 14 —— 守卫测试盯着 */
.prog-table .fz[data-fz="14"]{left:calc(var(--C14,0px) - var(--sec,0px))}
/* peel 列不 sticky，只保证表头有不透明底色（否则滑到 core 底下时内容会透出来） */
.prog-table thead th.pf{background:#f8fafd}
.prog-table thead th.fz{z-index:22;background:#f8fafd}
.prog-table tbody td.fz{z-index:3}
/* 层级：core 必须压在 peel 之上（peel 滑进 core 底下才算「隐藏」）。
   放在最后 + 提高选择器权重，避免被上面两行通用 z-index 反压。 */
.prog-table thead th.fz.fz-core{z-index:26}
.prog-table tbody td.fz.fz-core{z-index:5}
/* 带区表头也要跟着钉住，否则横向滚动时「项目信息 / 进度信息」标题会漂走 */
.prog-table .h-band th.fzb{position:sticky;z-index:23;background:var(--band-bg,#f0f4fa)}
.prog-table .h-owner th.fzb{position:sticky;z-index:23;background:var(--own-bg,#f6f8fc)}
.prog-table .h-band th.fzb[data-fzb="p"],
.prog-table .h-owner th.fzb[data-fzb="p"]{left:0}
/* 带区拆成两段后，peel 段（数量…PM 上方）不 sticky，但要和 core 段同色，
   否则滚动前肉眼看得出「项目信息」表格底部有一条色差接缝。 */
.prog-table .h-band th.fzb-px{background:var(--band-bg,#f0f4fa)}
.prog-table .h-owner th.fzb-px{background:var(--own-bg,#f6f8fc)}
/* 「进度信息」带区与 tail 第一列（阶段）同一套定位约束 → 永远不会错开。
   数学上 C9 − sec = C3（sec 的定义就是 C9 − C3），这里写成 calc 是为了
   「表头带区」和「表体阶段列」共用同一个公式，将来改列宽不会两边走偏。
   v1.43.7：v1.43.6 加「下单时间」后 peel 变 6 列，tail 第一列下标 8 → 9，
   这条规则的 --C8 没跟着改，「进度信息」带区就偏了一列宽、没跟类型列对齐。 */
.prog-table .h-band th.fzb[data-fzb="q"],
.prog-table .h-owner th.fzb[data-fzb="q"]{left:calc(var(--C9,0px) - var(--sec,0px))}
.prog-table .h-band th.fzb[data-fzb="q"]{background:var(--band-bg,#f0f4fa);border-left:1.5px solid rgba(16,20,27,.13)}
/* ⚠ v1.8.0 移除了这里的 clip-path（v1.7.0 用它把「项目信息」从右往左裁窄）。
   为什么删：clip-path 在**合成线程**上裁剪，sticky 的 left 变化要走**主线程 layout**，
   横向滚动时两者差一帧以上 —— 「项目信息」已经被裁窄了，右边的「进度信息」还没跟过来，
   中间就露出一条 4 列宽的空白（scrollLeft≈300 时最明显），停手后布局追上才恢复。
   就是这个「空白慢慢被覆盖」的现象，不是掉帧、不是卡。
   现在改成纯布局解：项目信息带区不裁不叠，fzb-q 用更高的 z-index + 不透明底色盖住它右侧，
   两者都只依赖 layout，任何一帧都一致，不可能再出现空洞。
   v1.9.1 补的一刀：「项目信息」不再是整块 colspan=8 的 sticky th，而是拆成
   fzb-pc（core 3 列，钉住）+ fzb-px（peel 6 列，跟着滚）—— 否则表头居中时
   标题文字会跑到整段中点，正好被钉住的 fzb-q 盖掉，看起来像标题消失了。 */
.prog-table .h-band th.fzb.fzb-q,
.prog-table .h-owner th.fzb.fzb-q{z-index:25}
/* 冻结区右边界投影 */
.prog-table .fz-last::after{
  content:'';position:absolute;top:0;right:-7px;bottom:0;width:7px;pointer-events:none;
  background:linear-gradient(90deg,rgba(16,20,27,.11),rgba(16,20,27,.03) 55%,transparent);
}
/* 项目信息 / 进度信息 之间的分隔 */
.prog-table .fz-cut{border-left:1.5px solid rgba(16,20,27,.13)}

/* ---- 表 头 配 色（v1.9.2：一个色，不再按段分色）----
   历史教训：v1.8.0 给五段各配一个色（--s1…--s5 递进明度），规则写成 5×2 条。
   之后每加一列、每拆一次带区（v1.9.0 加 PM、v1.9.1 拆「项目信息」），
   就多出一个没被覆盖的 th —— 表现出来就是峰哥说的「颜色又不统一了」，
   而且每次都只错一块，很难一眼看出来。
   现在彻底改成 **一个色**：
     第一行带区（项目信息 / 进度信息 / 各段 / 业务信息）→ var(--band-bg)
     第二行负责人行                                      → var(--own-bg)（同色淡一档）
   段与段的区分只靠竖线，不靠色差 —— 颜色不再承担分区职责，
   所以也不会再出现「某一小块的底色深一点」。
   ⚠ 新增带区 / 新列只许套 .h-band / .h-owner，不许自己写 background。 */

/* 段分隔竖线：统一灰（表头略深、表体略淡），不再取段色 */
.prog-table tbody .seg-b{border-left-color:rgba(16,20,27,.14)}
.prog-table thead th.seg-b{border-left-color:rgba(16,20,27,.16)}

/* ---- 设备升版（v1.6.0，v1.7.0 把版本标记挪到设备名称右侧）----
   同一个设备的多个版本共用一块合并的项目信息单元格。版本标记条贴在名称单元格的
   右边缘（而不是原来的最左边），按版本数均分高度，每一格正好对齐该版本的计划/实际两行。
   v1.6.0 时它贴在左边缘，把设备名称往右挤了 28px，峰哥看着别扭 —— 名称才是主角。 */
.ver-rail{
  position:absolute;right:0;top:0;bottom:0;width:17px;
  display:flex;flex-direction:column;pointer-events:none;
  border-left:1px solid rgba(31,95,208,.16);
  background:rgba(31,95,208,.035);
}
.ver-rail span{
  flex:1;display:flex;align-items:center;justify-content:center;
  font-family:var(--mono);font-size:8.5px;font-weight:700;color:#41639c;
  letter-spacing:-.02em;border-bottom:1px solid rgba(31,95,208,.1);
}
.ver-rail span:last-child{border-bottom:0}
.ver-rail span:nth-child(even){background:rgba(31,95,208,.06)}
.ver-rail span.cur{background:rgba(31,95,208,.16);color:#1f4fa8}
.nm-cell{display:flex;align-items:flex-start;gap:4px}
.nm-cell .nm-clamp{flex:1 1 auto;min-width:0}
.prog-table td.nm.has-rail .nm-cell{padding-right:19px}
/* 升版 / 删末版：悬浮在名称单元格右下角，hover 才显形。
   早前把两个按钮当成 flex 兄弟塞在名称右边 —— 名称列只有 170px，
   按钮一出来就把设备名称挤成一行半，看着更乱。改成绝对定位浮层后，
   名称始终占满整格，按钮只在鼠标进来时盖在空白处。 */
.nm-acts{
  position:absolute;right:2px;bottom:2px;display:flex;gap:3px;z-index:2;
  opacity:0;transition:opacity .12s;pointer-events:none;
}
.prog-table td.nm.has-rail .nm-acts{right:19px}
.prog-table tbody td.nm:hover .nm-acts{opacity:1;pointer-events:auto}
.ver-add,.ver-drop{
  flex:none;appearance:none;cursor:pointer;
  border:1px solid rgba(31,95,208,.3);background:rgba(255,255,255,.94);
  color:#1f5fd0;border-radius:5px;padding:0 4px;
  font-size:9px;line-height:15px;font-family:inherit;white-space:nowrap;
  transition:background .12s,color .12s;
}
.ver-drop{
  border-color:rgba(192,52,43,.28);background:rgba(255,255,255,.94);color:#b02a22;
}
.ver-add:hover{background:#1f5fd0;color:#fff;border-color:#1f5fd0}
.ver-drop:hover{background:var(--bad);color:#fff;border-color:var(--bad)}
/* 已下单的设备不能再升版：按钮变灰、鼠标提示原因 */
.ver-add:disabled,.ver-add[aria-disabled="true"]{
  opacity:.3;cursor:not-allowed;color:#8d97a6;border-color:rgba(16,20,27,.12);
  background:rgba(16,20,27,.04);
}
.ver-add:disabled:hover,.ver-add[aria-disabled="true"]:hover{
  background:rgba(16,20,27,.04);color:#8d97a6;border-color:rgba(16,20,27,.12);
}
/* 版本之间的分界线：新版本第一行顶部加一道淡蓝 */
.prog-table tbody tr.ver-start td{border-top:1.5px solid rgba(31,95,208,.22)}

/* ---- 表体 ----
   网格线强度可调（系统设置 → 界面 → 表格边框），三档写在同一组变量上：
     淡雅 faint / 标准 standard / 清晰 clear
   每个单元格都画右竖线，rowspan 合并的单元格（业务信息三列）也就自然一路延伸到底，
   不会再出现「业务那几列没有线、像是断掉」的观感。 */
.prog-table{
  --grid-c:rgba(16,20,27,.075);
  --grid-w:1px;
  --grp-c:rgba(16,20,27,.30);
  --grp-w:2px;
}
body[data-grid="faint"] .prog-table{--grid-c:rgba(16,20,27,.04);  --grp-c:rgba(16,20,27,.15);--grp-w:1.5px}
body[data-grid="clear"] .prog-table{--grid-c:rgba(16,20,27,.13);  --grp-c:rgba(16,20,27,.48);--grp-w:2.5px}

.prog-table tbody td{
  padding:5px 7px;vertical-align:middle;white-space:nowrap;
  position:relative;
  color:var(--ink);
  border-right:var(--grid-w) solid var(--grid-c);
}
.prog-table thead th{border-right:var(--grid-w) solid var(--grid-c)}
/* 每台设备的第一行顶部加一条粗线 —— 一台设备占 2×版本数 行，不画粗线很容易看串行 */
.prog-table tbody tr.grp-start td{border-top:var(--grp-w) solid var(--grp-c)}
.prog-table tbody tr.grp-start td.fz-last::after{top:-1px}
/* 行底：计划行纯白 / 实际行浅蓝；偶数设备整组再压深一档，便于区分设备组 */
.prog-table tbody tr.rt-plan td{
  background:#fff;border-bottom:1px dashed rgba(16,20,27,.06);
}
.prog-table tbody tr.rt-plan.is-even td{background:#f8fafd}
.prog-table tbody tr.rt-actual td{
  background:#eff3fb;border-bottom:1.5px solid rgba(16,20,27,.15);
}
.prog-table tbody tr.rt-actual.is-even td{background:#e7ecf7}
.prog-table tbody tr:hover td{background:#e4ebfb}
/* 单击选中（还没进入编辑）—— 先给个明确标记，避免「随手一点就弹输入框」 */
.prog-table tbody td.cell-sel{
  box-shadow:inset 0 0 0 2px var(--accent);
  background:#eef3ff !important;
}
.prog-table tbody td.cell-sel::before{
  content:'';position:absolute;inset:0;pointer-events:none;
  background:rgba(59,91,219,.06);
}
.prog-table tbody tr:hover td.rtyp-p{background:#dbe4fa}
.prog-table tbody tr:hover td.rtyp-a{background:#e6dcf8}

.prog-table .seq{font-family:var(--mono);font-size:11px;color:var(--ink3)}
.prog-table .mono{font-family:var(--mono);font-size:11.5px;color:var(--ink2)}
/* v1.50.3（峰哥）：设备名称不要加粗 —— 和其它列一样常规字重 */
.prog-table .nm{font-weight:normal}
/* 设备名称：最多两行，超出用省略号；完整名称见 title 与点击编辑面板 */
.prog-table .nm-clamp{
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;
  overflow:hidden;white-space:normal;word-break:break-word;line-height:1.35;
}
/* 备注：两行 + 右下角「⋯」提示可点击查看全文（浮动面板） */
.prog-table td.remark-cell{
  white-space:normal;line-height:1.45;font-size:11.5px;color:var(--ink2);cursor:zoom-in;
}
.prog-table td.remark-cell .rm-clamp{
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;
  max-height:3.1em;word-break:break-word;padding-right:11px;
}
.prog-table td.remark-cell .rm-more{
  position:absolute;right:2px;bottom:1px;font-style:normal;font-size:11px;line-height:1;
  color:var(--accent);background:#fff;border-radius:3px;padding:1px 3px;pointer-events:none;
  box-shadow:-6px 0 6px #fff;
}
.prog-table td.remark-cell:hover{background:#eaf0ff}
.prog-table td.remark-cell:hover .rm-more{background:#eaf0ff;box-shadow:-6px 0 6px #eaf0ff}
.prog-table .num{text-align:right;font-family:var(--mono);font-size:11.5px}
.prog-table .dt{font-family:var(--mono);font-size:11.5px;text-align:center;color:var(--ink2)}
.prog-table .nil{font-style:normal;color:#c9d1dc}
.prog-table .nil-cell{color:#c9d1dc}
.prog-table .act.filled{color:var(--ink);font-weight:640}
.prog-table .dt.late{color:var(--bad);background:rgba(192,52,43,.07);font-weight:640}
/* v1.43.1：里程碑「实际」格琥珀 —— 实际晚于本节点计划、且本节点自己耗时超计划
   （判定见 app.js actLateCls；和计划行的红 .late 区分开：红=计划已过期没填，琥珀=实际真的拖晚了） */
.prog-table .dt.act.late-act{color:var(--warn);background:rgba(176,104,0,.13);font-weight:640}
/* 锁定单元格：计划日期已填（仅管理员可改）/ 前段实际未完成（后段不可填）
   小锁用纯 CSS 画（锁体 + 锁梁），比 emoji 清晰、能跟主题色走 */
.prog-table td.locked{
  cursor:not-allowed;color:#8d97a6;
  background-image:repeating-linear-gradient(135deg,rgba(16,20,27,.038) 0 4px,transparent 4px 8px);
}
.prog-table td.locked::before{
  content:'';position:absolute;top:3px;right:4px;width:5px;height:4px;
  border-radius:1px;background:#b3bcc9;
}
.prog-table td.locked::after{
  content:'';position:absolute;top:1px;right:5px;width:3px;height:3px;
  border:1px solid #b3bcc9;border-bottom:none;border-radius:2px 2px 0 0;
}
.prog-table td.locked:hover{background-color:rgba(16,20,27,.045)}

/* 未下单设备的「不适用」节点（v1.9.0）
   是否下单=否 → 方案阶段做完就结束，BOM 下发及之后根本不会发生。
   这些格子不是「还没填」，而是「不需要填」，所以用比 locked 更彻底的灰：
   斜纹 + 浅灰底 + 不可点，连日期占位符都不显示。 */
.prog-table td.na-cell{
  cursor:not-allowed;
  background:repeating-linear-gradient(135deg,rgba(16,20,27,.055) 0 5px,rgba(16,20,27,.012) 5px 10px);
  color:#b6bfcb;box-shadow:inset 0 0 0 1px rgba(16,20,27,.03);
}
.prog-table td.na-cell::after{
  content:'';position:absolute;left:50%;top:50%;width:1px;height:11px;
  transform:translate(-50%,-50%) rotate(45deg);background:rgba(16,20,27,.10);
}
.prog-table td.na-cell:hover{background:repeating-linear-gradient(135deg,rgba(16,20,27,.075) 0 5px,rgba(16,20,27,.02) 5px 10px)}

/* 新增 / 改代码后自动定位的那一行：闪三下，告诉用户「挪到这儿了」 */
@keyframes rowFlash{
  0%,100%{box-shadow:inset 0 0 0 0 rgba(59,91,219,0)}
  18%{box-shadow:inset 0 0 0 999px rgba(59,91,219,.14)}
  60%{box-shadow:inset 0 0 0 999px rgba(59,91,219,.05)}
}
.prog-table tbody tr.row-flash td{animation:rowFlash 1.5s ease-out 1;background-color:#eef3ff}

/* 项目信息带区里的「项目负责人：易达」 */
.prog-table .h-owner th .proj-owner{
  display:inline-flex;align-items:center;gap:5px;text-transform:none;letter-spacing:0;
}
.prog-table .h-owner th .proj-owner i{font-style:normal;opacity:.62}
.prog-table .h-owner th .proj-owner b{font-size:11.5px}
.prog-table td.dri{text-align:center;font-size:11px;color:var(--ink2);border-left:1px solid rgba(16,20,27,.045)}

.prog-table td.money-cell{
  text-align:right;font-family:var(--mono);font-size:11.5px;cursor:pointer;
  background:rgba(15,123,79,.03);
}
.prog-table td.money-cell.total{font-weight:680;color:#0f7b4f}

.prog-table td.rtype-h{
  text-align:center;font-size:10px;font-weight:700;letter-spacing:.1em;
  color:var(--ink3);background:#f2f4fa;
}
.prog-table td.rtyp{
  text-align:center;font-size:10px;font-weight:700;letter-spacing:.1em;
}
.prog-table td.rtyp-p{color:var(--info);background:#e8eefb}
.prog-table td.rtyp-a{color:#6b33c4;background:#efe8fb}
.prog-table tbody tr.is-even td.rtyp-p{background:#dfe7f9}
.prog-table tbody tr.is-even td.rtyp-a{background:#e7ddf8}

/* 行内删除 */
.row-del{
  position:absolute;right:2px;bottom:3px;
  width:15px;height:15px;display:none;place-items:center;
  border:none;border-radius:4px;background:rgba(192,52,43,.1);
  color:var(--bad);font-size:9px;cursor:pointer;padding:0;
}
.prog-table tr.rt-plan:hover .row-del{display:grid}
.row-del:hover{background:var(--bad);color:#fff}

/* 状态徽章 */
.st.s-Delay{background:rgba(192,52,43,.11);color:#b02a22}
.st.s-On-going{background:rgba(31,95,208,.1);color:#1f5fd0}
.st.s-Closed{background:rgba(15,123,79,.1);color:#0f7b4f}

/* 阶段标签 */
.ph{
  display:inline-block;font-size:11px;font-weight:580;padding:2px 8px;border-radius:6px;
  background:rgba(16,20,27,.055);color:var(--ink2);white-space:nowrap;
}
/* 阶段配色按「当前段」分色：0 研发 / 1 采购 / 2 组装 / 3 调试 / 4 电气 / fin 已完成 */
.ph-s0{background:rgba(16,20,27,.06);color:var(--ink3)}
.ph-s1{background:rgba(176,104,0,.12);color:#96590a}
.ph-s2{background:rgba(122,60,208,.11);color:#6b33c4}
.ph-s3{background:rgba(31,95,208,.11);color:#1f5fd0}
.ph-s4{background:rgba(31,95,208,.09);color:#2b57bd}
.ph-fin{background:rgba(15,123,79,.13);color:#0f7b4f}

/* 里程碑进度条 */
.pg{
  display:block;height:3px;border-radius:2px;margin-top:4px;
  background:rgba(16,20,27,.08);overflow:hidden;
}
.pg b{display:block;height:100%;border-radius:2px;background:var(--accent);transition:width .4s}

/* 单元格编辑器 */
.cell-in{
  width:100%;min-width:56px;font:inherit;font-size:11.5px;
  border:1.5px solid var(--accent);border-radius:6px;
  padding:1px 4px;outline:none;background:#fff;
  box-shadow:0 0 0 3px var(--accent-soft);
}
.cell-in[type="date"]{padding:0 2px;min-width:120px}
.cell-in[type="number"]{text-align:right;font-family:var(--mono)}

/* 段显隐 chip */
.seg-chips{display:flex;gap:5px;flex-wrap:wrap;margin-right:6px}
.seg-chip{
  position:relative;overflow:hidden;cursor:pointer;
  border:1px solid var(--line-2);background:rgba(255,255,255,.72);
  border-radius:999px;padding:4px 10px;font-size:11.5px;color:var(--ink3);
  transition:color .16s,background .16s,border-color .16s;white-space:nowrap;
}
.seg-chip:hover{color:var(--ink2);border-color:rgba(16,20,27,.2)}
.seg-chip.on{background:var(--accent-soft);border-color:rgba(59,91,219,.32);color:var(--accent);font-weight:600}

/* 底部统计条 */
.stat-bar{
  display:flex;gap:26px;flex-wrap:wrap;align-items:center;
  padding:11px 18px;margin-top:14px;
}
.stat-bar .sb-item{display:flex;flex-direction:column;gap:1px;position:relative;z-index:1}
.stat-bar .sb-item span{font-size:10.5px;color:var(--ink3);letter-spacing:.04em}
.stat-bar .sb-item b{font-size:12.5px;font-weight:600;color:var(--ink2)}

/* 图例 */
.legend{display:flex;align-items:center;gap:6px;flex-wrap:wrap;font-size:11px;color:var(--ink3)}
.legend em{font-style:normal;opacity:.8;margin-left:6px}
.lg{width:9px;height:9px;border-radius:3px;display:inline-block;margin-left:4px}
.lg.plan{background:rgba(31,95,208,.16)}
.lg.actual{background:rgba(122,60,208,.16)}
.lg.late{background:rgba(192,52,43,.2)}

/* 产值录入提示条 */
.rev-notice{
  display:flex;gap:11px;align-items:center;
  border-radius:var(--r-md);padding:12px 16px;margin-bottom:12px;
  border-left:3px solid var(--warn);
}
.rev-notice svg{width:19px;height:19px;color:var(--warn);flex:none}
.rev-notice div{flex:1;min-width:0}
.rev-notice b{font-size:12.5px;display:block;margin-bottom:2px}
.rev-notice p{margin:0;font-size:11.5px;color:var(--ink3);line-height:1.55}

.btn.sm{padding:5px 11px;font-size:11.5px;border-radius:8px}

/* 段落配置 */
.seg-cfg{display:flex;flex-direction:column;gap:8px;position:relative;z-index:1}
.seg-row{
  display:grid;grid-template-columns:56px minmax(112px,.9fr) minmax(112px,.9fr) auto minmax(210px,1.7fr);gap:8px;align-items:center;
}
.seg-row b{font-size:12px;font-weight:620}
.seg-row label{display:flex;align-items:center;gap:5px;font-size:10.5px;color:var(--ink3)}
/* 加了「段内并行」勾选框后格位变窄，标签文字不能被挤成两行 */
.seg-row label>span{white-space:nowrap;flex:0 0 auto}
.seg-row input{
  border:1px solid var(--line-2);border-radius:6px;padding:4px 7px;
  font-size:11.5px;width:100%;background:rgba(255,255,255,.86);outline:none;
}
.seg-row label>input{flex:1 1 56px;min-width:0;width:auto}
.seg-row input:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft)}
/* 「段内并行」勾选框：不要撑满，跟着文字走 */
.seg-row .seg-par{gap:4px;white-space:nowrap;cursor:pointer;user-select:none}
.seg-row .seg-par input[type=checkbox]{
  width:auto;flex:0 0 auto;margin:0;padding:0;accent-color:var(--accent);cursor:pointer;
}
.seg-row .seg-par input[type=checkbox]:focus{box-shadow:none}
.seg-row em{
  font-style:normal;font-size:10.5px;color:var(--ink3);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}

/* ============================ v1.2.0 业务信息列 / 演示标记 ============================ */
/* 第三层带区：进度信息（H–L）用略深的底，与「项目信息」区分 */
.prog-table .h-band .band-prog{color:var(--ink2);background:var(--band-bg,#f0f4fa)}
.prog-table .h-owner .owner-blank{background:var(--own-bg,#f6f8fc);border-right:1.5px solid rgba(16,20,27,.1)}
.prog-table .h-owner .owner.money span{opacity:.72}
.prog-table .h-owner .owner.money b{color:#0f7b4f}

.prog-table .biz-col{font-size:11px;color:#0f7b4f}
.prog-table td.biz{
  text-align:center;font-size:11.5px;color:var(--ink2);cursor:pointer;
  background:rgba(15,123,79,.02);
}

/* 演示单价：数值右上角挂一个「演」角标 */
.demo-pin{
  font-style:normal;font-size:9px;line-height:1;font-weight:700;
  display:inline-block;margin-left:4px;padding:2px 3px;border-radius:4px;
  background:rgba(176,104,0,.14);color:#b06800;vertical-align:2px;
}
.prog-table td.price-demo{background:rgba(176,104,0,.055)}

/* KPI 标题右侧的口径小标签 */
.kpi .k-tag{
  font-style:normal;font-size:9.5px;font-weight:600;letter-spacing:0;
  padding:2px 6px;border-radius:999px;background:rgba(16,20,27,.055);
  color:var(--ink3);white-space:nowrap;
}

/* 演示数据提示条：换成琥珀色，和「缺数据」的警告区分开 */
.rev-notice.demo{border-left-color:#b06800}
.rev-notice.demo svg{color:#b06800}
.rev-notice code{
  font-family:var(--mono);font-size:10.5px;
  background:rgba(16,20,27,.055);padding:1px 4px;border-radius:4px;
}

.si-v.sm{font-size:12.5px;font-weight:600}

/* 设置页：业务信息那一行用虚线隔开 */
/* 业务信息那行没有「段内并行」勾选框，单独给 4 列模板，避免第 4 个元素落到 auto 轨道上 */
.seg-row.biz-row{
  margin-top:8px;padding-top:10px;border-top:1px dashed var(--line-2);
  grid-template-columns:56px 1fr 1fr 1.6fr;
}

/* ECN 演示行 */
.data-table tr.is-demo td{background:rgba(176,104,0,.028)}

/* 图表图例（多序列 / 带目标线时由 drawBar 等追加） */
.chart-legend{
  display:flex;flex-wrap:wrap;gap:12px;justify-content:center;
  margin-top:-4px;font-size:11px;color:var(--ink2);
}
.chart-legend .cl-item{display:inline-flex;align-items:center;gap:5px;white-space:nowrap}
.chart-legend .cl-dot{
  width:9px;height:9px;border-radius:3px;display:inline-block;flex:none;
}

/* ============================================================
   长文本浮动编辑面板（备注 / 长设备名）
   ============================================================ */
/* 用原生 dialog 顶层渲染，彻底避开 z-index / stacking context 干扰 */
.le-dialog{
  border:none;padding:0;background:transparent;max-width:96vw;max-height:92vh;
  overflow:visible;color:var(--ink);
}
.le-dialog::backdrop{background:rgba(16,20,27,.32)}
.le-box{
  width:min(560px,92vw);padding:14px;border-radius:15px;
  display:flex;flex-direction:column;gap:10px;
  background:#fff;border:1px solid rgba(16,20,27,.1);
  box-shadow:0 26px 60px -16px rgba(16,20,27,.4);
}
.le-box .le-head{display:flex;align-items:baseline;gap:10px}
.le-box .le-head b{font-size:13px;font-weight:700;color:var(--ink);flex:none}
.le-box .le-head span{
  font-size:11px;color:var(--ink3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
.le-box .le-ta{
  width:100%;min-height:128px;max-height:46vh;resize:vertical;
  border:1px solid var(--line);border-radius:9px;padding:10px 11px;
  font-family:inherit;font-size:12.5px;line-height:1.7;color:var(--ink);
  background:#fafbfe;white-space:pre-wrap;
}
.le-box .le-ta:focus{
  outline:none;border-color:var(--accent);background:#fff;
  box-shadow:0 0 0 3px rgba(59,91,219,.13);
}
.le-box .le-foot{display:flex;align-items:center;gap:8px}
.le-box .le-tip{flex:1;font-size:10.5px;color:var(--ink3)}
.le-box .le-count{font-family:var(--mono);font-size:10.5px;color:var(--ink3)}

/* 表脚：冻结开关 */
.foot-toggle{
  border:1px solid var(--line-2);background:rgba(255,255,255,.7);
  font-size:11px;color:var(--ink3);padding:3px 9px;border-radius:999px;cursor:pointer;
  white-space:nowrap;transition:all .18s;
}
.foot-toggle:hover{color:var(--accent);border-color:rgba(59,91,219,.35)}
.foot-toggle.on{color:var(--accent);background:rgba(59,91,219,.08);border-color:rgba(59,91,219,.28);font-weight:600}

/* ==================== 配色主题（v1.8.0 · 12 套）====================
   峰哥的要求：v1.7.0 那种「五段五个色相」太花哨，项目管理要严肃商业化；
   但每个人的审美和汇报场合不同，所以给一套主题库让他自己选，不少于 10 种。

   每套只定义 5 个段色（--s1…--s5）。段头底、负责人行底、段分隔线全部由
   color-mix 从段色派生 —— 加一套新主题只要写 5 个色值，不用再补 20 条规则。

   排布逻辑：前 5 套是「同一色相递进明度」（最克制，适合对外汇报），
   第 6–10 套换成不同色系但仍是低饱和（暖褐 / 酒红 / 蓝灰 / 驼 / 紫），
   第 11 套给黑白强对比，最后一套 rainbow 保留 v1.7.0 原样，想用彩色时还能切回来。 */
body[data-theme="business"]{--s1:#2b4f7d;--s2:#3b6a92;--s3:#4a7f9e;--s4:#5b8ea6;--s5:#6f9aad}
body[data-theme="graphite"]{--s1:#2e343c;--s2:#454c56;--s3:#5b636e;--s4:#717986;--s5:#878f9c;--seg-tint:17%;--seg-tint-2:8.5%}
body[data-theme="ink"]     {--s1:#14171c;--s2:#2a2f38;--s3:#404752;--s4:#565e6b;--s5:#6c7482;--seg-tint:18%;--seg-tint-2:9%}
body[data-theme="ocean"]   {--s1:#0b4a63;--s2:#11647f;--s3:#197d96;--s4:#2d95a8;--s5:#4aa9b8}
body[data-theme="forest"]  {--s1:#1b5540;--s2:#276b4f;--s3:#33825e;--s4:#4a9873;--s5:#64ac88}
body[data-theme="clay"]    {--s1:#6f4429;--s2:#875536;--s3:#9c6a44;--s4:#b0835c;--s5:#c09b78}
body[data-theme="wine"]    {--s1:#63212f;--s2:#7d2c3c;--s3:#964050;--s4:#a85a66;--s5:#b87d84}
body[data-theme="slate"]   {--s1:#37455a;--s2:#495a70;--s3:#5d6f86;--s4:#72859a;--s5:#8a9aae}
body[data-theme="sand"]    {--s1:#7d6a45;--s2:#8f7c55;--s3:#a08e66;--s4:#ae9f7d;--s5:#bcb096}
body[data-theme="violet"]  {--s1:#48386c;--s2:#5c4a85;--s3:#71609d;--s4:#8778b0;--s5:#9c90c2}
body[data-theme="contrast"]{--s1:#0b0d10;--s2:#1c2b4a;--s3:#4a2f10;--s4:#0a3f30;--s5:#58102a;--seg-tint:26%;--seg-tint-2:13%}
/* 保留 v1.7.0 的五色分色，想要彩色方案时切这套 */
body[data-theme="rainbow"] {--s1:#3b5bdb;--s2:#b0690a;--s3:#7a3cd0;--s4:#0b7a70;--s5:#b02a6a}
/* v1.38.0：清爽蓝 —— 取自峰哥给的参考稿（Ant Design 标准色板）。
   五段不再是同一色相的明度递进，而是「蓝 / 浅蓝 / 绿 / 橙 / 紫」分色：
   扫一眼就知道这是第几段，不用靠深浅去数。淡染也压到 10%，靠近参考稿的白净感。 */
body[data-theme="fresh"]   {--s1:#1677ff;--s2:#4096ff;--s3:#52c41a;--s4:#fa8c16;--s5:#722ed1;--seg-tint:10%;--seg-tint-2:5%}

/* 表头统一底色（v1.9.2 收口）
   为什么不写死色值、也不定义在 :root：
   - 要跟着主题走 → 用 --s1 派生；
   - 但 :root 上求值时 --s1 只有默认值，body[data-theme="…"] 的覆盖根本看不到，
     所以这两个变量必须定义在 body 上，由 body 上的最终 --s1 / --seg-tint 求值。
   ⚠ 以后加带区、加列，只许套 .h-band / .h-owner，不许再自己写 background ——
     漏一条就是峰哥看到的「颜色又不统一了」。 */
body{
  --band-bg:color-mix(in srgb,var(--s1,#2b4f7d) var(--seg-tint,13%),#fff);
  --own-bg: color-mix(in srgb,var(--s1,#2b4f7d) var(--seg-tint-2,6.5%),#fff);
}

/* 设置页里的主题预览：5 个小色块，跟着选择实时变 */
.theme-prev{display:flex;gap:5px;align-items:center;height:30px}
.theme-prev i{
  width:26px;height:16px;border-radius:4px;display:block;
  box-shadow:inset 0 0 0 1px rgba(16,20,27,.12);
}

/* ---- 变更数 / 现场数（v1.55.0，原「异常数量」一拆二）：链接样式，双击开明细页签 ---- */
.abn-link{
  appearance:none;border:0;background:transparent;padding:0 3px;margin:0;
  font:inherit;font-size:11.5px;font-weight:650;font-family:var(--mono);
  color:var(--info);cursor:pointer;border-radius:4px;
  text-decoration:underline;text-decoration-style:dotted;text-underline-offset:2px;
  transition:background .12s,color .12s;
}
.abn-link:hover{background:rgba(31,95,208,.12);color:#123f8c}
.abn-link:focus-visible{outline:2px solid var(--accent);outline-offset:1px}
.prog-table td[data-abn]{text-align:center}
.prog-table td[data-abn] .abn-zero{color:var(--ink3);opacity:.5}
/* 现场数用琥珀色，和变更数（蓝）一眼区分开 */
.prog-table td[data-abnk="san"] .abn-link{color:#a05a00}
.prog-table td[data-abnk="san"] .abn-link:hover{background:rgba(176,104,0,.12);color:#7a4100}

/* ---- 设备异常明细弹窗的页签（v1.55.0：变更 / 现场异常）---- */
.abn-tabs{display:inline-flex;gap:4px;margin-left:10px;vertical-align:middle}
.abn-tab{
  appearance:none;border:1px solid var(--glass-brd);background:transparent;border-radius:999px;
  font:inherit;font-size:11px;font-weight:650;color:var(--ink3);
  padding:3px 10px;cursor:pointer;transition:all .12s;
}
.abn-tab:hover{color:var(--ink);background:var(--glass)}
.abn-tab.on{background:var(--accent);border-color:var(--accent);color:#fff}

/* ---- ECN 明细弹窗（双击异常数量打开）---- */
.ecn-dialog{
  border:none;padding:0;background:transparent;max-width:96vw;max-height:94vh;
  overflow:visible;color:var(--ink);
}
.ecn-dialog::backdrop{background:rgba(16,20,27,.34)}
.ecn-box{
  width:min(1360px,96vw);max-height:88vh;padding:14px;border-radius:15px;
  display:flex;flex-direction:column;gap:10px;position:relative;
  background:#fff;border:1px solid rgba(16,20,27,.1);
  box-shadow:0 26px 60px -16px rgba(16,20,27,.4);
}
.ecn-box .ecn-head{display:flex;align-items:baseline;gap:10px;flex:none}
.ecn-box .ecn-head b{font-size:13px;font-weight:700;flex:none}
.ecn-box .ecn-head span{
  font-size:11px;color:var(--ink3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
.ecn-box .ecn-scroll{
  flex:1 1 auto;min-height:120px;overflow:auto;
  border:1px solid var(--line-2);border-radius:10px;
}
.ecn-tbl{width:100%;border-collapse:separate;border-spacing:0;font-size:12px}
.ecn-tbl th{
  position:sticky;top:0;z-index:2;background:#f5f7fb;text-align:left;
  padding:7px 9px;font-size:10.5px;font-weight:650;color:var(--ink3);
  letter-spacing:.04em;white-space:nowrap;border-bottom:1px solid var(--line-2);
}
.ecn-tbl td{padding:4px 6px;border-bottom:1px solid var(--line);vertical-align:top}
.ecn-tbl tr:last-child td{border-bottom:0}
.ecn-tbl td[contenteditable]{
  min-width:86px;white-space:pre-wrap;border-radius:6px;outline:none;
  padding:5px 7px;transition:background .12s,box-shadow .12s;
}
.ecn-tbl td[contenteditable]:hover{background:#f7f9fd}
.ecn-tbl td[contenteditable]:focus{background:#eef3ff;box-shadow:inset 0 0 0 1.5px var(--accent)}
.ecn-tbl td[contenteditable]:empty::before{content:attr(data-ph);color:var(--ink3);opacity:.5}
.ecn-tbl td .sel{width:100%;height:26px;font-size:11.5px;border-radius:6px}
.ecn-empty{padding:30px 12px;text-align:center;color:var(--ink3);font-size:12px}
.ecn-box .ecn-foot{display:flex;align-items:center;gap:8px;flex:none}
.ecn-box .ecn-tip{flex:1;font-size:10.5px;color:var(--ink3)}

/* ---- v1.23.0：弹窗表头与 ECN 页统一 ----
   列宽照 ECN 页的设置走，所以必须 table-layout:fixed + 显式总宽，
   否则自动布局会把宽列压扁、窄列撑开，看起来又和 ECN 页不一样。 */
.ecn-box .ecn-head .ecn-colbtn{margin-left:auto;flex:none;font-size:11px;padding:4px 9px}
.ecn-box .ecn-head .ecn-colbtn.on{border-color:var(--accent);color:var(--accent);background:var(--accent-soft)}
.ecn-panel-tbl{table-layout:fixed}
.ecn-panel-tbl th{overflow:hidden;text-overflow:ellipsis}
.ecn-panel-tbl td{overflow:hidden}
.ecn-panel-tbl .ecn-rowop-h{width:34px}
.ecn-panel-tbl .ecn-rowop{text-align:center;overflow:visible}
.ecn-panel-tbl td.ecn-ro{color:var(--ink3);font-size:11.5px;padding:6px 7px;line-height:1.5}
.ecn-panel-tbl td.ecn-img{padding:3px 6px}
.ecn-panel-tbl .img-ro{color:var(--ink3);opacity:.45}

/* 「显示列」浮层：贴在标题栏下方，不挡表格 */
.col-pop{
  position:absolute;right:14px;top:46px;z-index:20;width:260px;max-height:min(420px,70vh);
  display:flex;flex-direction:column;gap:6px;padding:10px;border-radius:12px;
  background:#fff;border:1px solid rgba(16,20,27,.12);
  box-shadow:0 18px 40px -12px rgba(16,20,27,.34);
}
.col-pop[hidden]{display:none}
.col-pop-head{display:flex;align-items:baseline;justify-content:space-between;gap:8px;flex:none}
.col-pop-head b{font-size:12px;font-weight:650}
.col-pop-head span{font-size:10.5px;color:var(--ink3);font-variant-numeric:tabular-nums}
.col-pop-list{flex:1 1 auto;overflow:auto;display:flex;flex-direction:column;gap:1px}
.col-pop-row{
  display:flex;align-items:center;gap:7px;padding:5px 6px;border-radius:7px;
  font-size:12px;cursor:pointer;user-select:none;
}
.col-pop-row:hover{background:rgba(16,20,27,.04)}
.col-pop-row input{flex:none;margin:0;width:14px;height:14px;accent-color:var(--accent);cursor:pointer}
.col-pop-row span{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.col-pop-row i{
  font-style:normal;font-size:10px;color:var(--ink3);flex:none;
  border:1px solid var(--line-2);border-radius:4px;padding:0 4px;line-height:15px;
}
.col-pop-foot{display:flex;justify-content:flex-end;gap:6px;flex:none;padding-top:2px}

/* ==================== v1.15.0 导入 CSV 弹层 ====================
   复用 .ecn-dialog / .ecn-box 的骨架（同一个 <dialog> 观感），
   只补：工具条、填写说明、预览表、三种动作色。 */
.imp-dialog .imp-box{width:min(1080px,96vw);max-height:88vh}
.imp-tools{display:flex;align-items:center;gap:8px;flex:none;flex-wrap:wrap}
.imp-tip{flex:1;min-width:180px;font-size:10.5px;color:var(--ink3)}
.imp-tip b{color:var(--ink2);font-weight:650}
.imp-guide{
  flex:none;font-size:10.5px;line-height:1.75;color:var(--ink3);
  background:#f7f9fd;border:1px solid var(--line-2);border-radius:9px;padding:7px 10px;
}
.imp-guide b{color:var(--ink2);font-weight:650}
.imp-scroll.over{outline:2px dashed var(--accent);outline-offset:-6px;background:#f4f7ff}
.imp-drop{padding:36px 16px;text-align:center;font-size:12px;color:var(--ink3)}
.imp-drop.bad{color:var(--warn);font-weight:560;text-align:left;line-height:1.7;padding:18px 16px}
.imp-tbl{width:100%;border-collapse:separate;border-spacing:0;font-size:11.5px}
.imp-tbl th{
  position:sticky;top:0;z-index:2;background:#f5f7fb;text-align:left;padding:7px 8px;
  font-size:10.5px;font-weight:650;color:var(--ink3);letter-spacing:.04em;white-space:nowrap;
}
.imp-tbl td{padding:5px 8px;border-bottom:1px solid var(--line);vertical-align:top}
.imp-tbl tr:last-child td{border-bottom:0}
.imp-tbl .num{text-align:right;color:var(--ink3);font-variant-numeric:tabular-nums}
.imp-tbl .mono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px}
.imp-chg{color:var(--ink2);line-height:1.55}
.imp-err{color:var(--warn);font-weight:560}
.imp-act{font-style:normal;font-size:10px;font-weight:700;padding:1px 6px;border-radius:5px;white-space:nowrap}
.imp-act.add{background:rgba(15,123,79,.12);color:var(--ok)}
.imp-act.upd{background:rgba(59,91,219,.12);color:var(--accent)}
.imp-act.bad{background:rgba(176,104,0,.14);color:var(--warn)}
.imp-tbl tr.imp-bad td{background:rgba(176,104,0,.04)}
.imp-more{padding:8px 10px;font-size:10.5px;color:var(--ink3);border-top:1px solid var(--line)}

/* 导入方式：追加合并 / 替换全部（v1.20.0） */
.imp-mode-row{gap:16px;padding:1px 2px 0}
.imp-mode{
  display:inline-flex;align-items:center;gap:6px;cursor:pointer;
  font-size:11.5px;color:var(--ink2);user-select:none;
}
.imp-mode input{accent-color:var(--accent);cursor:pointer}

/* ECN 只读列（标记 / 录入人 / 录入时间，v1.20.0；项目代码 / 设备名称，v1.25.0）：
   只读 —— 底色压一档、字小一档，一眼就知道点进去改不了。
   这几列和导出 CSV 的对应列是同一批字段，别再单独拼尾巴。 */
#ecnTable td.ecn-ro{
  color:var(--ink3);font-size:11.5px;white-space:nowrap;
  background:rgba(16,20,27,.016);cursor:default;
}
#ecnTable tbody tr:hover td.ecn-ro{background:rgba(59,91,219,.02)}
/* 关联键（项目代码 / 设备名称）比审计列长得多，nowrap 会糊到下一格，允许折行 */
#ecnTable td.ecn-ro.ro-link,
.ecn-panel-tbl td.ecn-ro.ro-link{white-space:normal;word-break:break-word;line-height:1.35}
/* 孤儿行：这条 ECN 的设备在台账里找不到 → 它永远不会计入「异常数量」。
   用琥珀色 + 虚线下划线标出来，不然它会一直安静地漏掉。
   v1.53.0：主表里双击可跳「项目进度」改挂设备 —— 手势从 help 改 pointer，
   hover 时底色亮一下当可点提示（弹窗里只是提示文案，不给跳，仍是 help）。 */
#ecnTable td.ecn-ro.ro-orphan,
.ecn-panel-tbl td.ecn-ro.ro-orphan{
  color:var(--warn);text-decoration:underline dotted rgba(176,104,0,.55);
  text-underline-offset:2px;
}
#ecnTable td.ecn-ro.ro-orphan{cursor:pointer}
#ecnTable td.ecn-ro.ro-orphan:hover{background:rgba(230,145,20,.10)}
#ecnTable tr[data-orphan="1"] td:first-child{box-shadow:inset 2px 0 0 var(--warn)}

/* ECN 日期列（v1.40.0，v1.52.0 改三段式）
   v1.52.0（峰哥：日期填写要和项目管理界面一样）—— 不再常驻原生 date input
   （那个「yyyy/mm/E」占位没人看得懂）：平时就是一格文本，单击手打、双击弹日历。
   编辑器复用进度表的 .cell-in；这里只管平时那格文本的样子：
   等宽数字居中看着像日期，hover 给个浅底提示「这里能点」。 */
#ecnTable td.ecn-date,
.ecn-panel-tbl td.ecn-date{padding:3px 6px;white-space:nowrap;text-align:center;
  font-family:var(--mono);font-size:11.5px;color:var(--ink2)}
#ecnTable tbody td.ecn-date:not(.ecn-ro){cursor:text}
#ecnTable tbody tr:hover td.ecn-date:not(.ecn-ro),
.ecn-panel-tbl tbody tr:hover td.ecn-date:not(.ecn-ro){background:var(--glass)}
.ecn-nil{font-style:normal;color:#c9d1dc}
/* 值不是日期（老数据里写了「月底前」之类）：标出来但**不吞掉**原值 */
#ecnTable td.ecn-date.is-bad,
.ecn-panel-tbl td.ecn-date.is-bad{box-shadow:inset 0 0 0 1px rgba(192,52,43,.22);border-radius:6px}
.ecn-bad{
  font-style:normal;font-size:9.5px;font-weight:700;color:#b02a22;
  margin-left:2px;vertical-align:middle;opacity:.75;
}
/* 目前状态：复用项目进度表的 .st 胶囊，两页口径与配色一致 */
#ecnTable td.ecn-status,
.ecn-panel-tbl td.ecn-status{padding:4px 7px}
#ecnTable td.ecn-status .st,
.ecn-panel-tbl td.ecn-status .st{font-size:10.5px}

/* ============================ 响应式 ============================ */
.only-mobile{display:none}

/* 表格密度 */
body[data-density="compact"] .data-table tbody td{padding:3px 9px;font-size:12px}
body[data-density="compact"] .data-table thead th{padding:7px 9px}
body[data-density="comfortable"] .data-table tbody td{padding:7px 11px}

@media (max-width:1180px){
  /* 仍是 12 栏体系，只是基准列数降到 6：KPI 3 栏 = 2 个一排，面板 6 栏 = 整行 */
  .kpi-row{grid-template-columns:repeat(6,minmax(0,1fr))}
  .kpi-row>.kpi{grid-column:span 3}
  .grid-2{grid-template-columns:repeat(6,minmax(0,1fr))}
  .grid-2>.panel{grid-column:span 6}
  .settings-grid{grid-template-columns:repeat(6,minmax(0,1fr))}
  .settings-grid>.panel{grid-column:span 6}
}

@media (max-width:900px){
  .sidebar{transform:translateX(-118%);transition:transform .3s cubic-bezier(.22,1,.36,1);width:var(--side-w)}
  .sidebar.open{transform:none;box-shadow:var(--shadow-lg)}
  .sidebar .side-title,.sidebar .nav-item .txt,.sidebar .badge,.sidebar .ver-line,.sidebar .pin{opacity:1}
  .main{margin-left:0;padding:12px 12px 24px}
  body.pinned .main{margin-left:0}
  .only-mobile{display:grid}
  .topbar-main{padding:0 12px;gap:10px}
  .topbar-bar{padding:8px 10px 10px}
  .topbar-bar .search{width:150px}
  .u-meta{display:none}
  .pill{display:none}
  .crumb h2{font-size:15px}
}

@media (max-width:640px){
  .kpi-row{grid-template-columns:repeat(6,minmax(0,1fr));gap:10px}
  .kpi-row>.kpi{grid-column:span 3}
  .kpi .k-v{font-size:23px}
  .search input{width:130px}
  .data-table{min-width:900px}
  .chart-box{height:264px}
}

@media (prefers-reduced-motion:reduce){
  *{animation-duration:.01ms !important;transition-duration:.01ms !important}
}

/* ============================================================================
   v1.9.3
   ========================================================================== */

/* ---- 选设备提示条（新增 ECN 时先去进度表点设备） ---- */
.pickbar{
  display:flex;align-items:center;gap:10px;
  padding:9px 14px;margin-bottom:10px;border-radius:var(--r-md);
  background:rgba(59,91,219,.09);border:1px solid rgba(59,91,219,.28);
  font-size:12px;color:var(--ink2);
  animation:fade .2s ease;
}
.pickbar b{color:var(--ink);font-size:12.5px}
.pickbar .pk-ico{color:var(--accent);font-weight:700;font-size:14px;line-height:1}
.pickbar .pk-sub{color:var(--ink3);font-size:11.5px;margin-right:auto}
/* 选设备模式下，设备名称格子变成「可点选」的样子 —— 其它格子保持原样，避免误点 */
body.picking .prog-table td[data-k="deviceName"]{
  cursor:crosshair;
  background:rgba(59,91,219,.07);
  box-shadow:inset 0 0 0 1px rgba(59,91,219,.25);
}
body.picking .prog-table td[data-k="deviceName"]:hover{
  background:rgba(59,91,219,.18);
  box-shadow:inset 0 0 0 1.5px var(--accent);
}
body.picking .prog-table td[data-k="deviceName"] .nm-acts{display:none}

/* ---- Dashboard · 阶段分布 ---- */
/* v1.45.1：阶段分布独立卡（界面最底部）—— 每节点一根横向堆叠条，
   四段：绿 已完成 / 红 延期 / 蓝 进行中 / 灰 未启动；段宽 flex = 台数。
   峰哥参考样式：图例在卡片头右侧，条上直接标台数，行尾给本行合计。 */
.phase-dist{display:flex;flex-direction:column;gap:7px;padding:2px 0 4px}
.ph-row{display:flex;align-items:center;gap:10px}
.ph-name{width:110px;flex:none;text-align:right;font-size:12px;color:var(--ink2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.ph-stack{flex:1;display:flex;height:20px;border-radius:5px;overflow:hidden;background:#eef1f6;min-width:0}
.ph-seg{display:grid;place-items:center;font-size:10.5px;font-weight:700;color:#fff;min-width:0;overflow:hidden;white-space:nowrap;padding:0 2px}
.ph-done{background:#52c41a}
.ph-delay{background:#ff4d4f}
.ph-on{background:#1677ff}
.ph-idle{background:#b9c2cf}
.ph-num{width:42px;flex:none;text-align:right;font-size:12px;font-weight:650;color:var(--ink2);font-variant-numeric:tabular-nums}
.ph-legend{display:flex;gap:14px;align-items:center;font-size:11.5px;color:var(--ink2)}
.ph-legend i{display:inline-block;width:10px;height:10px;border-radius:3px;margin-right:5px}
.pd-empty{padding:18px 4px;color:var(--ink3);font-size:12px}

/* ---- ECN 表格：列宽可拖 / 可排序 / 表头可改名 ---- */
.ecn-table thead th{position:relative;user-select:none}
#ecnTable thead th{white-space:nowrap}
#ecnTable thead th .th-lbl{display:inline-block;pointer-events:none}
#ecnTable thead th.can-move{cursor:grab}
#ecnTable thead th.can-move:active{cursor:grabbing}
#ecnTable thead th.dragging{opacity:.4}
#ecnTable thead th.drop-l{box-shadow:inset 3px 0 0 var(--accent)}
#ecnTable thead th.drop-r{box-shadow:inset -3px 0 0 var(--accent)}
/* 拖宽手柄：压在表头右边缘，7px 宽，hover 才显形，平时不打扰 */
#ecnTable thead th .col-grip{
  position:absolute;top:0;right:0;width:7px;height:100%;
  cursor:col-resize;z-index:3;
}
#ecnTable thead th .col-grip:hover{background:rgba(59,91,219,.3)}
/* 表头改名输入框：铺满表头，看起来就是「就地编辑」 */
#ecnTable thead th .th-edit{
  width:100%;box-sizing:border-box;border:1px solid var(--accent);border-radius:4px;
  background:#fff;font:inherit;color:var(--ink);padding:1px 4px;outline:none;
}
#ecnTable thead th.is-locked .th-lbl::after{
  content:'🔒';font-size:8px;margin-left:3px;opacity:.45;vertical-align:1px;
}
/* ---- v1.52.0 列头筛选 ▾：贴在列名右侧，hover 显形，筛选中常亮 ---- */
#ecnTable thead th .col-filter{
  display:inline-block;margin-left:3px;padding:0 3px;border-radius:4px;
  font-size:9.5px;line-height:14px;color:var(--ink3);cursor:pointer;
  user-select:none;vertical-align:1px;
}
#ecnTable thead th .col-filter:hover{background:rgba(59,91,219,.14);color:var(--accent)}
#ecnTable thead th .col-filter.on{background:var(--accent);color:#fff;font-weight:700}
/* 列头筛选浮层：骨架复用 .dri-pop 的 .dp-* 类，只补定位和清单右侧的条数 */
.ecnf-pop{
  position:fixed;z-index:1200;width:240px;background:#fff;border:1px solid var(--line);
  border-radius:10px;box-shadow:0 18px 38px -18px rgba(16,20,27,.5);
  padding:8px;display:flex;flex-direction:column;gap:6px;
  font-weight:400;text-align:left;font-size:12px;
}
.ecnf-pop .dp-item em{margin-left:auto;font-style:normal;font-size:10.5px;color:var(--ink3);
  font-variant-numeric:tabular-nums;flex:none}
#ecnTable td[contenteditable="true"]:empty::before,
#ecnTable td[contenteditable="true"]:has(> span:empty)::before{content:''}
.col-tip{font-size:10.5px;color:var(--ink3);margin-right:2px}

/* ---- ECN 图片列（v1.18.0，v1.22.0 起一格多张）----
   行高是固定的紧凑档，缩略图统一 30px 高、宽度自适应，
   不然一张竖图会把整行撑成两倍高，表格立刻变得稀稀拉拉。
   多张时缩成 24px 小方块（object-fit:cover）横排，
   排满 2 张就换成「+N」，点开画廊看全部。 */
td.ecn-img{padding:2px 5px;cursor:pointer;vertical-align:middle}
td.ecn-img .img-cell{display:flex;align-items:center;gap:3px;overflow:hidden}
td.ecn-img img{
  height:30px;max-width:100%;width:auto;display:block;border-radius:4px;
  border:1px solid var(--line);background:#fff;cursor:zoom-in;flex:0 0 auto;
}
td.ecn-img.multi img{height:24px;width:24px;object-fit:cover}
td.ecn-img .img-more{
  font-style:normal;font-size:10.5px;font-weight:600;line-height:1;color:var(--ink2);
  background:rgba(16,20,27,.06);border-radius:5px;padding:5px 4px;flex:0 0 auto;cursor:zoom-in;
}
td.ecn-img .img-add.mini{
  display:flex;align-items:center;justify-content:center;flex:0 0 auto;
  width:18px;height:18px;border-radius:4px;border:1px dashed var(--line-2);
  color:var(--ink3);font-size:11px;line-height:1;transition:.15s;
}
td.ecn-img .img-add.mini:hover{border-color:var(--accent);color:var(--accent);background:var(--accent-soft)}
td.ecn-img.is-empty .img-add{
  display:flex;align-items:center;justify-content:center;height:26px;border-radius:5px;
  border:1px dashed var(--line-2);color:var(--ink3);font-size:11px;
  background:rgba(16,20,27,.02);transition:.15s;
}
td.ecn-img.is-empty:hover .img-add{border-color:var(--accent);color:var(--accent);background:var(--accent-soft)}
td.ecn-img.drop-hover{outline:2px dashed var(--accent);outline-offset:-2px;background:var(--accent-soft)}

/* 图片大图预览（v1.22.0：多图时底部加一条缩略图轨道） */
.img-view{
  display:flex;align-items:center;justify-content:center;padding:10px;border-radius:10px;
  background:rgba(16,20,27,.04);max-height:70vh;overflow:auto;
}
.img-view img{max-width:100%;max-height:64vh;border-radius:6px;display:block}
.img-strip{display:flex;gap:6px;overflow-x:auto;padding:8px 2px 0}
.img-strip:empty{display:none}
.img-strip img{
  height:46px;width:46px;object-fit:cover;border-radius:6px;cursor:pointer;flex:0 0 auto;
  border:2px solid transparent;background:#fff;transition:.14s;
}
.img-strip img:hover{border-color:var(--line-2)}
.img-strip img.on{border-color:var(--accent);box-shadow:0 0 0 2px var(--accent-soft)}

/* 下拉末项「＋ 管理选项…」：视觉上要像「操作」，不像一个业务状态 */
#ecnTable select option.opt-mgr{color:#8b95a3;font-style:italic}

/* ---- 下拉选项编辑框（v1.18.0）---- */
.opt-list{display:flex;flex-direction:column;gap:6px;padding:2px 2px 10px;max-height:46vh;overflow:auto}
.opt-row{display:flex;align-items:center;gap:8px}
.opt-in{
  flex:1;height:30px;padding:0 9px;border-radius:7px;border:1px solid var(--line-2);
  background:#fff;color:var(--ink);font-size:12.5px;
}
.opt-in:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft)}
.opt-use{
  font-style:normal;font-size:10.5px;color:var(--ink3);white-space:nowrap;
  min-width:44px;text-align:right;font-variant-numeric:tabular-nums;
}
.opt-use.zero{opacity:.55}
.opt-row .row-x{
  flex:none;width:22px;height:22px;padding:0;border-radius:6px;
  display:inline-flex;align-items:center;justify-content:center;
  border:1px solid var(--line);background:#fff;color:var(--ink3);
  font-size:14px;line-height:1;cursor:pointer;
}
.opt-row .row-x:hover{border-color:rgba(192,52,43,.45);color:var(--bad);background:rgba(192,52,43,.07)}

/* 隐藏态一定要写死 —— .pickbar 是 display:flex，会盖掉 [hidden] 的默认 display:none */
.pickbar[hidden]{display:none !important}

/* ECN 表：fixed 布局才吃得住「拖出来的像素宽度」。
   auto 布局下浏览器按内容重新分配，拖宽会立刻被内容撑回原样。
   总宽由 renderEcnHead() 按各列之和写到行内 style 上，这里只管算法。 */
#ecnTable{table-layout:fixed}
#ecnTable th,#ecnTable td{overflow:hidden}
#ecnTable td[contenteditable="true"]{white-space:pre-wrap;overflow-wrap:anywhere}
#ecnTable thead th.ecn-op{cursor:default}

/* ---- 进度视图：顶栏两行做紧凑化，把纵向空间全留给表格 ----
   目标不是「把工具栏搬上去」就完事：搬上去如果两行都保持原高度，
   表格反而没变大（顶栏 sticky，一直占着）。所以第一行降高、隐藏副标题，
   第二行控件统一收到 28px，配合 JS 的 fitProgHeight() 精确撑满剩余高度。 */
body[data-view="projects"] .topbar-main{height:48px}
body[data-view="projects"] .crumb p{display:none}
body[data-view="projects"] .crumb h2{font-size:15.5px}
body[data-view="projects"] .topbar-bar{padding:5px 12px 7px;gap:7px}
body[data-view="projects"] .topbar-bar .search{width:150px;height:28px;padding:0 8px;flex:none}
body[data-view="projects"] .topbar-bar .search input{font-size:12px;width:auto;flex:1;min-width:0}
body[data-view="projects"] .topbar-bar .sel{height:28px;font-size:11px;padding:0 14px 0 6px;background-position:right 4px center;flex:none}
body[data-view="projects"] .topbar-bar .btn{padding:4px 8px;font-size:11.5px;border-radius:8px;white-space:nowrap;flex:none}
body[data-view="projects"] .topbar-bar .seg-chip{padding:2px 6px;font-size:10.5px}
body[data-view="projects"] .stat-bar{margin-top:8px;padding:8px 16px}

/* 窄屏（≤1250）：一行方案下再压一档。nowrap 保证永远只有一行，
   这里继续收窄让 1080 宽也基本不用横向滚动。 */
@media (max-width:1250px){
  body[data-view="projects"] .topbar-bar{gap:5px;padding:4px 10px 6px}
  body[data-view="projects"] .topbar-bar .search{width:104px;height:26px}
  body[data-view="projects"] .topbar-bar .search input{font-size:11.5px}
  body[data-view="projects"] .topbar-bar .sel{font-size:10px;padding:0 12px 0 4px;height:26px;background-position:right 3px center}
  body[data-view="projects"] .topbar-bar .seg-chip{padding:2px 5px;font-size:10px}
  body[data-view="projects"] .topbar-bar .btn{padding:3px 6px;font-size:10.5px}
}

/* ============================================================================
   v1.9.4
   1) Dashboard「项目进度与产值」面板（从进度页底部统计栏搬过来）
   2) 进度页边距收紧 + 最大化（沉浸）模式
   ========================================================================== */

/* ---- 1) 进度与产值 ----
   v1.45.2：改成 display:block —— 原来是 flex+wrap，里面只剩一个 .ps-tiles 网格时
   flex 子项按 fit-content 收缩，auto-fit 网格拿不到确定宽度直接塌成一列
   （真机上 10 块瓦片竖着摞；jsdom 不做布局所以测试没拦住）。 */
.prog-dist{display:block;position:relative;z-index:1}
/* v1.45.1：ps-left / ps-bar / ps-late / ps-on / ps-ok 撤掉 ——
   说明文字和三态堆叠条都不要了，进度卡只剩一排彩色瓦片（.ps-tiles，见下方 v1.45.0 段） */

.ps-legend{display:flex;gap:22px;flex-wrap:wrap;margin-top:11px}
.ps-item{display:flex;align-items:baseline;gap:6px;font-size:11.5px;color:var(--ink3);white-space:nowrap}
.ps-dot{width:8px;height:8px;border-radius:2px;flex:none;align-self:center}
.ps-item b{font-size:15px;font-weight:650;color:var(--ink);font-variant-numeric:tabular-nums}
.ps-item em{font-style:normal;font-size:10.5px;color:var(--ink3);font-variant-numeric:tabular-nums}

/* 产值块：跟堆叠条用一根竖线分开，视觉上「左边是台数、右边是钱」 */
.ps-right{
  flex:none;min-width:196px;
  padding-left:24px;border-left:1px solid var(--line-2);
  display:flex;flex-direction:column;gap:2px;
}
.ps-amt-l{font-size:10.5px;color:var(--ink3);letter-spacing:.04em}
.ps-amt-v{
  font-size:24px;font-weight:700;letter-spacing:-.02em;color:var(--ink);
  line-height:1.18;font-variant-numeric:tabular-nums;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
.ps-amt-v small{font-size:11.5px;font-weight:600;color:var(--ink2);margin-left:4px;letter-spacing:0}
.ps-amt-n{font-size:10.5px;color:var(--ink3)}

@media (max-width:860px){
  .prog-dist{gap:14px}
  .ps-right{border-left:0;border-top:1px solid var(--line-2);padding-left:0;padding-top:10px;width:100%}
}

/* ---- 2a) 进度页边距收紧（默认就生效，不依赖最大化开关）----
   表格要「最大化」不能只靠一个沉浸按钮：日常使用时左边 94px 导航、右边 18px 留白、
   下边 28px 留白也都是成本。这里把非必要留白先削掉，沉浸模式再削一层。 */
/* 进度页的高度改成**浏览器自己算**：.main 定高 100vh → viewport / 视图 / 面板
   一路 flex:1 + min-height:0 往下传，最后那截全给 .prog-wrap。
   为什么不继续用 JS 量：手算 innerHeight − top − foot − padding，任何一层结构
   一变（顶栏加一行、面板加一个 foot）公式就错，实测还差过 30px —— 因为 renderProg
   执行的那一刻布局还没稳定，量到的是旧位置。flex 是布局引擎自己分配，没有这个时差。 */
body[data-view="projects"] .main{
  padding:10px 10px 10px 6px;
  height:100vh;overflow:hidden;
  display:flex;flex-direction:column;
  /* 进度页不需要 margin-left 的 300ms 滑动：切页/最大化都要求「立刻到位」，
     而且过渡期间量到的宽度是旧值，列宽档位会判错。 */
  transition:none;
}
body[data-view="projects"] .viewport{flex:1;min-height:0;display:flex;flex-direction:column}
body[data-view="projects"] #view-projects{flex:1;min-height:0;display:flex;flex-direction:column}
body[data-view="projects"] #view-projects .table-panel{
  margin-bottom:0;border-radius:14px;
  flex:1;min-height:0;display:flex;flex-direction:column;
}
/* min-height:0 是这套写法能成立的关键：flex 子项默认 min-height:auto，
   会被内部 2018 行表格撑破，max-height / 高度计算全部失效。 */
body[data-view="projects"] .prog-wrap{flex:1;min-height:0;max-height:none}
body[data-view="projects"] .topbar{top:10px;margin-bottom:8px;flex:none}
body[data-view="projects"] .pickbar{margin-bottom:8px;flex:none}

/* ---- 2b) 最大化（沉浸）模式 ----
   只作用于「项目进度」视图：class 留在 body 上，切到 Dashboard / ECN 时
   导航和标题行照常出现，不会出现「点了最大化进设置页找不到菜单」这种事。
   用属性选择器一起限定，比在 JS 里切页时手动增删 class 更不容易漏。 */
body.immersive[data-view="projects"] .sidebar{display:none}
body.immersive[data-view="projects"] .main{margin-left:0;padding:8px}
body.immersive[data-view="projects"] .topbar{top:8px;margin-bottom:6px;border-radius:14px}
body.immersive[data-view="projects"] .topbar-main{display:none}
body.immersive[data-view="projects"] .topbar-bar{
  display:flex;border-top:0;padding:6px 10px;border-radius:14px;
}
body.immersive[data-view="projects"] #view-projects .table-panel{border-radius:12px}

/* 最大化按钮的激活态：换个底色，一眼知道当前是「已展开」还是「已还原」 */
#btnMax.on{background:var(--accent);color:#fff;border-color:transparent}
#btnMax.on:hover{background:var(--accent-2)}

/* ---- 2c) ECN 页同样吃满窗口 ----
   峰哥说的是「整个界面最大化」，不是只有进度页。ECN 原来的表格上限是
   .tablewrap 的 calc(100vh - 300px)（那个 300 还是顶栏改两行之前的估值），
   下面永远空一截。这里同样用 flex 一路传到底，表格自动填满。 */
body[data-view="ecn"] .main{
  padding:10px 10px 10px 8px;
  height:100vh;overflow:hidden;
  display:flex;flex-direction:column;transition:none;
}
body[data-view="ecn"] .viewport{flex:1;min-height:0;display:flex;flex-direction:column}
body[data-view="ecn"] #view-ecn{flex:1;min-height:0;display:flex;flex-direction:column}
body[data-view="ecn"] #view-ecn .notice,
body[data-view="ecn"] #view-ecn .toolbar{flex:none;margin-bottom:10px}
body[data-view="ecn"] #view-ecn .table-panel{
  flex:1;min-height:0;display:flex;flex-direction:column;margin-bottom:0;
}
body[data-view="ecn"] #view-ecn .tablewrap{flex:1;min-height:0;max-height:none}

/* ============================================================
   v1.10.0 —— 业务信息权限 / 新增聚焦掩码 / 产值推移图 / 入职日期
   ============================================================ */

/* 掩码单元格：*** 用等宽灰字 + 细斜纹底，和「空值 —」「未下单 NA 斜纹」区分开。
   这些格子不带 data-edit，双击进不去编辑 —— 看得见的是***，改不了才是真隐藏。 */
.prog-table td.is-mask{
  text-align:center;color:var(--ink3);font-family:var(--mono);font-size:11.5px;
  letter-spacing:.14em;cursor:default;user-select:none;
  background:repeating-linear-gradient(135deg,rgba(16,20,27,.035) 0 6px,transparent 6px 12px);
}

/* 新增设备聚焦提示条：琥珀色，和「选设备」那条蓝色区分开 */
.bizbar{background:rgba(176,104,0,.09);border-color:rgba(176,104,0,.28)}
.bizbar .pk-ico{color:#b06800}

/* ============================================================================
   v1.21.0 —— 系统设置只读态（非管理员）
   ==========================================================================
   峰哥要的是「只能查看」，所以不能把参数藏起来，只能让它改不动。
   浏览器给 disabled 的输入框默认刷成灰字，金额 / 目标值就看不实了；
   这里把文字色改回正文色、背景压一层很淡的灰，一眼能看出「这是只读」。
   ========================================================================== */
.robar{
  background:color-mix(in srgb,var(--ink3) 7%,transparent);
  border-color:color-mix(in srgb,var(--ink3) 24%,transparent);
}
.robar .pk-ico{color:var(--ink3)}
#view-settings.ro-mode input:disabled,
#view-settings.ro-mode select:disabled,
#view-settings.ro-mode textarea:disabled{
  -webkit-text-fill-color:var(--ink);
  color:var(--ink);
  opacity:1;cursor:default;
  background:color-mix(in srgb,var(--ink3) 5%,transparent);
  border-color:color-mix(in srgb,var(--line-2) 72%,transparent);
}
#view-settings.ro-mode input[type="checkbox"]:disabled,
#view-settings.ro-mode input[type="range"]:disabled{cursor:not-allowed}
#view-settings.ro-mode button:disabled{opacity:.45;cursor:not-allowed}
#view-settings.ro-mode .mini-table td{color:var(--ink2)}

/* 图表右上角的历史数据开关（近 3 年 ⇄ 近 12 个月） */
.chart-tog{
  flex:none;height:24px;padding:0 9px;border-radius:7px;cursor:pointer;
  font-size:11px;font-weight:600;letter-spacing:.02em;
  border:1px solid var(--line);background:transparent;color:var(--ink3);
  transition:color .15s,border-color .15s,background .15s;
}
.chart-tog:hover{color:var(--ink)}
.chart-tog.on{color:#2f4fc0;border-color:rgba(47,79,192,.35);background:rgba(47,79,192,.08)}

/* 虚线图例（目标线）：画一条 2px 虚线，颜色由行内 border-top-color 给 */
.chart-legend .cl-dash{
  width:15px;height:0;flex:none;display:inline-block;
  border-top:2px dashed currentColor;margin-top:-1px;
}

/* 设置页：人员名单（研发 / 组装 / 电气）—— 一行的骨架 */
.join-cfg{display:flex;flex-direction:column;gap:2px}
.join-row{display:flex;align-items:center;gap:10px;padding:5px 0;border-bottom:1px dashed var(--line)}
.join-row:last-child{border-bottom:none}
.join-row b{width:64px;flex:none;font-size:12.5px;font-weight:620;color:var(--ink)}
.join-row input[type="date"]{
  width:126px;flex:none;height:28px;padding:0 6px;border-radius:7px;
  border:1px solid var(--line);background:#fff;color:var(--ink);font-size:12px;
}
/* v1.43.8：一行两个日期（入职 + 结束），提示列给足弹性空间自己收窄 */
.join-row em{font-style:normal;font-size:11px;color:var(--ink3);flex:1;min-width:0}
.join-row.no-date em{color:#b06800}
.join-empty{font-size:12px;color:var(--ink3);padding:6px 0}

/* ============================================================
   v1.17.0 —— 人员名单（研发 / 组装 / 电气）
   名称从 <b> 换成输入框（改错名不用删了重加），右侧多一个 × 删除；
   面板头右上角放「名单 N 人」计数 + 从账号同步 / 添加人员两个按钮。
   ============================================================ */
.ph-acts{display:flex;align-items:center;gap:6px;flex:none;flex-wrap:wrap;justify-content:flex-end}
.staff-cnt{
  font-style:normal;font-size:11px;color:var(--ink3);white-space:nowrap;
  background:rgba(16,20,27,.05);padding:3px 9px;border-radius:999px;
}
.staff-cnt.warn{color:#96590a;background:rgba(176,104,0,.12)}
.staff-den{
  display:inline-flex;align-items:center;gap:5px;font-size:11px;color:var(--ink3);
  background:rgba(16,20,27,.04);padding:2px 4px 2px 9px;border-radius:999px;white-space:nowrap;
}
.staff-den input{
  width:52px;height:22px;padding:0 4px;border-radius:999px;text-align:center;
  border:1px solid var(--line);background:#fff;color:var(--ink);font-size:11.5px;font-variant-numeric:tabular-nums;
}
.staff-den input:focus{outline:none;border-color:rgba(47,79,192,.45);box-shadow:0 0 0 3px rgba(47,79,192,.1)}
.staff-nm{
  width:88px;flex:none;height:27px;padding:0 7px;border-radius:7px;
  border:1px solid transparent;background:transparent;
  font-family:var(--sans);font-size:12.5px;font-weight:620;color:var(--ink);
}
.staff-nm::placeholder{font-weight:500;color:var(--ink3)}
.staff-nm:hover{border-color:var(--line)}
.staff-nm:focus{
  outline:none;border-color:rgba(47,79,192,.45);background:#fff;
  box-shadow:0 0 0 3px rgba(47,79,192,.1);
}
.join-row .row-x{
  margin-left:auto;flex:none;width:22px;height:22px;padding:0;border-radius:6px;
  display:inline-flex;align-items:center;justify-content:center;
  border:1px solid var(--line);background:#fff;color:var(--ink3);
  font-size:14px;line-height:1;cursor:pointer;
}
.join-row .row-x:hover{border-color:rgba(179,20,43,.4);color:#b3142b;background:rgba(179,20,43,.06)}

/* 账号列表：业务信息权限勾选 */
.mini-table .perm{display:inline-flex;align-items:center;gap:5px;font-size:11.5px;color:var(--ink2);cursor:pointer;white-space:nowrap}
.mini-table .perm input{cursor:pointer;accent-color:#2f4fc0}
.mini-table .perm input:disabled{cursor:default}

/* ---- v1.24.0 账号列表：启用 / 停用开关 + 删除 ----
   状态原来只是一枚静态标签，现在要能点 —— 所以做成按钮，
   外观沿用 .st 的胶囊观感（带小圆点），但保留按钮的 hover / disabled 反馈。
   停用的行整体压暗，一眼能看出"这个号现在登不上"。 */
.mini-table .u-op{width:34px;padding-left:0;text-align:center}
.mini-table tr.is-off td{opacity:.55}
.st-btn{
  display:inline-flex;align-items:center;gap:5px;font:inherit;font-size:11.5px;font-weight:580;
  padding:3px 9px;border-radius:999px;white-space:nowrap;cursor:pointer;
  border:1px solid transparent;background:none;transition:background .14s,border-color .14s;
}
.st-btn::before{content:"";width:5px;height:5px;border-radius:50%;background:currentColor;opacity:.85}
.st-btn.on{background:rgba(15,123,79,.1);color:#0f7b4f}
.st-btn.on:hover{background:rgba(15,123,79,.16);border-color:rgba(15,123,79,.28)}
.st-btn.off{background:rgba(16,20,27,.06);color:#8b95a3}
.st-btn.off:hover{background:rgba(176,104,0,.12);color:#9a5f00;border-color:rgba(176,104,0,.24)}
.st-btn:disabled{cursor:not-allowed;opacity:.6}
.st-btn:disabled:hover{background:rgba(15,123,79,.1);color:#0f7b4f;border-color:transparent}
.st-btn.lock{opacity:.55}
.mini-table .u-me{
  font-style:normal;font-size:10px;color:var(--ink3);margin-left:5px;
  border:1px solid var(--line-2);border-radius:4px;padding:0 4px;line-height:14px;
}
/* v1.36.0：多了一列「编辑权限」+ 一个拖动手柄，列为 10 列。
   面板只占半行（.settings-grid>.panel{span 6}），硬挤会把「初始密码（待修改）」
   折成两行、勾选框挤成一列字。给一个下限宽度，装不下就横向滚 ——
   和进度表 / ECN 表同一套「滚动条常驻」的观感。 */
/* v1.37.0：又加了一列（项目管理 / ECN 两档权限分开），11 列挤不进半宽面板 ——
   抬到 880px 让它横向滚动，列都保持单行不折行。 */
.mini-table.u-accounts{min-width:1000px}
.mini-table.u-accounts th,.mini-table.u-accounts td{white-space:nowrap}
.mini-table.u-accounts td[data-uedit]{white-space:nowrap;min-width:52px}

/* ---- v1.39.0 账号列表冻结列 ----
   峰哥：「向右拖动时，固定工号、姓名和角色」。这张表 12 列、min-width 1000px，
   设置页面板只占半行 —— 横向一拖就不知道这一行是谁的账号了。
   前四列（⋮⋮ / 工号 / 姓名 / 角色）随横向滚动钉在左边，
   left 取 --UC0…--UC3 —— 由 app.js 的 syncUserSticky() 按**实测列宽**累加写入，
   写死 px 一定错位（工号/姓名是 contenteditable，改个工号、换个字体都会让列宽变）。
   ⚠ 底色必须不透明：.panel 是玻璃拟态（半透明 + 背景模糊），
     冻结格若透明，后面的列会直接透上来 —— v1.38 的「冻结列透字」就是这个坑。 */
.mini-table.u-accounts th[data-uc],
.mini-table.u-accounts td[data-uc]{
  position:sticky;
  background:var(--u-stick);
  z-index:2;
}
/* 表头那四格已经在值 4（钉顶），要盖在冻结的 body 格上面 */
.mini-table.u-accounts thead th[data-uc]{z-index:6;background:#f9fafc}
.mini-table.u-accounts [data-uc="0"]{left:var(--UC0,0px)}
.mini-table.u-accounts [data-uc="1"]{left:var(--UC1,0px)}
.mini-table.u-accounts [data-uc="2"]{left:var(--UC2,0px)}
.mini-table.u-accounts [data-uc="3"]{left:var(--UC3,0px)}
/* 冻结尾列画一道竖线收边；横向滚过之后再加一层向右的投影，
   提示「右边还有列」—— 没有这层影子，看不出表格到底滚没滚 */
.mini-table.u-accounts [data-uc="3"]{box-shadow:inset -1px 0 0 var(--line)}
.tablewrap.has-uc.sc-x .mini-table.u-accounts [data-uc="3"]{
  box-shadow:inset -1px 0 0 var(--line), 10px 0 14px -12px rgba(16,20,27,.45);
}

/* 只读模式下状态按钮不该看起来还能点 */
.ro-mode .st-btn{cursor:default}

/* ---- v1.36.0 账号列表：拖动排序 ----
   起手位是左边那格 ⋮⋮（grip）。为什么不做整行可拖：这张表里全是 input 和
   contenteditable 的格子，整行可拖会让「点一下改工号」变成「把整行拖走」。
   落点提示用 box-shadow 而不是 border —— 加 border 会把行高顶开 2px，
   拖过去的时候整张表会抖一下。 */
.mini-table .u-grip-h{width:22px;padding-left:0;padding-right:2px;text-align:center;color:var(--ink3)}
.mini-table td.u-grip{
  width:22px;padding-left:0;padding-right:2px;text-align:center;
  color:var(--ink3);cursor:grab;user-select:none;
  letter-spacing:-1px;font-size:11px;line-height:1;
}
.mini-table td.u-grip:hover{color:var(--accent)}
.mini-table td.u-grip:active{cursor:grabbing}
#userBody tr.u-dragging td{opacity:.42}
#userBody tr.u-drop-before td{box-shadow:inset 0 2px 0 var(--accent)}
#userBody tr.u-drop-after td{box-shadow:inset 0 -2px 0 var(--accent)}
/* 非管理员：没有拖动手柄（那一列整个不占位，表宽和只读时一致） */
body:not(.is-admin) .mini-table .u-grip-h,
body:not(.is-admin) .mini-table td.u-grip{display:none}

/* 新增账号弹窗的表单：两列，工号/姓名在前 */
.uadd{display:grid;grid-template-columns:1fr 1fr;gap:9px 10px;padding:2px 2px 12px}
.uadd label{display:flex;flex-direction:column;gap:4px;font-size:11.5px;color:var(--ink3)}
.uadd label > span{display:flex;align-items:center;gap:3px}
.uadd label > span i{font-style:normal;color:#b3142b}
.uadd input,.uadd select{
  height:30px;padding:0 9px;border-radius:8px;font:inherit;font-size:12.5px;
  border:1px solid var(--line-2);background:#fff;color:var(--ink);outline:none;
}
.uadd input:focus,.uadd select:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft)}
.uadd .uadd-chk{grid-column:1/-1;flex-direction:row;align-items:center;gap:7px;font-size:12px;color:var(--ink2)}
.uadd .uadd-chk input{height:14px;width:14px;accent-color:var(--accent);cursor:pointer}

/* ============================================================
   v1.10.1 —— 月度折线图：1–12 月固定横轴 + 节点金额 / 虚线目标
   ============================================================ */

/* 节点金额标签。
   paint-order:stroke 让描边画在填充下面 —— 标签压到网格线或折线上时，
   先铺一层 2.6px 白描边「抠图」，数字就不会和线条糊在一起。 */
.c-node-val{
  font-family:var(--sans);font-size:9.6px;font-weight:700;letter-spacing:-.01em;
  paint-order:stroke;stroke:#fff;stroke-width:2.6px;stroke-linejoin:round;
}

/* 左上角单位角标（「单位：万元」）—— 节点上只标数字，单位统一写在这里 */
.c-unit-tag{fill:var(--ink3);font-size:10.5px;font-family:var(--sans)}

/* 叠在最底层的目标虚线（不用动画的那套 dash 描边） */
.c-line.dash{fill:none;stroke-width:1.8;stroke-dasharray:6 4;stroke-linecap:round;opacity:.92}

/* ============================================================
   v1.11.0 —— KPI 行压缩 + 滚动常驻 / 右上角清除测试数据 / 提示条瘦身
   ============================================================ */

/* ① KPI 行整体上移 + 压缩
   原来 padding 16/17/14、数值 28px，一行吃掉约 100px。
   压到 10/13/9、数值 21px，约 75px —— 省下的高度直接给下面两张产值图。 */
.kpi-row{gap:11px;margin-bottom:10px}
.kpi{padding:9px 12px 8px}
.kpi .k-l{font-size:10.5px;gap:5px}
.kpi .k-v{font-size:21px;margin:3px 0 1px}
.kpi .k-v small{font-size:11.5px}
/* 副标题强制单行：第 1 张卡那句「已交付 36,115.70 万元 · 占 25% · 仅计 559 台已下单 ·
   另 450 台未下单/部分未计入」会折成两行，四张卡高度就打架了。溢出省略，全文进 title。 */
.kpi .k-s{font-size:10.5px;line-height:1.4;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}

/* ② 滚动时钉在顶栏下方。
   top 值由 JS 量顶栏实高后写进 --kpi-top —— 顶栏高度改过三次（64/56/48），
   写死就会露缝或被压住。背景用不透明 --bg：glass 卡是半透明的，
   缝隙不填实的话滚动内容会从卡片之间穿过去。 */
body[data-view="dashboard"] .kpi-row{
  position:sticky;
  top:var(--kpi-top,66px);
  z-index:26;
  background:var(--bg);
  /* padding-top 6 不是装饰：sticky 时这 6px 用它自己的不透明背景填住
     顶栏和卡片之间的缝，否则下面的图例会从缝里穿过去。 */
  padding:6px 0 4px;
  margin:0 0 6px;
}
/* 钉住期间卡片改成近乎不透明的白：glass 是 .64 透明白，
   滚动时下面的柱状图会从卡片里透出来，看着像重影。 */
body[data-view="dashboard"] .kpi-row.stuck .kpi{
  background:rgba(255,255,255,.94);
  backdrop-filter:blur(10px);
}
/* 钉住后补一条向下投影，和下面的面板拉开层次。
   v1.47.1（峰哥：「还能看到背后的字」）：整行铺不透明底 —— 原来只有 .kpi 卡
   自己 94% 白，卡片之间 11px 的间隙和行 padding 是透的，滚过的内容从缝里漏出来；
   进度卡的 sticky top 也改成与 KPI 行底边齐平（JS 去掉 +6），零缝隙。 */
body[data-view="dashboard"] .kpi-row.stuck{
  box-shadow:0 12px 20px -16px rgba(16,20,27,.5);
  background:var(--bg,#f4f6fa);
}

/* ②b v1.47.0（峰哥）：整张「项目进度与产值」卡都要固定 ——
   「往下滚动时，这个小卡片就没有了……要固定这个项目进度与产值的卡片，
   多级缩进到上方，要始终显示」。原来 v1.46.0 只钉 panel-head（瓦片照样滚走），
   现在整卡 position:sticky，钉在 KPI 行正下方（top 由 syncKpiSticky 量出来写
   --prog-head-top = 顶栏 + KPI 行高 + 6，多级缩进不打架）。
   panel 是半透明 glass，钉住后底下滚过的图会透出来，所以必须不透明底。
   投影由 .stuck 挂（updateKpiStuck 滚动时切），平时与别的卡观感一致。 */
body[data-view="dashboard"] #progStatPanel{
  position:sticky;
  top:var(--prog-head-top,152px);
  z-index:24;
  background:var(--card,#fff);
}
body[data-view="dashboard"] #progStatPanel.stuck{
  box-shadow:0 12px 20px -14px rgba(16,20,27,.45);
}

/* ②c v1.47.0（峰哥）：图表吃满卡片 —— 同一行两张卡，矮的那张 chart-box
   固定 330px，行高被高卡撑起来后底部就空一大块。面板改纵向 flex、
   chart-box flex:1 吃满剩余高度（min-height 保底），行拉伸时图跟着长。
   v1.49.0（峰哥：「柱状图和折线图最大化」）：人员卡保底高度 264 → 320。 */
body[data-view="dashboard"] .grid-2>.panel,
body[data-view="dashboard"] #peopleGrid>.panel{display:flex;flex-direction:column}
body[data-view="dashboard"] .grid-2>.panel .chart-box,
body[data-view="dashboard"] #peopleGrid>.panel .chart-box{flex:1;height:auto;min-height:330px}
body[data-view="dashboard"] #peopleGrid>.panel .chart-box.short{min-height:320px}
.chart-box>.empty-chart{flex:1}

/* ③ 提示条瘦身（按钮挪到右上角后，这里只留一句话） */
.rev-notice{padding:8px 14px;margin-bottom:8px;gap:9px}
.rev-notice svg{width:17px;height:17px}
.rev-notice b{font-size:12px;margin-bottom:1px}
.rev-notice p{font-size:10.8px;line-height:1.45}

/* ④ 右上角「清除测试数据」：只在 Dashboard 且有测试数据时出现。
   起手 display:none 放在文件末尾，才能盖过 .btn 的 inline-flex；
   显示条件用 :not([hidden])，避免 inline-flex 把 hidden 吃掉。 */
.qa-clear-top{display:none}
body[data-view="dashboard"] .qa-clear-top:not([hidden]){
  display:inline-flex;align-items:center;
  height:27px;padding:0 10px;font-size:11.5px;border-radius:8px;
  color:var(--warn);border-color:rgba(176,104,0,.32);background:rgba(176,104,0,.07);
}
body[data-view="dashboard"] .qa-clear-top:not([hidden]):hover{
  background:rgba(176,104,0,.14);border-color:rgba(176,104,0,.5);
}


/* ============================================================
   v1.12.0 —— ① 标签自定义（字号 + 拖动位置）
   ============================================================ */

/* 编辑开关按钮：只在 Dashboard / 系统设置露脸（那两屏才有要调 layout 的标签） */
.lb-toggle{display:none}
body[data-view="dashboard"] .lb-toggle,
body[data-view="settings"] .lb-toggle{
  display:inline-flex;align-items:center;height:27px;padding:0 10px;
  font-size:11.5px;border-radius:8px;
}
body[data-view="dashboard"] .lb-toggle.on,
body[data-view="settings"] .lb-toggle.on{
  color:#fff;background:var(--accent);border-color:var(--accent);
}
/* v1.38.0：布局编辑按 Dashboard 权限开放 —— 原来死绑 is-admin，
   于是「想让某个人调看板」只能把他提成管理员。优先级和上面两条同档，靠写在后面生效 */
body.no-dash .lb-toggle{display:none}

/* 可编辑标签：虚线 + 抓手。用内联 class 标记而不是把一长串选择器写进 CSS，
   选择器清单只在 app.js 的 LB_SEL 一处维护，CSS 侧只认 .lb-able。 */
body.lb-editing .lb-able{
  cursor:move;
  outline:1px dashed rgba(59,91,219,.45);
  outline-offset:2px;
  border-radius:4px;
}
body.lb-editing .lb-able:hover{
  outline-color:var(--accent);
  background:rgba(59,91,219,.06);
}
body.lb-editing .lb-able.lb-sel{
  outline:2px solid var(--accent);
  outline-offset:2px;
  background:rgba(59,91,219,.10);
}
/* 编辑态下别让 KPI 卡 / 面板把拖拽中的标签裁掉 */
body.lb-editing .panel{overflow:visible}

/* 底部工具条：字号滑杆 + 重置 + 退出 */
.lb-bar{
  position:fixed;left:50%;bottom:18px;transform:translateX(-50%);
  z-index:98;display:flex;align-items:center;gap:8px;
  padding:8px 12px;border-radius:14px;
  background:rgba(255,255,255,.94);
  border:1px solid var(--line-2);
  box-shadow:var(--shadow-lg);
  font-size:12px;white-space:nowrap;
}
/* [hidden] 打不过 .lb-bar{display:flex}（作者样式优先级高于 UA 样式），
   不补这一条的话工具条在非编辑态也会常驻在屏幕底部。 */
.lb-bar[hidden]{display:none}
.lb-bar .lb-grip{color:var(--accent);font-size:13px;line-height:1}
.lb-bar .lb-name{
  max-width:190px;overflow:hidden;text-overflow:ellipsis;
  font-size:12px;font-weight:640;color:var(--ink);
}
.lb-bar .lb-sep{width:1px;height:18px;background:var(--line-2)}
.lb-bar .lb-lab{color:var(--ink3);font-size:11px}
.lb-bar .lb-range{width:132px;accent-color:var(--accent)}
.lb-bar .lb-pct{
  font-style:normal;min-width:40px;text-align:right;
  color:var(--accent);font-weight:640;font-family:var(--mono);font-size:11px;
}
.lb-bar .lb-btn{
  appearance:none;border:1px solid var(--line-2);background:#fff;
  border-radius:8px;padding:4px 9px;font-size:11.5px;cursor:pointer;color:var(--ink2);
  transition:background .14s,border-color .14s;
}
.lb-bar .lb-btn:hover{background:rgba(59,91,219,.07);border-color:var(--accent)}
.lb-bar .lb-btn.primary{background:var(--accent);border-color:var(--accent);color:#fff}

/* ============================================================
   v1.12.0 —— ② 分组视图：柱 = 实际 / 虚线 = 目标（同色）
   ============================================================ */
/* 柱顶数值：3 组 × 12 月 = 36 个数字，字号必须比单点折线图小一档 */
.c-val.sm{font-size:9px;font-weight:600;paint-order:stroke;stroke:#fff;stroke-width:2.4px}

/* 图例里的「色块 + 虚线」成对标记，说明柱与线是同一组 */
.cl-pair{
  position:relative;width:16px;height:9px;display:inline-block;flex:none;
}
.cl-pair::before{                       /* 色块 = 实际柱 */
  content:'';position:absolute;left:0;bottom:0;width:6px;height:9px;
  border-radius:2px;background:var(--p);opacity:.55;
  border:1px solid var(--p);box-sizing:border-box;
}
.cl-pair::after{                        /* 虚线 = 目标 */
  content:'';position:absolute;left:7px;top:50%;width:9px;height:0;
  border-top:1.8px dashed var(--p);
}

.grp-sum .gs-item.is-total{
  background:rgba(91,100,115,.1);border-color:rgba(91,100,115,.3);
}
.grp-sum .gs-item.is-total .gs-name{color:#3f4753}

/* 分组图上的目标线数值：比折线图的节点标签再小一档（3 组 × 12 月已经很密） */
.c-node-tgt{font-size:9px;font-weight:600}

/* 分组达成速览（图上镑趋势，这里给累计数字） */
.grp-sum{
  display:flex;flex-wrap:wrap;gap:8px;margin:0 0 8px;position:relative;z-index:1;
}
.grp-sum:empty{display:none}
.gs-item{
  display:inline-flex;align-items:center;gap:7px;
  padding:5px 10px;border-radius:9px;
  background:rgba(16,20,27,.035);border:1px solid var(--line);
  font-size:11px;color:var(--ink2);
}
/* v1.42.0：整块小标签进「文字模式」可调（峰哥：「一组、二组、三组这个小标签也要
   可以调整」）。字号若要跟着整块缩放，子项就不能写死 px —— 一律 em，
   基准就是 .gs-item 自己的 font-size。 */
.gs-item{cursor:default}
body.lb-editing .gs-item{cursor:move;outline:1px dashed transparent}
body.lb-editing .gs-item:hover{outline-color:rgba(59,91,219,.45)}
body.lb-editing .gs-item.lb-sel{outline-color:var(--accent);box-shadow:0 0 0 3px rgba(59,91,219,.13)}
.gs-item .gs-dot{width:9px;height:9px;border-radius:3px;flex:none}
.gs-item .gs-name{font-size:1.045em;font-weight:640;color:var(--ink)}
.gs-item .gs-mem{color:var(--ink3)}
.gs-item .gs-kv{color:var(--ink3)}
.gs-item .gs-kv b{color:var(--ink2);font-family:var(--mono);font-size:1em}
.gs-item .gs-rate{
  font-weight:660;font-family:var(--mono);
  padding:1px 7px;border-radius:999px;background:rgba(16,20,27,.06);color:var(--ink3);
}
.gs-item .gs-rate.ok{background:rgba(15,123,79,.12);color:var(--ok)}
.gs-item .gs-rate.warn{background:rgba(176,104,0,.14);color:var(--warn)}
.gs-item .gs-rate.bad{background:rgba(192,52,43,.12);color:var(--bad)}
/* v1.49.1（峰哥）：chip 字体全部变白 —— 底色改成组色实色（一组蓝/二组绿/三组紫/合计深灰），
   白字配深底才可读。放在 .gs-rate.ok/.warn/.bad 之后，靠源码顺序压过它们的浅色底。 */
.grp-sum .gs-item.gs-solid{
  background:var(--gs-c);border-color:transparent;
  color:rgba(255,255,255,.88);box-shadow:0 1px 4px rgba(16,20,27,.16);
}
.grp-sum .gs-item.gs-solid .gs-dot{background:rgba(255,255,255,.95)}
.grp-sum .gs-item.gs-solid .gs-name{color:#fff}
.grp-sum .gs-item.gs-solid .gs-mem,
.grp-sum .gs-item.gs-solid .gs-kv{color:rgba(255,255,255,.85)}
.grp-sum .gs-item.gs-solid .gs-kv b{color:#fff}
.grp-sum .gs-item.gs-solid .gs-rate{background:rgba(255,255,255,.94);color:var(--ink2)}
.grp-sum .gs-item.gs-solid .gs-rate.ok{color:var(--ok)}
.grp-sum .gs-item.gs-solid .gs-rate.warn{color:var(--warn)}
.grp-sum .gs-item.gs-solid .gs-rate.bad{color:var(--bad)}

/* ============================================================
   v1.12.0 —— ③ 系统设置「人员分组」面板
   ============================================================ */
.grp-panel .grp-cfg{display:flex;flex-direction:column;gap:14px;position:relative;z-index:1}
.grp-cat{
  border:1px solid var(--line-2);border-radius:11px;padding:10px 12px;
  background:rgba(255,255,255,.6);
}
.grp-cat-title{
  display:flex;align-items:center;gap:8px;flex-wrap:wrap;
  font-size:12.5px;font-weight:640;color:var(--ink);margin-bottom:8px;
}
.grp-cat-title em{
  font-style:normal;font-size:11px;font-weight:500;color:var(--ink3);
  background:rgba(16,20,27,.05);padding:1px 8px;border-radius:999px;
}
.grp-cat .grp-n-fld{max-width:190px;margin-bottom:8px}
.grp-list{display:flex;flex-direction:column;gap:7px}
.grp-row{
  display:grid;grid-template-columns:12px 132px 1fr 168px;
  align-items:center;gap:9px;
}
.grp-row .grp-dot{width:10px;height:10px;border-radius:3px}
.grp-row .grp-name-lbl{display:flex;flex-direction:column;gap:3px;min-width:0}
.grp-row .grp-name-lbl>span{font-size:10px;color:var(--ink3)}
.grp-row .grp-name-lbl input.grp-name{
  border:1px solid var(--line-2);background:#fff;border-radius:7px;
  padding:5px 8px;font-size:12px;font-weight:600;outline:none;width:100%;
}
.grp-row .grp-name-lbl input.grp-name:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft)}
.grp-mems{display:flex;flex-wrap:wrap;gap:5px;min-height:26px;align-items:center}
.grp-mems .mem-none{font-style:normal;font-size:11px;color:var(--ink3)}
.grp-mems .mem{
  display:inline-flex;align-items:center;gap:3px;
  padding:2px 3px 2px 8px;border-radius:999px;font-size:11.5px;
  background:rgba(59,91,219,.09);color:var(--ink2);border:1px solid rgba(59,91,219,.16);
}
.grp-mems .mem-x{
  appearance:none;border:none;background:rgba(16,20,27,.07);color:var(--ink3);
  width:16px;height:16px;line-height:1;border-radius:50%;cursor:pointer;
  font-size:11px;display:inline-flex;align-items:center;justify-content:center;padding:0;
}
.grp-mems .mem-x:hover{background:var(--bad);color:#fff}
.grp-row .grp-add{
  border:1px dashed var(--line-2);background:rgba(255,255,255,.8);
  border-radius:7px;padding:5px 9px;font-size:11.5px;outline:none;min-width:0;
}
.grp-row .grp-add:focus{border-style:solid;border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft)}
.grp-pool{font-size:11px;color:var(--ink3);margin-top:8px}
.grp-pool b{color:var(--warn);font-weight:600}

@media (max-width:1180px){
  .grp-row{grid-template-columns:12px 1fr;grid-template-areas:"dot name" "mems mems" "add add"}
  .grp-row .grp-dot{grid-area:dot}
  .grp-row .grp-name-lbl{grid-area:name}
  .grp-row .grp-mems{grid-area:mems}
  .grp-row .grp-add{grid-area:add}
}

/* ============================================================
   v1.12.1 —— 区域模式：整块卡片可拖动换位 / 改大小
   ============================================================ */
/* 编辑态下卡片要有「抓手」和虚线框；.kpi 自带 overflow:hidden 会裁掉右下角手柄，
   这里放开（编辑时不需要那层装饰溢出的裁切）。 */
body.lz-editing .lz-able{
  position:relative;
  cursor:move;
  outline:1.5px dashed rgba(59,91,219,.45);
  outline-offset:2px;
  border-radius:var(--r-lg);
  transition:outline-color .12s, box-shadow .12s;
  touch-action:none;
}
body.lz-editing .kpi.lz-able{overflow:visible}
body.lz-editing .lz-able:hover{outline-color:var(--accent)}
body.lz-editing .lz-able.lz-sel{
  outline:2px solid var(--accent);
  box-shadow:0 0 0 4px rgba(59,91,219,.10);
  z-index:5;
}
/* 落点高亮：拖动时压在哪块上，哪块就让出一条虚影 */
body.lz-editing .lz-able.lz-over{
  outline:2px dashed var(--ok);
  background-image:linear-gradient(rgba(15,123,79,.06),rgba(15,123,79,.06));
}
/* 拖拽中的卡片盖住邻居 */
body.lz-editing .lz-able{will-change:transform}
body.lz-editing .panel,body.lz-editing .kpi{min-width:0}

/* 右下角缩放手柄：18px 的圆角方块 + 两道斜纹，双向 nwse-resize。
   v1.14.0 起它真的两个方向都能拉（横向 = 12 栏步进，纵向 = 像素自由），
   所以把边长从 15 放到 18，好点中。 */
.lz-handle{
  position:absolute; right:6px; bottom:6px; z-index:6;
  width:18px; height:18px; border-radius:5px;
  background:
    linear-gradient(135deg, transparent 42%, rgba(255,255,255,.95) 42%, rgba(255,255,255,.95) 54%, transparent 54%),
    linear-gradient(135deg, transparent 62%, rgba(255,255,255,.75) 62%, rgba(255,255,255,.75) 72%, transparent 72%),
    var(--accent);
  box-shadow:0 1px 3px rgba(16,20,27,.28);
  cursor:nwse-resize;
  opacity:.6;
  transition:opacity .12s, transform .12s;
}
/* 单列容器（如「项目进度与产值」这种独占整行的面板）横向没有可去的栏位，
   只能改高度 —— 光标也说实话，别骗人往右拖。 */
.lz-able.lz-ns .lz-handle{cursor:ns-resize}
.lz-able:hover .lz-handle,.lz-able.lz-sel .lz-handle{opacity:1}
.lz-handle:hover{transform:scale(1.12)}

/* 拖动中的尺寸读数：横向是离散跳档，不给读数会以为没反应 */
.lz-dim{
  position:absolute; left:8px; bottom:8px; z-index:7;
  font-size:10.5px; font-weight:650; letter-spacing:.02em;
  color:#fff; background:rgba(16,20,27,.82);
  padding:3px 7px; border-radius:6px; pointer-events:none;
  font-variant-numeric:tabular-nums;
}

/* 工具条上的「区域 / 文字」分段 */
.lb-bar .lb-mode{display:inline-flex;gap:3px;padding:2px;border-radius:9px;background:rgba(16,20,27,.05)}
.lb-bar .lb-mode .lb-btn{border-color:transparent;background:transparent;padding:4px 9px;border-radius:7px}
.lb-bar .lb-mode .lb-btn.on{background:#fff;border-color:var(--line-2);color:var(--accent);font-weight:640;box-shadow:0 1px 2px rgba(16,20,27,.08)}
.lb-bar[data-mode="area"] .lb-text-sec{display:none}
.lb-bar[data-mode="text"] .lb-mode .lb-btn.on{color:var(--accent)}

/* ============================================================
   v1.13.0 —— 单元格右键菜单 + 粘贴高亮
   ============================================================ */
.cell-menu{
  position:fixed;z-index:120;min-width:212px;
  padding:5px;border-radius:11px;
  background:rgba(255,255,255,.98);
  border:1px solid var(--line-2);
  box-shadow:0 10px 28px rgba(16,20,27,.18), 0 2px 6px rgba(16,20,27,.10);
  font-size:12.5px;color:var(--ink2);
  animation:cmIn .1s ease-out;
}
@keyframes cmIn{from{opacity:0;transform:translateY(-3px)}to{opacity:1;transform:none}}
.cell-menu .cm-head{
  padding:4px 8px 6px;font-size:10.5px;color:var(--ink3);
  border-bottom:1px solid var(--line);
  margin-bottom:4px;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:230px;
}
.cell-menu button{
  display:flex;align-items:center;justify-content:space-between;gap:14px;
  white-space:nowrap;
  width:100%;padding:6px 9px;border:none;border-radius:7px;
  background:transparent;color:var(--ink2);font-size:12.5px;
  cursor:pointer;text-align:left;font-family:inherit;
}
.cell-menu button:hover:not(:disabled){background:rgba(59,91,219,.09);color:var(--accent)}
.cell-menu button:disabled{color:var(--ink3);opacity:.55;cursor:not-allowed}
.cell-menu button em{
  font-style:normal;font-size:10.5px;color:var(--ink3);flex:none;
  font-family:var(--mono);
}
.cell-menu button.danger{color:var(--bad)}
.cell-menu button.danger:hover:not(:disabled){background:rgba(192,52,43,.10);color:var(--bad)}
.cell-menu .cm-sep{display:block;height:1px;background:var(--line);margin:4px 6px}

/* 粘贴完成后的落点提示：绿底高亮，1.5 秒后由 JS 摘掉这个 class。
   刻意不用 animation —— 站内有 prefers-reduced-motion 降级（把 animation-duration
   压到 .01ms），用动画写的话系统开了「减少动效」的人就什么都看不到。
   静态 class + 定时移除，两种情况都看得见。 */
#progBody td.cell-flash{
  box-shadow:inset 0 0 0 2px rgba(15,123,79,.7);
  background-color:rgba(15,123,79,.14);
}

/* 表尾的复制粘贴提示（v1.13.0）：不抢眼，但一眼能看到 */
.table-foot .cp-hint{
  font-size:11px;color:var(--ink3);
  padding:1px 8px;border-radius:999px;
  background:rgba(59,91,219,.07);
  white-space:nowrap;
}

/* ==========================================================================
   v1.26.0 · 滚动条常驻可见（项目进度表底部、ECN 表格底部）
   --------------------------------------------------------------------------
   峰哥的原话：「项目管理界面下面的滚动条，鼠标放到下面要放好几次才显示出来」。

   原因不是滚动条没画出来，而是 Chromium 把**未自定义**的滚动条走 overlay 模式：
   平时透明、把鼠标精确停到那条 3~6px 的细带子上才临时浮出来。宽表格（进度表 34 列、
   ECN 十几列）恰恰最需要它常驻 —— 每回想横向拖一下都得先"找"滚动条。

   关键在「只要给 ::-webkit-scrollbar 写了任何一条规则，Chromium 就切回经典滚动条，
   不再用 overlay」。所以这里显式给尺寸 + 颜色，两条又宽又常亮的滚动条就回来了：
   竖向 13px、横向 13px（原来是隐藏状态下的 ~4px 热区），滑块常态染色，hover 加深。
   用 :root 上的 scrollbar-color/width 兜住 Firefox；`auto` 是为了不继承 thin。

   只写通用选择器会影响弹窗里的图片轨道（.img-strip）等所有可滚区域 —— 这正是想要的：
   站内滚动条统一长相，不会出现"这张表有、那个弹窗没有"的割裂感。 */
:root{
  scrollbar-width:auto;
  scrollbar-color:#c2ccda #eef2f7;
}
::-webkit-scrollbar{
  width:13px;
  height:13px;
  background:transparent;
}
::-webkit-scrollbar-track{
  background:#eef2f7;
  border-radius:8px;
}
/* 2px 的同色内边距让滑块不顶到轨道边缘（background-clip:padding-box 才有这个效果；
   用 border + content-box 会把滑块压成 7px，反而不好抓） */
::-webkit-scrollbar-thumb{
  background:#c2ccda;
  border:2px solid #eef2f7;
  border-radius:8px;
  background-clip:padding-box;
  min-height:44px;
  min-width:44px;
}
/* v1.43.11：进度工具栏 nowrap 一行化，横向兜底滚动条藏掉
   （放在通用 ::-webkit-scrollbar 之后 —— 别处测试按文件里首条规则量宽高） */
.topbar-bar::-webkit-scrollbar{display:none}
::-webkit-scrollbar-thumb:hover{background:#96a5b8;background-clip:padding-box}
::-webkit-scrollbar-thumb:active{background:#7b8a9d;background-clip:padding-box}
::-webkit-scrollbar-corner{background:#eef2f7}


/* ============================================================
   v1.30.0 —— 设置页表头钉住 / 账号表就地编辑 / DRI 多选浮层
   ============================================================ */

/* ① 账号列表（以及设置页其它 .mini-table）滚动时表头钉住。
   峰哥：「向下滚动的时候，表头固定不动」—— 原来 .mini-table th 没有 sticky，
   账号一多，滚到 G00xx 就不知道哪一列是「角色」、哪一列是「密码」了。
   ⚠ 这张表是 border-collapse:collapse，sticky 时 td 自己那条 border-bottom
   会跟着表格一起滚走（collapse 的边框由表格持有），所以下边线改用 inset
   box-shadow 画在 th 自己身上，滚到哪都有一条实线。 */
.tablewrap .mini-table thead th{
  position:sticky;top:0;z-index:4;
  background:#f9fafc;
  box-shadow:inset 0 -1px 0 var(--line);
}
/* 表头被钉住时，下面滚过的行不能透上来 —— 上面已经给了不透明底色，
   这里再补一层同为不透明的伪元素兜住圆角外的 1px 缝 */
.tablewrap .mini-table thead th::after{
  content:'';position:absolute;left:0;right:0;bottom:-1px;height:1px;background:var(--line);
}

/* ② 工号 / 姓名：点一下就能改（管理员）。非管理员那两格不可编辑，鼠标也不给文本光标。 */
.u-cell{cursor:text;border-radius:6px;transition:background .12s ease}
.u-cell:hover{background:rgba(59,91,219,.05)}
.u-cell[contenteditable=true]:focus{
  outline:none;background:#fff;
  box-shadow:inset 0 0 0 2px var(--accent);
}

/* ③ 进度表 DRI 多选浮层。
   挂在单元格里（td 临时 position:relative），不用 portal —— 表格横向滚动时
   浮层跟着格子走，不会出现「浮层留在原地、格子已经滑走」的错位。 */
/* v1.49.6（峰哥：双击 DRI 没反应 / 浮层碎片）：v1.43 给 tbody td 统一加了
   overflow:hidden（nowrap+ellipsis 出「…」），而 DRI 浮层就挂在这个 td 里 ——
   被整格裁掉：全裁 = 「点了没反应」，露出一条缝 = 截图里那条「名单里没有」碎片。
   开着浮层的那一刻必须放行溢出（特异性要压过 .prog-table tbody td）。 */
.prog-table tbody td.dri-pick{position:relative;overflow:visible;text-overflow:clip}
.dri-pop{
  position:absolute;z-index:60;left:0;top:calc(100% - 2px);
  width:236px;background:#fff;border:1px solid var(--line);
  border-radius:10px;box-shadow:0 18px 38px -18px rgba(16,20,27,.5);
  padding:8px;display:flex;flex-direction:column;gap:6px;
  font-weight:400;text-align:left;
}
.dri-pop .dp-head,.ecnf-pop .dp-head{display:flex;align-items:center;justify-content:space-between;
  font-size:11px;color:var(--ink3);padding:0 2px}
.dri-pop .dp-head b{color:var(--ink);font-size:11.5px}
.dri-pop .dp-list,.ecnf-pop .dp-list{max-height:186px;overflow:auto;display:flex;flex-direction:column;gap:2px;
  border:1px solid var(--line);border-radius:8px;padding:4px;background:#fbfcfe}
.dri-pop .dp-item,.ecnf-pop .dp-item{
  display:flex;align-items:center;gap:6px;padding:4px 6px;border-radius:6px;
  font-size:12px;color:var(--ink2);cursor:pointer;user-select:none;white-space:nowrap;
}
.dri-pop .dp-item:hover,.ecnf-pop .dp-item:hover{background:rgba(59,91,219,.08)}
.dri-pop .dp-item.on,.ecnf-pop .dp-item.on{background:rgba(59,91,219,.12);color:var(--ink);font-weight:600}
.dri-pop .dp-item i,.ecnf-pop .dp-item i{
  width:14px;height:14px;border-radius:4px;border:1.5px solid #c3ccda;
  display:grid;place-items:center;font-style:normal;font-size:9px;color:#fff;flex:none;
}
.dri-pop .dp-item.on i,.ecnf-pop .dp-item.on i{background:var(--accent);border-color:var(--accent)}
.dri-pop .dp-empty,.ecnf-pop .dp-empty{font-size:11.5px;color:var(--ink3);padding:6px;text-align:center}
.dri-pop .dp-add,.ecnf-pop .dp-add{display:flex;gap:4px}
.dri-pop .dp-add input,.ecnf-pop .dp-add input{
  flex:1;min-width:0;height:26px;border:1px solid var(--line);border-radius:7px;
  padding:0 7px;font-size:12px;background:#fff;
}
.dri-pop .dp-add input:focus,.ecnf-pop .dp-add input:focus{outline:none;border-color:var(--accent)}
.dri-pop .dp-foot,.ecnf-pop .dp-foot{display:flex;gap:6px;justify-content:flex-end}
.dri-pop .dp-foot .btn,.ecnf-pop .dp-foot .btn{height:26px;padding:0 10px;font-size:12px}
/* 表里一格多名：多了换行显示（行高由内容决定，不再撑破列宽） */
.prog-table td.dri{white-space:normal;line-height:1.35}

/* ④ v1.50.0 拖动排序（序号格 = 把手）＋ 落点指示线 */
.prog-table td[data-drag]{cursor:grab;user-select:none}
.prog-table td[data-drag]:active{cursor:grabbing}
body.prog-dragging,
body.prog-dragging *{cursor:grabbing !important}
.prog-table tr.drag-line-top td{border-top:2px solid var(--accent) !important}
.prog-table tr.drag-line-bottom td{border-bottom:2px solid var(--accent) !important}


/* ④ v1.30.0：人员产值按「段」拆开 —— 研发 / 调试 / 电气 三张卡同排。
   12 栏栅格下各占 4 栏；窄屏（<1240px）退回两栏，免得图表被压成一条。
   v1.35.0：加组装卡后是 4 张（研发 / 组装 / 调试 / 电气），各占 3 栏；
   1240~1600 之间退回两栏 × 两行，太窄的话 4 个纵排图会挤成牙签。 */
.grid-2.grid-3>.panel{grid-column:span 4}
.grid-2.grid-4>.panel{grid-column:span 3}
@media (max-width:1600px){
  .grid-2.grid-4>.panel{grid-column:span 6}
}
@media (max-width:1240px){
  .grid-2.grid-3>.panel{grid-column:span 6}
  .grid-2.grid-4>.panel{grid-column:span 6}
}
/* ⑤ KPI 行 5 张卡：12 栏里五等分（原来是 4 张 × span 3）。
   只在真的渲染出 5 张时才生效（.five 由 renderDashboard 加），
   这样用户自己用「布局」拉过宽度的卡不会被强制改回等宽。
   v1.35.0：组装人均卡进来后是 6 张，另给 .six 一档六等分。 */
#kpiRow.five{grid-template-columns:repeat(5,minmax(0,1fr))}
#kpiRow.five>.kpi{grid-column:span 1}
#kpiRow.six{grid-template-columns:repeat(6,minmax(0,1fr))}
#kpiRow.six>.kpi{grid-column:span 1}
@media (max-width:1240px){
  #kpiRow.five{grid-template-columns:repeat(2,minmax(0,1fr))}
  #kpiRow.five>.kpi{grid-column:span 1}
  #kpiRow.six{grid-template-columns:repeat(3,minmax(0,1fr))}
  #kpiRow.six>.kpi{grid-column:span 1}
}

/* ==========================================================================
   v1.31.0 ①：人员分组 —— 组员候选浮层（替代原生 datalist）
   ==========================================================================
   原来是 <input list="datalist">，靠浏览器原生下拉：Safari 根本不支持，
   Chrome / Edge 又只在「已经打了几个字」时才弹，所以同一份页面在不同电脑上
   有的点得出来、有的点了什么都不出来。改成自绘浮层后点一下必然弹出。
   浮层挂在 body 上 + position:fixed —— 设置面板里有 overflow 容器，
   挂在父级会被裁掉（DRI 浮层那边踩过同一个坑）。 */
.grp-pop{
  position:fixed;z-index:1300;background:#fff;border:1px solid var(--line);
  border-radius:10px;box-shadow:0 18px 38px -18px rgba(16,20,27,.45);
  padding:7px;display:flex;flex-direction:column;gap:6px;
  font-weight:400;text-align:left;
}
.grp-pop .gp-head{display:flex;align-items:center;justify-content:space-between;
  font-size:11px;color:var(--ink3);padding:0 2px;gap:8px}
.grp-pop .gp-head b{color:var(--ink);font-size:11.5px;font-weight:600;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.grp-pop .gp-list{max-height:224px;overflow-y:auto;overflow-x:hidden;display:flex;flex-direction:column;gap:2px;
  border:1px solid var(--line);border-radius:8px;padding:4px;background:#fbfcfe;
  /* v1.42.0：候选多到超出时，滚到底不要把设置页一起带着滚。
     注意不写 scrollbar-width 的 thin 值 —— v1.26 定过规矩，thin 在 Windows 上细到抓不住，
     统一用全局的 auto + 下面这套 8px 的 WebKit 样式。 */
  overscroll-behavior:contain;scrollbar-color:rgba(16,20,27,.24) transparent}
/* v1.42.0：滚动条自身要看得见、抓得住 —— 之前它被 mousedown 的 preventDefault
   拦成一根「看得见拖不动」的装饰条。这里只给 .gp-list 单独做窄样式，不影响全局。 */
.grp-pop .gp-list::-webkit-scrollbar{width:8px}
.grp-pop .gp-list::-webkit-scrollbar-track{background:transparent}
.grp-pop .gp-list::-webkit-scrollbar-thumb{
  background:rgba(16,20,27,.22);border-radius:8px;border:2px solid #fbfcfe}
.grp-pop .gp-list::-webkit-scrollbar-thumb:hover{background:rgba(16,20,27,.4)}
.grp-pop .gp-item{
  display:flex;align-items:center;justify-content:space-between;gap:8px;
  padding:5px 7px;border:0;border-radius:6px;background:transparent;width:100%;
  font-size:12px;color:var(--ink2);cursor:pointer;text-align:left;
}
.grp-pop .gp-item span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.grp-pop .gp-item em{font-style:normal;font-size:10.5px;color:var(--ink3);flex:none}
.grp-pop .gp-item:hover{background:rgba(59,91,219,.09);color:var(--ink)}
.grp-pop .gp-item:hover em{color:var(--accent)}
.grp-pop .gp-empty{font-size:11.5px;color:var(--ink3);padding:9px 6px;text-align:center;line-height:1.55}
.grp-pop .gp-foot{font-size:10.5px;color:var(--ink3);padding:0 2px;line-height:1.4}

/* ①② 人员卡下方的 CSV 工具条（四张卡共用，由 ensureStaffCsvBar 注入） */
.staff-csv-bar{display:flex;align-items:center;gap:8px;flex-wrap:wrap;
  margin:10px 0 2px;padding-top:9px;border-top:1px dashed var(--line)}
.staff-csv-bar .csv-tip{font-size:11px;color:var(--ink3)}
.staff-csv-bar .csv-tip code{background:rgba(59,91,219,.08);color:var(--accent);
  padding:1px 5px;border-radius:5px;font-size:10.5px}

/* ==========================================================================
   v1.32.0：两档新权限（项目管理 / 只读）
   ==========================================================================
   只读账号的服务端边界是「所有写接口 403」，前端这边只做两件事：
   ① 把写入口从视觉上收起来，别让人点了半天才发现改不了；
   ② 可编辑的格子换成 not-allowed 光标，一眼看出「这格不能动」。
   ========================================================================== */

/* 名字下面的权限徽标：只读挂红色、PM 挂蓝色，普通工种不挂 */
.user-chip .role-tag{
  display:inline-block;align-self:flex-start;font-style:normal;
  font-size:9.5px;font-weight:700;line-height:1.45;
  padding:0 5px;margin-top:1px;border-radius:4px;
  background:var(--accent-soft);color:var(--accent);letter-spacing:.03em;
}
.user-chip .role-tag.ro{background:rgba(192,52,43,.12);color:var(--bad)}

/* v1.37.0：名字下面那行改成**只显示角色**，不再因为只读而染红 ——
   红色是「权限属性」的提示，峰哥这次要的是角色信息。权限改到账号区悬停提示里。 */

/* 所有写入口在只读模式下收起（服务端另有 403，这里只是不给误触的机会）。
   v1.37.0：权限拆成「项目管理 / ECN」两档，所以按模块各收一遍 ——
   只关了 ECN 的人，进度表的删除按钮还得留着。 */
body.readonly .row-del,
body.readonly [data-del],
body.no-proj .row-del,
body.no-proj [data-del],
body.no-proj [data-dropver],
body.no-proj [data-upgrade],
body.readonly [data-ecndel],
body.readonly [data-dropver],
body.readonly [data-upgrade],
body.readonly .ecn-rowop,
body.readonly .img-add,
body.readonly .ecn-rowadd,
body.no-ecn [data-ecndel],
body.no-ecn .ecn-rowop,
body.no-ecn .img-add,
body.no-ecn .ecn-rowadd{ display:none !important }

/* 可编辑的格子：只读时换成禁止光标。
   ⚠ v1.38.0 修的 bug：这里原来带一句 `background:transparent`，把**冻结单元格**的
   底色一起清空了。冻结列（core 的设备名称、tail 的备注）是靠「不透明背景压在
   滚过去的内容上面」实现多级隐藏的 —— 一旦透明，底下的「数量 / 立项 / 计划完成 /
   是否下单」和后面的里程碑列就直接透上来，看着像「隐藏失效、内容还显示得出来」。
   只在非管理员身上出现：只有他们的 <body> 挂着 .readonly / .no-proj。
   行底色（计划行白 / 实际行浅蓝 / 偶数组再压深）由 tr.rt-plan td 等规则提供，
   这两条规则**不许再碰 background** —— 一碰就又是这个 bug。 */
body.readonly .prog-table td[data-edit],
body.no-proj .prog-table td[data-edit]{cursor:not-allowed}
body.readonly .prog-table td[data-edit]:hover,
body.no-proj .prog-table td[data-edit]:hover{box-shadow:none}
/* 只读时「系统设置」整个导航项也撤掉 —— 那一页全是改参数的入口，
   让只读的人进得去、点什么都改不了，只会以为是系统坏了。
   v1.35.0：口径收紧成「系统设置只对管理员开放」，PM / 各工种一并不再看到入口。
   body.is-admin 由 syncAdminBtns() 统一切换（登录后 / 换账号后都会重算）。 */
body.readonly .nav-item[data-view="settings"]{display:none}
body:not(.is-admin) .nav-item[data-view="settings"]{display:none}


/* ==========================================================================
   v1.33.0 · 设备回收站
   --------------------------------------------------------------------------
   删设备不再当场消失，先进这里。骨架照抄 .ecn-dialog / .ecn-box，
   观感统一；列表用「左侧主体 + 右侧元信息 + 操作」三段式，
   窄屏（<720px）自动折行，不然按钮会被挤出去。
   ========================================================================== */
#btnTrash.has-items{
  border-color:var(--accent);
  color:var(--accent);
  background:var(--accent-soft);
}
.trash-dialog{
  border:none;padding:0;background:transparent;max-width:96vw;max-height:94vh;
  overflow:visible;color:var(--ink);
}
.trash-dialog::backdrop{background:rgba(16,20,27,.34)}
.trash-box{
  width:min(760px,96vw);max-height:82vh;padding:14px;border-radius:15px;
  display:flex;flex-direction:column;gap:10px;
  background:#fff;border:1px solid rgba(16,20,27,.1);
  box-shadow:0 26px 60px -16px rgba(16,20,27,.4);
}
.trash-head{display:flex;align-items:baseline;gap:10px;flex:none}
.trash-head b{font-size:13px;font-weight:700}
.trash-head .trash-sub{font-size:11px;color:var(--ink3)}
.trash-list{
  flex:1 1 auto;min-height:120px;overflow:auto;
  border:1px solid var(--line-2);border-radius:10px;
}
.trash-row{
  display:flex;align-items:center;gap:12px;padding:9px 11px;
  border-bottom:1px solid var(--line);
}
.trash-row:last-child{border-bottom:0}
.trash-main{flex:1 1 auto;min-width:0;display:flex;flex-direction:column;gap:2px}
.trash-main b{
  font-size:12.5px;font-weight:650;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
.trash-main em{font-size:10.5px;font-style:normal;color:var(--ink3)}
.trash-meta{
  flex:none;display:flex;flex-direction:column;gap:2px;align-items:flex-end;
  font-size:10.5px;color:var(--ink3);line-height:1.5;
}
.trash-act{flex:none;display:flex;gap:6px}
.trash-act .btn.sm{font-size:11px;padding:4px 10px}
.trash-act .btn.danger{color:#c0392b;border-color:rgba(192,57,43,.32)}
.trash-act .btn.danger:hover{background:rgba(192,57,43,.08);border-color:#c0392b}
.trash-act .btn[disabled]{opacity:.45;cursor:not-allowed}
.trash-empty{
  padding:34px 14px;text-align:center;color:var(--ink3);font-size:12px;line-height:1.9;
}
.trash-foot{display:flex;align-items:center;gap:8px;flex:none}
.trash-tip{flex:1;font-size:10.5px;color:var(--ink3);line-height:1.6}
@media (max-width:720px){
  .trash-row{flex-wrap:wrap}
  .trash-meta{align-items:flex-start;flex-direction:row;gap:8px;width:100%}
  .trash-act{width:100%;justify-content:flex-end}
}


/* ==========================================================================
   v1.34.0 · 进度表格的横向滚动条常驻
   --------------------------------------------------------------------------
   峰哥：「同一个账户，不同的设备，下面的进度条展示方式都不一样，
          要统一修改成进度条常驻。ECN 的进度条也要一同修改。」

   现象根因：`.prog-wrap / .tablewrap` 都是 `overflow:auto`，
   浏览器只在**内容真的溢出**时才画横向滚动条。于是同一份数据在不同窗口宽度、
   不同段显隐组合下，底部那条「有时在、有时不在」—— 表格可视高度跟着跳一行，
   看起来像两套界面。

   改成 `overflow-x:scroll`：滚动槽恒定占位，内容不满时显示一条禁用态的空槽。
   宁可留一条浅灰空槽，也不要同一页面两种高度。
   ========================================================================== */
.prog-wrap{
  overflow-x:scroll;
  overflow-y:auto;
}
body[data-view="ecn"] #view-ecn .tablewrap{
  overflow-x:scroll;
  overflow-y:auto;
}

/* 空槽本身在 Chrome/Edge 上是一条浅灰轨道，颜色跟页面底色接近；
   这里把两条轨道统一压暗一档，让人一眼看出「这是滚动条，不是分隔线」。 */
.prog-wrap::-webkit-scrollbar,
body[data-view="ecn"] #view-ecn .tablewrap::-webkit-scrollbar{
  width:11px;height:11px;
}
.prog-wrap::-webkit-scrollbar-track,
body[data-view="ecn"] #view-ecn .tablewrap::-webkit-scrollbar-track{
  background:rgba(16,20,27,.035);
}
.prog-wrap::-webkit-scrollbar-thumb,
body[data-view="ecn"] #view-ecn .tablewrap::-webkit-scrollbar-thumb{
  background:rgba(16,20,27,.22);
  border-radius:6px;
  border:2px solid transparent;
  background-clip:padding-box;
}
.prog-wrap:hover::-webkit-scrollbar-thumb,
body[data-view="ecn"] #view-ecn .tablewrap:hover::-webkit-scrollbar-thumb{
  background:rgba(16,20,27,.34);
  background-clip:padding-box;
}


/* ==========================================================================
   v1.34.0 · Dashboard「阶段分布 / 交付节奏」可以横向调宽
   --------------------------------------------------------------------------
   这两张卡原来直接挂在 view 上，而 view 在布局编辑器里被当成「单列容器」
   （lzCols 返回 1），所以拖右下角只能改高度，提示语是
   「这块独占整行，没有可变的宽度」。峰哥要的就是这里也能左右拉。

   包进 12 栏栅格后默认 span 12 —— 视觉上仍然各占一整行，和改之前一模一样；
   想并排就把其中一块拖成 span 6。默认值写在 CSS 类而不是 inline，
   是因为 lzApply 每轮都会 lzClear 清掉 inline 的 grid-column。
   ========================================================================== */


/* ==========================================================================
   v1.38.0 —— 外观 trio：实底面 / 单元格色调 / 卡片圆角
   --------------------------------------------------------------------------
   峰哥给的参考稿「很清爽」，拆开是三件事：白卡片压在浅灰底上（不是玻璃拟态）、
   单元格颜色克制（不靠深浅分区）、圆角小而统一。三样都改变量，
   不去逐条改选择器 —— 那样每加一个面板就要再补一处，迟早漏。 */
body[data-surface="flat"]{
    --bg:#f0f2f5;
    --glass:#fff;--glass-2:#fff;--glass-brd:#e8e8e8;--blur:0px;
    /* 平铺面下卡片是纯白，冻结列跟着纯白（否则会比卡片亮一档，看着像补丁） */
    --u-stick:#fff;
  --shadow-sm:0 1px 2px rgba(0,0,0,.04);
  --shadow:0 2px 8px rgba(0,0,0,.06);
  --shadow-lg:0 6px 18px rgba(0,0,0,.09);
  --r-md:10px;--r-lg:10px;
}
body[data-surface="flat"] .glass{backdrop-filter:none;-webkit-backdrop-filter:none}

/* 单元格色调：soft = 现状（计划白 / 实际浅蓝 / 偶数组再压深）
   plain  = 全部收成一档，只靠虚线分隔 —— 最干净，但相邻设备容易看串行
   zebra  = 奇偶对比拉开一档，快扫一列时最不容易串 */
body[data-tone="plain"] .prog-table tbody tr.rt-plan td,
body[data-tone="plain"] .prog-table tbody tr.rt-plan.is-even td{background:#fff}
body[data-tone="plain"] .prog-table tbody tr.rt-actual td,
body[data-tone="plain"] .prog-table tbody tr.rt-actual.is-even td{background:#fafbfc}
body[data-tone="zebra"] .prog-table tbody tr.rt-plan.is-even td{background:#f4f6f9}
body[data-tone="zebra"] .prog-table tbody tr.rt-actual td{background:#eaf0f9}
body[data-tone="zebra"] .prog-table tbody tr.rt-actual.is-even td{background:#dfe7f4}

/* ============================================================================
   v1.45.0 —— Dashboard 驾驶仓配色（参考「项目计划数据看板」）
   v1.48.0（峰哥）：深蓝顶栏推广到全部界面（项目管理 / ECN / 系统设置）——
   渐变底已上移到 .topbar.glass 基础规则；这里只留各视图通用的反白文字，
   和 dashboard 专属的标题字号（其它页标题维持 16.5px 不动）。
   ========================================================================== */
.crumb h2{color:#fff}
.crumb p{color:rgba(255,255,255,.75)}
.top-right .btn.ghost{color:#fff;border-color:rgba(255,255,255,.38);background:rgba(255,255,255,.10)}
.top-right .btn.ghost:hover{background:rgba(255,255,255,.22)}
.top-right .pill{background:rgba(255,255,255,.14);border-color:rgba(255,255,255,.32);color:#fff}
.top-right .pill.remote-hint{color:#fff;background:rgba(255,255,255,.24)}
.top-right .avatar{background:rgba(255,255,255,.22);color:#fff}
.top-right .icon-btn{color:#fff}
.top-right .user-chip b{color:#fff}
.top-right .user-chip small{color:rgba(255,255,255,.72)}
/* 项目进度页第二行筛选栏：分隔线换半透明白（浅灰线在深蓝底上发脏）；
   搜索框 / 下拉自带白底，在深蓝上正好是清爽的对比，不动。
   v1.49.3（峰哥）：右侧「研发段…新增 / 保存」一段 —— 段标签和 ghost 按钮
   原来是深灰字，深蓝底上直接隐形；全部反白：段 chip / ghost 按钮 = 半透明白底白字，
   选中段 chip 改白底深蓝字；主按钮「保存」改白底深蓝字（深蓝上最跳的主操作）。 */
.topbar-bar{border-top-color:rgba(255,255,255,.16)}
.topbar-bar .btn.ghost{color:#fff;border-color:rgba(255,255,255,.38);background:rgba(255,255,255,.10)}
.topbar-bar .btn.ghost:hover{background:rgba(255,255,255,.22);color:#fff;border-color:rgba(255,255,255,.5)}
.topbar-bar .btn.primary{background:#fff;border-color:#fff;color:#1a237e}
.topbar-bar .seg-chip{color:rgba(255,255,255,.85);border-color:rgba(255,255,255,.32);background:rgba(255,255,255,.08)}
.topbar-bar .seg-chip:hover{color:#fff;border-color:rgba(255,255,255,.5)}
.topbar-bar .seg-chip.on{background:#fff;border-color:#fff;color:#1a237e;font-weight:600}
/* dashboard 标题保持大一号（驾驶仓门面） */
body[data-view="dashboard"] .crumb h2{font-size:19px;letter-spacing:.05em}

/* ② KPI 卡：左侧彩色图标方块 + 同色大数字 */
.kpi{display:flex;gap:12px;align-items:flex-start}
.kpi .k-ic{
  width:38px;height:38px;border-radius:9px;flex:none;display:grid;place-items:center;
  color:#fff;font-size:16px;font-weight:700;font-style:normal;
  box-shadow:0 3px 9px rgba(0,0,0,.18);
}
.kpi .k-body{min-width:0;flex:1}
.kpi .k-v{color:var(--kc,var(--ink))}
.kpi.empty .k-v{color:var(--ink3)}

/* ③ 项目进度与产值：彩色大数字瓦片（逾期红 / 进行中橙 / 已闭环绿 / 产值深绿 / 在手紫）
      v1.45.1：一排 10 块（进度 5 + 下单 5），minmax 压到 124px 保住单行
      v1.46.0（峰哥）：图标统一缩小、padding 收紧 —— 原来的 36px 方块把
      「1,713.45」这种长数值挤成「1,7…」，缩完一排 10 块都放得下全值 */
.ps-tiles{display:grid;grid-template-columns:repeat(auto-fit,minmax(118px,1fr));gap:9px;margin:2px 0 4px}
.ps-tile{
  display:flex;align-items:center;gap:8px;
  background:var(--card,#fff);border:1px solid var(--line);border-radius:11px;
  padding:9px 10px;box-shadow:0 1px 3px rgba(0,0,0,.05);
  animation:tRise .5s cubic-bezier(.22,1,.36,1) both;
}
@keyframes tRise{from{opacity:0;transform:translateY(7px)}to{opacity:1;transform:none}}
.ps-tile .t-ic{
  width:26px;height:26px;border-radius:7px;flex:none;display:grid;place-items:center;
  color:#fff;font-size:12px;font-weight:700;font-style:normal;
  box-shadow:0 2px 6px rgba(0,0,0,.15);
}
.ps-tile .t-body{min-width:0}
.ps-tile .t-v{font-size:17px;font-weight:700;line-height:1.15;color:var(--tc,var(--ink));font-variant-numeric:tabular-nums;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.ps-tile .t-l{font-size:10px;color:var(--ink2);margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.ps-tile .t-l em{font-style:normal;font-size:10.5px;color:var(--ink3);margin-left:3px}
/* v1.45.1：ps-bar（三态堆叠条）/ ps-sec（阶段分布小节标题）撤掉 ——
   瓦片一排自明，阶段分布另立卡放界面最底部 */

/* ④ 图例开关：点一下隐藏 / 显示系列
   v1.46.0（峰哥：隐藏某组后图例「就变了」）—— 原来整项 opacity:.32+灰度，
   文字糊成一块浅灰像坏了。改：文字保持可读加删除线，只有色块单独置灰。 */
.chart-legend .cl-item.tog{cursor:pointer;user-select:none;border-radius:6px;padding:1px 6px;transition:opacity .18s ease}
.chart-legend .cl-item.tog:hover{background:rgba(0,0,0,.06)}
.chart-legend .cl-item.tog.off{color:var(--ink3);text-decoration:line-through}
.chart-legend .cl-item.tog.off .cl-pair,
.chart-legend .cl-item.tog.off .cl-dot,
.chart-legend .cl-item.tog.off .cl-dash{opacity:.3;filter:grayscale(1)}
/* v1.46.0：图例置顶（研发 / 组装 / 调试 / 电气卡的「年度达成率」挪到图上方，靠右）
   v1.49.0（峰哥）：置顶图例压成**一行** —— 8 个开关项（一组·实际 … 全体研发目标）
   不再换行；字号收一档、间距收紧，放不下时横向滚动兜底（不裁字）。 */
.chart-legend.lg-top{
  justify-content:flex-start;flex-wrap:nowrap;overflow-x:auto;overflow-y:hidden;
  gap:8px;font-size:10.5px;margin:0 0 4px;padding-bottom:2px;
}
.chart-legend.lg-top .cl-item{padding:1px 5px}
