/* ============================================================
   无人机数据回传平台 · 基础样式（语义 Token 驱动，四主题换肤）
   ============================================================ */
:root{
  /* ---- 颜色（各主题覆写） ---- */
  --c-bg:#f5f5f7;
  --c-surface:#ffffff;
  --c-surface-2:#f5f6f8;
  --c-border:#e5e6eb;
  --c-text:#1d1d1f;
  --c-text-2:#5b5c60;
  --c-text-3:#686970;   /* v9.17.54: 再深一档 —— #6e6f75 在 `.live-item` 的实底 #f2f2f5 上
                           只有 4.48:1（AA 差 0.02），#686970 = 4.89:1。
                           v9.17.18: 原 #9a9ba0 对 #f5f5f7 仅 2.55:1 ⇒ 改深到 4.61:1。
                           这是纯文字色（不用于圆点/进度条），改它零副作用。 */
  --c-primary:#0071e3;
  --c-primary-h:#0d7fe8;
  --c-primary-deep:#005bb8;
  /* v9.17.54: 主色的「文字版」—— v9.17.18 给下面 6 个语义色都建了 -text 变体，
     唯独**主色漏了**：#0071e3 当文字压在 #f5f5f7 / 自己 10% 的 soft 底上只有 3.57~4.31:1
     （AA 正文要 4.5:1），而它被 a / .link / .tag.blue / .btn.soft / 侧栏选中态 / .tree.on
     等**文字场景**大量使用（v9.17.54 实测 130 处）。
     原色继续留给背景/边框/图标（白字压它 4.70:1，且与品牌观感一致），
     文字场景一律改用下面这个「原色等比朝黑 14%」的值（最差 4.64:1，色相不变）。 */
  --c-primary-text:#0062c5;
  --c-primary-soft:rgba(0,113,227,.10);
  --c-on-primary:#ffffff;
  --c-ok:#34c759;      --c-ok-soft:rgba(52,199,89,.12);
  --c-warn:#ff9500;    --c-warn-soft:rgba(255,149,0,.12);
  --c-danger:#ff3b30;  --c-danger-soft:rgba(255,59,48,.10);
  --c-info:#5e7ce0;    --c-info-soft:rgba(94,124,224,.12);
  --c-purple:#8e6df0;  --c-cyan:#00c8d7;
  /* ---- 语义色的「文字版」（v9.17.18 建 · v9.17.54 重算）----
     上面那组原色（#34c759 / #ff9500 / …）是给圆点、进度条、图标、开关滑块用的，
     当**文字**用时对浅底只有 1.86~3.37:1（实测最差 1.86），远不到 AA 正文要求的 4.5:1。
     直接在原 token 上加深会改变图形观感，所以单独出一组「只在文字上用」的深色。
     🔴 v9.17.54 修正：v9.17.18 那版只按「白卡 / #f5f5f7 底」验过，**漏了 soft 徽标底**，
        而 `.tag.ok/.warn/.danger/.info/.purple/.cyan` 恰恰把它们压在自己 12% 的 soft 底上；
        且**主题只覆盖 -soft 不覆盖 -text**（tencent/xiaomi 的 soft 还是不透明色
        #e3f9e9 / #e8f9ef）⇒ 同一组 -text 要同时适配四套主题。实测现值最差只有
        3.99~4.19:1，**全都不达 AA**。
     现值 = 四套主题 × 全部真实底色（bg / card / surface-2 / soft 合成底，见 _u54_solve.py）
        的穷举解：取「最亮但仍 ≥4.60:1」的候选，即原色**等比朝黑**（色相不变）到刚够为止。
     改造后：ok 4.67 / warn 4.67 / danger 4.63 / info 4.68 / purple 4.60 / cyan 4.61（均为最差值）。 */
  --c-ok-text:#1e7334;      /* 原色 #34c759 朝黑 41% —— 最差 4.67 @google ok_soft@surface2 */
  --c-warn-text:#965800;    /* 原色 #ff9500 朝黑 41% —— 最差 4.67 @apple warn_soft@surface2 */
  --c-danger-text:#be2c24;  /* 原色 #ff3b30 朝黑 25% —— 最差 4.63 @apple danger_soft@surface2 */
  --c-info-text:#485fab;    /* 原色 #5e7ce0 朝黑 24% —— 最差 4.68 @tencent info_soft@bg */
  --c-purple-text:#6c53b7;  /* 原色 #8e6df0 朝黑 24% —— 最差 4.60 @tencent purple_soft@bg */
  --c-cyan-text:#00727b;    /* 原色 #00c8d7 朝黑 43% —— 最差 4.61 @tencent cyan_soft@bg */
  /* ---- 圆角 / 阴影 ---- */
  --r-card:18px; --r-btn:980px; --r-input:10px; --r-tag:980px; --r-modal:20px;
  --sh-card:0 4px 24px rgba(0,0,0,.06);
  --sh-hover:0 10px 34px rgba(0,0,0,.10);
  --sh-modal:0 24px 80px rgba(0,0,0,.22);
  --f:-apple-system,BlinkMacSystemFont,"SF Pro SC","PingFang SC","Microsoft YaHei",system-ui,sans-serif;
  /* ---- 布局 ---- */
  --sidebar-w:216px; --topbar-h:56px;
  --sidebar-bg:rgba(255,255,255,.72);
  --sidebar-text:#3a3a3c;
  /* ⚠️ v9.17.54: `--sidebar-active-bg` / `--sidebar-active-text` **故意不在这里声明**。
     原因是个很隐蔽的 CSS 规范点：自定义属性里的 `var()` 是在「**声明它的那个元素**」上
     完成替换的（不是使用处，也不跟着继承链重新解析）。写在这里 ⇒ `var(--c-primary-text)`
     取的是 `:root` 的值，**主题在 body 上的覆写根本传不下来**。
     2026-09-29 实测（_u54_probe_var.js）：把 body 上的 `--c-primary-text` 改成 #ff0000，
     侧栏选中态仍解析成 #0062c5 —— 即主题色对侧栏完全失效。
     ⇒ 两条已挪到下面的 `body{}`（主题正是在 body 上覆写 ⇒ 替换点与主题同层，才跟得上换肤）。 */
  --topbar-bg:rgba(255,255,255,.78);
  /* ---- 图表色环 ---- */
  --ch1:#0071e3;--ch2:#00c8d7;--ch3:#34c759;--ch4:#ffd60a;--ch5:#ff9500;--ch6:#ff3b30;--ch7:#8e6df0;--ch8:#8e8e93;
}
*{box-sizing:border-box;margin:0;padding:0}
html,body{height:100%}
/* v9.17.54: 侧栏选中态的 bg/text 声明在 body（**与主题同层**），这样 `var(--c-primary-text)`
   才会用当前主题的值。写法说明见上面 `:root` 里那段注释（`:root` 会吃掉主题覆写）。
   注：tencent 在本文件之后以 `body[data-theme="tencent"]{...}` 覆写（特异度更高）⇒ 仍是白字深底。 */
body{--sidebar-active-bg:var(--c-primary-soft);--sidebar-active-text:var(--c-primary-text)}
body{font-family:var(--f);background:var(--c-bg);color:var(--c-text);font-size:14px;line-height:1.55;-webkit-font-smoothing:antialiased;transition:background .3s,color .3s}
a{color:var(--c-primary-text);text-decoration:none}
::-webkit-scrollbar{width:8px;height:8px}
::-webkit-scrollbar-thumb{background:rgba(128,128,140,.35);border-radius:8px}
::-webkit-scrollbar-track{background:transparent}

/* ==================== 登录页 ==================== */
.login-wrap{min-height:100vh;display:flex;align-items:center;justify-content:center;position:relative;overflow:hidden}
.login-bg-orb{position:absolute;border-radius:50%;filter:blur(80px);opacity:.5;pointer-events:none;animation:float 9s ease-in-out infinite alternate}
@keyframes float{to{transform:translateY(40px) scale(1.08)}}
.login-card{position:relative;width:400px;max-width:92vw;background:var(--c-surface);border:1px solid var(--c-border);border-radius:var(--r-modal);box-shadow:var(--sh-modal);padding:44px 40px;animation:fadeUp .5s ease}
.login-logo{display:flex;align-items:center;gap:12px;margin-bottom:8px}
.login-logo svg{width:40px;height:40px}
.login-title{font-size:24px;font-weight:700;letter-spacing:.5px}
.login-sub{color:var(--c-text-3);font-size:13px;margin-bottom:26px}
.login-tip{margin-top:18px;font-size:12px;color:var(--c-text-3);text-align:center}
@keyframes fadeUp{from{opacity:0;transform:translateY(16px)}to{opacity:1;transform:none}}

/* ==================== 主布局 ==================== */
.layout{display:flex;min-height:100vh}
.sidebar{width:var(--sidebar-w);flex:none;background:var(--sidebar-bg);border-right:1px solid var(--border,var(--c-border));backdrop-filter:blur(24px);position:sticky;top:0;height:100vh;display:flex;flex-direction:column;transition:width .25s}
.sidebar.collapsed{width:64px}
.sb-logo{display:flex;align-items:center;gap:10px;padding:16px 16px 14px;flex:none}
.sb-logo svg{width:30px;height:30px;flex:none}
.sb-logo b{font-size:15.5px;white-space:nowrap;color:var(--c-text)}
.sidebar.collapsed .sb-logo b,.sidebar.collapsed .sb-group>b span,.sidebar.collapsed .sb-item span{display:none}
.sb-scroll{flex:1;overflow-y:auto;padding:4px 10px 16px}
.sb-group{margin:2px 0}
.sb-group>b{display:flex;align-items:center;gap:9px;font-size:13px;font-weight:600;color:var(--sidebar-text);padding:9px 8px;border-radius:10px;cursor:pointer;user-select:none}
.sb-group>b svg{width:16px;height:16px;flex:none}
.sb-group>b .caret{margin-left:auto;transition:transform .2s;opacity:.5}
.sb-group.open>b .caret{transform:rotate(90deg)}
.sb-sub{display:none;padding-left:14px}
.sb-group.open .sb-sub{display:block}
.sb-item{display:flex;align-items:center;gap:8px;padding:8px 10px;margin:2px 0;border-radius:9px;color:var(--sidebar-text);font-size:13px;cursor:pointer;white-space:nowrap;transition:background .15s,color .15s}
.sb-item:hover{background:var(--c-surface-2)}
.sb-item.active{background:var(--sidebar-active-bg);color:var(--sidebar-active-text);font-weight:600}
.sb-item .ico{width:16px;text-align:center;flex:none;opacity:.85}
.sb-foot{flex:none;padding:12px;border-top:1px solid var(--c-border);font-size:12px;color:var(--c-text-3)}

.main{flex:1;min-width:0;display:flex;flex-direction:column}
.topbar{height:var(--topbar-h);flex:none;display:flex;align-items:center;gap:14px;padding:0 20px;background:var(--topbar-bg);backdrop-filter:blur(24px);border-bottom:1px solid var(--c-border);position:sticky;top:0;z-index:50}
.crumb{font-size:13px;color:var(--c-text-3);display:flex;align-items:center;gap:6px;min-width:0}
.crumb b{color:var(--c-text);font-weight:600}
.topbar-right{margin-left:auto;display:flex;align-items:center;gap:10px}
.avatar{width:30px;height:30px;border-radius:50%;background:linear-gradient(135deg,var(--c-primary),var(--c-purple));color:#fff;display:flex;align-items:center;justify-content:center;font-size:13px;font-weight:600}
.user-chip{display:flex;align-items:center;gap:8px;font-size:13px;cursor:pointer;padding:4px 8px;border-radius:980px}
.user-chip:hover{background:var(--c-surface-2)}

/* v9.12: 账号下拉菜单 —— user-chip 从「静态 div」升级为可点开的下拉（全站唯一登出入口） */
.user-wrap{position:relative}
.user-chip{user-select:none}
.user-chip .un{max-width:96px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.user-chip .caret{font-size:10px;opacity:.55;transition:transform .18s}
.user-chip[aria-expanded="true"]{background:var(--c-surface-2)}
.user-chip[aria-expanded="true"] .caret{transform:rotate(180deg)}
.user-chip:focus-visible{outline:2px solid var(--c-primary);outline-offset:2px}
.user-menu{position:absolute;right:0;top:calc(100% + 8px);min-width:216px;z-index:1200;padding:8px;display:none;
  background:var(--c-surface);border:1px solid var(--c-border);border-radius:14px;
  box-shadow:0 12px 34px rgba(0,0,0,.16),0 2px 8px rgba(0,0,0,.06);animation:umIn .16s ease}
.user-menu.on{display:block}
@keyframes umIn{from{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:none}}
.um-head{display:flex;align-items:center;gap:10px;padding:8px 10px 10px}
.um-id{min-width:0}
.um-id b{display:block;font-size:13px;color:var(--c-text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.um-id span{font-size:11px;color:var(--c-text-3)}
.um-sep{height:1px;background:var(--c-border);margin:4px 6px}
.um-item{display:flex;align-items:center;gap:9px;width:100%;border:0;background:transparent;text-align:left;
  padding:9px 10px;border-radius:9px;font-family:var(--f);font-size:13px;color:var(--c-text);cursor:pointer}
.um-item i{font-style:normal;font-size:14px;width:16px;text-align:center;opacity:.75}
.um-item:hover{background:var(--c-surface-2)}
.um-item.danger{color:var(--c-danger-text)}
.um-item.danger:hover{background:var(--c-danger-soft)}

/* 风格切换器 */
.style-switch{display:flex;gap:2px;background:var(--c-surface-2);border:1px solid var(--c-border);border-radius:980px;padding:3px}
.style-switch button{border:0;background:transparent;font-family:var(--f);font-size:12px;padding:4px 12px;border-radius:980px;cursor:pointer;color:var(--c-text-2);transition:all .2s}
.style-switch button.on{background:var(--c-surface);color:var(--c-text);font-weight:600;box-shadow:0 1px 6px rgba(0,0,0,.12)}

/* 多标签页 */
.tabsbar{display:flex;align-items:center;gap:6px;padding:8px 16px 0;overflow-x:auto;flex:none}
.tb-tab{display:flex;align-items:center;gap:6px;padding:6px 12px;font-size:12.5px;border-radius:9px 9px 0 0;color:var(--c-text-2);cursor:pointer;white-space:nowrap;border:1px solid transparent;border-bottom:0}
.tb-tab.active{background:var(--c-surface);color:var(--c-primary-text);border-color:var(--c-border);font-weight:600}
.tb-tab .x{opacity:.5;font-style:normal}
.tb-tab .x:hover{opacity:1;color:var(--c-danger-text)}
.content{flex:1;padding:16px 20px 28px;min-width:0}

/* ==================== 通用组件 ==================== */
.page-head{display:flex;align-items:center;gap:12px;margin-bottom:14px;flex-wrap:wrap}
.page-title{font-size:20px;font-weight:700}
.page-desc{color:var(--c-text-3);font-size:12.5px}
.page-head .right{margin-left:auto;display:flex;gap:8px;flex-wrap:wrap}

.card{background:var(--c-surface);border:1px solid var(--c-border);border-radius:var(--r-card);box-shadow:var(--sh-card);transition:box-shadow .25s,transform .25s}
.card.pad{padding:20px}
/* v9.17.35: 安卓监控管理 —— 终端卡片可点击查看详情。
   hover 给「可点」反馈（主色描边 + 抬升阴影，过渡沿用 .card 自带的 box-shadow）；
   ⚠️ transition **不写 all** —— 本项目踩过 `.btn` 的 `transition:all` 吞掉焦点环的坑。 */
.hs-card{cursor:pointer}
.hs-card:hover{border-color:var(--c-primary);box-shadow:0 6px 22px rgba(0,113,227,.14)}
.hs-card:focus-visible{outline:2px solid var(--c-primary);outline-offset:2px;transition:none}
.card-h{display:flex;align-items:center;gap:10px;padding:16px 20px 0}
.card-h b{font-size:15px}
.card-h .right{margin-left:auto;display:flex;gap:8px;align-items:center}
.card-b{padding:16px 20px 20px}

.btn{display:inline-flex;align-items:center;gap:6px;border:1px solid transparent;background:var(--c-primary);color:var(--c-on-primary);font-family:var(--f);font-size:13px;font-weight:500;padding:7px 16px;border-radius:var(--r-btn);cursor:pointer;transition:all .18s;user-select:none}
.btn:hover{background:var(--c-primary-h)}
.btn:active{transform:scale(.97)}
.btn.plain{background:var(--c-surface);color:var(--c-text);border-color:var(--c-border)}
.btn.plain:hover{border-color:var(--c-primary);color:var(--c-primary-text);background:var(--c-surface)}
.btn.danger{background:var(--c-danger)}
.btn.danger:hover{filter:brightness(1.08)}
.btn.soft{background:var(--c-primary-soft);color:var(--c-primary-text)}
.btn.soft:hover{filter:brightness(1.03)}
.btn.sm{padding:4px 12px;font-size:12px}
.btn.lg{padding:10px 24px;font-size:14.5px}
.btn.block{width:100%;justify-content:center}
.btn:disabled{opacity:.5;cursor:not-allowed}

.link{color:var(--c-primary-text);cursor:pointer;font-size:13px}
.link:hover{text-decoration:underline}
.link.danger{color:var(--c-danger-text)}
.link.warn{color:var(--c-warn-text)}
.link.ok{color:var(--c-ok-text)}
.ops{display:flex;gap:4px;align-items:center;white-space:nowrap}
.ops .sep{color:var(--c-border);font-size:11px}

.input,.select{height:34px;border:1px solid var(--c-border);background:var(--c-surface);color:var(--c-text);border-radius:var(--r-input);padding:0 12px;font-family:var(--f);font-size:13px;outline:none;transition:border .18s,box-shadow .18s;min-width:0}
.input:focus,.select:focus{border-color:var(--c-primary);box-shadow:0 0 0 3px var(--c-primary-soft)}
.input::placeholder{color:var(--c-text-3)}
.select{appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%23999' fill='none' stroke-width='1.4'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 12px center;padding-right:30px;cursor:pointer}
.input.w-sm{width:160px}.input.w-md{width:220px}.input.w-lg{width:300px}

.toolbar{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-bottom:14px}
.toolbar label{font-size:13px;color:var(--c-text-2);display:flex;align-items:center;gap:8px}
.field{display:flex;align-items:center;gap:8px}

.tag{display:inline-flex;align-items:center;gap:5px;font-size:12px;padding:2px 10px;border-radius:var(--r-tag);background:var(--c-surface-2);color:var(--c-text-2)}
.tag.blue{background:var(--c-primary-soft);color:var(--c-primary-text)}
.tag.ok{background:var(--c-ok-soft);color:var(--c-ok-text)}
.tag.warn{background:var(--c-warn-soft);color:var(--c-warn-text)}
.tag.danger{background:var(--c-danger-soft);color:var(--c-danger-text)}
.tag.info{background:var(--c-info-soft);color:var(--c-info-text)}
.tag.purple{background:rgba(142,109,240,.12);color:var(--c-purple-text)}
.tag.cyan{background:rgba(0,200,215,.12);color:var(--c-cyan-text)}

.dot{width:7px;height:7px;border-radius:50%;display:inline-block;flex:none}
.dot.pulse{animation:pulse 1.6s infinite}
@keyframes pulse{0%,100%{box-shadow:0 0 0 0 rgba(52,199,89,.5)}50%{box-shadow:0 0 0 5px rgba(52,199,89,0)}}

.switch{width:42px;height:24px;border-radius:980px;background:#d0d1d6;position:relative;cursor:pointer;transition:background .2s;flex:none;border:none;outline:none}
.switch::after{content:"";position:absolute;top:2px;left:2px;width:20px;height:20px;border-radius:50%;background:#fff;box-shadow:0 1px 4px rgba(0,0,0,.25);transition:left .2s}
.switch.on{background:var(--c-ok)}
.switch.on::after{left:20px}

/* ── 个性化配置页 设置卡（v9.7.6：原样只在 manage.html 有定义，主站缺失致文字重叠） ── */
.setcard{background:var(--c-surface);border:1px solid var(--c-border);border-radius:14px;padding:20px 24px}
.setrow{display:flex;align-items:center;gap:24px;flex-wrap:wrap}
.setrow+.setrow{margin-top:18px}
.setinfo{flex:1;min-width:260px}
.st1{font-size:14.5px;font-weight:600;color:var(--c-text);letter-spacing:.5px;margin-bottom:6px}
.st1 .en{font-size:10.5px;color:var(--c-text-3);letter-spacing:2px;margin-left:8px;font-weight:400}
.st2{font-size:12.5px;color:var(--c-text-2);line-height:1.7}
.seg{display:flex;gap:8px;flex-wrap:wrap}
.seg .btn.on{background:var(--c-primary);color:var(--c-on-primary);border-color:var(--c-primary)}
.setstatus{margin-top:12px;font-size:12px;color:var(--c-text-3)}
/* demo 开关：复用全局 .switch 视觉，但页内结构是 label+input+slider */
label.switch{display:inline-block}
label.switch input:checked~.slider{background:var(--c-ok);border-color:var(--c-ok)}
label.switch input:checked~.slider::before{transform:translateX(24px);background:#fff}
label.switch .slider::before{content:"";position:absolute;height:20px;width:20px;left:3px;top:3px;border-radius:50%;background:#fff;box-shadow:0 1px 4px rgba(0,0,0,.25);transition:left .2s}

/* ── v9.7.6 图标选择器（大屏标记图标预览/选用） ── */
.icon-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));gap:12px}
.icon-cell{border:1.5px solid var(--c-border);border-radius:12px;background:var(--c-surface);padding:14px 10px 10px;
  display:flex;flex-direction:column;align-items:center;gap:8px;cursor:pointer;transition:border-color .15s,box-shadow .15s;position:relative}
.icon-cell:hover{border-color:var(--c-primary)}
.icon-cell.on{border-color:var(--c-primary);box-shadow:0 0 0 2px var(--c-primary-soft)}
.icon-cell .icbox{width:56px;height:56px;display:flex;align-items:center;justify-content:center}
.icon-cell .icbox svg{width:100%;height:100%}
.icon-cell .icname{font-size:12px;font-weight:600;color:var(--c-text)}
.icon-cell .ictip{font-size:10.5px;color:var(--c-text-3);text-align:center;line-height:1.5}
.icon-cell .badge{position:absolute;top:8px;right:8px;font-size:10px;padding:1px 7px;border-radius:6px;
  background:var(--c-primary-soft);color:var(--c-primary-text);font-weight:600}
.icon-cell.disabled{opacity:.45;cursor:not-allowed}

/* 表格 */
.table-wrap{overflow-x:auto;border-radius:12px}
table.tbl{width:100%;border-collapse:collapse;font-size:13px}
.tbl th{text-align:left;font-weight:600;color:var(--c-text-2);font-size:12.5px;padding:11px 14px;background:var(--c-surface-2);white-space:nowrap}
.tbl th:first-child{border-radius:8px 0 0 8px}
.tbl th:last-child{border-radius:0 8px 8px 0}
.tbl td{padding:11px 14px;border-bottom:1px solid var(--c-border);color:var(--c-text);white-space:nowrap;max-width:280px;overflow:hidden;text-overflow:ellipsis}
.tbl tbody tr{transition:background .12s}
.tbl tbody tr:hover{background:var(--c-surface-2)}
.tbl .num{font-variant-numeric:tabular-nums}

.pagination{display:flex;align-items:center;gap:8px;justify-content:flex-end;padding:14px 4px 2px;font-size:12.5px;color:var(--c-text-2)}
.pagination .pg{min-width:28px;height:28px;border-radius:8px;border:1px solid var(--c-border);background:var(--c-surface);display:inline-flex;align-items:center;justify-content:center;cursor:pointer;font-size:12.5px}
.pagination .pg.on{background:var(--c-primary);color:var(--c-on-primary);border-color:var(--c-primary)}
.pagination select{height:28px;font-size:12px}

.empty{padding:48px 0;text-align:center;color:var(--c-text-3);font-size:13px}
.empty svg{width:56px;height:56px;opacity:.35;margin-bottom:10px}

/* 弹窗 */
.mask{position:fixed;inset:0;background:rgba(0,0,0,.45);backdrop-filter:blur(4px);z-index:900;display:flex;align-items:center;justify-content:center;animation:fade .18s ease}
@keyframes fade{from{opacity:0}to{opacity:1}}
.modal{background:var(--c-surface);border-radius:var(--r-modal);box-shadow:var(--sh-modal);width:560px;max-width:94vw;max-height:90vh;display:flex;flex-direction:column;animation:fadeUp .22s ease;border:1px solid var(--c-border)}
.modal.wide{width:1060px}
.modal.lg{width:760px}
.modal-h{display:flex;align-items:center;padding:18px 22px 0;font-size:16px;font-weight:700}
.modal-h .x{margin-left:auto;cursor:pointer;color:var(--c-text-3);font-size:20px;line-height:1;padding:2px 6px;border-radius:8px}
.modal-h .x:hover{background:var(--c-surface-2);color:var(--c-text)}
.modal-b{padding:16px 22px;overflow-y:auto}
.modal-f{display:flex;justify-content:flex-end;gap:10px;padding:0 22px 20px}
.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px 20px}
.form-grid .full{grid-column:1/-1}
.fi{display:flex;flex-direction:column;gap:6px}
.fi>label{font-size:12.5px;color:var(--c-text-2)}
.fi>label b{color:var(--c-danger-text);margin-right:2px}
.fi .radio-row{display:flex;gap:16px;font-size:13px;align-items:center;height:34px}
.fi .radio-row label{display:flex;gap:6px;align-items:center;cursor:pointer}

/* Toast */
#toast-root{position:fixed;top:20px;left:50%;transform:translateX(-50%);z-index:1200;display:flex;flex-direction:column;gap:8px;align-items:center}
.toast{background:var(--c-surface);border:1px solid var(--c-border);box-shadow:var(--sh-hover);color:var(--c-text);padding:10px 18px;border-radius:12px;font-size:13px;display:flex;gap:8px;align-items:center;animation:fadeUp .25s ease}
.toast.ok i{color:var(--c-ok-text)}.toast.err i{color:var(--c-danger-text)}

/* KPI 统计卡 */
.stat-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:14px}
.stat-card{background:var(--c-surface);border:1px solid var(--c-border);border-radius:var(--r-card);box-shadow:var(--sh-card);padding:18px 20px;position:relative;overflow:hidden;transition:transform .25s,box-shadow .25s}
.stat-card:hover{transform:translateY(-3px);box-shadow:var(--sh-hover)}
.stat-card .lb{font-size:12.5px;color:var(--c-text-2);display:flex;align-items:center;gap:6px}
.stat-card .v{font-size:30px;font-weight:700;margin-top:6px;font-variant-numeric:tabular-nums;letter-spacing:.5px}
.stat-card .v small{font-size:13px;font-weight:500;color:var(--c-text-3);margin-left:3px}
.stat-card .tri{font-size:12px;margin-top:6px;color:var(--c-text-3)}
.stat-card .ic{position:absolute;right:14px;top:14px;width:38px;height:38px;border-radius:12px;display:flex;align-items:center;justify-content:center;font-size:18px}
.grid-2{display:grid;grid-template-columns:1fr 1fr;gap:16px}
.grid-3{display:grid;grid-template-columns:repeat(3,1fr);gap:16px}
.grid-23{display:grid;grid-template-columns:2fr 3fr;gap:16px}
.grid-32{display:grid;grid-template-columns:3fr 2fr;gap:16px}
@media (max-width:1100px){.grid-2,.grid-3,.grid-23,.grid-32{grid-template-columns:1fr}}

.chart-box{width:100%;min-height:240px;display:flex;align-items:center;justify-content:center}
.chart-legend{display:flex;flex-wrap:wrap;gap:10px 16px;font-size:12.5px;color:var(--c-text-2);padding:0 20px 16px}
.chart-legend i{width:10px;height:10px;border-radius:3px;display:inline-block;margin-right:6px}

/* 内联页签 */
.inline-tabs{display:flex;gap:6px;flex-wrap:wrap}
.inline-tabs button{border:1px solid var(--c-border);background:var(--c-surface);color:var(--c-text-2);font-family:var(--f);font-size:13px;padding:6px 16px;border-radius:980px;cursor:pointer;transition:all .18s}
.inline-tabs button.on{background:var(--c-primary);border-color:var(--c-primary);color:var(--c-on-primary);font-weight:600}

/* 进度条 */
.progress{height:8px;border-radius:980px;background:var(--c-surface-2);overflow:hidden;min-width:80px}
.progress>i{display:block;height:100%;border-radius:980px;background:var(--c-primary);transition:width .4s}
.progress>i.ok{background:var(--c-ok)}.progress>i.warn{background:var(--c-warn)}.progress>i.danger{background:var(--c-danger)}

/* 地图容器 */
.map-box{position:relative;border-radius:14px;overflow:hidden;border:1px solid var(--c-border);min-height:320px}
.map-box svg.map{display:block;width:100%;height:100%}
.map-tools{position:absolute;left:12px;top:12px;display:flex;gap:6px;z-index:5}
.map-tools button{border:1px solid var(--c-border);background:var(--c-surface);border-radius:980px;padding:4px 12px;font-size:12px;cursor:pointer;font-family:var(--f);color:var(--c-text)}
.map-tools button.on{background:var(--c-primary);color:var(--c-on-primary);border-color:var(--c-primary)}
.map-legend{position:absolute;left:12px;bottom:10px;z-index:5;display:flex;gap:14px;font-size:12px;background:var(--c-surface);border:1px solid var(--c-border);padding:6px 12px;border-radius:980px;color:var(--c-text-2)}
.map-legend i{width:9px;height:9px;border-radius:50%;display:inline-block;margin-right:5px}
.map-playbar{position:absolute;left:50%;bottom:14px;transform:translateX(-50%);z-index:6;display:flex;align-items:center;gap:10px;background:var(--c-surface);border:1px solid var(--c-border);box-shadow:var(--sh-card);padding:8px 16px;border-radius:980px;font-size:12.5px}
.map-playbar input[type=range]{width:220px;accent-color:var(--c-primary)}

/* 实时滚动列表 */
.live-list{display:flex;flex-direction:column;gap:8px;max-height:320px;overflow:hidden}
.live-item{display:flex;align-items:center;gap:10px;background:var(--c-surface-2);border:1px solid var(--c-border);border-radius:12px;padding:9px 14px;font-size:12.5px;animation:fadeUp .3s ease}
.live-item .t{color:var(--c-text-3);font-variant-numeric:tabular-nums}
.live-item b{font-weight:600}

/* 频谱条 */
.spectrum{display:flex;align-items:flex-end;gap:3px;height:56px}
.spectrum i{flex:1;background:var(--c-primary);opacity:.8;border-radius:3px 3px 0 0;transition:height .3s}

/* ==================== 数据大屏 ==================== */
.screen{position:fixed;inset:0;z-index:800;display:flex;flex-direction:column;color:#e8f0ff;
  background:radial-gradient(1200px 600px at 50% -10%,rgba(0,113,227,.28),transparent 60%),
             linear-gradient(160deg,#0a0e18 0%,#0d1526 55%,#0a0e18 100%)}
.screen>*{position:relative}
.scr-head{flex:none;display:flex;align-items:center;padding:14px 24px 8px;position:relative}
.scr-title{position:absolute;left:50%;transform:translateX(-50%);text-align:center}
.scr-title h1{font-size:26px;letter-spacing:6px;font-weight:700;background:linear-gradient(90deg,#8fd8ff,#ffffff,#8fd8ff);-webkit-background-clip:text;background-clip:text;color:transparent;text-shadow:0 0 30px rgba(80,180,255,.35)}
.scr-title p{font-size:11px;letter-spacing:3px;color:#5f7fa8;margin-top:2px}
.scr-head-l,.scr-head-r{display:flex;gap:10px;align-items:center}
.scr-head-r{margin-left:auto}
.scr-clock{font-variant-numeric:tabular-nums;font-size:13px;color:#9fc0e8;text-align:right}
.scr-clock b{font-size:18px;color:#e8f0ff;display:block;letter-spacing:1px}
/* v9.17.34: 原 .scr-exit（退出登录 / 退出大屏 ✕）随两个按钮一起删除。
   顶栏右上角现为「数据大屏」按钮 + 齿轮（设置），两者共用同一套胶囊外观。 */
.scr-nav,.scr-gear{border:1px solid rgba(140,190,255,.35);background:rgba(20,40,80,.4);color:#9fc0e8;
  border-radius:980px;padding:5px 14px;font-size:12px;cursor:pointer;font-family:var(--f);
  display:inline-flex;align-items:center;gap:6px;line-height:1.5;white-space:nowrap;
  transition:color .15s,border-color .15s,background .15s}
.scr-nav i{font-style:normal;font-size:12px}
.scr-nav:hover,.scr-gear:hover{color:#fff;border-color:#8fd8ff;background:rgba(30,60,110,.55)}
.scr-gear{width:32px;height:32px;padding:0;justify-content:center;font-size:15px}
.scr-gear.on{color:#fff;border-color:#8fd8ff;background:rgba(0,120,255,.28);box-shadow:0 0 10px rgba(0,180,255,.45)}
/* ═══ v9.17.34: 大屏设置面板（齿轮打开）—— 锚在齿轮正下方，右上角 ═══ */
.fs-set{position:absolute;top:58px;right:22px;z-index:990;width:264px;display:none;
  background:linear-gradient(165deg,rgba(10,24,48,.96),rgba(7,16,34,.94));border:1px solid rgba(110,170,255,.35);
  border-radius:14px;backdrop-filter:blur(14px);box-shadow:0 12px 40px rgba(0,8,26,.62);overflow:hidden}
.fs-set.on{display:block;animation:setin .16s ease-out}
@keyframes setin{from{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:none}}
.fs-set .sh{display:flex;align-items:center;gap:10px;padding:11px 12px;
  background:linear-gradient(180deg,rgba(0,150,255,.14),transparent)}
.fs-set .sh .ava{width:32px;height:32px;flex:none;border-radius:50%;display:flex;align-items:center;justify-content:center;
  font-size:14px;font-weight:800;color:#0b2038;background:linear-gradient(180deg,#7fdcff,#22a9d8)}
.fs-set .sh .id{flex:1;min-width:0}
.fs-set .sh .id b{display:block;font-size:13.5px;color:#eaf6ff;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.fs-set .sh .id span{font-size:10px;color:#6f92bf;letter-spacing:.5px}
.fs-set .sh .cl{flex:none;cursor:pointer;color:#8fb2e0;font-size:12px;line-height:1;padding:7px 6px;border-radius:6px}
.fs-set .sh .cl:hover{color:#fff;background:rgba(255,255,255,.08)}
.fs-set .sb{padding:0 0 6px}
.fs-set .gt{font-size:9.5px;letter-spacing:1.5px;color:#5f83b4;padding:9px 12px 4px}
.fs-set .row{display:flex;align-items:center;gap:10px;padding:5px 12px}
.fs-set .row i{font-style:normal;font-size:11.5px;color:#8fb2e0;flex:none}
.fs-set .row>b{margin-left:auto;font-size:11.5px;font-weight:700;color:#eaf6ff;
  font-variant-numeric:tabular-nums;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:152px}
.fs-set .row>b.ver{color:#9fd4ff}
.fs-set .mini{margin-left:auto;border:1px solid rgba(0,150,255,.45);background:rgba(0,120,255,.2);color:#cfe9ff;
  font:inherit;font-size:11px;letter-spacing:.5px;padding:3px 10px;border-radius:6px;cursor:pointer}
.fs-set .mini:hover{background:rgba(0,150,255,.36)}
.fs-set .it{display:flex;align-items:center;gap:9px;width:100%;border:0;background:none;color:#cfe4ff;
  font:inherit;font-size:12px;padding:8px 12px;cursor:pointer;text-align:left}
.fs-set .it i{font-style:normal;font-size:13px;width:16px;text-align:center;flex:none}
.fs-set .it:hover{background:rgba(0,150,255,.14);color:#fff}
.fs-set .it.danger{color:#ff9d94;border-top:1px dashed rgba(110,170,255,.18);margin-top:4px}
.fs-set .it.danger:hover{background:rgba(255,80,70,.14);color:#ffc2bb}
.scr-body{flex:1;display:grid;grid-template-columns:300px 1fr 300px;gap:14px;padding:8px 20px 16px;min-height:0}
.scr-col{display:flex;flex-direction:column;gap:14px;min-height:0;position:relative}
.scr-panel{background:rgba(13,26,52,.55);border:1px solid rgba(90,150,230,.22);border-radius:14px;padding:14px 16px;position:relative;backdrop-filter:blur(6px);display:flex;flex-direction:column;min-height:0}
.scr-panel::before,.scr-panel::after{content:"";position:absolute;width:14px;height:14px;border:2px solid rgba(0,200,255,.5)}
.scr-panel::before{left:-1px;top:-1px;border-right:0;border-bottom:0;border-radius:4px 0 0 0}
.scr-panel::after{right:-1px;bottom:-1px;border-left:0;border-top:0;border-radius:0 0 4px 0}
.scr-panel h3{font-size:14px;letter-spacing:2px;color:#cfe4ff;display:flex;align-items:center;gap:8px;margin-bottom:10px;flex:none}
.scr-panel h3::before{content:"";width:4px;height:14px;border-radius:2px;background:linear-gradient(180deg,#00c8ff,#0066ff);box-shadow:0 0 8px #00c8ff}
.scr-panel h3 .en{margin-left:auto;font-size:10px;color:#4c6a94;letter-spacing:1px}
.scr-kpis{display:flex;gap:12px;justify-content:center;margin:2px 0 10px;flex:none}
.scr-kpi{min-width:150px;text-align:center;background:rgba(10,24,50,.6);border:1px solid rgba(90,150,230,.25);border-radius:12px;padding:10px 18px;position:relative}
.scr-kpi .v{font-size:34px;font-weight:800;font-variant-numeric:tabular-nums;color:#fff;text-shadow:0 0 18px rgba(0,180,255,.8);line-height:1.1}
.scr-kpi .v.warn{color:#ffb03a;text-shadow:0 0 18px rgba(255,150,0,.6)}
.scr-kpi .v.ok{color:#3ee08f;text-shadow:0 0 18px rgba(0,255,140,.5)}
.scr-kpi .lb{font-size:12px;color:#8fb2e0;letter-spacing:2px;margin-top:2px}
.scr-map{flex:1;min-height:0;position:relative;border-radius:14px;overflow:hidden;border:1px solid rgba(90,150,230,.22)}
.scr-map svg.map{width:100%;height:100%}
.scr-legend{position:absolute;left:14px;bottom:12px;display:flex;gap:16px;font-size:12px;color:#9fc0e8;background:rgba(10,24,50,.7);padding:6px 14px;border-radius:980px;border:1px solid rgba(90,150,230,.3)}
.scr-legend i{width:8px;height:8px;border-radius:50%;display:inline-block;margin-right:5px}
/* v9.17.29: 告警日志改**手动滚动**。旧实现是 CSS 跑马灯 ——
   `.scr-list ul{animation:scrollUp 18s linear infinite}`（靠内容重复两遍做无缝），
   但外层 loadReal 每 500ms 无条件重建 <ul>，动画被反复重置（视觉上永远滚不起来），
   且 overflow:hidden 让滚轮/拖动条也完全滚不动 ⇒ 用户实测「滚动卡顿 不滚」。
   现在：去动画 + 容器 overflow:auto + 加粗滚动条（4px 太细，拖不住）。 */
.scr-list{flex:1;min-height:0;overflow:auto;position:relative;overscroll-behavior:contain}
.scr-list ul{list-style:none;display:flex;flex-direction:column;gap:6px}
/* id 特异性高于上面的 `.fs-panel ::-webkit-scrollbar{width:4px}`，不受声明顺序影响 */
#scr-log::-webkit-scrollbar{width:8px}
#scr-log::-webkit-scrollbar-track{background:rgba(255,255,255,.05);border-radius:4px}
#scr-log::-webkit-scrollbar-thumb{background:rgba(0,200,255,.42);border-radius:4px}
#scr-log::-webkit-scrollbar-thumb:hover{background:rgba(0,200,255,.72)}
.scr-list li{display:flex;gap:8px;align-items:center;font-size:12px;color:#bcd4f5;background:rgba(20,44,86,.45);border-left:2px solid #00c8ff;padding:7px 10px;border-radius:6px;white-space:nowrap}
.scr-list li .t{color:#6280ad;font-variant-numeric:tabular-nums}
.scr-list li b{color:#fff}
.scr-list li.lvl1{border-left-color:#ff4d4d}
.scr-list li.lvl2{border-left-color:#ffb03a}
.scr-list li.lvl3{border-left-color:#3ee08f}
.scr-row{display:flex;align-items:center;gap:10px;font-size:12.5px;color:#bcd4f5;margin:7px 0}
.scr-row .n{width:86px;flex:none;white-space:nowrap}
.scr-row .bar{flex:1;height:8px;border-radius:980px;background:rgba(30,60,110,.6);overflow:hidden}
.scr-row .bar i{display:block;height:100%;border-radius:980px;background:linear-gradient(90deg,#0066ff,#00c8ff);box-shadow:0 0 8px rgba(0,200,255,.7)}
.scr-row .num{width:36px;text-align:right;font-variant-numeric:tabular-nums;color:#fff}
.scr-mini{display:grid;grid-template-columns:1fr 1fr;gap:10px}
.scr-mini .m{background:rgba(20,44,86,.4);border:1px solid rgba(90,150,230,.2);border-radius:10px;padding:10px 12px}
.scr-mini .m .lb{font-size:11px;color:#8fb2e0}
.scr-mini .m .v{font-size:22px;font-weight:700;color:#fff;font-variant-numeric:tabular-nums}
.scr-mini .m .v small{font-size:11px;color:#6280ad;font-weight:400}
/* v9.17.111（用户选定「方案 A · 更透气」）：站点分布行距与分隔优化。
   触发 · 用户逐字：「最后心跳不要显示  点击去可以看的  然后站点间距优化下」
     ① 第二行「最后心跳 X 前」在 v9.17.111 已从列表移除（信息在「终端详情」底栏仍有两段：
        绝对时间 + 相对时长 hbAgo）⇒ 行不再有第二行，可以放心加高。
     ② 行高：padding 6→8px（行 33→37px）—— 实测可见区 358px 下一屏约 8 行。
     ③ 分隔线：原来**每行一条虚线**（19 行显得碎）⇒ 改为更淡的实线；
        并把「在线组 → 离线组」那一条**加亮**，让「在线置顶 / 离线在后」一眼可见。
     ④ 末行去掉多余的分隔线（面板底部本来就有边框）。
   ⛔ 只改观感与行高，不动列宽、不动 .nm/.bat-tag/.postag/.cnt 的既定宽度（列对齐实测仍为 312）。 */
.scr-device-row{display:flex;align-items:center;gap:8px;font-size:12.5px;color:#bcd4f5;padding:8px 2px;border-bottom:1px solid rgba(90,150,230,.10)}
.scr-device-row.off{border-bottom-color:rgba(90,150,230,.07)}
.scr-device-row:not(.off)+.scr-device-row.off{border-top:1px solid rgba(110,170,255,.30);margin-top:2px;padding-top:10px}
.scr-device-row:last-child{border-bottom:0}
.scr-device-row .sq{width:9px;height:9px;border-radius:2px;flex:none}
.scr-device-row .cnt{margin-left:auto;font-variant-numeric:tabular-nums;color:#fff}
.scr-device-row .cnt small{color:#6280ad}

/* 滚动数字脉冲 */
@keyframes blink{50%{opacity:.55}}
.blink{animation:blink 1s infinite}

/* 组织树 */
.tree-box{border:1px solid var(--c-border);border-radius:12px;background:var(--c-surface);width:230px;flex:none;overflow:auto;max-height:70vh}
.tree-box .tb-search{padding:10px;border-bottom:1px solid var(--c-border)}
.tree{padding:8px 6px;font-size:13px}
.tree details{padding-left:0}
.tree summary{list-style:none;cursor:pointer;padding:6px 8px;border-radius:8px;display:flex;align-items:center;gap:6px;white-space:nowrap}
.tree summary::-webkit-details-marker{display:none}
.tree summary:hover{background:var(--c-surface-2)}
.tree summary.on{background:var(--c-primary-soft);color:var(--c-primary-text);font-weight:600}
.tree .tw{padding-left:16px}
.tree .leaf{padding:6px 8px 6px 26px;border-radius:8px;cursor:pointer;white-space:nowrap}
.tree .leaf:hover{background:var(--c-surface-2)}
.tree .leaf.on{background:var(--c-primary-soft);color:var(--c-primary-text);font-weight:600}
.tree .arrow{font-size:10px;color:var(--c-text-3)}

/* 菜单树表格缩进 */
.indent{display:inline-flex;align-items:center;gap:6px}
.indent .tw{color:var(--c-text-3);font-size:11px;width:14px}
.lvl-pad-1{padding-left:22px}.lvl-pad-2{padding-left:44px}

/* 描述列表 */
.desc-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:0;border:1px solid var(--c-border);border-radius:12px;overflow:hidden}
.desc-grid .di{padding:12px 16px;border-bottom:1px solid var(--c-border);font-size:13px}
.desc-grid .di:nth-last-child(-n+2){border-bottom:0}
.desc-grid .di .k{color:var(--c-text-3);font-size:12px;margin-bottom:2px}
.desc-grid .di.full{grid-column:1/-1;border-bottom:0}

/* 徽标数字 */
.badge-n{min-width:16px;height:16px;border-radius:980px;background:var(--c-danger);color:#fff;font-size:10px;display:inline-flex;align-items:center;justify-content:center;padding:0 4px;font-weight:600}

/* 回传通道卡 */
.chan-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:14px}
.chan{background:var(--c-surface);border:1px solid var(--c-border);border-radius:var(--r-card);box-shadow:var(--sh-card);padding:16px 18px}
.chan .hd{display:flex;align-items:center;gap:10px;font-weight:600;font-size:14px}
.chan .hd .ic{width:34px;height:34px;border-radius:10px;display:flex;align-items:center;justify-content:center;font-size:16px}
.chan .hd .right{margin-left:auto}
.chan .meta{display:flex;gap:18px;margin-top:12px;font-size:12px;color:var(--c-text-3)}
.chan .meta b{display:block;font-size:16px;color:var(--c-text);font-variant-numeric:tabular-nums}

/* 文件预览小卡 */
.filegrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:12px}
.filecard{border:1px solid var(--c-border);border-radius:12px;overflow:hidden;background:var(--c-surface);cursor:pointer;transition:transform .2s,box-shadow .2s}
.filecard:hover{transform:translateY(-2px);box-shadow:var(--sh-hover)}
.filecard .thumb{height:92px;display:flex;align-items:center;justify-content:center;font-size:30px;background:var(--c-surface-2)}
.filecard .inf{padding:8px 10px;font-size:11.5px}
.filecard .inf b{display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.filecard .inf span{color:var(--c-text-3)}

/* 展开子行 */
.subrow{background:var(--c-surface-2)}
.subrow td{font-size:12px;color:var(--c-text-2)}

/* 屏幕保护：小于高度时滚动 */
@media (max-width:900px){
  .sidebar{display:none}
  .scr-body{grid-template-columns:1fr;overflow-y:auto}
  .scr-map{min-height:300px}
}

/* ==================== 登录页 · 分屏品牌版式 ==================== */
.login-stage{position:relative;display:flex;gap:22px;width:min(1100px,94vw);align-items:stretch;animation:fadeUp .5s ease}
.login-brand{flex:1.15;display:flex;flex-direction:column;padding:46px 44px 34px;border-radius:var(--r-modal);
  background:linear-gradient(150deg,var(--c-primary) 0%,var(--c-primary-deep) 118%);color:#fff;box-shadow:var(--sh-modal);
  position:relative;overflow:hidden;min-height:560px}
.login-brand::after{content:"";position:absolute;right:-80px;bottom:-90px;width:300px;height:300px;border-radius:50%;background:rgba(255,255,255,.08)}
.login-brand::before{content:"";position:absolute;right:60px;top:-70px;width:200px;height:200px;border-radius:50%;background:rgba(255,255,255,.07)}
.lb-mark{width:52px;height:52px;color:#fff;opacity:.95}
.lb-mark svg{width:100%;height:100%}
.lb-name{font-size:34px;font-weight:800;letter-spacing:2px;margin-top:20px}
.lb-en{font-size:10.5px;letter-spacing:3px;opacity:.72;margin-top:8px}
.lb-tag{font-size:14.5px;line-height:1.85;opacity:.94;margin-top:20px}
.lb-feats{display:flex;flex-direction:column;gap:13px;margin-top:26px;position:relative}
.lb-feat{display:flex;gap:12px;align-items:flex-start}
.lb-feat .ic{width:36px;height:36px;border-radius:11px;background:rgba(255,255,255,.16);display:flex;align-items:center;justify-content:center;font-size:15px;flex:none;backdrop-filter:blur(6px)}
.lb-feat b{display:block;font-size:13.5px;letter-spacing:.3px}
.lb-feat span{font-size:12px;opacity:.72}
.lb-stats{display:flex;gap:30px;margin-top:auto;padding-top:26px;position:relative}
.lb-stat b{font-size:22px;font-weight:800;letter-spacing:-.5px;display:block}
.lb-stat span{font-size:11px;opacity:.68;letter-spacing:1px}
.lb-foot{margin-top:20px;font-size:11px;opacity:.6;letter-spacing:.5px;border-top:1px solid rgba(255,255,255,.18);padding-top:14px}
@media (max-width:1020px){.login-stage{flex-direction:column}.login-brand{min-height:0;padding:32px 28px}.login-card{width:100%}}

/* 大屏品牌角标 */
.scr-brand{display:inline-flex;align-items:center;gap:7px;font-size:12.5px;letter-spacing:1px;color:#cfe0f5;
  background:rgba(20,40,80,.45);border:1px solid rgba(140,190,255,.3);border-radius:980px;padding:6px 16px;white-space:nowrap}

/* ═══ 数据大屏：天地图 marker 层（v9 无人机/飞手样式见下方 v9 块） ═══ */
.scr-maplayer{position:absolute;inset:0;z-index:860;pointer-events:none;overflow:hidden}
/* v9.9: 适飞空域 canvas —— 放在 .scr-maplayer 内最底层（z-index:1），
   上盖天地图瓦片、下压轨迹(trackSvg 850)/标记(910+)，完全不抢鼠标事件 */
.scr-maplayer .scr-airspace-canvas{position:absolute;inset:0;width:100%;height:100%;z-index:1;pointer-events:none}
.scr-maplayer .dmark{--mc:#FF453A}
.scr-maplayer .dmark.wl{--mc:#30D158}
.scr-maplayer .dmark.off{--mc:#8E8E93}
.scr-maplayer .dmark{position:absolute;z-index:950;transform:translate(-50%,-50%);cursor:pointer;
  pointer-events:auto;transition:opacity .3s}
.scr-maplayer .dmark .lb{position:absolute;top:60px;left:50%;transform:translateX(-50%);font-size:10px;
  padding:2px 9px;border-radius:4px;white-space:nowrap;font-weight:600;
  background:rgba(2,18,30,.9);border:1.5px solid var(--mc);color:#ffe9e9;
  box-shadow:0 0 8px rgba(255,69,58,.35)}
.scr-maplayer .dmark.wl .lb{color:#e9fff1;box-shadow:0 0 8px rgba(48,209,88,.35)}
.scr-maplayer .dmark.off .lb{color:#cfd8dc;box-shadow:none}
/* v9.17.21: 点击聚焦态 —— 复用既有状态色变量 --mc 做强调，**绝不碰 transform**（那是标记居中定位的关键），
   也不改状态语义（白名单绿 / 未知红 / 失联灰一律照旧）。
   .dmark 本身无显式宽高，被 .dico(58×58) 撑开 ⇒ 50%/50% 即图标中心，76px 环正好套在外面一圈。 */
.scr-maplayer .dmark.focus{z-index:960}
.scr-maplayer .dmark.focus .dico{filter:drop-shadow(0 0 9px var(--mc)) brightness(1.15)}
.scr-maplayer .dmark.focus .lb{color:#fff;font-weight:700;border-color:var(--mc);box-shadow:0 0 12px var(--mc)}
.scr-maplayer .dmark.focus::after{content:'';position:absolute;left:50%;top:50%;width:76px;height:76px;
  margin:-38px 0 0 -38px;border:2px solid var(--mc);border-radius:50%;pointer-events:none;
  animation:dfocus 1.6s ease-out infinite}
@keyframes dfocus{0%{transform:scale(.62);opacity:.9}100%{transform:scale(1.25);opacity:0}}
/* 🔴🔴 v9.17.90：标记锚点 = **图标圆心**（不是整块底边）。
   病因（2026-10-09 用户「手机在同一个位置 放大缩小 差距很大」）：
     原为 `translate(-50%,-100%)` ⇒ 对齐到**整块的底边**；而标签 `.sinfo` 写在正常流里、位于图标**下方**
     ⇒ 真正压在经纬度点上的是**标签底边**，图标（含从圆心外扩的 stwave 波纹圈）
     被整体顶高 **59.6 屏幕像素**。该偏移是**屏幕像素固定值**，
     折算地面距离 = 偏移 × 米/像素 ⇒ 线上实测：z12 偏 **1913 m**、z13 偏 956 m、z15 偏 239 m
     —— 所以「同一台手机、放大缩小位置差很多」。
   修法：只要把元素盒上移「图标高的一半」即可让图标圆心落在坐标点上；
   该值与标签高度**无关**（标签高度还会随文案变化），因此比 -100% 更稳。
   🔴 元素盒 == 图标盒 的标记（如 .dmark，标签 position:absolute）本来就对，别去动它。 */
.scr-maplayer .smark{--smark-pic:54px;position:absolute;z-index:910;
  transform:translate(-50%,calc(var(--smark-pic) * -0.5));display:flex;flex-direction:column;
  align-items:center;cursor:pointer}
.scr-maplayer .smark .dot{width:14px;height:14px;border-radius:50%;background:#35e08f;
  border:2.5px solid rgba(255,255,255,.9);box-shadow:0 0 10px rgba(53,224,143,.9),0 0 22px rgba(53,224,143,.5)}
.scr-maplayer .smark.off .dot{background:#5a6f94;box-shadow:none}
.scr-maplayer .smark .sinfo{margin-top:4px;font-size:10px;padding:2px 8px;border-radius:4px;white-space:nowrap;
  background:rgba(2,18,30,.85);border:1px solid rgba(53,224,143,.4);color:#d5ffe9}
.scr-maplayer .smark.off .sinfo{border-color:rgba(90,111,148,.5);color:#b9c6d8}
@keyframes dfl{0%,100%{transform:translateY(0) scale(1)}50%{transform:translateY(-1.4px) scale(1.02)}}
@keyframes opbreathe{0%,100%{transform:scale(1)}50%{transform:scale(1.05)}}
@keyframes oppulse{0%{transform:scale(.55);opacity:.85}100%{transform:scale(1.55);opacity:0}}
.scr-empty{height:100%;display:flex;flex-direction:column;gap:10px;align-items:center;justify-content:center;
  color:#4c6a94;font-size:13px;letter-spacing:1px}

/* ═══ 大屏 v2：全屏天地图 + 悬浮玻璃面板（参考第一版 HUD） ═══ */
.scr-fs{position:fixed;inset:0;background:#050d1c}
.scr-fs .scr-map{position:absolute;inset:0;border-radius:0;border:0}
.scr-fs .scr-maplayer{z-index:860}
/* 顶部悬浮条 */
.fs-top{position:absolute;top:0;left:0;right:0;z-index:970;padding:12px 22px 30px;pointer-events:none;
  background:linear-gradient(180deg,rgba(4,12,26,.88) 0%,rgba(4,12,26,.45) 60%,rgba(4,12,26,0) 100%);
  display:flex;align-items:flex-start;gap:12px}
.fs-top .scr-brand{pointer-events:auto}
.fs-title{position:absolute;left:50%;top:8px;transform:translateX(-50%);text-align:center;white-space:nowrap}
.fs-title h1{margin:0;font-size:21px;letter-spacing:5px;color:#eaf6ff;text-shadow:0 0 18px rgba(0,180,255,.55)}
.fs-title p{margin:3px 0 0;font-size:9.5px;letter-spacing:3.5px;color:#5f83b4}
.fs-right{margin-left:auto;display:flex;align-items:center;gap:10px;pointer-events:auto}
/* KPI 胶囊（标题下居中一排） */
.fs-chips{position:absolute;left:50%;top:60px;transform:translateX(-50%);display:flex;gap:12px;z-index:970}
.fs-chip{display:flex;align-items:center;gap:9px;min-width:116px;padding:7px 14px;border-radius:11px;
  background:linear-gradient(160deg,rgba(10,26,52,.85),rgba(8,18,38,.72));border:1px solid rgba(110,170,255,.28);
  backdrop-filter:blur(10px);box-shadow:0 4px 18px rgba(0,10,30,.45)}
.fs-chip i{font-style:normal;font-size:19px;color:#37c6ff;filter:drop-shadow(0 0 8px rgba(0,190,255,.6))}
.fs-chip b{display:block;font-size:20px;font-weight:800;line-height:1.05;color:#fff;font-variant-numeric:tabular-nums}
.fs-chip span{font-size:10.5px;color:#8fb2e0;letter-spacing:1px}
.fs-chip.warn i{color:#ffb03a}.fs-chip.warn b{color:#ffc76d}
.fs-chip.ok i{color:#35e08f}.fs-chip.ok b{color:#7df5b9}
/* 悬浮面板列（v9.17.29: 只剩左栏；右栏 .fs-col.right 已随「协议分布」面板一起删除）
   v9.17.110（用户「顶部还有空间 也可以适当拉宽」· 方案 A）：top 126→90px、width 292→344px。
     ① top：顶栏 `.fs-top` 占 0..90、品牌药丸占 y12..45 ⇒ 90 正好贴顶栏下沿，**零重叠**；
        原 126 是硬编码，白留了 36px 地图（用户红框圈的就是这块）。
     ② width：+52px，站点分布行更舒展（被迫换行的行 18→17）。
     ③ KPI 胶囊居中在 x646..1274，与左栏**不横向重叠** ⇒ 上移不会撞胶囊。
     ④ 底部不动：`.fs-status` 在 y972..1004 且与左栏横向重叠，列底 964 已贴住（bottom:56px 必须保留）。
     ⑤ ⛔ 只改 base.css 这一处；窄屏各档的 `.fs-col` 覆盖（≤1180 的 width:250px 等）**未动**。 */
/* v9.17.114（用户 2026-10-10 逐字：「不要换行  可以适当拉伸 宽度」）：344 → 400px。
   根因实测：v9.17.113 的「无定位」签比原来的「--」宽 21px，把「云鹏-11」
   （同时带「挂载 XXX」标签 + 无 GPS）那行挤成两行 ⇒ 行高 64。
   该行单行所需 = 332.3px；列宽 344 时行可用宽仅 314px（面板左右各 14px padding）。
   出稿实测：364px 仍换行（差 1.7px 被亚像素吃掉），376px 起才不换行；用户选定 400px（+56）。
   影响面：仅此一处 —— 窄屏各档另有 !important 覆盖，不受影响；
   列底仍为 964，不压底部状态条（y972+）。 */
.fs-col{position:absolute;top:90px;bottom:56px;width:400px;z-index:960;display:flex;flex-direction:column;gap:12px;pointer-events:none}
.fs-col.left{left:16px}
.fs-panel{pointer-events:auto;background:linear-gradient(165deg,rgba(10,24,48,.84),rgba(7,16,34,.7));
  border:1px solid rgba(110,170,255,.24);border-radius:14px;padding:12px 14px;backdrop-filter:blur(12px);
  box-shadow:0 6px 24px rgba(0,8,26,.5);display:flex;flex-direction:column;min-height:0;position:relative;overflow:hidden}
.fs-panel::before{content:"";position:absolute;left:0;top:0;right:0;height:2px;
  background:linear-gradient(90deg,transparent 10%,rgba(0,200,255,.55),transparent 90%)}
.fs-panel h3{font-size:13.5px;letter-spacing:2px;color:#cfe4ff;display:flex;align-items:center;gap:8px;margin:0 0 9px;flex:none;font-weight:700}
.fs-panel h3::before{content:"";width:4px;height:13px;border-radius:2px;background:linear-gradient(180deg,#00c8ff,#0066ff);box-shadow:0 0 8px #00c8ff}
.fs-panel h3 .en{margin-left:auto;font-size:9.5px;color:#4c6a94;letter-spacing:1px;font-weight:400}
.fs-panel h3 .n{margin-left:auto;font-size:10.5px;color:#37c6ff;font-variant-numeric:tabular-nums;font-weight:600;letter-spacing:0}
.fs-panel ::-webkit-scrollbar{width:4px}
.fs-panel ::-webkit-scrollbar-thumb{background:rgba(0,200,255,.3);border-radius:2px}
/* 底部：图层切换 / 状态 / 图例 */
.fs-layers{position:absolute;left:50%;bottom:16px;transform:translateX(-50%);z-index:960;display:flex;border-radius:980px;overflow:hidden;border:1px solid rgba(110,170,255,.3);backdrop-filter:blur(8px);box-shadow:0 4px 16px rgba(0,10,30,.5)}
.fs-layers button{border:0;background:rgba(8,20,40,.78);color:#8fb2e0;font-size:12px;padding:7px 18px;cursor:pointer;letter-spacing:2px}
.fs-layers button.on{background:rgba(0,150,255,.4);color:#fff}
/* v9.9: 适飞空域叠加开关（与底图按钮同胶囊，用分隔线区分；开状态用青绿，和图例同色系） */
.fs-layers .sep{width:1px;background:rgba(110,170,255,.3);margin:7px 0;flex:none}
.fs-layers button[data-ly="air"].on{background:rgba(45,212,191,.32);color:#d6fff8}
.fs-status{position:absolute;left:16px;bottom:16px;z-index:960;font-size:11.5px;color:#7fa3cf;background:rgba(8,20,40,.72);border:1px solid rgba(110,170,255,.25);border-radius:980px;padding:6px 14px;backdrop-filter:blur(8px)}
.fs-legend{position:absolute;right:16px;bottom:16px;z-index:960;display:flex;gap:12px;font-size:11.5px;color:#9fc0e8;background:rgba(8,20,40,.72);padding:6px 14px;border-radius:980px;border:1px solid rgba(110,170,255,.25);backdrop-filter:blur(8px)}
.fs-legend i{width:8px;height:8px;border-radius:50%;display:inline-block;margin-right:5px}
/* 概览大数字：v9.17.29 「目标概览」面板已删除，.fs-over 系列一并移除（无引用） */
@media (max-width:1180px) and (min-height:561px){.fs-col{width:250px}.fs-title h1{font-size:17px;letter-spacing:3px}}

/* ═══ 大屏 v9：地图无人机/飞手标记 APP 同款（MapMarkerFactory 移植） ═══ */
/* 无人机：极光涡轮四轴 SVG——螺旋桨流动青弧（正反桨）+ 桨叶残影旋转 + 呼吸浮动 */
.scr-maplayer .dmark .dico{width:58px;height:58px;display:flex;align-items:center;justify-content:center}
.scr-maplayer .dmark .dico svg{width:100%;height:100%;display:block;overflow:visible}
.scr-maplayer .dmark .halo{position:absolute;left:50%;top:50%;width:56px;height:56px;margin:-28px 0 0 -28px;
  border-radius:50%;background:radial-gradient(circle,rgba(0,213,238,.34) 0%,transparent 70%);
  animation:dhalo 2.6s ease-in-out infinite}
@keyframes dhalo{0%,100%{transform:scale(1);opacity:.75}50%{transform:scale(1.09);opacity:1}}
/* ═══ v9.17.117: **被挂载的飞机**波纹放大 3 倍 + 更明显 ═══
   用户 2026-10-10 逐字：「挂载飞机 的波纹效果要比其他飞机大3倍 并且更明显」。
   普通机 56px / α.34 / 微弱呼吸 ⇒ 挂载机 168px（正好 3 倍）/ α.55 / 扩散式脉冲
   + 一圈 1.5px 青色实线外环同步外扩（纯呼吸看久了会腻，外环让它一眼抓住）。
   ⚠️ 只加 .mounted 这一档，⛔ 未改普通机的任何数值。
   ═══ v9.17.166（用户 2026-10-16 21:23 逐字）：「呼吸效果 更强烈」「不改变颜色」═══
   近身呼吸全面加劲（1km 地理波纹见 screen.js updMountRings，那是另一个量级）：
     · 扩散脉冲 dhbig：scale .55→1.35 变 **.4→1.7**、周期 2.2s→**1.6s**（dhbig 只被 .mounted 引用，普通机零影响）
     · 外环 dhring：线宽 1.5→**2.5px**、scale 上限 1.15→**1.5**、直径 152→**190px**
     · 新增 ::before **第二道外环**（错相 0.8s）——E-109 已核：.halo::after 被 .mounted 占用、
       .halo::before 空闲、.dico 伪元素空闲，不与 .warn 警戒环抢位
     · 本体 .dico 加 **发光脉冲** dglo（drop-shadow 同色 rgba(0,213,238)——只加辉光强度，
       ⛔ 本体任何填充/描边色都不动，正合「不改颜色」）
   全部色值仍是 rgba(0,213,238,α)，只动 α / 尺寸 / 周期。
   ═══ v9.17.170（用户 2026-10-16 22:32 逐字）：「波纹效果有点过了 减弱点」═══
   · **浓度收一档**（v9.17.166/168 的加劲被反馈「有点过了」）：
     halo 渐变 α .85/.4 → **.5/.22**；外环 4px/α.9 → **3px/α.6**；第二环 3px/α.8 → **2px/α.5**；
     发光脉冲 5↔18px/α.6↔1 → **4↔11px/α.45↔.75**。周期 1.6s **不动**（用户只提浓度，未提节奏）。
   ═══ 🔴 v9.17.171（用户 2026-10-17 07:48 逐字）：「波纹改 向外吧 向内 好奇怪」═══
   · **方向改回外向**：v9.17.170 试过「向内汇聚（收缩）」，用户实看后否掉 ⇒
     dhbig / dhring / dhring2 关键帧全部恢复**由小到大向外扩**（0% 的 scale < 100% 的 scale）。
     ⛔ 只回退方向，v9.17.170 的**浓度档位一字不动**（halo .5/.22、外环 3px/α.6、第二环 2px/α.5、dglo 4↔11px）。
     ⛔ 别再改回收缩 —— 已实测否掉过一轮（判据见 `_verify/_r171_outward_probe.js`：0% 的 scale 必须 < 100%）。
   全部色值仍是 rgba(0,213,238,α)，只动 α / 尺寸 / 缩放方向。 */
.scr-maplayer .dmark.mounted .halo{width:168px;height:168px;margin:-84px 0 0 -84px;
  background:radial-gradient(circle,rgba(0,213,238,.5) 0%,rgba(0,213,238,.22) 45%,transparent 72%);
  animation:dhbig 1.6s ease-out infinite}
@keyframes dhbig{0%{transform:scale(.4);opacity:1}100%{transform:scale(1.7);opacity:0}}
.scr-maplayer .dmark.mounted .halo::after{content:'';position:absolute;left:50%;top:50%;
  /* E-109: 外环挂在 .halo 的 ::after 上（原挂 .dmark::before，运行时实测该伪元素未生成）；
     .halo 本身 position:absolute ⇒ 伪元素相对它定位，稳。 */
  width:190px;height:190px;margin:-95px 0 0 -95px;border-radius:50%;pointer-events:none;
  border:3px solid rgba(0,213,238,.6);animation:dhring 1.6s ease-out infinite}
@keyframes dhring{0%{transform:scale(.35);opacity:1}100%{transform:scale(1.5);opacity:0}}
.scr-maplayer .dmark.mounted .halo::before{content:'';position:absolute;left:50%;top:50%;
  width:190px;height:190px;margin:-95px 0 0 -95px;border-radius:50%;pointer-events:none;
  border:2px solid rgba(0,213,238,.5);animation:dhring2 1.6s ease-out .8s infinite}
@keyframes dhring2{0%{transform:scale(.35);opacity:.85}100%{transform:scale(1.5);opacity:0}}
.scr-maplayer .dmark.mounted .dico{animation:dglo 1.5s ease-in-out infinite}
@keyframes dglo{0%,100%{filter:drop-shadow(0 0 4px rgba(0,213,238,.45))}
  50%{filter:drop-shadow(0 0 11px rgba(0,213,238,.75))}}
/* ═══ v9.17.129: 非白名单（会报警）目标 —— 飞机 / 飞手**本体整体变红** + 红色警戒环 ═══
   用户 2026-10-11 指令（客户要求）逐字：
     「不是白名单的  飞机地图显示  都变红色  飞手+飞机都变红」
   用户从四套出稿里**选定 B**（本体红 + 警戒环），并明确另外三条口径：
     · 白名单「先不改  还是用现在的青色」⇒ ⛔ 不加任何 .wl 本体规则
     · 失联「还是用现在的灰色」        ⇒ ⛔ 不加任何 .off 本体规则
     · 「飞手—飞机那根橙色虚线不动」    ⇒ ⛔ 不碰 TRK.linkC(#FFB300) 相关样式
   🔴 改动前的事实（这就是客户说「没变红」的原因）：
     base.css:571-573 的 `--mc` **只驱动名字标签 .lb 的边框色**（:578）；
     飞机本体是 data.js 里 ICON_DRONE_SVG 的**硬编码青色**（渐变 #53E8FF→#00B8D4、桨弧 #00D5EE、
     桨叶 #24485c），SVG 内联属性无法被 --mc 影响 ⇒ 图标恒青。
     飞手同理：base.css:733-735 只把脉冲环 .opup 变红/绿，OP_SVG 的定位针本体（#22D3EE→#1E6BFF）恒蓝。
   ✅ 修法：**纯 CSS 覆盖内联表现属性**（SVG 的 stroke=/fill= 是 presentation attribute，
      优先级低于任何 CSS 规则 ⇒ 不需要改 data.js 的 SVG、不需要改 screen.js 的 JS）。
      ⛔ 为什么不动 ICON_DRONE_SVG 本身：它是大屏与轨迹回放**共用**的真源（data.js 头行已声明），
         改它会把回放页一起改掉，且会破坏「一份 SVG 两处引用」的既有约定。
   ⚠️ 警戒环挂在 .halo 的 ::before 上 —— 不挂 .dmark::before：
      v9.17.117 的 E-109 已实测「.dmark::before 运行时根本不生成」，外环因此改挂 .halo::after。
      .halo::after 被 .mounted 占用，故本档取 .halo::before（当前无人使用）。
   ⚠️ 状态类由 screen.js:1146 的 markers[].classList.toggle('warn', !stale&&!whitelisted) 驱动，
      失联时该行传 false ⇒ 自动摘掉 .warn ⇒ 灰态不受本档影响（符合用户「失联还是灰」的口径）。 */
/* ① 飞机：呼吸光晕换红 + 更急 */
.scr-maplayer .dmark.warn .halo{
  background:radial-gradient(circle,rgba(255,69,58,.42) 0%,rgba(255,69,58,.12) 45%,transparent 72%);
  animation:dhaloWarn 1.5s ease-out infinite}
@keyframes dhaloWarn{0%{transform:scale(.78);opacity:.9}100%{transform:scale(1.28);opacity:0}}
/* ② 飞机：警戒外环（持续外扩涟漪） */
.scr-maplayer .dmark.warn .halo::before{content:'';position:absolute;left:50%;top:50%;
  width:56px;height:56px;margin:-28px 0 0 -28px;border-radius:50%;pointer-events:none;
  border:1.5px solid rgba(255,69,58,.8);animation:dringWarn 1.5s ease-out infinite}
@keyframes dringWarn{0%{transform:scale(.6);opacity:.95}100%{transform:scale(1.95);opacity:0}}
/* ②b 飞机：**常驻红色虚线警戒圈** —— 出稿 B 里那圈虚线就是它。
   ⚠️ 为什么必须单独加一圈常驻环（自查出的自身缺陷）：
     只靠上面那条涟漪的话，它 scale .6→1.95 且 opacity .95→0 ⇒
     **最亮的时候(scale .6=34px)整个被 58px 的图标挡住，等扩到图标外面(≈75px)时已淡到近乎不可见**
     ⇒ 实际观感是「看不见」，与用户选 B 的理由（远看最醒目）正相反。
     常驻虚线环负责「一眼可辨」，涟漪负责「它在报警」的动感，两者互补。
   ⚠️ 挂 .dico::after：.dmark::after 已被 .dmark.focus 占用（聚焦环 76px），
     .halo::after 已被 .dmark.mounted 占用（挂载环 152px）——一个飞机可能同时被挂载，
     ⛔ 不能去抢这两个，否则会互相顶掉。.dico 的伪元素当前无人使用（已核）。 */
.scr-maplayer .dmark.warn .dico{position:relative}
.scr-maplayer .dmark.warn .dico::after{content:'';position:absolute;left:50%;top:50%;
  width:84px;height:84px;margin:-42px 0 0 -42px;border-radius:50%;pointer-events:none;
  border:1.5px dashed rgba(255,69,58,.72)}
/* ③ 飞机本体：机臂 / 桨盘 / 桨弧 / 桨叶 / 机身 —— 逐件换红（白包边与中心白点保留） */
.scr-maplayer .dmark.warn .arms line{stroke:#FF6B5E}
.scr-maplayer .dmark.warn .prop>circle:nth-of-type(1){fill:rgba(255,69,58,.12)}
.scr-maplayer .dmark.warn .prop>circle:nth-of-type(2){stroke:rgba(255,69,58,.38)}
.scr-maplayer .dmark.warn .arcA{stroke:#FF453A}
.scr-maplayer .dmark.warn .arcB{stroke:#FF453A}
.scr-maplayer .dmark.warn .blades line{stroke:#5A1F1A}
.scr-maplayer .dmark.warn .bodyg>rect:nth-of-type(1){fill:#FF453A}
/* ④ 飞手本体：定位针的渐变填充换红（白边/白点保留）；脉冲环 .opup 已由 :734 变红 */
.scr-maplayer .omark.nwl svg{filter:drop-shadow(0 0 6px rgba(255,69,58,.75))}
.scr-maplayer .omark.nwl .opbody>circle[fill^="url("]{fill:#FF453A}
.scr-maplayer .omark.nwl .opbody>path[fill^="url("]{fill:#FF453A}
.scr-maplayer .omark.nwl .olb{border-color:rgba(255,120,110,.55);color:#ffd7d2}
/* 🔴 v9.17.87: 无人机图标的浮动/桨叶旋转动画 —— **唯一真源**。
   大屏(.scr-maplayer .dmark)与轨迹回放(.rm-drone)共用同一批 class 名（quadsvg / arms /
   prop / blades）与同一套 @keyframes，故这里用**并列选择器**只写一份。
   ⛔ 不要在任何一侧再写一份 —— 本轮修的就是「同一个图标两套动画」这个分叉
      （realmap.js 原先自带 rmFloat / rmSpin 一套，已删）。 */
.scr-maplayer .dmark .quadsvg, .rm-drone .quadsvg{animation:dfl 2.6s ease-in-out infinite}
/* 桨叶残影：绕各自桨盘中心旋转（fill-box 取该组自身包围盒定中心），
   正反桨方向（data-dir 决定 rotate 方向），0.4s/圈 与 APP 33ms×12帧一致 */
.scr-maplayer .dmark .prop .blades, .rm-drone .prop .blades{transform-box:fill-box;transform-origin:center;
  animation:propspin .4s linear infinite}
.scr-maplayer .dmark .prop[data-dir="-1"] .blades, .rm-drone .prop[data-dir="-1"] .blades{animation-direction:reverse}
@keyframes propspin{to{transform:rotate(360deg)}}
/* 机臂：实色 #2fb9d6。这里原来还有一句 stroke:url(#aurg)，但**紧随其后就被下一行覆盖** ⇒ 死声明，
   v9.17.87 删掉（回放侧的 gradient id 是 rmAurg，两份共用一条规则时留着它只会让人误读谁生效）。 */
.scr-maplayer .dmark .arms line, .rm-drone .arms line{stroke:#2fb9d6;stroke-width:7;
  stroke-linecap:round;opacity:.95}
.scr-maplayer .dmark.off .dico svg{filter:grayscale(1) opacity(.55)}
/* 飞手：深蓝定位针（保留原 SVG），呼吸 + 青脉冲 */
/* 🔴 v9.17.90：与 .smark 同一约定 —— 锚点 = **图标圆心**（见上方 .smark 注释）。
   原 `translate(-50%,-100%)` 把整块底边（= .olb 标签底边）压在坐标点上，
   svg 图标（44px）被顶高 **42.7 屏幕像素**（线上实测），同样随缩放放大成地面误差。
   上移 22px = 图标高的一半 ⇒ 与标签高度解耦（.olb 文案会在「飞手」/「飞手 · 报 3 分钟前」之间变，
   用 -100% 时这个变化还会带来额外抖动）。 */
.scr-maplayer .omark{--omark-ico:44px;position:absolute;z-index:930;
  transform:translate(-50%,calc(var(--omark-ico) * -0.5));display:flex;flex-direction:column;
  align-items:center;pointer-events:none}
.scr-maplayer .omark svg{width:var(--omark-ico);height:var(--omark-ico);filter:drop-shadow(0 0 6px rgba(30,107,255,.75))}
.scr-maplayer .omark .opbody{transform-origin:72px 43px;animation:opbreathe 2.4s ease-in-out infinite}
.scr-maplayer .omark .opup{transform-origin:72px 43px;animation:oppulse 1.8s ease-out infinite}
.scr-maplayer .omark.wl svg .opup{stroke:#30D158}
.scr-maplayer .omark.nwl svg .opup{stroke:#FF453A}
.scr-maplayer .omark.off svg .opup{stroke:#8E8E93}
.scr-maplayer .omark.off svg{filter:grayscale(1) opacity(.55)}
.scr-maplayer .omark .olb{font-size:9.5px;margin-top:2px;padding:1px 7px;border-radius:3px;
  background:rgba(2,18,30,.85);border:1px solid rgba(140,190,255,.4);color:#bfe9f8;white-space:nowrap}
/* v9.17.30: 飞手坐标**非实时**（来自更早的一次上报，实测 4G 目标的 op 常 >5 分钟）——
   标签改虚线 + 降饱和，并停在呼吸动画上：一眼能分清「飞手就在这」和「飞手最后出现在这」。
   ⛔ 不要用「灰到看不见」的强度 —— 用户要先能看见它，才谈得上分辨时效（v9.17.24 的教训）。 */
.scr-maplayer .omark.stale svg{filter:grayscale(.35) opacity(.85)}
.scr-maplayer .omark.stale .opbody{animation:none}
.scr-maplayer .omark.stale .opup{animation:none;stroke:#FFB300}
.scr-maplayer .omark.stale .olb{border-style:dashed;border-color:rgba(255,179,0,.55);color:#ffd894}
/* ═══ v9: 轨迹层 / 彗星光点 / 流动箭头 ═══ */
/* 🔴 v9.17.87: 彗星光点 / 流动箭头 —— **唯一真源**（大屏 .scr-flowlayer 与轨迹回放 .rm-flowlayer
   共用同一批声明；两边元素节奏也同值：一圈 2.4s、50ms 重算、相位错 1/3 与 0/0.5）。
   ⛔ 回放侧不要在 realmap.js 里另写一份色值/尺寸。 */
.scr-flowlayer, .rm-flowlayer{position:absolute;inset:0;z-index:855;pointer-events:none;overflow:hidden}
.scr-flowlayer .comet, .rm-flowlayer .comet{position:absolute;width:12px;height:12px;margin:-6px 0 0 -6px;z-index:856;
  filter:drop-shadow(0 0 5px rgba(127,233,255,.9));display:none}
.scr-flowlayer .comet.op, .rm-flowlayer .comet.op{width:9px;height:9px;margin:-4.5px 0 0 -4.5px}
/* ═══ v9.17.172（用户 2026-10-17 08:54 逐字）：「轨迹上的 移动方向箭头 改成 圆点 向前 移动 可以参考手机版」═══
   原 `.flow-arrow`（3 个 `›` 字符 + 青色 text-shadow）**整条规则已删除** ⇒ 改为 **3 枚跟随圆点**：
     · 元素 = `class="comet dot"`（复用 .comet 的定位/display 与 COMET_SVG 素材 = 白芯 + #7FE9FF 青晕）；
     · 这里只把尺寸收小一档（12→8px），与 12px 的龙头圆点组成「龙头 + 拖尾」的向前流动串；
     · 与手机版同款：手机侧真源 = MapMarkerFactory.createCometDot()（10dp、白芯 16%、#7FE9FF 青晕）。
   ⛔ 大屏(.scr-flowlayer) 与轨迹回放(.rm-flowlayer) 共用这一批声明 ⇒ 改类名必须两边一起改。 */
.scr-flowlayer .comet.dot, .rm-flowlayer .comet.dot{width:8px;height:8px;margin:-4px 0 0 -4px;
  filter:drop-shadow(0 0 3.5px rgba(127,233,255,.8))}
.scr-maplayer .dmark .lb{top:60px}
/* 实时目标卡片：机型缩略图行 */
.tgt-row{display:flex;align-items:center;gap:9px;padding:8px 6px;border-bottom:1px dashed rgba(110,170,255,.14);cursor:pointer;border-radius:8px;transition:.15s}
.tgt-row:hover{background:rgba(0,150,255,.1)}
.tgt-row .th{width:46px;height:40px;flex:none;display:flex;align-items:center;justify-content:center;border-radius:8px;
  background:radial-gradient(circle at 50% 45%,rgba(0,200,255,.14),transparent 75%)}
.tgt-row .th img{max-width:42px;max-height:36px;object-fit:contain;filter:drop-shadow(0 2px 3px rgba(0,0,0,.5))}
.tgt-row .th.unknown img{opacity:.32;filter:grayscale(1)}
.tgt-row .th.unknown::after{content:"?";position:absolute;font-size:15px;font-weight:800;color:#5f8ba0}
.tgt-row .th{position:relative}
.tgt-row .tm{flex:1;min-width:0}
.tgt-row .t1{font-size:12.5px;font-weight:700;color:#eaf6ff;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:flex;align-items:center;gap:4px}
/* v9.17.63: 机型名必须是**可收缩**的弹性项，而且这一行的宽度预算要算得过来。
   ① 病根（本轮实测，见 `_u63_out.txt` / `_u63_measure.txt`）：
      `.t1` 是 `display:flex;white-space:nowrap;overflow:hidden`，而机型名原本是**裸文本节点**
      ⇒ 匿名弹性项按 min-content 撑住、**不能收缩**；于是 RID / 挂载 / 信号丢失 三枚
      `flex:none` 徽标一起把行撑出容器，多出来的部分被 `overflow:hidden` **硬裁**（无任何提示）：
        · 只有 RID          → 富余 20px（旧版正常）
        · +挂载             → 溢出 12px ⇒ 徽标右侧被切掉一半（本轮新引入，实拍图上直接看得出来）
        · +挂载+信号丢失    → 溢出 62px ⇒ 既有裁剪，v9.17.63 之前就存在（只是没人量过）
      修法：包一层 `.mn`，把名字变成**唯一可收缩项**：
        · `overflow:hidden` ＋ `min-width:0` —— 依 CSS 规范，flex 项的**自动最小尺寸**只在
          「`overflow:visible` 且 `min-width:auto`」时才取 content-based 值（对 nowrap 文本
          就是整段宽度 ⇒ 撑住不让收）；`overflow` 一旦非 visible，或显式写了 `min-width:0`，
          自动最小尺寸即为 0 ⇒ 允许收缩。
          🔴 反证实测（`_u63_inj/A_css_mn.txt`）：**只删 `min-width:0` 不溢出**（`overflow:hidden` 已够）；
          两条都删才复现溢出。⇒ 两条都写上：`min-width:0` 是**显式加固**，读代码的人不必翻规范。
          ⛔ 别只留一条、然后以为另一条冗余可以删。
        · `flex:0 1 auto` —— 富余时不抢空间（观感与旧版一致），不足时才收缩；
        · `text-overflow:ellipsis` ＋ `white-space:nowrap` —— 收缩时给省略号，不是无声硬裁；
        · 名字上另给 `title="<全名>"`（在 screen.js 模板里）⇒ 省略号不是信息黑洞。
   ② 宽度预算（`_u63_measure.js` / `_u64_verify.js` 实测，视口 1920×1080）：`.tm` 可用宽 **152px**；
      「DJI Matrice 4TD」整名宽 **101px**；RID 徽标 25px。
      · v9.17.62（只有 RID）：名字拿 **97px** ⇒ 完整显示（实测 `mnCut: 0`）。
      · v9.17.63（加「挂载」徽标 20px）：名字一度只剩 89px；当时为此调了三处
        （`.t1` gap `6→4px` / `.mount` 收到 20px / `.mn` 字号 `12.5→12px`）把名字补到 99px。
      · 🔴 v9.17.64（用户 2026-09-30 选定「卡片也改『挂载云鹏-02』」）：徽标 **20px → 49px**
        ⇒ 名字只剩 **70px、截断 27px**（实测 `mnCut: 27`、`mnCliW: 70`）。
        ⚠️ **双徽标**（挂载 + 信号丢失）时名字只剩 **22px**（`mnCut: 75`）—— 约 2 个字符。
        这是**用户明示接受的代价**（AskUserQuestion 三个选项里明确选了「卡片也改」那一项，
        不是本侧自拟）。`.t1` 的 `overflow:hidden` 仍兜底 ⇒ 容器**不溢出**
        （实测 `t1Overflow: 0` / `t1EdgeOver: 0`），只是名字按省略号被切。
        ⛔ 别把 `.mn` 的 `flex:0 1 auto` 改成 `flex:none`、也别给它加 `min-width` ——
           双徽标时本行需要 30+4+25+4+49+4+42 = **158px** > 152px ⇒ 名字一旦不可收缩就会**真溢出**，
           反而把徽标裁掉。**宁可名字短，不可徽标断。**
      ⚠️ 更长的机型名（如 Mavic 3 Enterprise）一样按省略号截断 —— 那是本行的固有容量；
         `.mn` 的 `title` 里始终有全名可查（省略号不是信息黑洞）。 */
.tgt-row .t1 .mn{font-size:12px;min-width:0;flex:0 1 auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.tgt-row .t1 .rid{font-size:8.5px;font-weight:800;letter-spacing:.5px;color:#0b2a3d;background:linear-gradient(180deg,#4fd8ff,#00b0e0);
  border-radius:3px;padding:1px 4px;flex:none}
.tgt-row .t1 .new-tag{font-size:8.5px;font-weight:700;color:#ffc76d;border:1px solid rgba(255,176,58,.5);
  background:rgba(255,176,58,.12);border-radius:3px;padding:0 4px;flex:none}
/* v9.17.63: 「挂载」徽标 —— 与 RID 徽标并排（用户 2026-09-30 指定「绑定的无人机需要在红框位置显示 挂载」，
   红框圈的就是目标卡片机型名右侧的 RID 徽标位置）。含义 = 该目标被某个云鹏模块登记为**载机**
   （数据源 `/api/stations` 顶层 `podBindings`）。
   配色沿用同排徽标的既定做法（亮底 + 深字，**不随主题翻转** —— 目标卡片表面在所有主题下都是深色，
   与 `.rid` 同一前提，故不做 `-soft`/`-text` 那套双变量）；
   取紫色系以便与青色 RID、琥珀色「信号丢失」三者在色相上可区分。
   对比度实测：文字 #1a1030 对渐变最暗端 #a78bf7 ≈ 6.6:1（≥4.5:1 达标）。
   ⚠️ 尺寸受**宽度预算**约束（见上面 `.mn` 注释 ②）：本行只有 152px，RID 占 25px。
      · v9.17.63 文案是「挂载」（2 字）⇒ 8px 字号 / `padding:1px 2px` / 无字距 = **20px**。
      · 🔴 **v9.17.64 起文案改为「挂载云鹏-02」**（带模块号，与**地图目标标签** `.dmark .lb span`
        的后缀是**同一句文案、同一个 `mountModOf()` 判据**；用户 2026-09-30 在 AskUserQuestion
        里明确选了「卡片也改」）⇒ 实测宽 **49px**（同样 8px 字号，仅靠字符数变长）。
        代价：机型名可用宽 97px → **70px**（双徽标时 **22px**），已由用户明示接受，账见 `.mn` 注释 ②。
      ⛔ 别再放大字号/padding —— 每多 1px 都直接从机型名里扣（实测账见 `_u64_verify.js` 输出）。 */
.tgt-row .t1 .mount{font-size:8px;font-weight:800;letter-spacing:0;color:#1a1030;
  background:linear-gradient(180deg,#cbbaff,#a78bf7);border-radius:3px;padding:1px 2px;flex:none}
.tgt-row .sn{font-size:9.5px;color:#6280ad;margin-top:1px;font-family:ui-monospace,Consolas,monospace;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.tgt-row .t3{display:flex;gap:10px;font-size:10px;margin-top:3px;font-variant-numeric:tabular-nums}
.tgt-row .t3 span{display:flex;align-items:baseline;gap:3px}
.tgt-row .t3 i{font-style:normal;font-size:9px;color:#5f83b4}
.tgt-row .t3 b{color:#cfe4ff;font-weight:600;font-size:10.5px}
.tgt-row .sig{flex:none;display:flex;flex-direction:column;align-items:center;gap:2px;min-width:34px}
.tgt-row .sig .bars{display:flex;align-items:flex-end;gap:2px;height:14px}
.tgt-row .sig .bars i{width:4px;border-radius:1px;background:#30D158;display:block}
.tgt-row .sig .bars i:nth-child(1){height:4px}.tgt-row .sig .bars i:nth-child(2){height:7px}
.tgt-row .sig .bars i:nth-child(3){height:10px}.tgt-row .sig .bars i:nth-child(4){height:14px}
.tgt-row .sig b{font-size:11px;font-weight:800;line-height:1;font-variant-numeric:tabular-nums}
.tgt-row .sig small{font-size:8px;color:#5f83b4}
.tgt-row.stale .th img,.tgt-row.stale .tm{opacity:.5;filter:grayscale(.7)}
/* 目标详情浮卡（点击目标卡片/标记弹出） */
.fs-detail{position:absolute;z-index:980;width:288px;display:none;
  background:linear-gradient(165deg,rgba(10,24,48,.95),rgba(7,16,34,.92));border:1px solid rgba(110,170,255,.35);
  border-radius:14px;backdrop-filter:blur(14px);box-shadow:0 10px 40px rgba(0,8,26,.6);overflow:hidden}
.fs-detail .dh{display:flex;gap:11px;padding:12px 14px;align-items:center;
  background:linear-gradient(180deg,rgba(0,150,255,.14),transparent)}
.fs-detail .dh .im{width:86px;height:70px;flex:none;display:flex;align-items:center;justify-content:center;position:relative;
  border-radius:10px;background:radial-gradient(circle at 50% 45%,rgba(0,200,255,.16),rgba(0,40,90,.25))}
.fs-detail .dh .im img{max-width:80px;max-height:64px;object-fit:contain;filter:drop-shadow(0 3px 6px rgba(0,0,0,.5))}
.fs-detail .dh .im .scanline{position:absolute;left:10%;right:10%;bottom:6px;height:1.5px;border-radius:2px;
  background:linear-gradient(90deg,transparent,#00c8ff,transparent);animation:scanmv 2.2s linear infinite}
@keyframes scanmv{0%{transform:translateY(-26px);opacity:0}12%{opacity:1}88%{opacity:1}100%{transform:translateY(26px);opacity:0}}
.fs-detail .dh .hd{flex:1;min-width:0}
.fs-detail .dh .hk{font-size:9px;letter-spacing:2px;color:#5f8ba0;margin-bottom:3px}
.fs-detail .dh .hn{font-size:15px;font-weight:800;color:#eaf6ff;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.fs-detail .dh .hs{font-size:10px;color:#6280ad;margin-top:2px;font-variant-numeric:tabular-nums}
.fs-detail .chips{display:flex;gap:5px;flex-wrap:wrap;padding:0 14px 9px}
.fs-detail .chips span{font-size:9.5px;padding:2px 8px;border-radius:4px;background:rgba(0,150,255,.14);
  border:1px solid rgba(0,150,255,.3);color:#9fd4ff;letter-spacing:.5px}
/* v9: APP 同款六宫格数据区（高度/速度/距离/信号 + 经纬度/来源站宽栏） */
.fs-detail .grid6{display:grid;grid-template-columns:1fr 1fr 1fr;gap:6px;padding:2px 14px 10px}
.fs-detail .grid6 .g{background:rgba(20,44,86,.4);border:1px solid rgba(110,170,255,.16);border-radius:8px;
  padding:6px 8px;min-width:0}
.fs-detail .grid6 .g.wide{grid-column:span 3}
/* v9.17.100: 末行只剩 1 格余量时，让收尾的「接入方式」跨 2 列把行填满
   （三列网格下 4 个数据格 = 1 行 + 1 格，只补 1 个普通格仍会留空槽）。 */
.fs-detail .grid6 .g.w2{grid-column:span 2}
.fs-detail .grid6 .g i{font-style:normal;display:block;font-size:9px;color:#5f83b4;letter-spacing:.5px;margin-bottom:2px}
.fs-detail .grid6 .g b{display:block;font-size:12px;font-weight:700;color:#eaf6ff;
  font-variant-numeric:tabular-nums;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.fs-detail .grid6 .g b.good{color:#3ee08f}
.fs-detail .grid6 .g b.mid{color:#ffc76d}
.fs-detail .grid6 .g b.bad{color:#ff7a70}
/* v9.17.56: 「口径」脚注行的 b —— 🔴 必须覆盖上面 .g b 的 nowrap+ellipsis，
   否则那段「该机上报帧族 X 不含高度/速度…」的长说明会被截成一个省略号（等于没写）。
   只放开换行，不改字号/配色体系（沿用 chips 的 #9fd4ff）。 */
.fs-detail .grid6 .g.note b{white-space:normal;overflow:visible;text-overflow:clip;
  line-height:1.55;font-size:11px;font-weight:400;color:#9fd4ff}
/* v9.17.24: 「飞手经纬度」行 —— 形式对齐 APP 详情页的位置行（距离 + 坐标 + 导航按钮）。
   标签独占一行（i 宽 100%），下一行是坐标（flex:1，过长省略）+ 右端导航按钮。
   未上报时按钮 [disabled] 降透明度，配合 title 说明原因。 */
.fs-detail .grid6 .g.op6{display:flex;flex-wrap:wrap;align-items:center}
.fs-detail .grid6 .g.op6 i{width:100%}
.fs-detail .grid6 .g.op6 i .dst{color:#7fb0ff;font-variant-numeric:tabular-nums}
/* v9.17.25: 「RID」来源徽标 —— 标在「飞机经纬度 / 飞手经纬度」标签后，
   说明这两格坐标是**从 RID 遥测里解出来的**（飞机=机体 Location，飞手=Operator Location），
   不是探测端/手机自己的 GPS。配色沿用目标列表卡片的 RID 徽标（base.css 的 .tgt-row .t1 .rid）。 */
.fs-detail .grid6 .g.op6 i .src{display:inline-block;margin-left:5px;padding:0 4px;border-radius:3px;
  font-size:8px;font-weight:800;letter-spacing:.5px;line-height:13px;vertical-align:1px;
  color:#0b2a3d;background:linear-gradient(180deg,#4fd8ff,#00b0e0)}
/* 🔴 v9.17.24 实测坑：写 `flex:1`（= flex-basis:0%）会让**换行算法**看到 0 宽，
   于是坐标与标签挤在同一行、被压成 0 宽（overflow:hidden）⇒ 用户看不到经纬度。
   必须用 `flex:1 1 auto`（basis=内容宽）才会正确折到第二行，与按钮同行。 */
.fs-detail .grid6 .g.op6 b{flex:1 1 auto;min-width:0}
.fs-detail .grid6 .g.op6 .navb{flex:none;margin-left:8px;border:1px solid rgba(0,150,255,.45);
  background:rgba(0,120,255,.2);color:#cfe9ff;font:inherit;font-size:10.5px;letter-spacing:1px;
  padding:2px 10px;border-radius:6px;cursor:pointer;line-height:1.5}
.fs-detail .grid6 .g.op6 .navb:hover:not([disabled]){background:rgba(0,150,255,.36)}
.fs-detail .grid6 .g.op6 .navb[disabled]{opacity:.4;cursor:not-allowed}
/* v11: 「发现者」行 —— 该目标被哪些探测端发现（云鹏 4G 探测站 / 检测手机）
   允许多端并列换行；站名与编号相同时不重复（与地图标签/站点面板同口径） */
.fs-detail .grid6 .g.det b{display:flex;flex-wrap:wrap;gap:4px 10px;white-space:normal;overflow:visible;
  font-weight:600;font-size:11.5px;line-height:1.55}
.fs-detail .grid6 .g.det .who{display:inline-flex;align-items:center;gap:4px;max-width:100%;min-width:0}
.fs-detail .grid6 .g.det .who em{font-style:normal;color:#eaf6ff;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:128px}
.fs-detail .grid6 .g.det .who i{display:inline-block;margin:0;font-style:normal;font-size:8.5px;letter-spacing:.5px;
  padding:0 5px;border-radius:4px;color:#8fd8ff;background:rgba(0,150,255,.16);border:1px solid rgba(0,150,255,.3)}
.fs-detail .grid6 .g.det .who.yp i{color:#7df5b9;background:rgba(53,224,143,.14);border-color:rgba(53,224,143,.35)}
.fs-detail .grid6 .g.det .who u{text-decoration:none;font-size:9px;color:#8fa9cc}
.fs-detail .grid6 .g.det .dico{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;flex:none}
.fs-detail .grid6 .g.det img.dico{width:15px;height:auto;object-fit:contain}
.fs-detail .grid6 .g.det .dico svg{width:12px;height:16px}
/* v9: APP 同款底栏（首次发现 · 持续）+ 右端「最近发现 XX 前」
   v9.17.58: 加 flex-wrap:wrap —— 当时底栏要新增「最近发现 XX 前」，量得内容区仅 258px
             而一行需 291.2px（含「实时跟踪中」）⇒ 换行，.fresh 独占第 2 行右对齐。
   v9.17.60: 用户明确要求「删除 实时跟踪中　　把 最近发现xxS前 挪上去 一行显示」——
             删掉「· 实时跟踪中 / · 信号丢失」这条状态尾巴后，**一行共需 232.9px**
             （实测：截图场景 232.9 / fresh 最坏「最近发现 23 小时前」248.8 / 持续 720:00 254.6）
             ⇒ 258px 放得下 ⇒ **改回 nowrap 单行**（.fresh 的 margin-left:auto 此刻正好
             把它顶到右缘，填掉余量）。
             ⚠️ 尾巴是**整条**删的，取舍已与用户确认（AskUserQuestion 选「尾巴整条删掉」）：
                正常态「实时跟踪中」是恒真常量、零信息量；陈旧态「信号丢失」在本浮卡顶部标签区
                与左侧目标列表行本来就各有一个 ⇒ 底栏那句是重复信息。
                实测只删「实时跟踪中」而保留陈旧态尾巴 ⇒ 需 297.1px，溢出 39.1px，做不到「一行显示」。
   🔴 兜底：`#fd-foot` 给 min-width:0 + 省略号。屏幕长开时 `持续` 是**总分钟数**（fmtD 输出），
      页面开满 24h 就到 4 位数 ⇒ 实测 1440:00 为 260.5px、10079:59 为 266.4px，都会溢出。
      有了省略号兜底，「一行显示」在任何时长下都成立；且正常量程内**一个像素都不截**
      （720:00 = 254.6px 仍完整显示）。⛔ 别把兜底删掉 —— 那时长一超就会顶破面板右缘。 */
.fs-detail .dft{display:flex;flex-wrap:nowrap;align-items:center;gap:7px;padding:8px 14px;font-size:10px;color:#9fc0e8;
  border-top:1px dashed rgba(110,170,255,.18);background:rgba(0,120,255,.06)}
/* v9.17.60: 单行后 #fd-foot 是**唯一**会被压缩的项
   （.live 明写 flex:none；.fresh 靠 min-width:auto 的自动最小尺寸压不动，见下方注释）
   ⇒ 余量不够时由它出省略号，而不是把「最近发现 XX 前」挤出去或顶破面板。 */
.fs-detail .dft #fd-foot{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.fs-detail .dft .live{width:7px;height:7px;border-radius:50%;background:#30D158;flex:none;
  box-shadow:0 0 6px rgba(48,209,88,.9);animation:stblink 1.6s ease-in-out infinite}
/* v9.17.58: 「最近发现 XX 前」= 最后数据更新距今多久。
   ⚠️ margin-left:auto 必须写在**要贴右的这个元素自己**身上（写在父容器上不会生效）。
   数字用 tabular-nums 防「秒数跳字时整行左右抖」。
   🔴 v9.17.60：为什么这里**不需要**写 flex:none ——
      `.fresh` 是 overflow:visible 的 flex 项 ⇒ `min-width:auto` 已解析为**内容最小尺寸**
      （文本 nowrap ⇒ 就是整串文字宽），它**本来就压不下去**。
      这个结论是实测出来的，不是推的：反证 `A_freshflex`（删掉 flex:none）跑完
      19 条断言**逐条不变** ⇒ 那一句是**死代码**，已删除。
      真正的机制是 `min-width:auto` —— 反证 `A_minwidth`（改成 min-width:0）能让 F9 变红，
      这就是它的活性证明。⛔ 别给 .fresh 加 overflow:hidden（会把 auto 最小尺寸打掉 ⇒ 被挤扁）。 */
.fs-detail .dft .fresh{margin-left:auto;white-space:nowrap;color:#8fd8ff;font-variant-numeric:tabular-nums}
.fs-detail table{width:100%;border-collapse:collapse;font-size:11px}
.fs-detail td{padding:5.5px 14px;border-top:1px dashed rgba(110,170,255,.14);color:#8fb2e0;white-space:nowrap}
.fs-detail td+td{color:#eaf6ff;text-align:right;font-variant-numeric:tabular-nums}
.fs-detail .df{display:flex;gap:8px;padding:10px 14px 12px}
.fs-detail .df button{flex:1;border:1px solid rgba(0,150,255,.4);background:rgba(0,120,255,.18);color:#cfe9ff;
  font-size:12px;padding:7px 0;border-radius:8px;cursor:pointer;letter-spacing:2px}
.fs-detail .df button:hover{background:rgba(0,150,255,.32)}
.fs-detail .dx{position:absolute;top:8px;right:10px;color:#5f8ba0;cursor:pointer;font-size:14px;line-height:1}
.fs-detail .dx:hover{color:#fff}
/* v8.4 检测手机/地面站标记：无底圈裸图标 + 荧光描边（浅色地图显眼），波纹以图标为中心向四周扩散 */
.scr-maplayer .smark .pic{position:relative;width:var(--smark-pic);height:var(--smark-pic);display:flex;align-items:center;justify-content:center}
.scr-maplayer .smark .pic svg{width:44px;height:44px;
  filter:drop-shadow(0 0 4px rgba(0,213,255,.75)) drop-shadow(0 1px 3px rgba(10,40,80,.45))}
.scr-maplayer .smark-station .pic svg{width:50px;height:48px;
  filter:drop-shadow(0 0 4px rgba(0,230,118,.75)) drop-shadow(0 1px 3px rgba(10,40,80,.45))}
/* v11: 探测站专属图标（用户提供的深蓝无人机+信号波 PNG，3x 透明底）—— 替换原机房+天线 SVG */
.scr-maplayer .smark-station .pic img.stico{width:48px;height:auto;display:block;
  filter:drop-shadow(0 0 1px rgba(255,255,255,.9))
    drop-shadow(0 0 5px rgba(0,120,255,.55)) drop-shadow(0 1px 3px rgba(10,40,80,.5))}
.scr-maplayer .smark.off .pic img.stico{filter:grayscale(1) opacity(.5)}
.scr-maplayer .smark .waves{position:absolute;top:0;left:50%;width:var(--smark-pic);height:var(--smark-pic);
  margin-left:calc(var(--smark-pic) * -0.5);pointer-events:none}
.scr-maplayer .smark .waves i{position:absolute;inset:0;border:2px solid #00d5ff;border-radius:50%;
  animation:stwave 2.2s ease-out infinite;opacity:0}
.scr-maplayer .smark-station .waves i{border-color:#00e676}
.scr-maplayer .smark .waves i:nth-child(2){animation-delay:.7s}
.scr-maplayer .smark .waves i:nth-child(3){animation-delay:1.4s}
.scr-maplayer .smark.off .waves i{display:none}
@keyframes stwave{0%{transform:scale(.45);opacity:.85}100%{transform:scale(2.4);opacity:0}}
.scr-maplayer .smark .sinfo{margin-top:5px;display:inline-flex;align-items:center;gap:5px;font-size:12px;font-weight:600;
  padding:3.5px 11px;border-radius:980px;white-space:nowrap;
  background:rgba(255,255,255,.95);border:1.5px solid #00d5ff;color:#0c4a5e;
  box-shadow:0 2px 8px rgba(10,40,80,.28)}
.scr-maplayer .smark-station .sinfo{border-color:#00e676;color:#14532d}
.scr-maplayer .smark .sinfo::before{content:"";width:7px;height:7px;border-radius:50%;background:#00d5ff;
  box-shadow:0 0 5px #00d5ff;animation:stblink 1.6s ease-in-out infinite}
.scr-maplayer .smark-station .sinfo::before{background:#00e676;box-shadow:0 0 5px #00e676}
.scr-maplayer .smark.off .pic svg{filter:grayscale(1) opacity(.5)}
.scr-maplayer .smark.off .sinfo{border-color:#8E8E93;color:#5f6b7a;background:rgba(240,242,245,.95)}
.scr-maplayer .smark.off .sinfo::before{background:#8E8E93;box-shadow:none;animation:none}
/* v9.8.3: 多设备近距离时标签自动外移避让 + 引线回连图标（用户需求） */
.scr-maplayer .smark .sinfo{transition:transform .22s cubic-bezier(.22,.61,.36,1)}
.scr-maplayer .scr-leadlayer{position:absolute;inset:0;width:100%;height:100%;overflow:visible;
  pointer-events:none;z-index:905}
.scr-maplayer .scr-leadlayer line{stroke-width:1.5;stroke-dasharray:4 3;stroke-linecap:round}
@keyframes stblink{0%,100%{opacity:1}50%{opacity:.35}}
/* ==================== 登录页 · 深空科幻版式（地球/轨道/无人机线框） ==================== */
.lgx{background:#020617;color:#e6f1ff;font-family:"SF Pro Display","PingFang SC",sans-serif}
.lgx .login-card{background:linear-gradient(160deg,rgba(14,28,54,.82) 0%,rgba(8,18,38,.78) 100%);
  border:1px solid rgba(80,220,255,.25);border-radius:18px;
  box-shadow:0 0 0 1px rgba(2,8,20,.6),0 24px 70px rgba(0,0,0,.65),inset 0 1px 0 rgba(140,230,255,.14);
  backdrop-filter:blur(20px) saturate(1.2);padding:32px 32px 26px;
  transition:border-color .3s,box-shadow .3s}
.lgx .login-card:hover{border-color:rgba(80,220,255,.42);
  box-shadow:0 0 0 1px rgba(2,8,20,.6),0 28px 80px rgba(0,0,0,.7),0 0 44px rgba(60,160,255,.14),inset 0 1px 0 rgba(140,230,255,.18)}
/* 星空（三层视差闪烁，z-index:2 浮于地球背景之上） */
.lgx-stars{position:absolute;inset:0;pointer-events:none;z-index:2;background-image:
  radial-gradient(1px 1px at 8% 22%,#fff 50%,transparent 51%),
  radial-gradient(1.5px 1.5px at 26% 8%,#9fe8ff 50%,transparent 51%),
  radial-gradient(1px 1px at 44% 30%,#fff 50%,transparent 51%),
  radial-gradient(1px 1px at 63% 12%,#fff 50%,transparent 51%),
  radial-gradient(2px 2px at 82% 26%,#b7fff0 50%,transparent 51%),
  radial-gradient(1px 1px at 92% 60%,#fff 50%,transparent 51%),
  radial-gradient(1.4px 1.4px at 15% 74%,#fff 50%,transparent 51%),
  radial-gradient(1px 1px at 70% 82%,#9fe8ff 50%,transparent 51%),
  radial-gradient(1px 1px at 36% 58%,#fff 50%,transparent 51%),
  radial-gradient(1.6px 1.6px at 52% 90%,#fff 50%,transparent 51%);
  background-size:520px 520px;animation:lgxtw 5s ease-in-out infinite alternate}
.lgx-stars.s2{background-size:340px 340px;opacity:.55;animation-duration:7s;animation-delay:-2s}
.lgx-stars.s3{background-size:820px 820px;opacity:.8;animation-duration:9s;animation-delay:-4s}
@keyframes lgxtw{from{opacity:.4}to{opacity:1}}
/* 透视地网（已移除：与弧顶地球重叠） */
.lgx-grid{display:none}
/* 四角 HUD 括弧（z-index:3 浮于背景之上） */
.lgx-corner{position:absolute;width:34px;height:34px;border:2px solid rgba(61,255,192,.5);pointer-events:none;z-index:3}
.lgx-corner.tl{top:16px;left:16px;border-right:0;border-bottom:0}
.lgx-corner.tr{top:16px;right:16px;border-left:0;border-bottom:0}
.lgx-corner.bl{bottom:16px;left:16px;border-right:0;border-top:0}
.lgx-corner.br{bottom:16px;right:16px;border-left:0;border-top:0}
/* 左上系统状态（z-index:3） */
.lgx-sysinfo{position:absolute;top:24px;left:64px;font-size:11px;letter-spacing:2.5px;color:#57e6ff;z-index:3;
  display:flex;align-items:center;gap:9px;text-shadow:0 0 10px rgba(87,230,255,.6)}
.lgx-sysinfo i{width:7px;height:7px;border-radius:50%;background:#3dffc0;box-shadow:0 0 8px #3dffc0;animation:stblink 1.6s infinite}
.lgx-sysinfo span{color:rgba(180,220,255,.55);letter-spacing:1.5px}
/* ── 星辰 hero（v9.11）：全屏星系 canvas，引擎见 js/galaxy.js（CFG.bg = #04070f）──
   原「地球自转」版式（.lgx-hero / .lgx-earth / .lgx-glow + lgxspin / lgxglow）
   已整体下线，改为 Canvas 双旋臂星系动效。背景色须与 galaxy.js 的 CFG.bg 一致。 */
.lgx-galaxy{position:absolute;inset:0;width:100%;height:100%;display:block;z-index:0}
/* 无人机（已按需求移除，保留动画键供其他元素复用） */
@keyframes lgxhover{0%,100%{transform:translateY(0)}50%{transform:translateY(-14px)}}
/* ── 左侧内容区 ── */
.lgx-panel{position:relative;z-index:2;display:flex;flex-direction:column;align-items:flex-start;justify-content:center;
  min-height:100vh;padding:8vh 5vw;max-width:640px;animation:fadeUp .6s ease}
.lgx-brand{display:flex;align-items:center;gap:12px;margin-bottom:30px}
.lgx-brand img{width:46px;height:46px;object-fit:contain;filter:drop-shadow(0 0 12px rgba(87,230,255,.45))}
.lgx-brand b{display:block;font-size:20px;font-weight:800;letter-spacing:3px;color:#fff}
.lgx-brand span{display:block;font-size:10px;letter-spacing:4px;color:#57e6ff;margin-top:2px}
.lgx-title{font-size:clamp(30px,3.6vw,44px);font-weight:900;letter-spacing:2px;line-height:1.3;color:#fff;
  text-shadow:0 0 24px rgba(87,230,255,.35)}
.lgx-title em{font-style:normal;background:linear-gradient(92deg,#3dffc0 0%,#57e6ff 55%,#3dffc0 100%);
  -webkit-background-clip:text;background-clip:text;color:transparent;
  filter:drop-shadow(0 0 14px rgba(61,255,192,.45))}
.lgx-sub{margin-top:14px;font-size:11.5px;letter-spacing:3px;color:rgba(150,200,240,.6)}
.lgx-card{margin-top:30px;width:min(380px,88vw);position:relative}
.lgx-card::before{content:"";position:absolute;top:0;left:18px;right:18px;height:1px;
  background:linear-gradient(90deg,transparent,rgba(87,230,255,.8),transparent)}
.lgx-card-hd{display:flex;justify-content:space-between;align-items:center;font-size:12.5px;letter-spacing:2px;
  color:#bfe9ff;margin-bottom:18px;padding-bottom:12px;border-bottom:1px dashed rgba(87,230,255,.2)}
.lgx-card-hd span{font-size:10px;color:#3dffc0;letter-spacing:3px}
.lgx-input{background:rgba(4,14,30,.7)!important;border:1px solid rgba(87,230,255,.25)!important;color:#e6f1ff!important;
  height:44px!important;border-radius:10px!important;transition:border-color .2s,box-shadow .2s}
.lgx-input::placeholder{color:rgba(140,180,220,.45)}
.lgx-input:focus{border-color:rgba(61,255,192,.65)!important;box-shadow:0 0 0 3px rgba(61,255,192,.14)!important;outline:none}
.lgx-remember{font-size:12.5px;color:rgba(170,205,235,.75);display:flex;gap:8px;align-items:center;cursor:pointer;user-select:none}
.lgx-remember input{display:none}
.lgx-remember i{width:15px;height:15px;border-radius:4px;border:1px solid rgba(87,230,255,.45);
  display:inline-flex;align-items:center;justify-content:center;transition:background .15s}
.lgx-remember input:checked+i{background:#3dffc0;border-color:#3dffc0}
.lgx-remember input:checked+i::after{content:"✓";font-size:11px;color:#04222b;font-weight:800}
.lgx-btn{height:46px;border-radius:10px;border:0;cursor:pointer;font-size:15px;font-weight:700;letter-spacing:6px;
  color:#eaffff;background:linear-gradient(92deg,#0a84ff,#00c6c0);
  box-shadow:0 0 22px rgba(10,132,255,.45),inset 0 1px 0 rgba(255,255,255,.35);
  transition:filter .2s,box-shadow .2s,transform .15s;position:relative;overflow:hidden}
.lgx-btn::after{content:"";position:absolute;top:0;left:-80%;width:50%;height:100%;
  background:linear-gradient(100deg,transparent,rgba(255,255,255,.35),transparent);
  transform:skewX(-20deg);animation:lgxsweep 4.5s ease-in-out infinite}
@keyframes lgxsweep{0%,55%{left:-80%}75%,100%{left:130%}}
.lgx-btn:hover{filter:brightness(1.12);box-shadow:0 0 36px rgba(10,132,255,.6);transform:translateY(-1px)}
.lgx-btn:active{transform:translateY(0) scale(.985)}
.lgx-btn:disabled{opacity:.6;cursor:default}
.lgx-btn span{text-shadow:none}
.lgx .login-tip{color:#ff7d9c!important}
.lgx-foot{margin-top:26px;font-size:11px;letter-spacing:1px;color:rgba(140,175,210,.5);display:flex;align-items:center;gap:10px}
.lgx-foot-logo{height:26px;opacity:.9;flex:none}
/* v9.17.174：工信部 ICP 备案号（linkvise.cn 备案通过 2026-10-20，苏ICP备2026079698号-1）
   法规要求备案号可点击并跳转 beian.miit.gov.cn；⛔ 不要加 nofollow（备案链接必须允许爬虫跟踪） */
.lgx-icp{color:rgba(140,175,210,.72);text-decoration:none;border-bottom:1px solid rgba(140,175,210,.28);transition:color .18s,border-color .18s}
.lgx-icp:hover,.lgx-icp:focus-visible{color:#8fc3ff;border-bottom-color:rgba(143,195,255,.6)}
@media (max-width:900px){
  .lgx-panel{max-width:100%;padding:8vh 7vw}
  .lgx-sysinfo{left:60px}
}

/* 星辰登录页背景覆盖（不受主题浅色背景影响）
   ::before = 左侧压暗层：星系亮区在右，表单在左，保证输入框/文字对比度 ≥4.5:1 */
body[data-theme] .login-wrap.lgx{background:#04070f;justify-content:flex-start}
body[data-theme] .login-wrap.lgx .lgx-panel{margin-left:0;padding-left:9vw;max-width:740px}
body[data-theme] .login-wrap.lgx::before{content:"";position:absolute;inset:0;pointer-events:none;z-index:1;
  background:radial-gradient(900px 560px at 74% 44%,rgba(30,110,230,.16),transparent 64%),
    linear-gradient(96deg,rgba(4,7,15,.94) 0%,rgba(4,7,15,.86) 20%,rgba(4,7,15,.5) 38%,rgba(4,7,15,.12) 54%,transparent 66%),
    linear-gradient(to top,rgba(4,7,15,.55) 0%,transparent 26%),
    linear-gradient(to bottom,rgba(4,7,15,.5) 0%,transparent 18%)}
@media (max-width:900px){
  body[data-theme] .login-wrap.lgx .lgx-panel{padding-left:7vw}
  /* 窄屏星系居中，左侧渐变式压暗会只压住半边 → 改为整层均匀压暗，表单对比度全屏一致 */
  body[data-theme] .login-wrap.lgx::before{
    background:radial-gradient(620px 500px at 50% 46%,rgba(30,110,230,.14),transparent 66%),
      linear-gradient(rgba(4,7,15,.74),rgba(4,7,15,.84))}
}

/* ══════════════════════════════════════════════════════════════════════════
   大屏 v9.17.5 · 「发现无人机」告警 —— 右上角雷达动效 + 全屏呼吸红
   ──────────────────────────────────────────────────────────────────────────
   动效移植自 APP  `app/.../ui/common/DroneAlarmView.kt`
   （其文件头自述「移植自 ui-alarm-fx.html」），取 `discTransparent = true` 那一档：
   **不画暗盘**，只保留笔画（涟漪 / 双弧环 / 粒子 / 无人机 / 幽灵文字），
   叠在地图上不遮底图 —— 与 APP 叠在浅色地图上的做法一致。

   周期与几何**逐条对齐 Kotlin 常量**，改一边要同步改另一边，别让两端漂移：
     CYCLE_MS=2400（呼吸/涟漪/文字主周期）｜ARC_A_MS=7000 顺时针｜ARC_B_MS=13000 逆时针
     DISC_R=0.47｜CORE_Y=0.46｜DRONE_W=0.56｜涟漪 3 层错拍 800ms｜航行灯 1300ms 交错
     ripple α=peak·(1−p)、前 8% 淡入 ｜ 粒子 r=0.05~0.43×边长、dur 2200~4000ms
   坐标基准：SVG viewBox `0 0 240 240`，圆心 (120, 110.4) = (120, 0.46×240)。

   ⚠️ 颜色是本移植里**唯一的有意偏离**：APP 透明档位用深红字（#D91F33）+ 白描影，
      那是为**浅色地图**选的；本大屏底图是深色，深红不可读 ⇒ 换亮红系（下同）。
      这与 APP 自己按 DayNight 二选一（见它 isLightMode 分支）的做法同源，不算改设计。
      对比度（对卡片底 #3A0A12）：机体 #FF4655 = 5.1:1，文字 #FF8A93 = 7.6:1，均 ≥4.5:1。
   ══════════════════════════════════════════════════════════════════════════ */

/* ── 1. 全屏呼吸红 ─────────────────────────────────────────────────────────
   只在告警窗口内挂 .on；周期 2.4s **与告警动效主周期同相**，两者一起"呼吸"。

   🔴 浮层强度是**实测标定**的，不是拍脑袋：
      最初写成 `radial-gradient(118% 88% at 50% 50%, transparent 40%, …)`，
      单看没问题，实测却几乎看不见 —— 那个尺寸把渐变的 100% 停靠点甩到了**屏幕外**
      （水平半径 1.18×屏宽，而视口边缘只在半径的 ~42% 处 ⇒ 落进 transparent 段）。
      开关对照采样（同点位两次截图求 ΔRGB）显示：浅色底图上仅 (+6,−20,−18)，局部 α≈13%，
      完全不像"闪红光"；反而是深色面板上加了 ΔR +33~+75（深底更容易显红）。
      本大屏底图是**浅色天地图**，占屏面积最大，所以必须让浅色区先红起来。
      改法：① 用默认 `farthest-corner` 尺寸 —— 视口边中点正好落在 ~87%、四角 100%，
      过渡刚好铺满整屏；② 呼吸改挂在**外层 opacity**（.55→1），α 强度写进渐变里，
      这样"最红"与"能看清"两档都可控；③ 再用 inset box-shadow 补一圈红边。
      峰值下实测面板文字对比度仍 ≥5.2:1（峰值 α=.34 时算过），底图与面板都还读得清。 */
.scr-redflash{position:absolute;inset:0;z-index:958;pointer-events:none;opacity:0}
.scr-redflash.on{animation:scrABreathe 2.4s ease-in-out infinite}
@keyframes scrABreathe{0%,100%{opacity:.55}50%{opacity:1}}
/* 全身红洗（浅→浓由中心向四角递进）。z-index 958 **刻意低于 .fs-col(960)**：
   强红光只烧地图区，面板保持完全可读 —— 峰值时面板底色会被压到 (142,27,43)，
   文字对比度掉到 3.35:1（算过），低于 4.5:1 不可接受。 */
.scr-redflash .v{position:absolute;inset:0;
  background:radial-gradient(at 50% 50%,
    rgba(255,26,40,.34) 0%, rgba(255,26,40,.40) 58%,
    rgba(238,10,28,.74) 88%, rgba(226,6,22,.86) 100%)}
/* 四边红色辉光框（不碰中心，保证中心区仍可读） */
.scr-redflash .w{position:absolute;inset:0;
  box-shadow:inset 0 0 130px 26px rgba(255,32,46,.55),inset 0 0 36px 4px rgba(255,66,78,.42)}

/* 面板之上再叠一层**轻红**，让"整个网页"（含左右面板与顶栏）都在呼吸，
   而不是只有地图在红。α 压到峰值 .22 —— 实测面板文字对比度仍有 ~6.5:1。
   两层共用 scrABreathe ⇒ 同相呼吸，不会各闪各的。 */
.scr-redtint{position:absolute;inset:0;z-index:966;pointer-events:none;opacity:0;
  background:radial-gradient(at 50% 50%,
    rgba(255,30,45,.22) 0%, rgba(255,30,45,.26) 60%, rgba(240,12,30,.38) 100%)}
.scr-redtint.on{animation:scrABreathe 2.4s ease-in-out infinite}

/* ── 2. 右上角告警卡 ───────────────────────────────────────────────────────
   v9.17.57（用户原话「告警紧贴 右上角」）：位置由 right:308px 改为**紧贴屏幕右上角**
   （对齐手机 APP 的 DroneAlarmView：`Gravity.TOP or Gravity.END`）。
     · right:16px —— 与 .fs-legend(16) / .scr-maptools(16) / .fs-col.left(16) 同一套边距；
     · top:60px   —— 与 .fs-chips（KPI 胶囊，也 top:60）齐平；顶栏 .fs-right（时钟 + 按钮，
                      实测底边 ≈49px）之下、KPI 胶囊（水平居中，1920 宽时右缘 ≈1274px）之右，
                      三者互不相交（1280 宽下仍有余量）；
     · z-index:971 —— 抬到 .fs-top(970) 之上。否则顶栏那层「由深到透明的暗色渐变」会压在
                     卡片顶上 ⇒ 顶部变暗 + 吃掉 ::before 的顶线红光。
   ⛔ 旧值 right:308px 是按**已删除的右栏** .fs-col.right（宽 292）算出来的历史残留
      （v9.17.29 起右栏已删），别再改回去。 */
.scr-alarm{position:absolute;top:60px;right:16px;z-index:971;width:296px;
  border-radius:14px;padding:11px 13px 13px;
  background:linear-gradient(165deg,rgba(58,10,18,.94),rgba(26,5,11,.9));
  border:1px solid rgba(255,70,84,.55);
  box-shadow:0 12px 44px rgba(120,0,12,.6),inset 0 0 0 1px rgba(255,70,84,.12);
  backdrop-filter:blur(12px);
  opacity:0;visibility:hidden;transform:translateY(-10px) scale(.96);
  transition:opacity .22s ease,transform .22s ease,visibility .22s;
  pointer-events:none}
.scr-alarm.on{opacity:1;visibility:visible;transform:none;pointer-events:auto;cursor:pointer}
/* 顶部一线红光，与 .fs-panel::before 同构，视觉上归入同一套面板语言 */
.scr-alarm::before{content:"";position:absolute;left:0;top:0;right:0;height:2px;border-radius:14px 14px 0 0;
  background:linear-gradient(90deg,transparent 6%,rgba(255,70,84,.9),transparent 94%)}
/* 数量气泡（对齐 APP 的 bg_alert_badge：红圆底 + 白描边 + 白字假粗） */
.scr-alarm .hd{display:flex;align-items:center;gap:8px;margin-bottom:6px}
.scr-alarm .hd .ic{font-style:normal;font-size:15px;color:#ff5f6d;
  filter:drop-shadow(0 0 7px rgba(255,60,76,.85));
  animation:scrAIc 2.4s ease-in-out infinite}
@keyframes scrAIc{0%,100%{opacity:.62}50%{opacity:1}}
.scr-alarm .hd b{font-size:14px;letter-spacing:2.5px;color:#ffe2e5;font-weight:800;
  text-shadow:0 0 12px rgba(255,60,76,.6)}
.scr-alarm .hd .bdg{margin-left:auto;min-width:22px;height:22px;padding:0 7px;border-radius:11px;
  display:inline-flex;align-items:center;justify-content:center;
  background:linear-gradient(180deg,#ff3b4c,#cf0f22);
  border:1.5px solid rgba(255,255,255,.92);color:#fff;font-size:12px;font-weight:800;
  font-variant-numeric:tabular-nums;box-shadow:0 0 12px rgba(255,60,76,.8)}
/* v9.17.57: 语音播报开关。APP 侧没有静音开关（speakTts 无条件执行），
   但本页是**值班大屏**，会一直念 ⇒ 给一个小开关（存在 localStorage，默认开）。
   注意：它是卡片里的 <button>，点击处理必须先于「点卡片看详情」，见 screen.js 点击分支。 */
.scr-alarm .hd .snd{flex:none;width:22px;height:22px;padding:0;border-radius:11px;cursor:pointer;
  font-size:11px;line-height:1;display:inline-flex;align-items:center;justify-content:center;
  background:rgba(255,70,84,.14);border:1px solid rgba(255,90,105,.5);color:#ff9aa4;
  transition:color .15s,border-color .15s,background .15s}
.scr-alarm .hd .snd.on{background:rgba(53,224,143,.12);border-color:rgba(53,224,143,.5);color:#7df5b9}
.scr-alarm .hd .snd:hover{border-color:#fff;color:#fff}
.scr-alarm .hd .snd:focus-visible{outline:2px solid #7fe9ff;outline-offset:1px}
/* 雷达动效舞台：正方形，SVG 100% 撑满 */
.scr-alarm .rad{position:relative;width:100%;aspect-ratio:1/1;max-height:236px;
  display:flex;align-items:center;justify-content:center;margin:2px 0 6px}
.scr-alarm .rad svg{width:100%;height:100%;display:block}
/* 分隔线 */
.scr-alarm .ln{height:1px;margin:2px 0 9px;
  background:linear-gradient(90deg,transparent,rgba(255,70,84,.42),transparent)}
/* 目标信息（对齐实时目标面板的字段口径，不新造指标） */
.scr-alarm .tgt{display:flex;align-items:center;gap:8px;margin-bottom:6px}
.scr-alarm .tgt .nm{font-size:13px;font-weight:700;color:#ffd9dd;white-space:nowrap;
  overflow:hidden;text-overflow:ellipsis}
.scr-alarm .tgt .wt{margin-left:auto;flex:none;font-size:10px;padding:1px 8px;border-radius:8px;
  color:#ff9aa4;border:1px solid rgba(255,90,105,.5);background:rgba(255,70,84,.12)}
.scr-alarm .sn{font-size:11px;color:#c98d96;font-variant-numeric:tabular-nums;
  letter-spacing:.4px;margin-bottom:7px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.scr-alarm .kv{display:grid;grid-template-columns:1fr 1fr 1fr;gap:6px}
.scr-alarm .kv .c{background:rgba(255,70,84,.1);border:1px solid rgba(255,70,84,.22);
  border-radius:9px;padding:6px 8px;min-width:0}
.scr-alarm .kv .c i{display:block;font-style:normal;font-size:9.5px;color:#c07f89;letter-spacing:1px}
.scr-alarm .kv .c b{display:block;font-size:14px;font-weight:800;color:#fff;
  font-variant-numeric:tabular-nums;line-height:1.25;white-space:nowrap;
  overflow:hidden;text-overflow:ellipsis}
.scr-alarm .ft{margin-top:8px;font-size:10.5px;color:#b1747e;
  display:flex;align-items:center;gap:6px;white-space:nowrap;overflow:hidden}
.scr-alarm .ft .dot{width:6px;height:6px;border-radius:50%;background:#ff4655;flex:none;
  box-shadow:0 0 8px #ff4655;animation:scrAIc 2.4s ease-in-out infinite}

/* ── 3. 雷达动效各图层 ─────────────────────────────────────────────────────
   transform-box 的选择是刻意的：
     · 圆（涟漪/光晕）用 fill-box + center —— 圆心就是自身包围盒中心，无歧义；
     · 弧环用 view-box + 圆心绝对坐标 —— 弧段自身的包围盒中心不是圆心，用 fill-box 会跑偏；
     · 无人机用 fill-box + center —— 其包围盒中心≈Kotlin 的 (cx,cy)，误差 <2px。 */
.scr-alarm .aglow{transform-box:fill-box;transform-origin:center;
  animation:scrAGlow 2.4s ease-in-out infinite}
@keyframes scrAGlow{0%,100%{transform:scale(.92);opacity:.55}50%{transform:scale(1.05);opacity:1}}

.scr-alarm .arp{fill:none;stroke:#FF5C68;stroke-width:2.2;vector-effect:non-scaling-stroke;
  transform-box:fill-box;transform-origin:center;opacity:0;
  animation:scrARp 2.4s linear infinite}
.scr-alarm .arp.d1{animation-delay:-.8s}
.scr-alarm .arp.d2{animation-delay:-1.6s}
@keyframes scrARp{0%{transform:scale(.1);opacity:0}8%{opacity:.9}100%{transform:scale(1);opacity:0}}

.scr-alarm .aarc{fill:none;stroke-linecap:round;transform-box:view-box;
  transform-origin:120px 110.4px}
.scr-alarm .aarc.A1{animation:scrAA 7s linear infinite}
.scr-alarm .aarc.B1{animation:scrAB 13s linear infinite}
@keyframes scrAA{to{transform:rotate(360deg)}}
@keyframes scrAB{to{transform:rotate(-360deg)}}

/* 粒子：从中心向外辐射的短点（位置+角度在 JS 里按种子随机生成，见 alarmFxSvg） */
.scr-alarm .apt{transform-box:fill-box;transform-origin:center;opacity:0;
  animation:scrAPt var(--pd) linear infinite}
@keyframes scrAPt{
  0%{transform:translate(0,0) scale(.85);opacity:0}
  12%{opacity:.95}
  70%{opacity:.55}
  100%{transform:translate(var(--px),var(--py)) scale(1);opacity:0}}

/* 无人机：呼吸缩放 + 桨叶虚线相位旋转 + 航行灯交错闪烁 */
.scr-alarm .adrone{transform-box:fill-box;transform-origin:center;
  animation:scrADrone 2.4s ease-in-out infinite}
@keyframes scrADrone{0%,100%{transform:scale(1)}50%{transform:scale(1.06)}}
.scr-alarm .aspin{fill:none;stroke:#FF96A0;stroke-width:1.6;stroke-dasharray:10 16}
.scr-alarm .aspin.l{animation:scrASpin .5s linear infinite}
.scr-alarm .aspin.r{animation:scrASpin .42s linear infinite}
@keyframes scrASpin{to{stroke-dashoffset:-26}}
.scr-alarm .anav{fill:#FFE2E5}
.scr-alarm .anav.n1{animation:scrANav 1.3s ease-in-out infinite}
.scr-alarm .anav.n2{animation:scrANav 1.3s ease-in-out infinite;animation-delay:-.65s}
@keyframes scrANav{0%,100%{opacity:.2}50%{opacity:1}}
/* 机体：实心件 / 描边件 / 镜头孔三类分开。
   ⚠️ 别用「父级给一次 fill」的写法：机臂与起落架靠 `fill:none` 才是描边件，
      而 CSS 的 fill 会压过内联 presentation attribute —— 父级一给 fill，机臂就被填成实心棍。 */
.scr-alarm .abf{fill:#FF4655;stroke:none}
.scr-alarm .abs{fill:none;stroke:#FF4655;stroke-linecap:round}
.scr-alarm .hole{fill:#2A070C;stroke:none}

/* 幽灵文字：明暗呼吸（还原网页版 flicker 关键帧 0%:.3→30~58%:1→100%:.3） */
.scr-alarm .acap{text-anchor:middle;font-weight:800;letter-spacing:4px;
  animation:scrACap 2.4s linear infinite}
.scr-alarm .acap.t1{font-size:20.4px;fill:#FF8A93}
.scr-alarm .acap.t2{font-size:5.8px;fill:#FF97A0;letter-spacing:2.4px}
@keyframes scrACap{
  0%{opacity:0}6%{opacity:.42}12%{opacity:.3}22%{opacity:.72}34%{opacity:1}
  58%{opacity:1}72%{opacity:.5}86%{opacity:.82}100%{opacity:.3}}

/* 窄屏：告警卡让位（右栏 250px 时 right 同步收窄），并压扁雷达舞台免得顶到面板 */
@media (max-width:1180px) and (min-height:561px){
  .scr-alarm{width:252px;right:274px;top:112px}
  .scr-alarm .rad{max-height:196px}
  .scr-alarm .kv{grid-template-columns:1fr 1fr}
}

/* ============================================================
   v9.17.18 无障碍：键盘焦点可见（体检 P2-7）
   原来全站只有 .user-chip 一处 :focus-visible；而 .input/.select/.switch/.lgx-input
   都写了 outline:none，其中 **.switch 连 box-shadow 替代都没有**
   ⇒ 键盘 Tab 到开关/输入框时完全看不到焦点，违反 WCAG 2.4.7「焦点可见」。

   ⚠️ 必须放在文件**最末尾**：全局 `:focus-visible` 与 `.switch{outline:none}`
      的特异性相同（都是 0-1-0），谁在后面谁生效 —— 放前面会被那类规则盖掉。
   ⚠️ `:focus:not(:focus-visible)` 关掉鼠标点击产生的焦点环：
      键盘用户看得到环，鼠标用户不被环打扰（对齐现代浏览器的默认行为）。
   ============================================================ */
:focus-visible{outline:2px solid var(--c-primary);outline-offset:2px;transition:none}
/* ↑ `transition:none` 是**必需的**，不是随手加的：
   `.btn` 用了 `transition:all .18s`，会把 outline-width 也一起过渡（0px → 2px 要 180ms），
   实测「Tab 按下瞬间」读到的 computed outline 仍是 `solid 0px`（等于没焦点框）。
   焦点环必须**立即可见**，所以聚焦期间禁掉过渡。 */
:focus:not(:focus-visible){outline:none}
/* 大屏是深空底，主色蓝环在暗背景上不够醒目，换亮青（特异性更高，不会互相打架） */
.screen :focus-visible{outline-color:#5ac8fa}

/* ============================================================
   【手机竖屏适配 · A 档】@media (max-width:480px)
   ============================================================
   背景（2026-10-01，用户投诉「用手机打开这个网页 显示错乱 没有等比例缩放」）：
   本文件现有断点最低只到 900px，layouts.css / themes/apple.css 更是**零 @media**
   ⇒ 手机竖屏（375~430px）**一档都不命中**，桌面那套绝对定位 + 固定 px 原样铺开
   ⇒ 实测 17~18 个 UI 元素横向越界（.fs-title 522px、.fs-chips 628px、
      .scr-gear left=481 跑出视口…）。

   🔴 布局契约（实测 _m2_diag.js，⛔ 别靠猜）：
     · #scr = display:flex / flex-direction:column / position:fixed，尺寸 = 视口
     · 14 个直接子元素里**除 1 个 relative 占位外全部 position:absolute**，
       其中 **#scr-map（天地图容器）自己也是 absolute 且铺满视口**
     · .fs-title 不在 #scr 下，而在 .fs-top > .fs-title；.fs-right 在 .fs-top > .fs-right
   ⛔ 所以**绝不能把 #scr 改成 display:block**（地图 absolute 铺满后会把浮层全盖住，
      2026-10-01 实测翻车过一次）。
   ✅ 正确做法：**保持 #scr 的 flex/absolute 骨架不动**，只把各浮层收进视口安全区。

   ⛔ 也不要走「#scr transform:scale(vw/1920) 整体等比缩」那条路（B 档，已否决）：
      天地图是 canvas 渲染，transform 只做视觉缩放、不重算地图视口；
      且 #scr 是 position:fixed，transform 与 fixed 叠加会产生新包含块
      ⇒ 实测**画面直接塌掉（右侧大片空白）**，不是「字小」而是「画不出来」。
   ============================================================ */
@media (max-width:480px){
  /* ① 顶部标题条 .fs-top（#scr 直接子元素）：取消左右溢出，标题允许换行 */
  .fs-top{ left:0 !important; right:0 !important; width:auto !important;
    height:auto !important; max-height:none !important; }
  .fs-title{ position:static !important; left:auto !important; top:auto !important;
    transform:none !important; white-space:normal !important; width:auto !important;
    text-align:left !important; margin:2px 0 0 !important; flex:1 1 auto; min-width:0; }
  .fs-title h1{ font-size:14px !important; letter-spacing:0 !important; line-height:1.2 !important;
    white-space:normal !important; overflow:hidden; }
  .fs-title p{ font-size:9px !important; letter-spacing:0 !important; margin:1px 0 0 !important;
    white-space:normal !important; overflow:hidden; }

  /* ② 右上工具条 .fs-right：原 left=275 right=513（顶出视口）⇒ 整行右对齐可换行 */
  .fs-right{ position:static !important; margin-left:auto !important; flex-wrap:wrap !important;
    justify-content:flex-end !important; gap:4px !important; }
  .scr-nav{ width:auto !important; min-width:0 !important; padding:0 6px !important;
    height:26px !important; font-size:10px !important; }
  .scr-gear{ width:26px !important; height:26px !important; }

  /* ③ KPI 胶囊 .fs-chips：原 628px 宽 + translateX 居中 ⇒ left=-117 right=511
        ⇒ 收进左右 6px 内、允许换行、缩小间距与字号 */
  .fs-chips{ left:6px !important; right:6px !important; width:auto !important;
    transform:none !important; flex-wrap:wrap !important; justify-content:center !important;
    gap:5px !important; top:64px !important; }
  .fs-chip{ height:22px !important; font-size:10px !important; padding:0 6px !important;
    white-space:nowrap !important; }
  .fs-chip *{ font-size:10px !important; }

  /* ④ 告警卡 .scr-alarm：原 left=-128 right=114（整块跑出左框）⇒ 贴右上、收窄 */
  .scr-alarm{ left:auto !important; right:6px !important; top:100px !important;
    width:210px !important; }

  /* ⑤ 左列面板 .fs-col：宽 250 / top126 / bottom56 ⇒ 收窄贴左 */
  .fs-col{ width:200px !important; left:6px !important; top:280px !important;
    bottom:64px !important; }
  .fs-col.left{ left:6px !important; }

  /* ⑥ 底部三条浮层：原 fs-status@(16,804) / fs-legend@(62,804) / fs-layers@(53,756)
        三者**互相压叠** ⇒ 改成竖排堆叠，互不重叠 */
  .fs-status{ left:6px !important; right:auto !important; bottom:44px !important; width:auto !important; }
  .fs-legend{ left:6px !important; right:auto !important; bottom:26px !important; width:auto !important; }
  .fs-layers{ left:6px !important; right:auto !important; bottom:6px !important;
    transform:none !important; width:auto !important; }

  /* ⑦ 地图工具条 .scr-maptools（81x65）：缩到不挡内容 */
  .scr-maptools{ right:6px !important; bottom:6px !important; transform:scale(.86) !important;
    transform-origin:bottom right !important; }
}

/* ══════════════════════════════════════════════════════════════════════════
   v9.17.70 · 窄屏 / 手机 流式布局（第 32 轮，2026-10-01）

   用户投诉原话：「手机打开 大屏还是很乱  没有等比例缩放   很多窗口重叠  继续优化」
   （v9.17.69 的 480px 写死像素方案**没解决问题**）

   ── 实测基线（_m4_overlap.js，tag=base3，只量"有可见表面的内容块"）──
     · 1280x800 / 1920x1080 ：重叠 0 处  ⇒ 桌面是好的，别动
     · 393x852 / 375x667 / 412x915（竖）：重叠 15 处
     · 852x393 / 915x412（横）          ：重叠 8~9 处
     · 768x1024（平板竖）               ：重叠 11 处

   ── 根因（三条，全部是"写死像素 + 各元素独立定位"）──
     ① .fs-title 522px + nowrap + 绝对居中 ⇒ 压 .scr-brand / .fs-right
     ② .fs-chips 绝对 top:60px（480 档改 64px）与 .fs-top 的**实际高度脱钩**：
        窄屏 .fs-right 换行后 .fs-top 长到 439px，chips 仍钉在 64 ⇒ 必撞
     ③ 底部 .fs-layers / .fs-status / .fs-legend 各自 bottom:16px 独立定位，
        三者宽度合计 426+339+316=1081px ⇒ 屏宽 < ~1150 时**必然互相压叠**；
        .scr-maptools 又与 .fs-layers 抢右下角

   ── 修法：把"纵向耦合"交给 flex 流，不再猜高度 ──
     · #scr 本来就是 display:flex / column ⇒ 让 .fs-top / .fs-chips / .fs-col / .scr-alarm
       回到**正常流**，顺序为 顶栏 → KPI 胶囊 → 告警卡 → 面板列（用 order 排）
     · #scr 用 padding-bottom 预留底部安全区；底部三条仍在流外（绝对定位），
       但改成**三档固定行高**（实测 48/32/32，稳定）：
         .fs-layers bottom:96 / .fs-legend bottom:56 / .fs-status bottom:8
       ⇒ 两两间隔 ≥8px，任何屏宽都不再互压
     · .fs-title 改 in-flow + min-width:0 + 省略号 ⇒ 与 brand/right **结构上不可能相撞**
     · 手机档 .fs-col 全宽 + margin-top:auto 贴底 + max-height 限高 ⇒ 地图始终露出来

   ⛔ 仍然不动 #scr 的 flex/absolute 骨架，也不走 transform:scale（见上文 1265 行）
   ══════════════════════════════════════════════════════════════════════════ */

/* ── 阶梯 A：≤1180px（平板 / 小笔电 / 手机全含） ───────────────────────── */
@media (max-width:1180px) and (min-height:561px){
  /* A0 预留底部安全区（底部三条 + 工具条 = 136 + 20 间隙） */
  #scr{ padding-bottom:156px !important; }

  /* A1 顶栏 .fs-top 回到正常流：brand | right 同排，title 独占第二排居中 */
  .fs-top{ position:relative !important; top:auto !important; left:auto !important; right:auto !important;
    padding:8px 10px 14px !important; gap:6px 8px !important;
    flex-wrap:wrap !important; align-items:center !important; }
  .scr-brand{ order:1 !important; flex:0 1 auto !important; min-width:0 !important; max-width:52vw !important;
    font-size:11.5px !important; padding:5px 12px !important; gap:5px !important;
    overflow:hidden !important; text-overflow:ellipsis !important; }
  .fs-right{ order:2 !important; flex:0 0 auto !important; margin-left:auto !important;
    flex-wrap:nowrap !important; gap:6px !important; }
  .scr-clock{ font-size:11px !important; }
  .scr-clock b{ font-size:15px !important; }
  .fs-title{ order:3 !important; position:static !important; left:auto !important; top:auto !important;
    transform:none !important; width:auto !important; flex:1 1 100% !important;
    margin:2px 0 0 !important; min-width:0 !important; text-align:center !important;
    white-space:nowrap !important; overflow:hidden !important; text-overflow:ellipsis !important; }
  .fs-title h1{ white-space:nowrap !important; overflow:hidden !important; text-overflow:ellipsis !important; }
  .fs-title p{ white-space:nowrap !important; overflow:hidden !important; text-overflow:ellipsis !important; }

  /* A2 KPI 胶囊回到正常流：整行居中，窄了自动换行（不再和顶栏抢位置） */
  .fs-chips{ position:relative !important; left:auto !important; right:auto !important; top:auto !important;
    transform:none !important; width:auto !important; margin:0 !important;
    padding:0 10px 8px !important; gap:6px !important;
    flex-wrap:wrap !important; justify-content:center !important; }

  /* A3 告警卡回到正常流：紧贴胶囊下方（order:1），不显示时不占位 ——
        ⇒ 无论告警卡多高，都只会把面板往下推，**不可能压到别的东西** */
  .scr-alarm{ position:relative !important; top:auto !important; left:auto !important; right:auto !important;
    width:auto !important; margin:0 10px 8px !important; order:1 !important;
    max-height:44vh !important; overflow:auto !important; }
  .scr-alarm:not(.on){ display:none !important; }
  .scr-alarm .rad{ max-height:170px !important; }
  .scr-alarm .kv{ grid-template-columns:1fr 1fr !important; }

  /* A4 面板列回到正常流：占满剩余高度，面板自动压缩，内部 .scr-list 自带滚动 */
  .fs-col{ position:relative !important; top:auto !important; bottom:auto !important;
    margin:0 0 0 10px !important; flex:1 1 auto !important; min-height:0 !important; order:2 !important; }

  /* A5 底部三条：三档固定行高，互不重叠（宽 426 / 339 / 316 ⇒ 同排必撞，故改竖排） */
  .fs-status{ left:10px !important; right:auto !important; bottom:8px !important; width:auto !important;
    max-width:calc(100% - 104px) !important;
    white-space:nowrap !important; overflow:hidden !important; text-overflow:ellipsis !important; }
  .fs-legend{ left:10px !important; right:auto !important; bottom:56px !important; width:auto !important; }
  .fs-layers{ left:10px !important; right:10px !important; bottom:96px !important;
    transform:none !important; width:auto !important; justify-content:center !important; }

  /* A6 地图工具条：钉右下，与 status 同一排，靠 max-width 隔开 */
  .scr-maptools{ right:10px !important; bottom:8px !important; transform:scale(.8) !important;
    transform-origin:bottom right !important; }
}

/* ── 阶梯 B：≤640px（手机竖屏） ─────────────────────────────────────── */
@media (max-width:640px) and (min-height:561px){
  /* B1 顶栏更紧凑：字号/间距按 vw 走 → 视觉上「等比缩」 */
  .fs-top{ padding:6px 8px 10px !important; gap:4px 6px !important; }
  .scr-brand{ font-size:clamp(10px,2.9vw,12.5px) !important; padding:4px 10px !important; max-width:46vw !important; }
  .fs-title h1{ font-size:clamp(13px,4.2vw,17px) !important; letter-spacing:1px !important; }
  .fs-title p{ font-size:9px !important; letter-spacing:1px !important; }
  .scr-nav{ font-size:clamp(10px,2.8vw,12px) !important; padding:4px 10px !important; }
  .scr-gear{ width:28px !important; height:28px !important; }

  /* B2 KPI 胶囊：字号 vw 化 + 允许换行 */
  .fs-chips{ padding:0 8px 8px !important; gap:5px !important; }
  .fs-chip{ min-width:0 !important; padding:5px 9px !important; gap:6px !important; border-radius:9px !important; }
  .fs-chip i{ font-size:clamp(13px,4vw,19px) !important; }
  .fs-chip b{ font-size:clamp(13px,4.4vw,20px) !important; }
  .fs-chip span,.fs-chip *{ font-size:clamp(9px,2.6vw,10.5px) !important; }

  /* B3 面板列：改为**全宽 + 贴底 + 限高**，把地图让出来（关键）
        margin-top:auto 把面板推到流区底部；max-height 保证上方留出地图 */
  .fs-col{ width:auto !important; margin:0 8px !important; margin-top:auto !important;
    max-height:44vh !important; overflow-y:auto !important; gap:8px !important;
    overscroll-behavior:contain !important; }
  .fs-panel{ padding:9px 11px !important; border-radius:11px !important; }
  .fs-panel h3{ font-size:12px !important; margin-bottom:6px !important; }

  /* B4 底部三条：手机档再压一档（行高 26/26/34） */
  .fs-status{ bottom:6px !important; max-width:calc(100% - 96px) !important;
    font-size:10.5px !important; padding:4px 10px !important; }
  .fs-legend{ bottom:44px !important; font-size:10.5px !important; padding:4px 10px !important; gap:9px !important; }
  .fs-layers{ bottom:78px !important; }
  .fs-layers button{ padding:5px 11px !important; font-size:11px !important; letter-spacing:1px !important; }
  #scr{ padding-bottom:124px !important; }
}

/* ── 阶梯 D：弹层（设置 #fs-set / 设备详情 #fs-detail）在「窄屏 或 矮屏」改整屏/贴底弹层 ──
   原来的绝对坐标在手机上两头都跑出视口，**两个方向都实测过**：
     · #fs-detail：JS 里 left = max(328, min(rb.left-dw-8, innerWidth-dw-16))
                   ⇒ 393 宽屏上 left=328 + 宽 288 = 616，**右半边整块出界**
     · #fs-set   ：top:58 + 内容高约 380 ⇒ 393 高的横屏上 bottom≈438，**底部被切掉**
   ⇒ 手机上本来就不该用「贴某元素」的浮层，改成整屏（设置）/ 贴底（详情）。
   ⚠️ 必须用 !important 才压得住 JS 写进 style 的 inline left/top。 */
@media (max-width:640px), (max-height:560px){
  .fs-set{ left:8px !important; right:8px !important; top:8px !important; bottom:8px !important;
    width:auto !important; max-height:none !important; overflow:auto !important; }
  .fs-detail{ left:8px !important; right:8px !important; top:auto !important; bottom:8px !important;
    width:auto !important; max-height:74vh !important; overflow:auto !important; }
}

/* ── 阶梯 C：≤560px 高（桌面矮窗 / 超过 1000 宽的矮屏） ──────────────── */
/* ⚠️ v9.17.74：加 (min-width:1001px) —— 手机横屏（≤1000 宽）改走「整体等比缩放」，
      不再套本档；否则 40vh 会按**视口**高算，在 1920x1080 的缩放画布里只剩 157px。 */
@media (max-height:560px) and (min-width:1001px){
  /* C1 竖向一寸都不能浪费：标题条与日期让位，KPI 压成一行 */
  .fs-title{ display:none !important; }
  .scr-clock{ display:none !important; }
  .fs-top{ padding:6px 8px 8px !important; }
  .fs-chip{ min-width:0 !important; padding:4px 8px !important; }
  .fs-chip b{ font-size:15px !important; }
  .fs-chip i{ font-size:14px !important; }
  .fs-chip span,.fs-chip *{ font-size:9.5px !important; }
  .fs-col{ max-height:40vh !important; margin:0 8px !important; margin-top:auto !important; }
  /* C2 底部三条再压一档（行高 ~20），把高度让给面板 */
  .fs-status{ bottom:5px !important; font-size:10px !important; padding:2px 8px !important;
    max-width:calc(100% - 92px) !important; }
  .fs-legend{ bottom:31px !important; font-size:10px !important; padding:2px 8px !important; gap:8px !important; }
  .fs-layers{ bottom:57px !important; }
  .fs-layers button{ padding:3px 9px !important; font-size:10.5px !important; }
  .scr-maptools{ transform:scale(.72) !important; }
  #scr{ padding-bottom:96px !important; }
  /* C3 🔴 面板头/内边距压扁 —— 这不是审美，是**防丢数据**：
        横屏 393px 高时，若沿用桌面 padding(12/14)+13.5px 标题，
        面板 3 条 h3 就吃掉全部高度 ⇒ .scr-list 实测 clientHeight=0，
        列表内容看不见也滚不动（_m5_scroll.js 探针 S3 实测变红）。
        ⇒ 压到 padding:6px 9px + 标题 11.5px，让每块面板至少留出 30px 列表视口。 */
  .fs-panel{ padding:6px 9px !important; }
  .fs-panel h3{ font-size:11.5px !important; margin-bottom:4px !important; }
}

/* ── 阶梯 C2：横屏且够宽（>=641px）⇒ 面板是「窄左栏」，不遮地图，
      所以 40vh 的限高没必要，放开让它吃满可用高度，多露几行内容 ──────── */
@media (orientation:landscape) and (min-width:641px) and (max-height:560px){
  .fs-col{ max-height:none !important; }
}

/* ══════════════════════════════════════════════════════════════════════════
   v9.17.72 · 手机 / 平板端「左侧导航抽屉」（第 33 轮，2026-10-01）

   用户投诉原话：「数据 页面 左侧导航栏 也没有了」（附 393 宽真机截图）

   ── 只读取证（_m9_sidebar.js，本地 46 PASS / 0 FAIL，含桌面对照）──
     vw=393 / 375 / 412 / 768（**含 iPad 竖屏**）：
        .sidebar        display=none  0x0          ← 用户看到的"导航栏没有了"
        可见导航栏数     = 0                        ← 全站没有任何导航入口
        #sb-toggle      display=flex  33x26 可见    ← 按钮在，不是"没渲染"
        点 ☰ 之后  → class 由 "sidebar" 变成 "sidebar collapsed"（事件链是通的）
                   → 但 .sidebar 仍 display=none 0x0（**画面零变化**）⇒ 死按钮
     vw=1280（对照）：.sidebar 216x772 可见，点 ☰ → 64x772（桌面本来就是好的）

   ── 根因（一个只写了一半的功能，两条历史遗留叠在一起）──
     ① base.css:521-525  @media (max-width:900px){ .sidebar{display:none} }
        —— 既有规则，v9.17.69 备份里行号完全相同（**不是 v9.17.70/71 引入的**）；
     ② app.js:295  tgl.onclick = () => q('#sb').classList.toggle('collapsed')
        —— 只切 width:216→64，而 display:none 下 width **完全无效**。
     ⇒ CSS 把侧栏打掉了，JS 却只会改宽度。手机端因此**没有任何导航入口**
       （用户只能靠改 URL / 重新输地址）。☰ 按钮从诞生起就是死按钮。

   ── 改法：补完它（窄屏＝离屏抽屉；宽屏一字不改）──
     ≤900px ：.sidebar 变 position:fixed 离屏抽屉，点 ☰ 从左滑入 + 半透明遮罩；
              遮罩 / ESC / 选中菜单项 自动收起；打开时锁底层滚动（body.sb-lock）。
     ≥901px ：无任何变化（本块整体在 max-width:900px 内）。

   ⚠️ 为什么选择器要带 body[data-layout] 前缀（而不是裸 .sidebar）：
      dock 版式有 `body[data-layout="dock"] .sidebar{top:14px;height:calc(-28px + 100vh);
      border-radius:24px;box-shadow:…}` —— 特异性 (0,2,1) 高于 `.sidebar` (0,1,0)，
      用裸 .sidebar 会被它覆盖（抽屉会变成"离屏的悬浮卡片"）。
      这里用 (0,2,1) 同级 + 排在其后 ⇒ 稳定胜出，且不必上 !important。
   ══════════════════════════════════════════════════════════════════════════ */
.sb-backdrop{ display:none; }   /* 桌面：这个元素彻底不参与渲染 */

@media (max-width:900px){
  body[data-layout] .sidebar{
    display:flex;
    position:fixed; left:0; top:0; bottom:0;
    height:100vh; height:100dvh;
    width:min(82vw,286px);
    border-radius:0; border:0; border-right:1px solid var(--c-border);
    background:var(--c-surface);
    z-index:900;
    transform:translateX(-104%);
    transition:transform .26s cubic-bezier(.22,.61,.36,1);
    box-shadow:0 12px 48px rgba(0,0,0,.26);
  }
  body[data-layout] .sidebar.sb-open{ transform:none; }
  body[data-layout] .sidebar.collapsed{ width:min(82vw,286px); }

  body[data-layout] .sb-backdrop{
    display:block;
    position:fixed; inset:0; z-index:899;
    background:rgba(8,14,24,.44);
    opacity:0; visibility:hidden;
    transition:opacity .26s, visibility .26s;
    -webkit-tap-highlight-color:transparent;
  }
  body[data-layout] .sb-backdrop.on{ opacity:1; visibility:visible; }
  body.sb-lock{ overflow:hidden; }
}

@media (max-width:900px) and (prefers-reduced-motion:reduce){
  body[data-layout] .sidebar{ transition:none; }
  body[data-layout] .sb-backdrop{ transition:none; }
}

/* ══════════════════════════════════════════════════════════════════════════
   v9.17.73 · 手机竖屏「地图优先」—— 把被面板吃掉的视口还给地图（第 34 轮）

   用户原话：「屏幕全部挡住了  地图也看不到 了」（附 393 宽真机截图：从上到下
   被「站点分布 / 实时目标 / 告警日志」三条面板 + 顶部标题 chips + 底部三条按钮铺满）

   ── 只读量化（_m16_mapcover.js，tag=base34，30x60 网格 + elementFromPoint）──
     · 393x852 地图可见 **46.4%** ｜ .fs-col 独占 **42.2%** 视口高 ｜ .fs-top 15.1% + .fs-chips 6.7%
     · 375x667 地图可见 **30.0%** ｜ .fs-col 42.1% ｜ .fs-top 19.0%
     · 412x915 地图可见 **39.4%** ｜ .fs-col 42.3%
     · 768x1024 63.2% ／ 1280x800 71.9%（平板与桌面是好的，不要动）
   ⇒ 病根：手机竖屏下 .fs-col 是**正常流 + 整行全宽**，44vh 的限高把地图挤没。

   🔴 这是第 32 轮埋的坑：那一轮我自拟的验收判据只有「零重叠 / 不出视口」，
      **没有一条断言"地图还看得见"** ⇒ 探针 29/0 全绿，但把地图换没了。
      典型的「自拟判据驱动的零和取舍」。本轮把「地图可见覆盖率」变成一等判据。

   ── 改法（只动 ≤700px 且竖屏；平板/桌面/横屏一字不改）──
     ① 顶部瘦身：.fs-top 内边距/行距压扁、品牌 12.5px、主标题降到 13px 一行、
        副标题（纯英文装饰行）隐藏、chips 收紧 ⇒ 顶部从 ~186px 降到 ~106px；
     ② 面板列改「半宽浮层」：width 66%/max 300px（原 100% 整行），仍贴底，
        限高 30vh 内部滚动 ⇒ 右侧露出整条竖向地图；
     ③ 底部三条与 #scr padding 收紧。

   ⚠️ 代价（必须说清楚）：
     · 面板变窄（377px → ~250px）且限高 30vh ⇒ 面板内容要**滚动**才能看全；
     · 副标题一行在手机上不再显示（纯装饰，无数据）。
     这是**视口面积的零和交换**：地图多一点，面板就少一点。
     目标「地图 ≥ 55%」为**我方自拟**（用户只说「地图也看不到了」，未给具体数值）。

   ⚠️ 本块用 (max-width:700px) and (orientation:portrait) 圈定「手机竖屏」：
     横屏矮屏（852x393 实测 63.6%）与 iPad 竖屏 768（63.2%）本来就够，
     不进来掺和，避免把已经合格的场景改坏。
   ══════════════════════════════════════════════════════════════════════════ */
@media (max-width:700px) and (orientation:portrait){

  /* ① 顶部瘦身 */
  .fs-top{ padding:5px 8px 4px !important; row-gap:3px !important; }
  /* 品牌列收窄到 36vw：实测 48vw 时 .scr-brand(188) + .fs-right(201) 刚好挤不下，
     把「数据大屏 + 齿轮」整排挤到第二行，白占 40px 视口高（.fs-top 100px = 三行）。
     收窄后两者同行，.fs-top 回到两行 ≈ 60px。 */
  .scr-brand{ max-width:36vw !important; min-width:0 !important; }
  .scr-brand b{ font-size:12.5px !important; }
  .fs-title h1{ font-size:13px !important; line-height:1.15 !important; letter-spacing:1px !important; }
  .fs-title p{ display:none !important; }
  /* chips：5 个中文 chip 在 375 宽放不下 —— 横滑会让最后一个 chip 默认看不见
     （实测 right=422 > 393，被 _m4_overlap 判「出视口」）。横滑虽然不丢信息但不可接受。
     ⇒ 正解：把它们**真的缩到一行放得下**。图标 11 / 数字 13 / 标签 10 / padding 3x6，
       单个约 67px ⇒ 5x67 + 4x3 = 347 < 375，一行搞定，且总高比原来 50px 更矮。 */
  .fs-chips{ margin:3px 0 0 !important; gap:3px !important;
             flex-wrap:wrap !important; justify-content:center !important; }
  .fs-chip{ flex:0 1 auto !important; min-width:0 !important; height:auto !important;
            padding:3px 5px !important; gap:3px !important; }
  .fs-chip i{ font-size:10px !important; }
  .fs-chip b{ font-size:12.5px !important; }
  .fs-chip span{ font-size:9.5px !important; white-space:nowrap !important; }

  /* ② 面板列：半宽 + 限高 + 内部滚动（让出右侧整条竖向地图） */
  .fs-col{ width:66% !important; max-width:300px !important;
           margin:0 8px !important; margin-top:auto !important;
           max-height:30vh !important; overflow-y:auto !important; gap:6px !important; }
  .fs-panel{ padding:6px 9px !important; border-radius:10px !important; min-width:0 !important; }
  .fs-panel h3{ font-size:11px !important; margin-bottom:3px !important; }

  /* ③ 底部三条与底部预留收紧 */
  .fs-layers{ bottom:62px !important; }
  .fs-legend{ bottom:34px !important; }
  .fs-status{ bottom:4px !important; }
  #scr{ padding-bottom:108px !important; }

  /* ④ 底部工具条：**按钮内部文字换行**（不是 flex 换行 —— .fs-layers 是 nowrap 的 flex，
     是每个 button 里的 4 个汉字自己折成两行），整条高度从 27px 涨到 42px（375 实测）。
     既难看（「卫星地 / 图」）又白吃 15px 视口 ⇒ 禁换行 + 收内边距与字距。
     算过账：5 键 × (4 汉字 × 11px + 2×6px padding) ≈ 276px + 4 个 1px 分隔 ≈ 280px，
     最窄的 320 宽机型可用 ~300px 也放得下，不会横向溢出。 */
  .fs-layers button{ white-space:nowrap !important; padding:5px 6px !important;
                     font-size:11px !important; letter-spacing:0 !important; }
}


/* ══════════════════════════════════════════════════════════════════════════
   v9.17.74 · 手机横屏「整个大屏等比缩放」（第 35 轮，2026-10-01）

   用户原话：「可以等比例缩放显示整个屏幕  你在微调下细节么」
   ⇒ 我给了 4 个选项（竖屏等比 / 竖屏手机版面等比 / 只微调细节 / 强制竖屏等比）并附实测数字，
     用户选定 **「横屏等比缩放」**：竖屏保持 v9.17.73 响应式不动，横屏时把 1920×1080
     大屏整体等比缩小、一屏看全。

   ── 为什么这次能做，而 v9.17.69 的 B 档「塌了」（见上面 L1265-1268 的历史结论）──
   历史结论写的是「transform 只做视觉缩放、不重算地图视口；且 fixed 与 transform 叠加
   产生新包含块 ⇒ 实测画面直接塌掉（右侧大片空白）」。
   🔴 **真实病根是：当时只加了 transform，没有改 #scr 自身的尺寸** ——
      #scr 仍是「视口」大小（393px 宽），于是内部 layout 按 393 排完再被整体缩小
      ⇒ 左边一条内容 + 右边整片空白，看起来就像「画不出来」。
   ✅ 正解 ＝ **先把 #scr 变成 1920×1080（让内部 absolute 布局按 1920 计算），再整体 scale**。
      此时 transform 产生的「新包含块」不是 bug 而是**我们要的**：
      #fs-set / #fs-detail / .scr-alarm 这些 fixed/absolute 后代本来就该跟着大屏一起缩。

   ── 实测（_m17_scalemode.js，本地 + 线上）──
     852×393（iPhone 14 Pro 横屏）：scale = min(852/1920, 393/1080) = 0.36389
        ⇒ 内容 698×393，居中，左右各留 77px 黑边；整屏零滚动、零重叠
     基准 12px 字 ⇒ 4.37 CSS px ⇒ **13.1 物理像素**（DPR3）—— 结构完整、可读
     对照（为什么竖屏不套这一档）：393 宽下 scale=0.2047
        ⇒ 内容只有 393×221（**占屏高 26%**）、字 7.4 物理像素 ⇒ **不可读**，故竖屏不启用。

   ⚠️ 圈定条件 (orientation:landscape) and (max-height:560px) and (max-width:1000px)
      只圈「手机横屏」：
        · 852×393 ✓ ／ 932×430 ✓ ／ 740×360 ✓
        · iPad 横屏 1024×768：高 768 > 560 ⇒ **不命中**（保持 ≤1180 响应式）
        · iPad 竖屏 768×1024、手机竖屏 393×852：非 landscape ⇒ 不命中
        · 桌面矮窗 1280×500：宽 1280 > 1000 ⇒ **不命中**（保持原样，⛔ 不给桌面加黑边）
   ⚠️ 为了让内部真的是「桌面 1920 布局」，上面 4 个窄屏档已加 (min-height:561px) 排除本档：
        L642 / L1221 / L1353（≤1180）与 L1408（≤640）；L1456（≤560 高）已加 (min-width:1001px)。
   ⚠️ 不动 #scr 的 flex/absolute 骨架，不动任何 JS / HTML。
   🔴 **踩过的坑（_m18_cssprobe.js 实测）**：`scale()` 只接受 `<number>`，而
      `calc(100vw / 1920)` 算出来是**长度**（vh/vw 除以纯数字仍是长度）⇒ 整条 transform
      被浏览器**静默丢弃**，表现为「媒体查询命中了、width:1920px 也生效了、但 scale 是 1」
      ⇒ #scr 以一个 1920x1080 的原尺寸框挂在 half-viewport 中点，只露出一角。
      ✅ 正解 = 把除数写成**带单位**，让「长度 ÷ 长度 = 纯数」：
         `min(100vw / 1920px, 100vh / 1080px)` → 实测 computed = 0.363889（对）
         `min(calc(100vw / 1920), calc(100vh / 1080))` → CSS.supports = false（错）
   ══════════════════════════════════════════════════════════════════════════ */
@media (orientation:landscape) and (max-height:560px) and (max-width:1000px){
  /* 黑边底色（.fs-col 等浮层外露出的部分） */
  html,body{ overflow:hidden !important; background:#05070c !important; }
  #scr{
    left:50% !important; top:50% !important; right:auto !important; bottom:auto !important;
    width:1920px !important; height:1080px !important;
    padding:0 !important; margin:0 !important;
    transform:translate(-50%,-50%) scale(min(100vw / 1920px, 100vh / 1080px)) !important;
    transform-origin:center center !important;
  }
}

/* ============================================================
   v9.17.80 账号体系 —— 账号管理 / 登录记录 两页专用样式
   只新增 5 个类（.ac-note/.ac-perm/.gp/.ac-mono/.ac-u），
   ⛔ 不改任何既有选择器（避免影响其他页面的既有排版与对比度）。
   ⚠️ 颜色全部走主题变量：本项目只有 --c-primary/-cyan/-ok/-warn/-danger/-info/-purple
      与它们的 -soft / -text 配套；⛔ 没有 --c-blue / --c-surface-3 这类名字。
   ============================================================ */

/* 口径说明条（账号页 / 登录记录页顶部） */
.ac-note{
  background:var(--c-primary-soft);
  border:1px solid var(--c-border);
  border-left:3px solid var(--c-primary);
  border-radius:10px;
  padding:12px 14px;
  margin-bottom:16px;
  font-size:13px;
  line-height:1.75;
  color:var(--c-text);
}
.ac-note>b{display:block;margin-bottom:2px;color:var(--c-primary-text)}
.ac-note a{color:var(--c-primary-text);text-decoration:none;border-bottom:1px solid var(--c-primary-soft);cursor:pointer}
.ac-note a:hover{border-bottom-color:var(--c-primary-text)}
.ac-note code{
  font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
  font-size:12px;background:var(--c-surface);border:1px solid var(--c-border);
  border-radius:4px;padding:1px 5px;color:var(--c-text-2);
}

/* 权限矩阵分组标题 */
.ac-perm .gp{
  font-size:12px;font-weight:600;letter-spacing:.4px;color:var(--c-text-2);
  margin:10px 0 6px;
}
.ac-perm>div:first-child .gp{margin-top:0}

/* 权限矩阵：按分组铺 chip（每组一行，自动换行） */
.ac-perm .row{display:flex;flex-wrap:wrap;gap:7px}
.ac-perm label{
  display:inline-flex;align-items:center;gap:7px;
  font-size:12.5px;line-height:1.2;
  padding:6px 12px;border-radius:980px;
  border:1px solid var(--c-border);background:var(--c-surface);color:var(--c-text-2);
  cursor:pointer;user-select:none;
  transition:border-color .16s,background .16s,color .16s;
}
.ac-perm label:hover{border-color:var(--c-primary)}
.ac-perm label.on{
  background:var(--c-primary-soft);border-color:var(--c-primary);
  color:var(--c-primary-text);font-weight:600;
}
/* 原生 checkbox 重绘成小方框：⛔ 不用 opacity:0 隐藏 —— 那会把焦点环一起吃掉（无障碍回归） */
.ac-perm input[type=checkbox]{
  appearance:none;-webkit-appearance:none;
  width:15px;height:15px;flex:none;margin:0;
  border:1.5px solid var(--c-border);border-radius:4px;
  background:var(--c-surface);cursor:pointer;position:relative;
  transition:background .15s,border-color .15s;
}
.ac-perm input[type=checkbox]:checked{background:var(--c-primary);border-color:var(--c-primary)}
.ac-perm input[type=checkbox]:checked::after{
  content:"";position:absolute;left:4px;top:1px;width:5px;height:9px;
  border:solid #fff;border-width:0 2px 2px 0;transform:rotate(45deg);
}
.ac-perm input[type=checkbox]:focus-visible{outline:2px solid var(--c-primary);outline-offset:2px}

/* IP / 时间等「要比对字符」的列：等宽 + 数字对齐 */
.ac-mono{
  font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
  font-size:12px;font-variant-numeric:tabular-nums;color:var(--c-text-2);
}

/* UA 原文：单行截断，完整值放 title */
.ac-u{
  display:inline-block;max-width:240px;overflow:hidden;text-overflow:ellipsis;
  white-space:nowrap;vertical-align:bottom;
  font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
  font-size:11.5px;color:var(--c-text-3);
}

/* ── 指令查询：固件命令表（v9.17.144）──────────────────────────────────────
   `table.tbl` 的基础样式是 nowrap + max-width + ellipsis（列表页要单行截断），
   但命令表的「作用 / 备注」是整句话，被截断就看不清了 ⇒ 用修饰类放开换行。
   同时给分组标题行一个底色，让 46 条命令按类目分段可读。 */
.tbl.fw-cmd td{white-space:normal;max-width:none;overflow:visible;text-overflow:clip;vertical-align:top;line-height:1.65}
.tbl.fw-cmd td:first-child code{font-weight:600;font-size:12.5px}
.tbl.fw-cmd tr.fw-grp>td{
  background:var(--c-surface-2);color:var(--c-text-2);
  font-size:12px;font-weight:600;letter-spacing:.3px;padding:8px 14px;
  position:sticky;top:0;z-index:1;
}
