/* 龙岗住建 V7 · 共享界面样式库 app.css
   Phase0拆分:从v7_mvp.py内嵌<style>原样搬出,零回归。所有页面统一调用。
   维护:库管(lock:shared-ui),页面Agent只引用不改;缺组件提component-request。
   参照原型 docs/原型样式规范.md */

:root{--bg:#eef1f5;--card:#fff;--ink:#1f2a37;--mut:#6b7280;--line:#dfe4ea;--br:#1565c0;--br2:#0d47a1}
*{box-sizing:border-box}
body{margin:0;font-family:"Microsoft YaHei",system-ui,sans-serif;background:var(--bg);color:var(--ink);line-height:1.5}
header{background:linear-gradient(135deg,#1565c0,#0d47a1);color:#fff;padding:14px 20px;display:flex;justify-content:space-between;align-items:center}
h1{font-size:18px;margin:0;color:#fff}.hsub{color:#a0c4ff;font-size:12px;margin-top:3px}
.rolenav{background:#0d47a1;padding:0 20px;display:flex;gap:2px;flex-wrap:wrap;border-bottom:2px solid #1565c0;position:sticky;top:0;z-index:20;box-shadow:0 2px 6px rgba(13,71,161,.25)}
.rolenav a{font-size:13px;color:#bbd0ee;padding:10px 14px;cursor:pointer;text-decoration:none;border-bottom:3px solid transparent;display:inline-block}
.rolenav a:hover{color:#fff;background:rgba(255,255,255,.1)}
.rolenav a.cur{color:#fff;background:rgba(255,255,255,.15);border-bottom:3px solid #fff;font-weight:600}
main{max-width:1240px;margin:0 auto;padding:14px 20px}
.view{display:none}.view.on{display:block}
.panel{background:var(--card);border:1px solid var(--line);border-radius:8px;padding:14px 16px;margin-bottom:14px;box-shadow:0 1px 3px rgba(0,0,0,.04)}
.panel h2{font-size:14px;color:var(--br2);margin:0 0 10px;border-left:3px solid var(--br);padding-left:8px}
.vtitle{font-size:16px;color:var(--br2);margin:0 0 4px;border-left:4px solid var(--br);padding-left:9px;font-weight:600}
.vsub{color:var(--mut);font-size:12px;margin:0 0 12px}
.cards{display:flex;gap:10px;flex-wrap:wrap;margin-bottom:14px}
.card{background:var(--card);border:1px solid var(--line);border-radius:8px;padding:10px 16px;min-width:110px;box-shadow:0 1px 3px rgba(0,0,0,.04)}
.card .n{font-size:24px;font-weight:700;color:var(--br)}.card .l{font-size:11px;color:var(--mut);margin-top:2px}
/* [A4 指标口径标注] 率类 KPI 卡底部的「分子 / 分母」行:比标签更小更淡,不抢主数字。 */
.card .cal{font-size:10px;color:var(--mut);opacity:.75;margin-top:1px;font-variant-numeric:tabular-nums}
.row{display:flex;gap:14px;flex-wrap:wrap}
.sidebar{width:200px;flex-shrink:0;min-width:180px;resize:horizontal;overflow:auto}
.col{flex:1;min-width:220px}
table{width:100%;border-collapse:collapse;font-size:13px;margin:4px 0}
th,td{border-bottom:1px solid var(--line);padding:7px 8px;text-align:left}
th{background:#f0f4f8;color:var(--mut);font-weight:600;font-size:12px}
tbody tr{cursor:pointer}tbody tr:hover{background:#f5f8ff}
.pager{font-size:12px;color:var(--mut);margin:6px 0;display:flex;align-items:center;gap:4px;flex-wrap:wrap}
.pager b{padding:2px 7px;border:1px solid var(--line);border-radius:4px;background:#fff;cursor:pointer}
.pager b.cur,.pager b:hover{background:var(--br);color:#fff;border-color:var(--br)}
.toolbar{background:#f4f7fb;border:1px solid var(--line);border-radius:7px;padding:8px 10px;margin:0 0 12px;font-size:12px;display:flex;gap:6px;flex-wrap:wrap;align-items:center}
.toolbar input,.toolbar select{padding:4px 8px;border:1px solid #ccd;border-radius:5px;font-size:12px;background:#fff;color:var(--ink)}
.btn{display:inline-flex;align-items:center;padding:5px 12px;border-radius:5px;background:var(--br);color:#fff;font-size:12px;border:none;cursor:pointer;text-decoration:none;margin:1px 2px;white-space:nowrap}
.btn.g{background:#2e7d32}.btn.o{background:#ef6c00}.btn.r{background:#c62828}.btn.d{background:#607d8b}.btn.s{background:#fff;color:var(--br);border:1px solid var(--br)}
.btn:disabled{opacity:.5;cursor:not-allowed}
.tag{display:inline-block;padding:1px 7px;border-radius:4px;font-size:11px;color:#fff;white-space:nowrap}
.tag.t1{background:#c62828}.tag.t2{background:#ef6c00}.tag.t3{background:#0d47a1}.tag.t4{background:#2e7d32}
.tag.s-new{background:#607d8b}.tag.s-pushed{background:#0d47a1}.tag.s-verify{background:#1565c0}
.tag.s-rectify{background:#ef6c00}.tag.s-done{background:#2e7d32}.tag.s-super{background:#e65100}
.tag.s-listed{background:#c62828}.tag.s-closed{background:#37474f}
.stat-chip{display:inline-block;padding:2px 8px;border-radius:4px;font-size:11px;margin:1px}
.pill{display:inline-block;padding:1px 7px;border-radius:5px;background:#e8f0fe;color:var(--br);font-size:11px}
.sw{display:inline-block;width:38px;height:20px;border-radius:11px;position:relative;vertical-align:middle;cursor:pointer}
.sw.on{background:#2e7d32}.sw.off{background:#bbb}.sw.lock{background:#90a4ae;cursor:not-allowed}
.sw i{position:absolute;top:2px;width:16px;height:16px;border-radius:50%;background:#fff;transition:.15s}
.sw.on i,.sw.lock i{right:2px}.sw.off i{left:2px}
.crumb{font-size:12px;color:var(--mut);margin:0 0 10px}
.crumb span{color:var(--br);cursor:pointer}.crumb span:hover{text-decoration:underline}
.bar{display:flex;align-items:center;gap:8px;margin:4px 0;font-size:13px}
.bar .bname{width:130px;text-align:right;color:var(--ink);cursor:pointer;font-size:13px}
.bar .bname:hover{color:var(--br)}
.bar .track{flex:1;background:#e8edf2;border-radius:3px;overflow:hidden;height:16px}
.bar .fill{background:linear-gradient(90deg,#1565c0,#42a5f5);height:16px}
.bar .bv{width:70px;color:var(--mut);font-size:12px}
select,button{font-family:inherit}
#trend-svg{width:100%;height:120px;border:1px solid var(--line);border-radius:6px;background:#fff}
.thumb-cell img{width:52px;height:38px;object-fit:cover;border-radius:3px;border:1px solid var(--line);vertical-align:middle;cursor:pointer}
.thumb-cell .no-img{width:52px;height:38px;display:inline-flex;align-items:center;justify-content:center;background:#f0f4f8;border:1px solid var(--line);border-radius:3px;color:var(--mut);font-size:9px}
/* lgv7#742: 「数据侧没有图」与「图片请求失败」必须在界面上看得出区别 —— 二者都写成"无图"时,
   一次签名过期/CSP 拦截(2026-07 事故, 见 tools/csp_thumbnail_gate.js)会被读成"这条告警本来就没图"。 */
.img-failed{width:52px;height:38px;display:inline-flex;align-items:center;justify-content:center;background:#fff7e6;border:1px solid #d48806;border-radius:3px;color:#ad6800;font-size:9px;cursor:help}
.tree{font-size:13px;line-height:2}
.tree .l1{font-weight:700;color:var(--br2)}.tree .l2{padding-left:14px;color:var(--ink)}.tree .l3{padding-left:28px;color:var(--mut);font-size:12px}
.tree span.node{cursor:pointer}.tree span.node:hover{color:var(--br)}
.note{color:var(--mut);font-size:12px}
.err{color:#c62828;font-size:12px}
#mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:1000}
#modal{display:none;position:fixed;top:5vh;left:50%;transform:translateX(-50%);width:min(700px,94vw);
 max-height:88vh;overflow:auto;background:#161f29;border:1px solid #30363d;border-radius:10px;z-index:1001;padding:18px}
#modal h3{margin:0 0 10px;font-size:15px;color:#c9d6e2}
.kv{display:grid;grid-template-columns:110px 1fr;gap:4px 10px;font-size:13px;margin-bottom:10px}
.kv .k{color:#8b98a5}.insight{background:#0d141c;border:1px solid #24313f;border-radius:8px;padding:10px;font-size:13px;line-height:1.6}
.insight .ttl{color:#f0883e;font-weight:600}.close-btn{background:#30363d;color:#e6edf3;border:none;border-radius:5px;padding:4px 10px;cursor:pointer;float:right}
#mimg{max-width:100%;border-radius:8px;margin-top:8px}
.mact-row{border-top:1px solid #24313f;margin-top:10px;padding-top:10px}
.mact-row button{background:#1f6feb;color:#fff;border:none;border-radius:5px;padding:5px 10px;font-size:12px;cursor:pointer;margin:2px}
.mact-row button.d{background:#30363d}
.timeline-item{font-size:12px;color:#8b98a5;line-height:1.8}
.lmodal{display:none;position:fixed;top:15vh;left:50%;transform:translateX(-50%);width:min(360px,92vw);
 background:#fff;border:1px solid var(--line);border-radius:10px;z-index:1002;padding:22px;box-shadow:0 8px 24px rgba(0,0,0,.15)}
.lmodal h3{margin:0 0 14px;font-size:15px;color:var(--br2)}
/* 由来:这条规则原本写成 `.lmodal input` 通配,width:100% 连 type=checkbox 一起套上了——
   勾选框被撑成整行宽的 flex 项,把同一行的"我确认是本人真实信息""记住密码"挤成逐字换行。
   文本输入框与勾选/单选框的样式必须分开,别再用通配 input 选择器。 */
.lmodal input:not([type=checkbox]):not([type=radio]),.lmodal select{width:100%;box-sizing:border-box;margin-bottom:10px;padding:9px 12px;border:1px solid #ccd;border-radius:6px;font-size:13px;font-family:inherit}
.lmodal input[type=checkbox],.lmodal input[type=radio]{width:auto;flex:0 0 auto;margin:0;padding:0}
.lmodal .lerr{color:#c62828;font-size:12px;min-height:16px;margin-bottom:8px}
.lmodal button{width:100%;padding:9px;border:none;border-radius:6px;font-size:13px;cursor:pointer}
.lmodal .lbtn-main{background:var(--br);color:#fff}
.lmodal .lbtn-sec{background:transparent;color:var(--br);border:1px solid var(--br);margin-top:6px}
/* 登录框页签条「账号登录 / 🆕 首次登录·激活账号」。由来(两条,缺一都会复发):
   ① 布局原先全写在 HTML 内联 style 上,而 core.js 的 _forcedPwLockUI 解锁时执行
      `tabs.style.display=''` —— 内联 display:flex 被清掉后没有样式表兜底,页签条退化成 block,
      两个页签竖着堆叠。布局必须落在这里,内联只留 onclick 要切换的 background/color。
   ② 两个页签等宽(flex:1 1 0),但右侧文案 9 个字,弹窗只有 min(360px,92vw):
      靠 nowrap + 窄内边距 + 窄屏降字号保证不折行,别把字号调回 15px。 */
#ltabs{display:flex;margin:6px 0 14px;border:1px solid #1565c0;border-radius:8px;overflow:hidden;font-size:14px;font-weight:700;white-space:nowrap}
#ltabs>div{flex:1 1 0;min-width:0;text-align:center;padding:11px 4px;cursor:pointer}
@media (max-width:420px){#ltabs{font-size:12px}#ltabs>div{padding:10px 2px}}

/* ===== 批次2: 告警处理流程说明卡 processFlowCard(2026-07-16 UX整改) ===== */
.pfc{background:var(--card);border:1px solid var(--line);border-radius:10px;padding:14px 16px;margin-bottom:14px}
.pfc-desc{font-size:13px;color:var(--ink);line-height:1.6;background:#f2f6fc;border-left:3px solid var(--br);border-radius:4px;padding:8px 12px;margin-bottom:12px}
.pfc-desc b{color:var(--br)}
.pfc-title{font-size:13px;font-weight:600;color:var(--br2);margin-bottom:10px;display:flex;align-items:center;gap:6px}
.pfc-flow{display:flex;align-items:stretch;flex-wrap:wrap;gap:0}
.pfc-step{display:flex;flex-direction:column;align-items:center;text-align:center;min-width:78px;position:relative;padding:2px 2px 4px}
.pfc-num{width:26px;height:26px;border-radius:50%;background:var(--br);color:#fff;font-size:13px;font-weight:600;display:flex;align-items:center;justify-content:center;margin-bottom:5px;box-shadow:0 1px 3px rgba(21,101,192,.25)}
.pfc-nm{font-size:12px;color:var(--ink);font-weight:500;white-space:nowrap}
.pfc-sub{font-size:11px;color:var(--mut);white-space:nowrap;margin-top:1px}
.pfc-arr{align-self:center;color:var(--br);font-size:18px;font-weight:700;margin:0 2px;transform:translateY(-8px);opacity:.55}
/* 当前阶段高亮:脉动圆圈 + 圈变深 + 名字加粗 */
.pfc-step.on .pfc-num{background:var(--br2);animation:pfcPulse 1.8s ease-in-out infinite;box-shadow:0 0 0 4px rgba(21,101,192,.18)}
.pfc-step.on .pfc-nm{color:var(--br2);font-weight:700}
.pfc-step.on::after{content:'当前';position:absolute;top:-9px;right:2px;font-size:9px;background:var(--br2);color:#fff;border-radius:6px;padding:0 4px;line-height:14px}
@keyframes pfcPulse{0%,100%{box-shadow:0 0 0 4px rgba(21,101,192,.18)}50%{box-shadow:0 0 0 7px rgba(21,101,192,.06)}}
/* 超时升级分支:警告色 + 缩进感 */
.pfc-esc{margin-top:10px;padding-top:9px;border-top:1px dashed var(--line);display:flex;align-items:center;flex-wrap:wrap;gap:0}
.pfc-esc-lbl{font-size:11px;color:#b26a00;margin-right:6px;white-space:nowrap;align-self:center;transform:translateY(-4px)}
.pfc-step.esc .pfc-num{background:#ef8c00}
.pfc-step.esc .pfc-arr,.pfc-esc .pfc-arr{color:#ef8c00}
.pfc-foot{font-size:11px;color:var(--mut);margin-top:10px;line-height:1.6;border-top:1px solid var(--line);padding-top:8px}
.pfc-foot b{color:var(--br)}
@media(max-width:640px){.pfc-flow,.pfc-esc{gap:2px 0}.pfc-step{min-width:64px}.pfc-arr{font-size:15px}}

/* ===== 批次3: 表格统一增强(2026-07-16 UX整改·纯样式增量,不改渲染结构) ===== */
/* 斑马纹:提升多行数据可读性(奇数行淡底) */
tbody tr:nth-child(even){background:#fafbfd}
tbody tr:nth-child(even):hover,tbody tr:hover{background:#eef4ff}
/* 表头更清晰:轻微字间距 + 不换行 */
th{letter-spacing:.3px;white-space:nowrap}
/* 数字/日期列右对齐(渲染时给 td/th 加 class=num 即生效;数字右对齐便于领导扫读比较) */
td.num,th.num{text-align:right;font-variant-numeric:tabular-nums}
/* 状态标签列居中 */
td.ctr,th.ctr{text-align:center}
/* 长文本单元格省略号(给 td 加 class=ell + title 提示全文) */
td.ell{max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* 关键标识列(告警号/设备号/工单号)统一加粗蓝,渲染时给 td 加 class=idcol */
td.idcol{font-weight:600;color:var(--br)}
/* 空数据行:居中灰色,视觉统一 */
td.note[colspan]{text-align:center;padding:16px 8px;color:var(--mut)}

/* ===== 批次4: 宽表横向滚动 + 冻结列(2026-09-10 · 资产运维「设备明细」表格展示缺陷) =====
   由来(实测,1440×900 视口,OPS 角色,本地 7021 台设备):
     · 设备明细 13 列的**固有最小宽度合计 1301px**(th 全局 white-space:nowrap + 操作列
       4 个 nowrap 按钮独占 218px + 街道/社区/工地三个筛选 select),
       而它所在的 .col 容器只有 **984px**(main 1240 − 内外边距 − 200px 组织树侧栏)。
     · 链路上**没有任何 overflow 容器**(table 的父节点就是 .panel,overflow-x:visible),
       于是表格直接**溢出面板边框 332px、溢出视口 212px**,整页被撑出横向滚动条
       (document.body.scrollWidth 1652 > clientWidth 1440)。
     · 后果不只是难看:**「操作」列整列落在视口之外**,绑定/解绑/迁移/退租/调整归属
       这些按钮在默认视口下根本点不到 —— 这就是用户报的「表格展示问题 + 按钮问题」。
   修法(纯增量,只加**新的 opt-in class**,不动任何既有全局选择器,故对其余 18 个页面零影响):
     · .tbl-scroll —— 给宽表套一层横向滚动容器,表格取 max-content 自然宽度,
       不再被压扁,也不再溢出面板/视口;滚动条加粗到 12px 并上色,保证用户看得见。
     · .tbl-freeze —— 横向滚动时冻结**左侧身份列**(勾选 + SN)与**右侧操作列**,
       使「我在看哪一行」与「我能对它做什么」在任何滚动位置都不丢失。
       这是本修法的关键:只加滚动条会让操作列滚出去仍然难点,冻结右列才真正解决按钮可达性。
   为什么不引三方库(Bootstrap / Element UI):见 MR 说明。要点是二者都会与本文件既有的
     .btn/.row/.col/.card/.panel/table/th/td 全局选择器正面冲突(Element UI 还要求 Vue,
     本仓前端是无构建步骤的原生 JS),代价是全站回归,而本缺陷只需上面两个 class。 */
.tbl-scroll{overflow-x:auto;overflow-y:visible;max-width:100%;
  scrollbar-width:thin;scrollbar-color:#9db3cc #eef2f7}
.tbl-scroll>table{min-width:max-content;margin:0}
.tbl-scroll::-webkit-scrollbar{height:12px}
.tbl-scroll::-webkit-scrollbar-track{background:#eef2f7;border-radius:6px}
.tbl-scroll::-webkit-scrollbar-thumb{background:#9db3cc;border-radius:6px;border:2px solid #eef2f7}
.tbl-scroll::-webkit-scrollbar-thumb:hover{background:#6f8bab}

/* 冻结列。左1=勾选框(定宽 34px,故左2 的 left 偏移可以写死 34px);右1=操作列。
   :not([colspan]) 是必需的 —— 空数据行/加载行是一个 colspan=N 的单元格,
   若被 nth-child(1) 命中并按 34px 定宽,空状态会被压成一条竖缝。 */
.tbl-freeze th:nth-child(1),.tbl-freeze td:nth-child(1):not([colspan]){
  width:34px;min-width:34px;max-width:34px;padding-left:6px;padding-right:6px}
.tbl-freeze thead th:nth-child(1),.tbl-freeze tbody td:nth-child(1):not([colspan]),
.tbl-freeze thead th:nth-child(2),.tbl-freeze tbody td:nth-child(2):not([colspan]){
  position:sticky;left:0;z-index:2}
.tbl-freeze thead th:nth-child(2),.tbl-freeze tbody td:nth-child(2):not([colspan]){left:34px}
.tbl-freeze thead th:last-child,.tbl-freeze tbody td:last-child:not([colspan]){
  position:sticky;right:0;z-index:2}
/* 表头的冻结格要盖住表体的冻结格 */
.tbl-freeze thead th:nth-child(1),.tbl-freeze thead th:nth-child(2),
.tbl-freeze thead th:last-child{z-index:4}
/* 冻结格必须不透明,否则下层内容会从它底下穿过去。三种行态(常规/斑马/悬停)各配一份,
   顺序即优先级:悬停写在最后,保证 hover 时冻结列与普通列同色,不出现"半行高亮"。 */
.tbl-freeze tbody td:nth-child(1):not([colspan]),
.tbl-freeze tbody td:nth-child(2):not([colspan]),
.tbl-freeze tbody td:last-child:not([colspan]){background:var(--card)}
.tbl-freeze tbody tr:nth-child(even) td:nth-child(1):not([colspan]),
.tbl-freeze tbody tr:nth-child(even) td:nth-child(2):not([colspan]),
.tbl-freeze tbody tr:nth-child(even) td:last-child:not([colspan]){background:#fafbfd}
.tbl-freeze tbody tr:hover td:nth-child(1):not([colspan]),
.tbl-freeze tbody tr:hover td:nth-child(2):not([colspan]),
.tbl-freeze tbody tr:hover td:last-child:not([colspan]){background:#eef4ff}
.tbl-freeze thead th{background:#f0f4f8}
/* 冻结边界的投影:让"这里被钉住了、右边还有内容"这件事在视觉上成立 */
.tbl-freeze th:nth-child(2),.tbl-freeze td:nth-child(2):not([colspan]){
  box-shadow:6px 0 6px -6px rgba(15,35,60,.28)}
.tbl-freeze th:last-child,.tbl-freeze td:last-child:not([colspan]){
  box-shadow:-6px 0 6px -6px rgba(15,35,60,.28)}

/* ===== 行内「更多」浮层菜单(2026-09-10,同批) =====
   由来:操作列此前平铺 4 个按钮 = 218px,是把表格撑爆的最大单一贡献者。
   改为「主操作 + 更多▾」后该列降到约 110px。菜单必须挂在 <body> 上并用 position:fixed ——
   若留在 .tbl-scroll 内部,会被那层 overflow-x:auto 直接裁掉(这正是加滚动容器的副作用,
   顺手一并解决,不留下"点了更多什么都没出来"的新坑)。 */
.menu-pop{position:fixed;z-index:1200;background:#fff;border:1px solid var(--line);border-radius:6px;
  box-shadow:0 6px 20px rgba(15,35,60,.18);padding:4px;min-width:130px;display:flex;flex-direction:column;gap:2px}
.menu-pop button{display:block;width:100%;text-align:left;padding:7px 10px;border:none;background:transparent;
  font-size:12px;font-family:inherit;cursor:pointer;border-radius:4px;white-space:nowrap}
.menu-pop button:hover,.menu-pop button:focus{background:#eef4ff;outline:none}

/* ===== 批次5: 智能告警表格展示整改(2026-09-11 · 用户报「类型/状态/操作 三列文字换行 +
   搜索工具条导出CSV换行」) =====
   由来(实测):三处换行是**同一个根因的两层**,分开修会漏。
     · 直接层 —— .btn(inline-flex) 与 .tag(inline-block) 此前**都没有 white-space 约束**。
       容器一窄,浏览器就在中文之间断行:「未戴安全帽」断成两行、「⭳ 导出CSV」断成两行、
       操作列两颗按钮(误报/属实)各占一行。上面两行已就地补 nowrap。
     · 根因层 —— 光加 nowrap 只是把换行换成溢出。智能告警默认表 10 列,链路上同样
       **没有任何 overflow 容器**(table 的父节点就是 .panel),这与 2026-09-10「设备明细」
       是同型缺陷;容器不够宽时浏览器只能压缩列宽,于是触发上面的文字换行。
       故必须同时给它套「批次4」既有的 .tbl-scroll(自然宽度 + 可见横向滚动条)。
   ★为什么不直接复用 .tbl-freeze:智能告警的表头形状**随角色变化** ——
     巡查员/社区/街道 首列是勾选框,区局/运维 首列是「告警号」(见 pages/alarms.js::
     _renderAlarmsSortHeaders 四个分支)。.tbl-freeze 的「首列定宽 34px + 冻结左二列」
     会把区局视角的告警号列压成 34px。故只取其中与列形状无关的那一项,单列出本 class。 */
/* ★冻结「操作」列的方案试过并**撤回**,记在这里防后人重走(实测,1440×900,OPS 角色,真实数据):
     加 position:sticky;right:0 之后,用 elementFromPoint 打在各表头中心实测 ——
       状态:被『操作』遮挡   阶段:完全在滚动视口外   其余 7 列:可见
     原因是本表自然宽 1069px > 容器 952px,sticky 列必然压住右侧相邻两列,
     而「状态」是本页主扫读列。那等于把用户报的「状态换行」改成「状态看不见」,
     是更糟的缺陷 —— 冻结列与全列可见在本表上互斥,只能二选一,这里选全列可见。
     (设备明细能用冻结是因为它溢出 317px、操作列整列在视口外、且首列是勾选框不是主扫读列;
      本表只溢出 117px,小幅横滚即可够到操作列,不值得用遮挡去换。)
   ★同批还有一条判据教训:最初我用「th.right <= scroller.right」判可见,它对 状态 返回 true,
     因为那条判据只覆盖了"在不在滚动视口内",没覆盖"有没有被 sticky 列盖住" ——
     判据的观测面比它要断言的命题窄。正确判据是 elementFromPoint 打点看最上层是不是它自己。 */

/* 工具条「标签+控件」成组不拆行。.toolbar 是 flex-wrap:wrap,而「导出近」「类型」这类
   裸文本在 flex 容器里各自成为**匿名 flex item** ⇒ 换行点可以落在标签与它的下拉之间,
   于是出现「导出近」在上一行、下拉和「⭳ 导出CSV」被甩到下一行。包一层 inline-flex
   即把一组绑成单个不可分割的 flex item —— 整组一起换行,不再从中间劈开。 */
.toolbar .tb-grp{display:inline-flex;align-items:center;gap:4px;white-space:nowrap}
