/* 通用组件：场景卡片、列表、表单、按钮、地图浮层、状态灯、弹窗 */
  /* ---- 场景列表（每个场景占满整行 + 底部虚线新建）---- */
  #sceneGrid{display:flex;flex-direction:column;gap:8px;margin-top:8px}
  .scene-card{position:relative;border:1px solid var(--line);border-radius:9px;background:var(--panel2);
    padding:10px 11px;cursor:pointer;transition:.15s;display:flex;align-items:center;gap:10px}
  .scene-card:hover{border-color:var(--accent2)}
  .scene-card .sc-info{flex:1;min-width:0}
  .scene-card .sc-name{display:flex;align-items:center;gap:6px;font-size:13px;font-weight:600;color:var(--txt);min-width:0}
  .scene-card .sc-nm{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}
  .scene-card .sc-name .renameinput{flex:1;min-width:0;font-size:13px;font-weight:600;
    padding:1px 5px;background:var(--input-bg);border:1px solid var(--accent2);border-radius:5px;color:var(--txt)}
  .scene-card .sc-name .renameinput:focus{outline:none}
  .scene-card .sc-meta{font-size:10px;color:var(--muted);margin-top:2px}
  .scene-card .sc-ops{display:flex;gap:8px;flex:none}
  .scene-add{border:1.5px dashed var(--line);border-radius:9px;background:transparent;cursor:pointer;
    display:flex;align-items:center;justify-content:center;gap:8px;
    padding:12px 0;color:var(--muted);transition:.15s}
  .scene-add:hover{border-color:var(--accent2);color:var(--accent2)}
  .scene-add .plus{font-size:20px;line-height:1}
  .scene-add .txt{font-size:12px}
  /* 场景内部编辑视图头部 */
  .scene-editor-head{display:flex;align-items:center;gap:8px;margin-bottom:8px}
  .scene-editor-head .back{flex:none;padding:4px 9px}
  .scene-editor-head .nm{flex:1;font-size:13px;font-weight:600;color:var(--txt);
    white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .sceneview{display:none}
  .sceneview.on{display:block}
  .scroll{flex:1;overflow-y:auto;padding:10px 12px}
  .scroll::-webkit-scrollbar{width:8px}
  .scroll::-webkit-scrollbar-thumb{background:var(--scroll);border-radius:4px}
  .card{background:var(--panel2);border:1px solid var(--line);border-radius:8px;
    margin-bottom:10px;overflow:hidden}
  /* 「载入场景」的下拉是绝对定位的，会被卡片的 overflow:hidden 连同滚动条一起裁掉
     （看着就是只露两三行、且滚不动）。这张卡片里只有它会超出边界，故单独放开。 */
  .card[data-card="sim"]{overflow:visible}
  .card>.head{display:flex;align-items:center;gap:8px;padding:9px 11px;cursor:pointer;
    user-select:none;font-size:13px;font-weight:600}
  .card>.head .dot{width:8px;height:8px;border-radius:50%}
  .card>.body{padding:4px 11px 11px}
  .card>.body.collapsed{display:none}
  label.fld{display:block;font-size:11px;color:var(--muted);margin:8px 0 3px}
  input,select,textarea{width:100%;background:var(--input-bg);border:1px solid var(--line);
    color:var(--txt);border-radius:6px;padding:6px 8px;font-size:12px;font-family:inherit}
  input:focus,select:focus,textarea:focus{outline:none;border-color:var(--accent2)}
  /* 可搜索下拉组合框（载入场景模糊搜索）*/
  .combo{position:relative}
  .combo>input{padding-right:24px}
  .combo .combo-caret{position:absolute;right:8px;top:50%;transform:translateY(-50%);
    color:var(--muted);font-size:11px;pointer-events:none}
  /* 锁定（推演运行中不可切换场景，见 updateRunSceneGate，js/scene.js）：
     置灰点不了即可，不再弹窗说明——那个下拉一拿到焦点就展开，弹窗会自己把自己再触发一遍。 */
  .combo>input.locked{opacity:.5;cursor:not-allowed}
  /* 一次露 5 行、其余滚动：--co-h 为一行的实高（行高 16 + 上下内边距 12 + 分隔线 1），
     行数写在 calc 里，改几行只动这一个数字；+2px 是列表自身的上下边框（border-box）。 */
  .combo-list{position:absolute;left:0;right:0;top:calc(100% + 2px);z-index:60;
    --co-h:29px;max-height:calc(var(--co-h) * 5 + 2px);
    overflow-y:auto;background:var(--panel2);border:1px solid var(--line);
    border-radius:6px;box-shadow:0 8px 24px rgba(0,0,0,.35)}
  .combo-list.hide{display:none}
  .combo-opt{padding:6px 9px;font-size:12px;line-height:16px;color:var(--txt);cursor:pointer;
    border-bottom:1px solid var(--line);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .combo-opt:last-child{border-bottom:none}
  .combo-opt:hover,.combo-opt.active{background:var(--panel);color:var(--accent2)}
  .combo-opt.sel{color:var(--accent);font-weight:600}
  .combo-opt .co-sub{color:var(--muted);font-size:11px;font-weight:400}
  .combo-empty{padding:8px 9px;font-size:12px;color:var(--muted);text-align:center}
  .combo-list::-webkit-scrollbar{width:8px}
  .combo-list::-webkit-scrollbar-thumb{background:var(--scroll);border-radius:4px}
  .row{display:flex;gap:8px}
  .row>*{flex:1}
  button{cursor:pointer;border:none;border-radius:6px;padding:7px 10px;font-size:12px;
    font-weight:600;color:var(--btn-txt);background:var(--accent);transition:.15s;font-family:inherit}
  button:hover{filter:brightness(1.1)}
  button:focus{outline:none}
  button:focus-visible{outline:2px solid var(--accent2);outline-offset:1px}
  button.sec{background:var(--btn-sec);color:var(--txt)}
  button.ghost{background:transparent;border:1px solid var(--line);color:var(--muted)}
  button.danger{background:var(--danger);color:var(--btn-txt)}
  button.warn{background:var(--warn);color:var(--btn-txt)}
  /* 测距进行中 / 跟随本船开启：按钮转青色实底（覆盖 .ghost 的透明底/灰字）*/
  #measureBtn.on,#focusShipBtn.on{background:var(--accent);color:var(--btn-txt);border-color:transparent}
  /* 回放时间轴：完全自绘（不用原生 range —— 其滑块块两端内缩，0/末尾贴不到边，
     且滑块块被压扁后无法拖动）。指针位置按轨道宽度换算比例，与时间严格对应。 */
  /* 录制库列表（录制回放 / 场景复现 共用）*/
  .reclib{margin-top:6px}
  .reclib .reclib-head{font-size:11px;color:var(--muted);margin-bottom:4px}
  .reclib .reclib-notice{font-size:11px;color:var(--warn);margin-bottom:4px}
  .reclib .item .sub{font-size:10px;color:var(--muted);margin-top:2px;white-space:nowrap;
    overflow:hidden;text-overflow:ellipsis}
  /* 徽标不参与挤压：栏宽不够时该让位的是包名（下面那条给 b 加了省略号），
     不是把「已导出」这三个字压成两行 */
  .reclib .item .badge{margin-left:4px;flex:none;white-space:nowrap}
  .reclib .item .trow b{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}
  /* 体积排在副行开头：放末尾会被省略号截掉（推演库副行超宽），放标题行则挤扁包名与徽标 */
  .reclib .item .sub .sz{font-variant-numeric:tabular-nums}
  .reclib .reclib-head .lk{color:var(--warn)}
  /* 整行即选择控件：点行选中（行内导出/删除按钮除外）*/
  .reclib .item.sel{cursor:pointer;transition:border-color .15s,background .15s}
  .reclib .item.sel:hover{border-color:var(--accent2)}
  /* 当前已选中的那份：整行高亮，与列表里其余候选区分开（不再另挂「当前」徽标，见 renderLibList）*/
  .reclib .item.on{border-color:var(--accent2);background:var(--badge-bg);cursor:default}
  /* 回放/复现运行期间：除当前那份外都不可选（按钮仍可用，故只压暗信息区）*/
  .reclib .item.lock{cursor:not-allowed}
  .reclib .item.lock:hover{border-color:var(--line)}
  .reclib .item.lock .meta{opacity:.5}
  .reclib .empty{font-size:11px;color:var(--faint);padding:8px 4px;text-align:center}
  /* 复选行（如「同时录制本次运行」）*/
  .chk{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--txt);cursor:pointer;user-select:none}
  .chk input{width:14px;height:14px;margin:0;accent-color:var(--accent);cursor:pointer}
  .chk input:disabled,.chk input:disabled+span{cursor:not-allowed;opacity:.55}
  .rp-bar{position:relative;height:16px;margin-top:2px;cursor:pointer;touch-action:none;outline:none}
  .rp-bar .rp-track{position:absolute;left:0;right:0;top:6px;height:4px;border-radius:2px;
    background:var(--line);overflow:hidden}
  .rp-bar .rp-fill{height:100%;width:0;background:var(--accent);border-radius:2px}
  .rp-bar .rp-knob{position:absolute;top:3px;left:0;width:10px;height:10px;margin-left:-5px;
    border-radius:50%;background:var(--accent);border:2px solid var(--panel2);
    box-shadow:0 0 0 1px var(--accent);transition:transform .1s}
  .rp-bar:hover .rp-knob,.rp-bar.dragging .rp-knob{transform:scale(1.25)}
  .rp-bar:focus-visible .rp-track{outline:2px solid var(--accent2);outline-offset:2px}
  /* 只读进度条（场景复现）：没有滑块，也不该给出「可以拖」的手感 */
  .rp-bar.ro{cursor:default}
  /* ---- 回放时间轴 / 场景复现进度：地图底部通栏（#rpDock / #scDock）----
     地图浮层，选中一份录制（场景）后出现；播放/停止/倍速、开始/停止复现仍在右侧面板，
     当前是哪份录制由左上角状态栏 #modeBar 的第二行显示。
     bottom:26px 让开高德左下角的 logo/版权条。
     两条通栏同位置，但互斥出现（见 renderReplayDock / scenDockVisible）。 */
  #rpDock,#scDock{position:absolute;left:14px;right:14px;bottom:26px;z-index:46;
    padding:8px 12px 10px;border-radius:10px;border:1px solid var(--line);
    background:var(--overlay);backdrop-filter:blur(6px);
    box-shadow:0 6px 22px rgba(0,0,0,.28)}
  #rpDock.hide,#scDock.hide{display:none}
  /* 右侧内容区是浮在地图上的（#sideContent 绝对定位），展开时会盖住地图右侧 --pane-w 宽。
     时间轴据此让开，右端停在面板左缘，不再压到面板上。
     图标竖条 #sideRail 是 flex 项、本来就不与地图重叠，故只需避让内容区这一段。 */
  #app:has(#side:not(.collapsed)) #rpDock,
  #app:has(#side:not(.collapsed)) #scDock{right:calc(var(--pane-w) + 14px)}
  #rpDock .rpd-row,#scDock .rpd-row{display:flex;align-items:baseline;gap:10px;font-size:11px;
    color:var(--muted);font-variant-numeric:tabular-nums;margin-bottom:2px}
  #rpDock .rpd-t,#scDock .rpd-t{flex:none;white-space:nowrap}   /* 录制开始/结束墙上时刻 */
  #rpDock .rpd-mid,#scDock .rpd-mid{flex:1;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  #rpDock .rpd-mid b,#scDock .rpd-mid b{color:var(--txt);font-size:12px}   /* 当前位置 */
  #rpDock .rp-bar,#scDock .rp-bar{margin-top:0}
  #scDock .rp-fill{background:var(--accent2)}   /* 与「场景复现」面板同色，和回放的进度区分开 */
  button:disabled{opacity:.45;cursor:not-allowed}
  .btnrow{display:flex;gap:6px;flex-wrap:wrap}
  .btnrow>button{flex:1;white-space:nowrap}
  .list{margin-top:8px}
  .item{display:flex;align-items:center;gap:8px;padding:7px 8px;border:1px solid var(--line);
    border-radius:6px;margin-bottom:6px;background:var(--item-bg);font-size:12px}
  .item .meta{flex:1;min-width:0}
  .item .meta .trow{display:flex;align-items:center;gap:6px;min-width:0}
  .item .meta .t{font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}
  .item .meta .s{font-size:10px;color:var(--muted);margin-top:2px}
  /* 列表内联重命名输入框（Windows 式重命名：点重命名→可编辑并全选→回车完成）*/
  .item .meta .renameinput{flex:1;min-width:0;font-weight:600;font-size:12px;
    padding:1px 5px;background:var(--input-bg);border:1px solid var(--accent2);border-radius:5px;color:var(--txt)}
  .item .meta .renameinput:focus{outline:none}
  .item .mini,.mini{padding:3px 6px;font-size:11px}
  .swatch{width:10px;height:10px;border-radius:3px;flex:none}
  .hint{font-size:11px;color:var(--muted);line-height:1.5;margin:6px 0 0}
  .empty{font-size:11px;color:var(--faint);text-align:center;padding:10px 0}
  .badge{display:inline-block;font-size:10px;padding:1px 6px;border-radius:10px;
    background:var(--badge-bg);color:var(--accent2);border:1px solid var(--badge-line)}
  /* drawing banner over map */
  #banner{position:absolute;top:14px;left:50%;transform:translateX(-50%);z-index:50;
    background:var(--overlay);border:1px solid var(--accent);border-radius:8px;
    padding:9px 14px;font-size:13px;display:none;align-items:center;gap:12px;color:var(--txt);
    box-shadow:0 6px 24px rgba(0,0,0,.4)}
  #banner.show{display:flex}
  #banner b{color:var(--accent)}
  #banner button{padding:5px 10px;font-size:12px}
  /* 横幅上的「取消」：跟随主题取文字色（原先写死白色，浅色底图上看不清）*/
  #banner button.ghost{color:var(--txt);border-color:var(--line);background:transparent}
  #banner button.ghost:hover{background:var(--btn-sec)}
  /* 左上角运行状态栏：常驻显示当前运行态（空闲/场景运行/场景复现/录制回放 + 录制叠加）*/
  /* z-index 低于 #banner：与顶部居中的绘制横幅同处一行，窄窗口下让横幅盖在上面
     （横幅是操作指引，比状态更需要看清）；宽度也收窄，正常窗口下两者互不相碰 */
  #modeBar{position:absolute;left:14px;top:14px;z-index:45;
    display:flex;flex-direction:column;align-items:stretch;gap:3px;max-width:min(34vw,420px);
    background:var(--overlay);border:1px solid var(--line);border-radius:8px;
    padding:7px 12px;font-size:12px;color:var(--txt);
    box-shadow:0 4px 16px rgba(0,0,0,.32);pointer-events:none;user-select:none}
  #modeBar .mb-main{display:flex;align-items:center;gap:8px;min-width:0}
  /* 第二行：当前回放的录制文件。文件名长，允许折行整条读全（状态栏 pointer-events:none，
     没法靠 title 悬停补看），最多两行、超出再省略，避免一份长文件名把状态栏撑得过高 */
  /* 多行省略号目前只有 -webkit- 这一套写法能跨浏览器生效（Chrome/Edge/Safari/Firefox 都认它）；
     line-clamp 是新标准属性，一并写上，新版浏览器走它、旧版继续走带前缀的那条。
     -webkit-box-orient 没有对应的标准属性，编辑器对它的 vendorPrefix 提示可忽略。 */
  #modeBar .mb-file{color:var(--txt);font-size:11px;line-height:1.45;word-break:break-all;
    display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;line-clamp:2;overflow:hidden;
    padding-top:3px;border-top:1px solid var(--line)}
  #modeBar .mb-file:empty{display:none}
  /* 回放/场景复现放宽一点：要装下「录制文件名 · N 帧」这一行 */
  #modeBar.replay,#modeBar.scen{max-width:min(44vw,520px)}
  #modeBar .mb-dot{width:9px;height:9px;border-radius:50%;flex:none;background:var(--faint)}
  #modeBar .mb-txt{flex:none;font-weight:700;letter-spacing:.3px;white-space:nowrap}
  #modeBar .mb-sub{color:var(--muted);font-variant-numeric:tabular-nums;
    white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  #modeBar .mb-sub:empty{display:none}
  /* 录制是叠加态：与主状态并存，单独一段用分隔线隔开 */
  #modeBar .mb-rec{display:none;align-items:center;gap:5px;flex:none;white-space:nowrap;
    font-weight:600;color:var(--danger);padding-left:9px;border-left:1px solid var(--line)}
  #modeBar.recording .mb-rec{display:flex}
  #modeBar .mb-rec i{width:8px;height:8px;border-radius:50%;background:var(--danger);
    font-style:normal;animation:mbBlink 1.2s ease-in-out infinite}
  @keyframes mbBlink{0%,100%{opacity:1}50%{opacity:.25}}
  /* 各状态配色：与右侧功能栏对应图标同色 */
  #modeBar.idle   .mb-txt{color:var(--muted)}
  #modeBar.run    .mb-dot{background:var(--accent)}   #modeBar.run    .mb-txt{color:var(--accent)}
  #modeBar.scen   .mb-dot{background:var(--accent2)}  #modeBar.scen   .mb-txt{color:var(--accent2)}
  #modeBar.replay .mb-dot{background:var(--danger)}   #modeBar.replay .mb-txt{color:var(--danger)}
  #modeBar.deduc  .mb-dot{background:var(--purple)}   #modeBar.deduc  .mb-txt{color:var(--purple)}
  /* 非空闲时状态点做呼吸动画，余光即可分辨「在跑」与「空闲」*/
  #modeBar:not(.idle) .mb-dot{animation:mbPulse 1.6s ease-in-out infinite}
  /* 只是「选中了一份录制」还没开播：显示同一条栏目让人看见选的是哪份，
     但不呼吸、点子压暗——它不是在跑的状态，不该和真正回放中长得一样 */
  #modeBar.sel .mb-dot{animation:none;opacity:.45}
  #modeBar.sel .mb-txt{opacity:.7}
  @keyframes mbPulse{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.45;transform:scale(.82)}}
  /* 实时日期时间面板（左栏顶部，本地时区，精确到毫秒）*/
  #rtClock{background:var(--overlay);border:1px solid var(--line);border-radius:8px;
    padding:8px 12px;margin-bottom:8px;text-align:center;color:var(--txt)}
  #rtClock .rt-date{font-size:11px;color:var(--muted);letter-spacing:.5px}
  #rtClock .rt-time{font-size:22px;font-weight:700;color:var(--accent);
    font-variant-numeric:tabular-nums;line-height:1.15;margin-top:2px}
  #rtClock .rt-ms{font-size:14px;font-weight:600;color:var(--accent2);margin-left:1px}
  /* ---- 「运行控制」：四个模块共用的开始‑结束按钮，固定在画面左下角 ----
     它是左栏 #sideLeft 的最后一个孩子、排在 .scroll(flex:1) 之后，
     故不随船体状态那一大堆内容滚动，始终停在左下方。
     不贴到最底边：留 26px 下边距把它托起来一点，与顶部的 #rtClock 一样是块独立卡片
     （左右 12px 与 .scroll 的内边距对齐，视觉上同一条竖线）。
     模块名 + 状态徽标 + 一个大按钮 + 一行说明。按钮底色随模块走
     （与右侧图标竖条 #sideRail 的选中色一致），运行中一律转红。 */
  #mainCtrl{flex:none;margin:4px 12px 60px;padding:9px 11px 10px;
    border:1px solid var(--line);border-radius:8px;background:var(--overlay)}
  #mainCtrl .mc-head{display:flex;align-items:center;gap:8px;margin-bottom:7px}
  #mainCtrl .mc-mod{flex:1;font-size:12px;font-weight:700;color:var(--txt);letter-spacing:.3px}
  #mainCtrl .mc-badge{flex:none;font-size:10px;line-height:16px;padding:0 7px;border-radius:8px;
    border:1px solid var(--line);color:var(--muted);white-space:nowrap}
  #mainCtrl.on .mc-badge{color:var(--btn-txt);border-color:transparent;background:var(--danger)}
  #mainCtrl .mc-btns{display:flex;gap:6px}
  #mainCtrlBtn{flex:1;min-width:0}
  #mainStopBtn{flex:none}
  #mainCtrl .mc-sub{margin:7px 0 0;font-size:11px;color:var(--muted);line-height:1.5;min-height:16px}
  .kv{display:flex;justify-content:space-between;font-size:11px;margin-top:3px}
  .kv span:first-child{color:var(--muted)}
  /* 「下发内容」面板：三栏各自贡献了哪些条目。条目行复用 .item，只补分组标题 */
  .pubgrp{margin-top:10px}
  .pubgrp .ph{display:flex;align-items:center;gap:6px;font-size:11px;font-weight:600;
    padding-bottom:5px;border-bottom:1px solid var(--line);margin-bottom:6px}
  .pubgrp .item{margin-bottom:5px}
  /* 推演复现：当前不在下发的行（实时动目标的「未开始」「已结束」）整行调暗。
     只压不透明度、不禁用交互——▶ 按钮正是要在这种状态下点的 */
  .pubgrp .item.dim{opacity:.55}
  .pubgrp .empty{padding:6px 0}
  .seg{display:flex;border:1px solid var(--line);border-radius:6px;overflow:hidden;margin-top:6px}
  /* 模块三态：body[data-view] 控制列表区(.listview)与绘制区(.drawview)互斥显示；
     无 data-view=收起(都不显示)，"list"=只显列表，"draw"=只显绘制 */
  .body .listview,.body .drawview{display:none}
  .body[data-view="list"] .listview{display:block}
  .body[data-view="draw"] .drawview{display:block}
  /* 标题栏「＋」添加按钮：点击展开/收起该模块的添加方式区 */
  .addtoggle{flex:none;width:26px;height:26px;line-height:1;padding:0;border-radius:6px;
    border:1px solid var(--line);background:var(--seg-bg);color:var(--accent2);
    font-size:19px;font-weight:700;cursor:pointer;display:flex;align-items:center;justify-content:center}
  .addtoggle:hover{border-color:var(--accent2)}
  .addtoggle.on{background:var(--accent2);color:var(--btn-txt);border-color:var(--accent2)}
  .seg button{flex:1;border-radius:0;background:var(--seg-bg);color:var(--muted);font-weight:500}
  .seg button:not(:disabled):hover{color:var(--accent2);box-shadow:inset 0 0 0 1px var(--accent2)}   /* 鼠标经过：边框高亮（同场景卡片/入口菜单）；禁用按钮(如倍速)不高亮 */
  .seg button.on{background:var(--accent2);color:var(--btn-txt)}
  .seg button.on:hover{color:var(--btn-txt);box-shadow:none}   /* 已选中态不叠加 hover 高亮 */
  /* 本船位置强调：半透明深蓝圆圈脉冲扩散特效（固定像素，随本船移动）*/
  .ship-pulse{position:relative;width:60px;height:60px;pointer-events:none}
  .ship-pulse::before,.ship-pulse::after{content:'';position:absolute;left:50%;top:50%;
    width:60px;height:60px;margin:-30px 0 0 -30px;border-radius:50%;
    background:rgba(var(--accent-rgb),.35);
    animation:shipPulse 2s ease-out infinite}
  .ship-pulse::after{animation-delay:1s}
  /* 离线：停止脉动，显示为固定静态圈（不扩散、不淡出）*/
  .ship-pulse.paused::before,.ship-pulse.paused::after{
    animation:none;transform:scale(.7);opacity:.5}
  @keyframes shipPulse{
    0%{transform:scale(.3);opacity:.9}
    100%{transform:scale(1);opacity:0}
  }
  #keytip{position:absolute;right:14px;bottom:40px;z-index:40;font-size:10px;color:var(--faint);
    background:var(--overlay);padding:4px 8px;border-radius:6px}
  .apiwarn{position:absolute;inset:0;display:none;align-items:center;justify-content:center;
    background:var(--bg);z-index:100;text-align:center;padding:30px;color:var(--txt)}
  .apiwarn.show{display:flex}
  .apiwarn div{max-width:520px}
  .apiwarn code{background:var(--panel2);padding:2px 6px;border-radius:4px;color:var(--accent)}
  /* ---- 登录浮层（填写本船标识）---- */
  #loginGate{position:fixed;inset:0;z-index:200;display:flex;align-items:center;justify-content:center;
    background:var(--bg)}
  #loginGate.hide{display:none}
  #loginGate .box{background:var(--panel);border:1px solid var(--line);border-radius:12px;
    padding:26px 30px;min-width:300px;box-shadow:0 10px 40px rgba(0,0,0,.4);text-align:center}
  #loginGate h2{margin:0 0 4px;font-size:18px;color:var(--txt)}
  #loginGate p{margin:0 0 16px;font-size:12px;color:var(--muted)}
  #loginGate label{display:block;font-size:11px;color:var(--muted);text-align:left;margin-bottom:4px}
  /* 标识组合框：外框让出与按钮之间的间距（间距给 .combo 而非 input——列表是相对 .combo
     绝对定位的，间距放在 input 上会把列表顶到输入框下方 14px 处，中间空一条） */
  #loginGate .combo{margin-bottom:14px;text-align:left}   /* .box 是居中排版，列表项要左对齐 */
  #loginGate input{margin-bottom:0}   /* 宽度/配色沿用本文件顶部 input,select,textarea 的通用样式 */
  #loginGate input:disabled{opacity:.6;cursor:not-allowed}   /* 登录探测期间与按钮一起变灰 */
  #loginGate button{width:100%;padding:9px}
  #loginGate button:disabled{opacity:.6;cursor:not-allowed}
  #loginGate p#loginMsg{margin:12px 0 0;font-size:12px;text-align:center}   /* 提高特异性覆盖 #loginGate p 的底部 16px，避免圆圈下方留白 */
  #loginMsg.hide{display:none}
  #loginMsg.info{color:var(--muted)}
  #loginMsg.err{color:var(--danger)}
  /* 登录中旋转特效：环形，缺口高亮，持续旋转 */
  #loginMsg .spinner{display:block;margin:16px auto 0;width:22px;height:22px;border:2.5px solid var(--line);border-top-color:var(--accent2);border-radius:50%;animation:loginSpin .7s linear infinite}
  @keyframes loginSpin{to{transform:rotate(360deg)}}
  /* ---- 实时只读面板 ---- */
  .mqtt-bar{display:flex;align-items:center;gap:8px;font-size:12px;margin-bottom:6px}
  .mqtt-bar .st-dot{width:9px;height:9px;border-radius:50%;background:var(--faint);flex:none;
    box-shadow:0 0 0 3px rgba(var(--faint-rgb),.18)}
  .mqtt-bar .st-lbl{font-size:10px;color:var(--muted);flex:none}
  /* 链路层灯色：已连=绿，连接中/重连中=黄，断开/离线/错误=红 */
  .mqtt-bar.lk-connected .st-dot{background:var(--ok);box-shadow:0 0 0 3px rgba(var(--ok-rgb),.2)}
  .mqtt-bar.lk-connecting .st-dot,.mqtt-bar.lk-reconnecting .st-dot{background:var(--warn);box-shadow:0 0 0 3px rgba(var(--warn-rgb),.2)}
  .mqtt-bar.lk-closed .st-dot,.mqtt-bar.lk-offline .st-dot,.mqtt-bar.lk-error .st-dot{background:var(--danger);box-shadow:0 0 0 3px rgba(var(--danger-rgb),.2)}
  /* 船只状态灯色：在线=绿，离线=红，无连接=灰 */
  .mqtt-bar.hb-alive .st-dot{background:var(--ok);box-shadow:0 0 0 3px rgba(var(--ok-rgb),.2)}
  .mqtt-bar.hb-lost .st-dot{background:var(--danger);box-shadow:0 0 0 3px rgba(var(--danger-rgb),.2)}
  .mqtt-bar.hb-none .st-dot{background:var(--faint);box-shadow:0 0 0 3px rgba(var(--faint-rgb),.18)}
  .mqtt-bar .st-txt{font-weight:600}
  .mqtt-bar .st-delay{font-size:10px;padding:0 5px;border-radius:8px;font-family:Consolas,monospace;
    background:var(--badge-bg);color:var(--muted);border:1px solid var(--badge-line)}
  .mqtt-bar .st-delay.okv{color:var(--ok);border-color:rgba(var(--ok-rgb),.42)}
  .mqtt-bar .st-delay.warnv{color:var(--warn);border-color:rgba(var(--warn-rgb),.42)}
  .mqtt-bar .st-delay.badv{color:var(--danger);border-color:rgba(var(--danger-rgb),.42)}
  .mqtt-bar .st-delay:empty{display:none}
  /* 延迟明细浮层：自绘，不用原生 title 提示框。
     原生的由浏览器渲染，没有任何 API 能在它显示期间改内容 —— 一改 title 属性就必然
     消失重来，而这块内容每秒都在变，鼠标停上去只能看见它一秒闪一次。自绘的就只是个
     div：改它的文字不影响它显不显示，于是能「边看边跟着变」（见 latSetTip，js/mqtt.js）。
     position:fixed + 挂在 body 下：不被侧栏的滚动容器裁掉。
     pointer-events:none：不抢 badge 的 hover，否则鼠标一碰浮层就 leave、来回抖。
     z-index 150：盖得住侧栏(30)，又低于各弹窗(200+)。 */
  .lat-tip{position:fixed;z-index:150;pointer-events:none;display:none;
    background:var(--overlay);border:1px solid var(--line);border-radius:6px;
    padding:6px 9px;font-size:11px;line-height:1.65;color:var(--txt);
    font-family:Consolas,monospace;white-space:pre;
    box-shadow:0 6px 18px rgba(0,0,0,.35)}
  .lat-tip.on{display:block}
  .mqtt-bar .st-sub{color:var(--muted);font-size:10px;margin-left:auto;white-space:nowrap}
  .grp{margin-top:6px}
  .grp>.gt{font-size:11px;color:var(--accent2);font-weight:600;margin:8px 0 4px;
    text-transform:uppercase;letter-spacing:.4px}
  .rt{display:grid;grid-template-columns:auto 1fr;gap:2px 10px;font-size:11.5px}
  .rt .k{color:var(--muted);white-space:nowrap}
  .rt .v{color:var(--txt);font-family:"SFMono-Regular",Consolas,monospace;text-align:right;
    overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .rt .v.warnv{color:var(--warn)} .rt .v.okv{color:var(--ok)} .rt .v.badv{color:var(--danger)}
  /* ---- 设置浮层（右上角齿轮）---- */
  #settingsBtn{flex:none;padding:5px 9px;font-size:15px;line-height:1}
  #settingsModal{position:fixed;inset:0;z-index:210;display:flex;align-items:center;justify-content:center;
    background:rgba(0,0,0,.45)}
  #settingsModal.hide{display:none}
  #settingsModal .settings-box{background:var(--panel);border:1px solid var(--line);border-radius:12px;
    width:420px;max-width:calc(100vw - 40px);max-height:calc(100vh - 60px);display:flex;flex-direction:column;
    box-shadow:0 12px 48px rgba(0,0,0,.5)}
  .settings-head{display:flex;align-items:center;gap:8px;padding:14px 16px;border-bottom:1px solid var(--line)}
  .settings-head h2{flex:1;margin:0;font-size:16px;color:var(--txt)}
  .settings-head button{flex:none;padding:4px 10px}
  .settings-body{padding:4px 16px 16px;overflow-y:auto}
  .settings-body::-webkit-scrollbar{width:8px}
  .settings-body::-webkit-scrollbar-thumb{background:var(--scroll);border-radius:4px}
  .settings-sec{padding:12px 0;border-bottom:1px solid var(--line)}
  .settings-sec:last-child{border-bottom:none}
  .settings-sec-title{font-size:12px;font-weight:700;color:var(--accent2);text-transform:uppercase;
    letter-spacing:.5px;margin-bottom:6px}
  .settings-row{display:flex;align-items:center;gap:10px;margin:8px 0}
  .settings-row .settings-label{flex:1;font-size:12px;color:var(--txt)}
  .settings-row button{flex:none;width:auto}
  /* ---- 动目标编辑弹窗（一次编辑全部属性）---- */
  #tgtEditModal{position:fixed;inset:0;z-index:220;display:flex;align-items:center;justify-content:center;
    background:rgba(0,0,0,.45)}
  #tgtEditModal.hide{display:none}
  #tgtEditModal .edit-box{background:var(--panel);border:1px solid var(--line);border-radius:12px;
    width:400px;max-width:calc(100vw - 40px);max-height:calc(100vh - 60px);display:flex;flex-direction:column;
    box-shadow:0 12px 48px rgba(0,0,0,.5)}
  #tgtEditModal .edit-head{display:flex;align-items:center;gap:8px;padding:13px 16px;border-bottom:1px solid var(--line)}
  #tgtEditModal .edit-head h2{flex:1;margin:0;font-size:15px;color:var(--txt)}
  #tgtEditModal .edit-body{padding:12px 16px;overflow-y:auto}
  #tgtEditModal .edit-body::-webkit-scrollbar{width:8px}
  #tgtEditModal .edit-body::-webkit-scrollbar-thumb{background:var(--scroll);border-radius:4px}
  #tgtEditModal .fgrid{display:grid;grid-template-columns:1fr 1fr;gap:8px 10px}
  #tgtEditModal .fgrid .full{grid-column:1 / -1}
  #tgtEditModal label.fld{margin:0 0 3px}
  #tgtEditModal .edit-foot{display:flex;gap:8px;padding:12px 16px;border-top:1px solid var(--line)}
  #tgtEditModal .edit-foot button{flex:1}
  /* ---- 命名弹窗（绘制完成前输入名称），复用编辑弹窗盒子样式 ---- */
  #nameModal{position:fixed;inset:0;z-index:230;display:flex;align-items:center;justify-content:center;
    background:rgba(0,0,0,.45)}
  #nameModal.hide{display:none}
  #nameModal .edit-box{background:var(--panel);border:1px solid var(--line);border-radius:12px;
    max-width:calc(100vw - 40px);display:flex;flex-direction:column;box-shadow:0 12px 48px rgba(0,0,0,.5)}
  #nameModal .edit-head{display:flex;align-items:center;gap:8px;padding:13px 16px;border-bottom:1px solid var(--line)}
  #nameModal .edit-head h2{flex:1;margin:0;font-size:15px;color:var(--txt)}
  #nameModal .edit-body{padding:12px 16px}
  #nameModal .edit-foot{display:flex;gap:8px;padding:12px 16px;border-top:1px solid var(--line)}
  #nameModal .edit-foot button{flex:1}
