/* ============================================================================
 * base.css —— 全局骨架与设计令牌
 *
 * ★ 技术栈：原生 CSS（无构建、无预处理器）。所有变量写在 :root 上，
 *   light.css / dark.css 只覆盖颜色令牌，不改布局 —— 这样"换肤"不会改结构。
 *
 * ★★ 状态色口径（老系统 acq.css 的原值，一个字都不许改）：
 *       绿 = 正常      #16a34a
 *       橙 = 一级报警  #e2840a  ← ★ V2.0 起【作废】：一号位 2026-10-27 定
 *                                  「没有橙色这件事了，只有黄红绿 + 灰」，
 *                                  一级改**正黄** #FACC15（见下面 :root）
 *       红 = 二级报警  #dc2626
 *       灰 = 离线/错误 #6b7280
 *       浅灰 = 空位    #fff + 虚线边 #b4b2a9
 *   为什么必须与老系统逐值一致：现场工程师在旧界面里形成的"颜色直觉"
 *   是我们的资产；换一个近似色（比如 #22c55e）就要重新训练一遍眼睛。
 *   颜色定义只出现一次（.st-* / .sw-*），任何地方都不许再手写色值。
 * ========================================================================== */

/* ---------------------------------------------------------------- 令牌（浅色，默认） */
/* ★★★ 换主题色就改这一块 —— 这是【唯一事实源】（V1.5.0 收口）。
 *
 *   一号位 2026-10-22 定：「我们公司的主色调也是蓝色，你就按它这个界面和 UI 做」
 *   → 原来整套是**青色**（#0E9C9C，见下方注释），现在换成**蓝色**。
 *
 *   ★★ 为什么用 #2B7DE9 这个值：它是从"有人云"截图里取的近似值（行业中等偏上水平）。
 *      **公司 VI 正式色值一到，只改这 4 个变量即可**，全站跟着变
 *      （登录页渐变、侧栏选中态、按钮、链接、图表主色都从这里来）。
 *
 *   ★★★ 不许在别处再手写十六进制色值 —— 这是本项目从 v1.0 就定的纪律
 *      （见文件头："颜色定义只出现一次，任何地方都不许再手写色值"）。
 */
:root {
  /* 品牌（蓝）—— ★ ★ ★ 换色只改这 4 行 ★ ★ ★ */
  --brand:        #2B7DE9;   /* 主蓝（V1.5.0 起；原青色 #0E9C9C 已弃用）*/
  --brand-dark:   #1A63C4;   /* 按下 / 深色态 */
  --brand-soft:   #E8F1FE;   /* 浅蓝底（选中态、徽章底）*/
  --brand-text:   #1557B0;   /* 蓝底上的可读字色 */

  /* 面 / 底 / 线 —— ★ 一并调成【中性冷灰】，跟蓝主色才配（原来是暖米色）*/
  --bg:           #F4F6FA;
  --bg-header:    #FFFFFF;
  --bg-side:      #F7F9FC;
  --bg-card:      #FFFFFF;
  --bg-soft:      #F2F5FA;   /* 顶栏 KPI 卡底 */
  --bg-hover:     #E8EEF6;   /* ★ hover：浅灰蓝（比原来的 #EFEFEC 明显得多）*/
  --bg-active:    #E1ECFD;   /* ★ 当前项：浅蓝（跟 hover 拉开，再配左侧竖条）*/
  --bg-code:      #F4F6FA;
  --line:         #E3E8F0;
  --line-strong:  #C9D3E2;

  /* 字 —— 暖黑换中性深蓝灰 */
  --ink:          #1F2733;
  --ink-2:        #55637A;
  --ink-3:        #8492A6;
  --ink-on-dark:  #FFFFFF;

  /* 语义色（★ 业务口径，换主题色时【不许动】）
     绿=正常 / 黄=一级 / 红=二级 / 灰=无法判断
     —— 见文件头，这是训练过的眼睛资产。★ V2.0 起一级是【黄】不是橙（没有橙色这个色）。 */
  /* ★★★ V1.9.2（一号位 2026-10-24 13:19）：**全站 4 色统一**
   *   「一级报警是黄色，二级报警是红色，正常是绿色，**所有无法判断的都是灰色**。
   *    无法判断就包括离线、校验失败和返回错误代码。**让圆点和色块的颜色都统一就行了。**」
   *
   *   ★ 原来的坑：`--warn1` 是橙 `#e2840a`，但圆点被我改成黄 `#F2C230` ——
   *     同一个"一级"出现两套颜色（正是他骂过的"不可能两个颜色"）。
   *   ★★ 更麻烦的是：`--warn1` **同时被当"面"和"墨"用** ——
   *     当色块底（黄底配白字看不清）、当文字色（黄字白底也看不清）。
   *   → 所以拆两档：**面 = --warn1（黄）**，**墨 = --warn1-ink（深琥珀，白底清楚）**。
   */
  --ok:           #16a34a;   /* 正常：绿 */
  /* ★★★ V2.0（一号位 2026-10-27 15:48）：「**没有橙色这件事了**，只有黄红绿，
   *   跟红绿灯的三种颜色一样，再加一个灰。」
   *   原来 #F0B429 色相 42°、墨 #A66A00 色相 38° —— 屏幕上看着是橙/琥珀，
   *   跟"一级=黄"的口径对不上。改成**正黄**（色相 48°），墨用暗金（不是棕橙）。 */
  --warn1:        #FACC15;   /* 一级：**面**（色块底 / 圆点 / 图例块）—— 黄（正黄，不偏橙） */
  --warn1-ink:    #8A6A00;   /* 一级：**墨**（文字 / 描边）—— 暗金，白底可读且不偏橙 */
  --warn2:        #dc2626;   /* 二级：红 */
  --warn2-ink:    #A31616;   /* 二级：墨（比面深一档，给圆点在红底上用） */
  --na:           #6b7280;   /* 无法判断：灰（离线 / 校验失败 / 返回错误码，三类合一） */
  --na-ink:       #4B5563;   /* 无法判断：墨（比面深一档，给圆点在灰底上用） */
  --empty:        #FFFFFF;
  --empty-line:   #B4BECF;
  --info:         #185FA5;   /* 传感器方块 / 链接 */
  --root:         #7F77DD;   /* ★ 紫 = 仅 root（33 号文档定的身份色） */
  --root-soft:    #EEEDFE;

  /* 尺寸 */
  /* ★★★ V1.5.1（P-17）：58px → 70px。
   *
   *   ★ 一号位 00:20 指着右上角一个"没用的小方块"问"这是啥"——
   *     那其实是**浏览器页面滚动条**。根因就在这里：
   *       .header { min-height: 58px; padding: 6px 16px }   ← 上下留白共 12px
   *       .kpi    { min-height: 58px }                        ← P-05 放大后的卡高
   *     → 58 + 12 = 顶栏实际 70px，但令牌还是 58 → 页面比窗口高 12px → 冒滚动条。
   *   ★★ 70 = 58(KPI 卡) + 12(padding) 严丝合缝，页面不再溢出，滚动条自然消失。
   *   ★ 代价：顶栏比原来高 12px —— 这是"字号放大"的必然代价，一号位已选"看得清"。
   */
  --h-header:     70px;
  /* ★ V1.5.2：208px → 244px。一号位 2026-10-23 01:46：
     「整个这左边的这个标记这个列表可以加宽一点，你现在一行显示不下」
     ★ 加宽后「项目管理」+ 三个按钮能排在一行，项目名也不容易被截断。 */
  --w-side:       244px;
  --radius:       8px;

  /* ---------------------------------------------------------------- 字号令牌
   *
   * ★★★ V1.5.0 新增（P-03）。一号位 2026-10-22：
   *   「你现在给我渲染的这些文字和色块**都太小了，看着有点费劲**……
   *    这的大小也用它（有人云）这种的。」
   *
   * ★★ 问题不在"基准小"（body 原本就是 14px），而在于：
   *    各处**硬写 12.5 / 13 / 12 / 11px** 把基准盖掉了 ——
   *    屏幕上绝大部分可读文字实际只有 12~13px。
   *    而且**一处都没用变量管字号** → 想整体放大得一个个抠，
   *    这正是"改不动"的根因。
   *
   * ★ 现在的规则（**以后调字号只改这里**）：
   *     --fs-xs   12px    辅助信息：徽章、时间戳、极次要说明
   *     --fs-sm   13.5px  次要文字：表单标签、卡片副标、表格表头
   *     --fs-base 14.5px  ★ 正文基准：表格内容、按钮、输入框（**主体**）
   *     --fs-lg   16px    卡片标题
   *     --fs-xl   18px    页面大标题
   *   ★★ 原则：**可读的一律 ≥13.5px**，只有"辅助信息"才允许 12px。
   *     这是个取舍——字号放大必然让一屏看到的行数变少，
   *     一号位明确选了"看得清"而不是"看得多"。
   */
  --fs-xs:        12px;
  --fs-sm:        13.5px;
  --fs-base:      14.5px;
  --fs-lg:        16px;
  --fs-xl:        18px;
  --radius-sm:    4px;
  --shadow:       0 2px 10px rgba(0, 0, 0, .08);
  --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, "Courier New", monospace;
}

/* ---------------------------------------------------------------- 复位 */
* { box-sizing: border-box; }
/* ★★★ V1.5.1（P-17）：页面框架本身【永不滚动】。
 *   一号位 00:20 指着右上角一个"没用的小方块"问这是啥 —— 那是页面滚动条。
 *   除了把 --h-header 改对（70px），这里再加一道兜底：
 *   以后不管谁把哪个区块撑高了，都不会再冒出"莫名其妙的小方块"。
 *   ★ 滚动交给 .pane / .proj-list / .tb-wrap 各自内部处理（它们都有 overflow）。
 */
html, body { height: 100%; overflow: hidden; }
body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  /* ★ V1.5.0（P-03）：基准字号走令牌（14.5px，原 14px）——
     一号位原话「文字和色块都太小了，看着有点费劲」。
     ★★ 用 font-size/line-height 分开写，不用 `font:` 简写 ——
        简写里塞不了 CSS 变量，以后调基准就又要回来改这里。 */
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Microsoft YaHei",
               "PingFang SC", "Hiragino Sans GB", sans-serif;
  font-size: var(--fs-base);
  line-height: 1.55;
  -webkit-text-size-adjust: 100%;
}
h1, h2, h3, h4 { margin: 0; font-weight: 600; }
a { color: var(--info); }
button, input, select, textarea { font: inherit; color: inherit; }
[hidden], .hide { display: none !important; }
.mono { font-family: var(--mono); }
.muted { color: var(--ink-2); }
/* ---------------------------------------------------------------- 说明文字（唯一口径）
 *
 * ★★★ 2026-11-02 12:54 一号位（截图圈着侧栏空状态那段小字）：
 *   「统一一下，所有的说明文字都用我圈出来这个大框里面的这个字号。
 *     …… 把全局所有的注释类的字号都改成这么大，就是**灰色这么大字号**，
 *     默认就让人一看就明白这是一个注释，一个说明。」
 *
 * ★ 以前"说明文字"有**三种写法、三种字号**，同一个界面里并排出现：
 *     .faint     → 只有颜色，字号靠继承（正文 14.5px）   ← 最乱的一个
 *     .card-sub  → 12px 灰
 *     .hint      → 13.5px 稍深的灰
 *   用户看到的是"有的注释大、有的注释小"，说不出哪里怪，但就是不整齐。
 *
 * ★ 现在**全部收到 --fs-xs（12px）+ --ink-3（浅灰）**：
 *     12px 是"一望而知是注释"的尺寸——比正文小一档，但还没小到看不清；
 *     浅灰则是第二层信号：**颜色说"这不是正文"，字号说"这不是重点"**，
 *     两个信号同向，不用读内容就知道该跳过。
 *
 * ★ 为什么 .faint 一定要写死 font-size 而不是继续继承：
 *   继承意味着"同一个类在表格里是 14.5、在卡片里是 12" —— 那就不是规范，是巧合。
 */
.faint { color: var(--ink-3); font-size: var(--fs-xs); }
.nowrap { white-space: nowrap; }
.right { text-align: right; }
.center { text-align: center; }

/* ★ 就地提示，不弹 alert（33 号 §7.2-5，一号位既有偏好）。
   提示挂在产生它的那个区域里，看得见、但打断不了操作。
   ★ 2026-11-02 12:54：字号收进 --fs-xs、颜色收到 --ink-3，与 .faint/.card-sub 同一口径
     （见上面「说明文字（唯一口径）」那段）。错误/成功态靠**颜色**区分，不靠放大字号。 */
.hint { font-size: var(--fs-xs); color: var(--ink-3); min-height: 18px; }
.hint.ok   { color: var(--ok); }
.hint.err  { color: var(--warn2); }
.hint.warn { color: var(--warn1-ink); }        /* ★ 墨用 ink：黄字在白底上看不清 */

/* ---------------------------------------------------------------- 登录页 */
/* ---------------------------------------------------------------- 登录页
 *
 * ★★★ V1.5.0 重做（P-10），按「有人云」复刻。一号位原话：
 *   「登录界面也太丑了，按有人的这个复刻……界面和颜色啥的、字体基本上
 *    就按他这个来就行了，大小就居中算就行了。」
 *
 * ★ 有人云那版的要点（下面逐条对应）：
 *   ① 背景**极浅蓝灰**（近白，不是深色渐变）—— 老版是深青绿渐变，很压抑
 *   ② 卡片**更宽**（约 480px）、大圆角、**很轻的阴影**（漂浮感）
 *   ③ 标题**大字加粗**居中（那是页面的绝对主角）
 *   ④ 输入框 **浅灰底 + 无边框**，靠底色区分边界，不用描边
 *   ⑤ 按钮 **全宽、亮蓝、够高**（一眼就是主操作）
 */
.login-mask {
  position: fixed; inset: 0; z-index: 900;
  display: flex; align-items: center; justify-content: center;
  /* ★ 极浅的蓝灰渐变 —— 学有人云那种"几乎白、但有一点点蓝"的调子 */
  background:
    radial-gradient(1200px 600px at 12% 8%, #E8F1FE 0%, rgba(232,241,254,0) 60%),
    linear-gradient(160deg, #F4F7FC 0%, #EDF2F9 55%, #E9EFF7 100%);
}
.login-card {
  width: 480px; max-width: calc(100vw - 32px);
  padding: 40px 40px 30px;
  background: var(--bg-card);
  border-radius: 16px;
  /* ★ 很轻的阴影 —— 有人云那种"浮在浅底上"的感觉，不是重投影 */
  box-shadow: 0 6px 28px rgba(31, 39, 51, .08), 0 1px 3px rgba(31, 39, 51, .04);
}
/* ★ 标题：整页唯一的主角 */
.login-card .login-title {
  font-size: 28px; font-weight: 700; letter-spacing: .5px;
  text-align: center; color: var(--ink);
  margin: 0 0 28px;
}
.login-card .field { margin-bottom: 14px; }
/* ★ 密码框要相对定位，给右眼的按钮留位置 */
.login-card .field-pwd { position: relative; }
/* ★ 输入框：浅灰底 + 无边框 + 够高（有人云就是靠底色划边界，不描边） */
.login-card .field input {
  width: 100%; box-sizing: border-box;
  height: 46px; padding: 0 14px;
  border: 1px solid transparent; border-radius: 8px;
  background: #F4F6FA;
  color: var(--ink); font-size: 14.5px;
  transition: background .15s, border-color .15s;
}
/* ★★★ V1.5.1（P-13①）：密码框【单独换等宽字体】—— 这是"小黑点大小不一"的根因修法。
 *
 *   一号位 00:59：「这个密码的这个小小黑点，**有大有一会大一会小，太丑了**。」
 *
 *   ★ 根因：密码掩码字符是 U+2022（•）。原来这个 input **没指定 font-family**，
 *     继承了 body 的 5 个候选字体：
 *       -apple-system, BlinkMacSystemFont, "Segoe UI", "Microsoft YaHei", "PingFang SC"...
 *     Windows 上 `Segoe UI` 的圆点**偏大**、`Microsoft YaHei` 的**偏小** →
 *     **浏览器做字体回退时，不同位置的字符落到不同字体 → 一颗大一颗小**。
 *
 *   ★★ 修法：密码框用【等宽字体】。
 *     等宽字体的圆点**每颗绝对一样大、间距一致**，这是它最稳的地方。
 *     密码里几乎不会有中文，所以等宽不影响可读性。
 *   ★ 账号框保持正文字体（中文要好看），**只有密码框换**。
 */
.login-card .field input[type="password"] {
  font-family: ui-monospace, "Consolas", "Cascadia Mono", "Menlo", monospace;
  letter-spacing: 1px;
}
/* ★★★ V1.9.0 post-fix（一号位 2026-10-23 22:17：
 *   「这两个字体写的一样不一样？你自己看一下。」）
 *
 *   ★ 上面那条等宽规则**连 placeholder 一起改了** —— 于是：
 *     · 用户名框的提示字 = 正文字体（微软雅黑）
 *     · 密码框的提示字   = 等宽字体（Consolas，中文回退到别的字体）+ 1px 字距
 *     → **两个提示字看起来是两套字体**。他截图里一眼就看出来了。
 *
 *   ★★ 正确做法：等宽字体只服务于"**掩码圆点**"（V1.5.1 治"黑点大小不一"才换的），
 *     **placeholder 不是圆点**，它应该跟用户名框完全一致。
 *   → 把 placeholder 的字体/字距**显式改回正文字体**。
 */
.login-card .field input[type="password"]::placeholder {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Microsoft YaHei",
               "PingFang SC", "Hiragino Sans GB", sans-serif;
  letter-spacing: normal;
}
/* 右眼按钮：贴在输入框右端，垂直居中 */
.login-card .pwd-eye {
  position: absolute; right: 6px; top: 50%;
  transform: translateY(-50%);
  width: 34px; height: 34px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border: 0; border-radius: 6px; background: none;
  color: var(--ink-3); cursor: pointer;
}
.login-card .pwd-eye:hover { background: var(--bg-hover); color: var(--ink-2); }
.login-card .pwd-eye:focus-visible {
  outline: none; box-shadow: 0 0 0 2px var(--brand);
}
/* ★ 有眼睛按钮时，输入框右侧要留出位置 —— 否则长密码压到图标下面 */
.login-card .field-pwd input { padding-right: 46px; }
.login-card .field input::placeholder { color: var(--ink-3); }
.login-card .field input:focus {
  outline: none; background: #FFFFFF;
  border-color: var(--brand);
  box-shadow: 0 0 0 3px rgba(43, 125, 233, .12);
}
/* ★ 登录按钮：全宽、够高、亮蓝 —— 页面唯一的主操作 */
.login-card .login-btn {
  width: 100%; height: 46px; margin-top: 10px;
  font-size: 15.5px; font-weight: 600; letter-spacing: 2px;
  border-radius: 8px;
}
/* 错误提示：给足高度避免布局跳动 */
.login-card .hint { min-height: 20px; margin-top: 10px; text-align: center; }
/* ★ 底部"忘记密码"—— 纯文本、灰、小，**不可点** */
.login-card .login-foot {
  margin-top: 14px; text-align: center;
  font-size: var(--fs-sm); color: var(--ink-3);
}

/* ---------------------------------------------------------------- 主框架 */
/* ★ 顶部通栏 + 左侧竖排 + 右侧内容区 —— 学有人云控制台的整体布局（33 号 §0.3）。
   为什么不做成可折叠侧栏：一号位口径「折叠了也没别的东西显示」。 */
.shell { display: flex; flex-direction: column; height: 100%; }

.header {
  flex: none; display: flex; align-items: center; gap: 18px;
  /* ★★★★★ 2026-10-15 修（一号位截图：网关那格被截断、显示不全）
   *
   *   原因（两个 CSS 问题叠加）：
   *     ① 这里原来是 `height: var(--h-header)`（固定 58px）——
   *        运营概况从 4 格加到 5 格、设备/网关又多了"在线"副标题之后，
   *        **内容撑不下，但高度锁死**，多出来的部分直接被裁掉。
   *     ② `.kpis` 没有 `flex` / `min-width`，在 flex 行里**不会收缩**，
   *        于是它保持原宽，把右侧的账户菜单挤出可视区。
   *
   *   → 改成 `min-height`（内容少时仍是 58px，内容多时允许撑高）
   *     ＋ 允许整体换行 ＋ 上下留 6px 余量（换行后不贴边）。
   *   ★ `--h-header` 只在本文件这一处使用，没有任何 JS 拿它做测量 ——
   *     所以改高度不会牵连别处布局。 */
  min-height: var(--h-header);
  padding: 6px 16px; flex-wrap: wrap; row-gap: 6px;
  background: var(--bg-header); border-bottom: 1px solid var(--line);
}
/* ---------------------------------------------------------------- 顶栏品牌块
 *
 * ★★★ 2026-11-02 11:48 一号位（截图红框圈着左上角那块）：
 *   「顶部这个 banner 现在把『物联网云平台/运营概览』两行折成一行、居中、加粗；
 *    把『物联网』这两个字删了，就叫『平台运营概览』6 个字；
 *    这 6 个字占的框**跟『项目列表』整个宽度一样**；
 *    底色用**跟旁边那些方块（项目数量/设备数）一样的圆角灰底**。」
 *
 * ★ 所以这里从"两行黑体文字"改成**跟 KPI 卡同款的灰底圆角卡片**：
 *   ① `background: var(--bg-soft)` + `border-radius: 8px` —— 跟 `.kpi` 同一个变量，
 *      改一处两边一起变，不会哪天调色只调一半
 *   ② 一行、居中、加粗
 *   ③ 宽度对齐左侧「项目列表」整块
 *
 * ★ 为什么是 `calc(var(--w-side) - 16px)` 而不是 `var(--w-side)`：
 *   `.header` 自己有 16px 左内边距（`padding: 6px 16px`），
 *   而 `.side` 是从视口最左边 0 开始的。直接写 `var(--w-side)` 会让这块
 *   往右多出 16px，**右边缘跟下面「项目列表」差一截，红框里的对齐就没了**。
 *   减掉 16 之后，右边缘正好落在 `--w-side` 上，跟侧栏右边缘齐平。
 *   ★ 侧栏在窄屏会被 `--w-side: 150px` 收窄（见文件末尾的媒体查询），
 *     这块用同一个变量 → 跟着一起收，不用另写断点。
 */
.brand {
  display: flex; align-items: center; justify-content: center;
  flex: none;
  /* ★ 撑满 header 的行高 —— 让这块跟右边 KPI 卡**上下边缘齐平**。
     `.header` 是 `align-items: center`，不写这行的话：
       brand 只有一行文字（高 58），KPI 卡是三行（高 69）
       → 中间对齐的结果是 brand 上下各空 5px，看着像"矮一截的小牌子"，
         跟旁边卡片不是一套东西。stretch 让它跟着最高的那张卡一起长。 */
  align-self: stretch;
  width: calc(var(--w-side) - 16px);
  min-height: 58px;
  padding: 7px 12px;
  border-radius: 8px;
  background: var(--bg-soft);
}
.brand .name {
  /* ★ 字体沿用原来那套（一号位：「用现在这个字体加粗」） */
  font-family: "Microsoft YaHei UI", "PingFang SC", "Source Han Sans SC",
               "Hiragino Sans GB", sans-serif;
  font-size: 15px;
  font-weight: 700;
  letter-spacing: 1.5px;
  line-height: 1.15;
  color: var(--ink);
  white-space: nowrap;
}

/* 运营概况：只读数字。★ 跨项目视角只给"数字"不给"操作"（33 号判据 2） */
/* ---------------------------------------------------------------- 运营概况 KPI
 *
 * ★★★ V1.5.0 重做（P-05）。一号位原话：
 *   「你看你这种就感觉**太小气了**，人家这种就**铺满整个导航条**」
 *
 * ★ 老写法的三个毛病（逐条对应下面的修法）：
 *   ① `.kpis` 是 flex + `.kpi{min-width:84px}`，**没有 flex:1**
 *      → 每张卡按内容宽度撑开、**不等分** → 小 chip 挤在左边，右边一大片空白
 *      → 修：改成 **grid 等分**，6 张卡自己铺满
 *   ② `.kpi b` 和 `.kpi i` 都是 block，副标一长就折行
 *      → 卡片**高低参差**
 *      → 修：三段固定行高 + `min-height` 统一
 *   ③ `设备 17` 里 **label 和 value 一样大**，数字没有主次
 *      → 修：**名称 12.5px / 数字 22px 加粗 / 副标 12px**
 */
.kpis {
  display: grid;
  /* ★ 5 张卡等分铺满整个可用宽度 —— 这是"不小气"的关键
   * ★ 2026-11-02 13:00：6 → 5。一号位把「结构物」那张卡划掉了（结构物层已取消），
   *   剩下 项目 / 设备 / 网关 / 数据回传 / 累计报警。
   *   ★ 少一张 = 每张多出约 20% 宽度，正好放得下"累计在台上、7 天在台下"那两行数字。 */
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 10px;
  flex: 1 1 auto;
  min-width: 0;
}
/* 窄屏兜底：少于 1100px 时两行，避免数字被挤断 */
@media (max-width: 1100px) {
  .kpis { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
.kpi {
  display: flex; flex-direction: column; justify-content: center;
  gap: 1px; min-width: 0;
  padding: 7px 12px; border-radius: 8px;
  background: var(--bg-soft);
  /* ★ 统一高度 —— 不再因为副标长短而参差 */
  min-height: 58px;
}
.kpi .k-label {
  font-size: var(--fs-xs); line-height: 1.2; color: var(--ink-2);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* ★ 数字是主角：比名称大一档多 */
.kpi .k-value {
  font-size: 21px; line-height: 1.15; font-weight: 700;
  color: var(--ink); white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
}
.kpi .k-sub {
  font-size: var(--fs-xs); line-height: 1.2; color: var(--ink-3);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* ★ 副标配色：有异常要一眼看到（绿=正常/黄=一级/红=二级 是业务口径，这里只借用色相） */
.kpi .k-sub.is-ok   { color: var(--ok); }
.kpi .k-sub.is-warn { color: var(--warn1-ink); }   /* ★ 同上，墨用 ink */
.kpi .k-sub.is-err  { color: var(--warn2); font-weight: 600; }

.header-right { margin-left: auto; display: flex; align-items: center; gap: 4px; flex: none; }
.header-right .linkbtn {
  padding: 6px 10px; border: 0; background: none; border-radius: 6px;
  color: var(--ink-2); cursor: pointer; white-space: nowrap;
}
.header-right .linkbtn:hover { background: var(--bg-hover); color: var(--ink); }
.header-right .linkbtn.rootonly { color: var(--root); font-weight: 600; }

.dropdown { position: relative; }
.dropdown .menu {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 60;
  min-width: 168px; padding: 4px; background: var(--bg-card);
  border: 1px solid var(--line); border-radius: var(--radius); box-shadow: var(--shadow);
}
.dropdown .menu button {
  display: block; width: 100%; padding: 7px 10px; text-align: left;
  border: 0; background: none; border-radius: var(--radius-sm); cursor: pointer;
}
.dropdown .menu button:hover:not(:disabled) { background: var(--bg-hover); }
/* ★ 后端暂无此接口的项：做成禁用（不隐藏）——让"缺口"在界面上是可见的 */
.dropdown .menu button:disabled { color: var(--ink-3); cursor: not-allowed; }

.main { flex: 1; display: flex; min-height: 0; }

/* ---------------------------------------------------------------- 左侧项目列表 */
.side {
  flex: none; width: var(--w-side); display: flex; flex-direction: column;
  background: var(--bg-side); border-right: 1px solid var(--line);
}
.side-head {  flex: none; display: flex; align-items: center; justify-content: space-between;
  /* ★ 2026-11-02 13:10：上下内边距原来是 12 / 8（不对称）—— 内容盒整体偏上 2px，
   *   所以标题旁边那个「新增」看着"浮"在行中间偏上的位置。改成上下相等。 */
  padding: 10px 12px; font-weight: 600;
  gap: 6px;
  /* ★★★ V1.5.1 修：加了三个按钮（新增/归档/删除）之后，208px 的侧栏放不下
      「项目管理」+ 三个 46px 按钮 → 标题被挤成竖排（"项 目 管 理"）。
      → 标题禁止折字；按钮组放不下就换到第二行。 */
  flex-wrap: wrap;
}
.side-head > span:first-child {
  white-space: nowrap;          /* ★ 标题永不断字 */
  flex: none;
}
/* ★ V1.5.0（P-09）：项目管理标题右侧的操作按钮组
   —— 由 sidebar.js 按角色动态插入（客户只看到「新建/删除」，
      root 看到「新建/归档/删除」）。
   ★ 按钮本身用 .btn.sm 的样式，这里只管排布：横向、紧凑、不换行。 */
.side-head-btns {
  display: inline-flex; align-items: center; gap: 6px;
  flex: none; font-weight: 400;
  margin-left: auto;            /* ★ 换行后仍然靠右对齐 */
  /* ★★★ 2026-11-02 13:10 一号位（截图圈着「新增」和下面两个按钮）：
   *   「新增按钮的大小跟编辑和删除保持一致，但**水平方向的位置不要跟下面两个按钮对齐**，
   *     大小和下面两个按钮一样，竖着在它自己这一行居中。」
   *
   *   ★ 为什么不能对齐：下面每一行是**项目**，右侧竖排「编辑 / 删除」属于那个项目；
   *     标题行的「新增」是**列表级**动作。两者右边缘贴在同一条竖线上时，
   *     眼睛会把「新增」读成"第一个项目的编辑/删除"——层级就串了。
   *   ★ 错开多少：先试了 12px，实测两边右边缘只差 8px（标题区 padding 12、
   *     项目行 padding 10，两处底子本来就差 2px），屏幕上几乎看不出，
   *     等于没改。→ 直接给到 24px，实际差约 20px，一眼能看出是两级。 */
  margin-right: 24px;
}
/* ★★★ V1.5.1（P-15）一号位 00:49：
 *   「三个按钮，新增、归档和删除，都调整一下间距，**改到一样大，按钮一样大，字都一样**。」
 *
 *   ★ 根因跟我 P-05 犯的是同一类错：只给了 padding、没给宽度 →
 *     每个按钮按**内容宽度**撑开（`＋` 最窄、「归档」「删除」各自不同宽）。
 *   ★★ 等宽要用 `min-width`，不要靠左右 padding 撑 ——
 *     三个词字数一样（新增/归档/删除 都是 2 字），min-width 一给就齐。
 *   ★★★ 但**颜色不统一**：删除必须保持红色（危险操作要一眼认出来），
 *     统一的是尺寸、字号、间距，不是语义色。
 */
.side-head-btns .btn.sm {
  min-width: 46px;
  /* ★★★ 2026-11-02 13:10：这里是「大小跟编辑/删除一致」的根因。
   *   原来这里写 `padding: 3px 0`（高 12×1.5 + 6 = 24px），
   *   而项目行按钮 `.proj-row-ops .btn.sm` 写的是 `padding: 1px 6px`（高 20px）——
   *   **两个类各自定义 padding，差了 4px 高**，所以「新增」总比下面两个胖一圈。
   *   → 现在两边同值（1px 6px），宽度靠 min-width:46px 兜住，字体字号本来就一样。 */
  padding: 1px 6px;
  text-align: center;
  font-size: var(--fs-xs);
  line-height: 1.5;
}
/* 禁用态要看得出来（比如已归档项目的「归档」按钮） */
.side-head-btns .btn:disabled {
  opacity: .45; cursor: not-allowed;
}
.proj-list { flex: 1; overflow-y: auto; padding: 0 8px 12px; }
/* ---------------------------------------------------------------- 左侧项目列表
 *
 * ★★★ V1.5.0（P-08）—— 一号位 2026-10-22 现场：
 *   「鼠标移到这个项目上的时候是有反色的，但是**那个颜色太淡了，很难看出来**，
 *    你到时候要弄的**明显一点**。」
 *   「我点到哪个项目上，只要我没切到另外一个项目上，这个项目上的颜色
 *    应该**加深或者反色**，总之**始终能让我明白当前所有配置、数据、图表绑在哪个项目上**。」
 *
 * ★ 参考「有人云」左侧导航的三态（一号位指定的参照）：
 *     当前项：**浅蓝底 + 文字变蓝 + 图标变蓝**
 *     hover ：浅灰底，文字不变
 *     普通  ：白底
 *   ★★ 它最关键的一点是**选中时文字也变色** ——
 *     光换底色，在浅底上跟 hover 很难分辨；字一变蓝就一眼看出来。
 *
 * ★ 咱们加码一条：当前项左侧再加 **3px 竖蓝条**，扫一眼就能定位。
 */
/* ---------------------------------------------------------------- 全新空账号：**主区域留白**
 *
 * ★ V1.5.1（P-16）曾经在这里放过一句居中大字：
 *     「在项目管理中点击新增按钮，新建第 1 个项目」
 *
 * ★★★ 2026-11-02 13:05 一号位（截图圈红，把这句划掉了）：
 *   「这两句话现在重复了…… 但是大的这句话**说的是错的** ——
 *     没有项目的时候，你说是在项目管理中新建，**没有项目你哪有项目管理**？
 *     整个这句话全部删掉就完了。」
 *
 *   ★ 错在**指了一个不存在的东西**：零项目时界面上根本没有「项目管理」这个菜单
 *     （标签栏此时是隐藏的），用户照着去找只会更迷。
 *   ★ 而且它跟侧栏那句是同一件事，重复：
 *       侧栏「还没有项目。点右上角「新增」建一个。」← 紧挨着那个按钮，**留着**
 *       主区域那句                                    ← **删掉**，`.empty-first` 随之废弃
 *   ★ 删掉后这块**就是空白的** —— 这是对的选择：此刻确实没有任何项目内容可看。
 *     提示只留在它该在的地方（侧栏、按钮旁边）。
 */

.proj-item {
  position: relative;                      /* ★ 给左侧竖条定位 */
  display: block; width: 100%; padding: 7px 10px; margin-bottom: 2px;
  border: 0; background: none; border-radius: 6px; text-align: left;
  cursor: pointer; color: var(--ink-2); overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap;
}
/* hover：浅灰蓝（原来的 #EFEFEC 跟白底几乎没区别，所以"看不出来"） */
.proj-item:hover { background: var(--bg-hover); color: var(--ink); }
/* ★ 当前项目：浅蓝底 + 蓝字加粗 + 左侧 3px 竖蓝条 —— 三重区分，绝不看错 */
.proj-item.on {
  background: var(--bg-active);
  color: var(--brand-text);
  font-weight: 600;
}
.proj-item.on::before {
  content: ''; position: absolute; left: 0; top: 4px; bottom: 4px;
  width: 3px; border-radius: 0 2px 2px 0; background: var(--brand);
}
.proj-item .tag {
  margin-left: 6px; padding: 0 4px; font-size: var(--fs-xs); border-radius: 3px;
  background: var(--bg-hover); color: var(--ink-3);
}

/* ---------------------------------------------------------------- 项目行（V1.6.0 / V1.6.2）
 *
 * ★★★ 一号位 2026-10-23 02:55 第一版：
 *   「每一个项目的名字后面加上一个删除，编辑也可以加上……
 *     因为项目名称有可能比较长，所以可以把编辑和删除按钮放在同一个色块的
 *     **下面一点，就是并排，分成两行放**。」
 *
 * ★★★ 03:29 又改（他画了图）：按钮挪到**右侧竖排**，名字占左边一栏 ——
 *   「这样的话留给项目名称这儿就有更多的空间，项目名称很长了就可以
 *     **折行折成两行**，更容易把名字放下去。」
 *   「一行没用完的时候，可以居中，也可以顶在上面放。」→ 取**垂直居中**。
 *
 *   所以现在的结构是：
 *     ┌──────────────────────────────┐
 *     │ 恒基碧翠锦华            [编辑] │
 *     │ （名字可折两行）        [删除] │
 *     └──────────────────────────────┘
 */
.proj-row {
  position: relative; display: flex; align-items: center; gap: 6px;
  border-radius: 6px; margin-bottom: 4px; padding: 6px 8px 6px 10px;
  overflow: hidden; cursor: pointer;
}
.proj-row.on { background: var(--bg-active); }
.proj-row.on::before {
  content: ''; position: absolute; left: 0; top: 4px; bottom: 4px;
  width: 3px; border-radius: 0 2px 2px 0; background: var(--brand); z-index: 1;
}
/* 名字：占满左边剩余的宽度，允许折行（最多两行，超了省略号兜底）
   ★ 坑：line-clamp 必须加在【直接包着文字的那个元素】上。
     一开始加在 .proj-item（外层按钮）上 → 完全没生效，名字折成了 3 行。
     真正装文字的是里面那个 .proj-name span，所以要挂在它身上。 */
.proj-row .proj-item {
  flex: 1 1 auto; min-width: 0; width: auto; margin: 0; padding: 2px 0;
  white-space: normal; overflow: visible; text-overflow: clip;
  line-height: 1.35; display: block;
}
.proj-row .proj-name {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; overflow-wrap: anywhere; word-break: break-word;
  line-height: 1.35;
}
.proj-row.on .proj-item { background: transparent; }
.proj-row.on .proj-item::before { display: none; }   /* 竖条归整行，不再重复 */

/* 按钮：右侧竖排，固定宽度（不跟名字抢空间） */
.proj-row-ops {
  flex: none; display: flex; flex-direction: column; gap: 3px;
}
.proj-row-ops .btn.sm {
  min-width: 46px; padding: 1px 6px; font-size: var(--fs-xs); line-height: 1.5;
  text-align: center;
}
.side-foot { flex: none; padding: 8px 12px; border-top: 1px solid var(--line); }
/* ★ V1.3：侧栏底部的版本号（app.js 从 /api/health 取回来填） */
.side-version { margin-top: 6px; font-size: var(--fs-xs); color: var(--faint, #9aa4b2);
                letter-spacing: .4px; user-select: text; }

/* ---------------------------------------------------------------- 右侧内容区 */
/* ★★★ V1.5.2：`.work` 必须同时有 `min-height: 0`。
 *
 *   一号位 01:52 指着右上角问「还有这两个地方的滚动条，不是说了都不要吗？」
 *
 *   ★ 根因：`.work` 是 `.main` 的 flex 子项，flex 子项的 `min-height` 默认是 `auto`，
 *     **不会收缩到比内容更小** → 面板内容一高，`.work` 就被撑高
 *     → 撑破 `.main` → 撑破 `.shell` → **一路顶到 body，冒出页面级滚动条**。
 *   ★★ 光给 `.main` 加 `min-height: 0` 不够 —— **链条上每一层 flex 容器都要加**。
 *   ★★★ 这是 flex 布局最经典的坑：`min-width:0` 管横向、`min-height:0` 管纵向，缺一不可。 */
.work { flex: 1; display: flex; flex-direction: column; min-width: 0; min-height: 0; }

.tabs {
  flex: none; display: flex; gap: 4px; align-items: flex-end;
  padding: 0 14px; background: var(--bg-header); border-bottom: 1px solid var(--line);
  overflow-x: auto;
}
.tabs .tab {
  position: relative; padding: 11px 14px 10px; border: 0; background: none;
  color: var(--ink-2); cursor: pointer; white-space: nowrap;
}
.tabs .tab:hover { color: var(--ink); }
.tabs .tab.on { color: var(--brand); font-weight: 600; }
.tabs .tab.on::after {
  content: ""; position: absolute; left: 10px; right: 10px; bottom: -1px;
  height: 2px; background: var(--brand);
}
/* ★ 调试标签用紫色 = "仅 root"的身份色（33 号 §4.2：颜色就是身份） */
.tabs .tab.root { color: var(--root); }
.tabs .tab.root.on::after { background: var(--root); }

.pane { flex: 1; overflow: auto; padding: 14px; min-height: 0; min-width: 0; }
.pane.flush { padding: 0; }

/* ---------------------------------------------------------------- 卡片 / 区块 */
.card {
  background: var(--bg-card); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 12px 14px; margin-bottom: 12px;
}
/* ★ 按钮紧贴标题右侧（33 号 §7.2-6，一号位既有偏好） */
.card-head {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 10px;
}
.card-head h3 { font-size: var(--fs-lg); }
.card-head .sp { margin-left: auto; }
.card-sub { font-size: var(--fs-xs); color: var(--ink-3); }

/* ★★★ V1.4.5：待分配 DTU 区块（自动认领后已上线、尚未归属项目） */
.pend-box {
  margin: 0 0 12px; padding: 10px 12px;
  border: 1px solid var(--warn1); border-radius: 8px;
  background: var(--bg-soft, #F1EFE8);
}
.pend-title { font-size: var(--fs-xs); color: var(--ink-2); margin-bottom: 8px; }
.pend-title b { color: var(--ink-1); }

.row { display: flex; gap: 12px; flex-wrap: wrap; align-items: flex-start; }
.row > .card { flex: 1 1 320px; min-width: 0; }
.grid2 { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 12px; }

.empty {
  padding: 26px 12px; text-align: center; color: var(--ink-3); font-size: var(--fs-base);
}
.loading { padding: 20px; text-align: center; color: var(--ink-3); font-size: var(--fs-base); }

/* ---------------------------------------------------------------- 按钮 / 表单 */
button.btn {
  padding: 6px 12px; border: 1px solid var(--line-strong); border-radius: 6px;
  background: var(--bg-card); cursor: pointer; white-space: nowrap;
}
button.btn:hover:not(:disabled) { border-color: var(--brand); color: var(--brand); }
button.btn.primary {
  background: var(--brand); border-color: var(--brand); color: var(--ink-on-dark);
}
button.btn.primary:hover:not(:disabled) { background: var(--brand-dark); color: #fff; }
/* ★★★ V1.5.8 一号位 2026-10-23 02:22 定的全局按钮配色：
 *   「**新增的蓝框蓝字、编辑都是蓝框蓝字，删除的是红框红字**」
 *   ★ 所以：新增=primary（蓝底白字，主操作）、编辑=edit（蓝框蓝字，同色系次权重）、
 *     删除=danger（红框红字）。
 *   ★★ 为什么编辑不也用蓝底：一排里三个按钮全蓝底，就看不出哪个是主操作了。
 *     同色系分权重，是表格行内操作区的通行做法。
 */
button.btn.edit,
button.btn.blue { border-color: var(--brand); color: var(--brand-text); }
button.btn.edit:hover:not(:disabled),
button.btn.blue:hover:not(:disabled) { background: var(--brand-soft); border-color: var(--brand); color: var(--brand-text); }
button.btn.danger { border-color: var(--warn2); color: var(--warn2); }
button.btn.danger:hover:not(:disabled) { background: var(--warn2); color: #fff; }
button.btn.sm { padding: 3px 8px; font-size: var(--fs-sm); }
button.btn:disabled { color: var(--ink-3); border-color: var(--line); cursor: not-allowed; }

.field { margin-bottom: 10px; }
.field > label { display: block; margin-bottom: 3px; font-size: var(--fs-sm); color: var(--ink-2); }
.field .req::after { content: " *"; color: var(--warn2); }
input[type=text], input[type=password], input[type=number], input[type=datetime-local],
input[type=date], select, textarea {
  width: 100%; padding: 6px 9px; background: var(--bg-card);
  border: 1px solid var(--line-strong); border-radius: 6px;
}
input:focus, select:focus, textarea:focus {
  outline: 2px solid var(--brand-soft); border-color: var(--brand);
}
textarea { min-height: 62px; resize: vertical; }
.inline { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.inline > * { width: auto; }
.inline input[type=text], .inline input[type=number], .inline select { width: auto; min-width: 90px; }
label.chk { display: inline-flex; align-items: center; gap: 5px; font-size: var(--fs-sm); }
label.chk input { width: auto; }

/* ---------------------------------------------------------------- 表格（配置页密集表，行业通行做法） */
table.tb { width: 100%; border-collapse: collapse; font-size: var(--fs-base); }
table.tb th, table.tb td {
  padding: 6px 8px; border-bottom: 1px solid var(--line); text-align: left;
  vertical-align: top;
}
table.tb th {
  background: var(--bg-soft); font-weight: 600; font-size: var(--fs-sm); color: var(--ink-2);
  white-space: nowrap;
}
table.tb tbody tr:hover { background: var(--bg-hover); }
/* ★ V1.5.10：操作列的按钮【不许折行】。
   行里实际用的是 <span class="act">（不是 td.act），老规则没盖住它 ——
   表格一窄（比如「断面配置」弹窗里 6 列 + 一张示意图挤在 900px），
   「改」「删除」就上下叠起来，看着像坏了。 */
table.tb td.act, table.tb span.act { white-space: nowrap; }
table.tb span.act .btn { vertical-align: middle; }
.tb-wrap { overflow-x: auto; }

/* ★★★ V2.0.1（docs/123 §5.4）：报警维度表 —— 一行 = 一个比对对象，
 *   行内直接填「一级阈值 / 二级阈值」。输入框给固定宽度，
 *   否则表格列宽会被浏览器按内容来回重算，改一个数整张表跟着跳。 */
table.tb.rule-tbl select { min-width: 148px; }
table.tb.rule-tbl input[type=number] { width: 88px; }
table.tb.rule-tbl input.rule-span { width: 74px; margin-left: 6px; }

/* ---------------------------------------------------------------- 滚动条（V1.8.3）
 *
 * ★★★ 一号位 2026-10-23 04:11 圈图：「这个东西还是没去掉，哪去了？想办法去掉。」
 *   他圈的是内容区右上角那对 **⇕ 上下箭头** —— Chromium 在 Windows 上画的经典滚动条按钮。
 *
 * ★ 排查过程（写下来免得以后再踩）：
 *   ① 实测 `.pane` 的 `offsetWidth − clientWidth = 15px` → 确认就是它的滚动条；
 *   ② 先试"细滚动条 + `::-webkit-scrollbar-button{display:none}`" → **宽度变了(15→8)、箭头还在**。
 *      根因：只要写了标准属性 `scrollbar-width`，Chromium 就整块忽略 `::-webkit-scrollbar-*`；
 *      去掉之后伪元素生效了、宽度也对，但**箭头仍然画**（Chromium 对 -button 的 display:none 不总认）。
 *   ③ 加 `--hide-scrollbars` 启动参数对比截图 → 箭头彻底消失，证明它百分之百是滚动条按钮。
 *
 * ★★ 结论：这套"经典滚动条"在当前 Chromium 上**没法只删箭头、保留滑块** ——
 *   按 selector 一个个列也列不全（那个 ▼ 挂在某层祖先上，`.pane` 并不是唯一滚动的容器）。
 *   他要的是"去掉"，那就**全站统一去掉**：
 *
 *     html, body, body * → `scrollbar-width: none`（标准）
 *     body *::-webkit-scrollbar → `display:none`（Chromium / Safari）
 *
 *   ★ 滚动能力一点没减：滚轮 / 触控板 / 键盘 / `scrollTop` 全部照常（实测 set300→300）。
 *     卡片内容会被视口切断 —— 那本身就是"还有下文"的提示，不需要一根灰条来提醒。
 *   ★ 以后真要某个容器显示滚动条，在它自己身上把这两条覆盖回来即可。
 */
html, body, body * {
  scrollbar-width: none;          /* Firefox / 新版标准 */
  -ms-overflow-style: none;       /* 老 Edge */
}
body *::-webkit-scrollbar {
  width: 0; height: 0; display: none;
}

/* ---------------------------------------------------------------- 调试台「收发表」（V1.7.0）
 *
 * ★ 一号位 2026-10-23 03:38：「相当于内置了一个网页版 SSCOM —— 显示窗口发了啥、
 *   收到了啥，带个时间戳。」
 * ★ 用浅底 + 等宽字，跟平台其他表格一个基调（不做黑底终端，避免页面变"另一个产品"）。
 * ★ TX 蓝 / RX 绿 / 解析 深绿 / 系统 灰 / 错误 红 —— 只用色相区分，不靠加粗堆视觉。
 */
.dbg-con {
  font-family: ui-monospace, Consolas, "Cascadia Mono", Menlo, monospace;
  font-size: 12.5px; line-height: 1.75;
  background: var(--bg-soft);
  border: 1px solid var(--line); border-radius: 6px;
  padding: 8px 10px; height: 320px; overflow: auto;
  white-space: pre-wrap; word-break: break-all;
  user-select: text;
}
.dbg-line { padding: 1px 0; }
.dbg-line.tx  { color: #1557B0; }          /* 发出去 */
.dbg-line.rx  { color: #0F7B32; }          /* 收回来 */
.dbg-line.ok  { color: #3F6B2A; }          /* 解析结果 */
.dbg-line.sys { color: var(--ink-3); }     /* 系统提示 / 无应答 */
.dbg-line.err { color: var(--warn2); }     /* 出错 */

/* ---------------------------------------------------------------- 弹窗 */
/* ★ 弹窗全部挂在 #modal-host 上（在 pane 之外）——
   老系统踩过的坑：放 pane 里切标签会被 display:none 一起藏掉（99 号 §一-6）。 */
.modal-mask {
  position: fixed; inset: 0; z-index: 800; display: flex;
  align-items: flex-start; justify-content: center; padding: 40px 16px;
  background: rgba(20, 22, 26, .45); overflow: auto;
}
.modal {
  width: 620px; max-width: 100%; background: var(--bg-card);
  border-radius: 10px; box-shadow: var(--shadow); overflow: hidden;
}
.modal.wide { width: 900px; }
.modal-head {
  display: flex; align-items: center; gap: 10px;
  padding: 12px 16px; border-bottom: 1px solid var(--line);
}
.modal-head h3 { font-size: var(--fs-lg); }
.modal-head .x {
  margin-left: auto; border: 0; background: none; font-size: 18px; line-height: 1;
  cursor: pointer; color: var(--ink-3);
}
.modal-body { padding: 14px 16px; }
.modal-foot {
  display: flex; gap: 8px; justify-content: flex-end;
  padding: 10px 16px; border-top: 1px solid var(--line); background: var(--bg-soft);
}

/* ---------------------------------------------------------------- 状态色（唯一出口） */
.st { display: inline-flex; align-items: center; gap: 5px; }
.sw {
  display: inline-block; width: 13px; height: 13px; border-radius: 3px;
  vertical-align: -2px; flex: none;
}
.sw-ok      { background: var(--ok); }
.sw-warn1   { background: var(--warn1); }
.sw-warn2   { background: var(--warn2); }
.sw-na      { background: var(--na); }
.sw-empty   { background: var(--empty); border: 1px dashed var(--empty-line); }

.badge {
  display: inline-block; padding: 1px 6px; border-radius: 10px;
  font-size: var(--fs-xs); background: var(--bg-hover); color: var(--ink-2);
}
.badge.ok    { background: #E8F5E9; color: var(--ok); }
.badge.warn1 { background: #FEF5D3; color: var(--warn1-ink); }   /* ★ 浅黄底配暗金字（V2.0 去橙） */
.badge.warn2 { background: #FBE4E4; color: var(--warn2); }
.badge.na    { background: #ECEEF1; color: var(--na); }
/* ★★★ 2026-10-29 一号位 10:13：「标定中不该用黄色 —— 黄色在我们的体系里表示有问题，
   标定中不是问题。」→ 新增【进行中】色：蓝。
   ★ 为什么是蓝，不新造色相：蓝在本系统里已经有明确归属（`--info`，链接 / 传感器方块），
     语义是"信息/过程"，跟绿黄红灰那四个【评价色】不是一类东西。
   ★ 为什么不用灰：灰在这个体系里已经等于"无法判断"（离线 / 校验失败），
     拿它表示"正在标定"会让用户以为这台读数有问题。
   ★ 为什么不用浅绿：绿 = 已生效的正常值，而标定中的值**还没落库**，不能让它们看起来一样。 */
.badge.run   { background: #E8F0FA; color: var(--info); }
.badge.root  { background: var(--root-soft); color: var(--root); }

/* ---------------------------------------------------------------- 网络拓扑（② 网络） */
/* ★★ 只有两层：DTU → 它下面的设备地址。**没有集线器那一层** ——
   一号位口径：「集线器在物理上、在整个网络通信里都是不存在的」。
   所以这里也不做"树"的通用样式，避免以后有人顺手往里塞第三层。 */
.topo { display: flex; flex-direction: column; gap: 10px; }
.topo-dtu {
  border: 1px solid var(--line-strong); border-left: 4px solid var(--na);
  border-radius: var(--radius); padding: 8px 10px; background: var(--bg-card);
}
.topo-dtu.on { border-left-color: var(--ok); }
.topo-dtu.warn { border-left-color: var(--warn1); }
.topo-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.topo-kids {
  display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px;
  padding-left: 12px; border-left: 1px dashed var(--line-strong);
}
.topo-dev {
  display: flex; flex-direction: column; gap: 1px; min-width: 140px;
  padding: 4px 8px; border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--bg-soft);
}
.topo-dev .addr { color: var(--info); font-weight: 600; font-size: var(--fs-sm); }
.topo-dev .who { font-size: var(--fs-xs); color: var(--ink-2); }

/* ---------------------------------------------------------------- 图例（带计数） */
.legend { display: flex; gap: 16px; flex-wrap: wrap; font-size: var(--fs-xs); color: var(--ink-2); }
.legend span { display: inline-flex; align-items: center; gap: 5px; }

/* ---------------------------------------------------------------- 通用小件 */
.kv { display: flex; flex-wrap: wrap; gap: 6px 18px; font-size: var(--fs-base); }
.kv b { font-weight: 600; }
.stat-line { font-size: var(--fs-base); color: var(--ink); display: flex; flex-wrap: wrap; gap: 6px 16px; }
.stat-line .k { color: var(--ink-3); }

.code {
  font-family: var(--mono); font-size: var(--fs-xs); background: var(--bg-code);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: 8px 10px; white-space: pre-wrap; word-break: break-all;
}

/* ★★ 手机适配边界（34 号 §七，一号位定）：
     ① 结构物 → 横屏看长条（由 fig_tunnel / dark.css 的响应式负责）
     ② 网络   → 简化版（列表为主，拓扑图提示"请用电脑"）
     ③ 配置   → ★ 不做手机适配
     ④ 日志   → 列表可以看
     ⑤ 调试   → ★ 不做（只有 root 在电脑上排障）
   所以这里【只】处理 ①②④ 三条，③⑤ 明确不做、也不许顺手做一半 ——
   半适配比不适配更糟：它会让"以为能用"的现场工程师卡在中间态。 */
@media (max-width: 820px) {
  :root { --w-side: 150px; }
  .header { gap: 10px; padding: 0 10px; height: auto; flex-wrap: wrap; padding-bottom: 6px; }
  /* ★ 上面把 header 的左右内边距从 16 改成了 10 ——
     顶栏品牌块那行 `calc(var(--w-side) - 16px)` 就多减了 6px，
     右边缘会比侧栏短一截。这里跟着改，保持"两块右边缘齐平"。 */
  .brand { width: calc(var(--w-side) - 10px); }
  /* ★ 顶栏那块现在是「物联网云平台运营概览」**9 个字**（联动的），
     窄屏 150px 侧栏 - 10 = 140px、减 padding 后只剩 116px 可用，
     15px 字号需要约 147px → 会溢出。这里按 11px 收，9 字约 103px，放得下。 */
  .brand .name { font-size: 11px; letter-spacing: 0.5px; }
  .kpis .kpi { min-width: 70px; }
  .pane { padding: 10px; }
  /* ③ 配置、⑤ 调试：不做适配 → 给一句明确提示，别让人以为是坏了 */
  body.no-mobile .pane { min-width: 900px; }
}

/* ---------------------------------------------------------------- 网络拓扑图（SVG）
 *
 * ★★★ V1.5.1：原来是"一堆卡片竖着排"，看不出链路。
 *   一号位 01:04 要求画成真正的拓扑图，而且明确了它的**价值**：
 *   「显示当前所有网络、所有设备的**在线还是离线**状态」。
 *
 * ★ 版式要点：
 *   ① 整张图**自适应宽度**（viewBox 固定 680，width:100%），不出现横向滚动
 *   ② 设备框的颜色只表达一件事：绿=在线、灰=离线
 *   ③ 有线实线、无线虚线 —— 这是"信息"，不是装饰
 */
.topo-svg-wrap {
  padding: 6px 4px 2px;
  overflow-x: auto;              /* ★ 万一一屏放不下（DTU 特别多），允许横向滚，不挤变形 */
  /* ★ V1.5.2：图按 1:1 显示（max-width=viewBox 宽）之后，容器比图宽 → 会靠左、
     右边空一大片。居中显示，视觉上才是"一张完整的图"。 */
  text-align: center;
}
/* ★★★ V1.5.2：限制最大宽度 —— 一号位 01:46「整个这个图太大了……比例根本不对」。
 *   viewBox 是 640 宽；SVG 的 width:100% 会把它**放大**到容器宽度（1000+px），
 *   于是 12px 的字被放大成 ~18px、整张图高出一倍。
 *   ★ 卡一个 max-width = viewBox 宽 → **1:1 显示**，字号就是设计的字号，
 *     整体高度也跟隧道图齐平（都 ≈280px）。 */
.topo-svg { display: block; min-width: 480px; max-width: 640px; margin: 0 auto; }
.topo-svg text { font-family: var(--font-sans); }

/* 图例：只解释"线型"和"状态色"两件事 */
.topo-legend {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 6px 18px; margin-top: 6px; padding-top: 8px;
  border-top: 1px dashed var(--line);
  font-size: var(--fs-xs); color: var(--ink-2);
}
.topo-legend .lg-item {
  display: inline-flex; align-items: center; gap: 6px;
}
.topo-legend .lg-item svg { display: block; }
.topo-legend .lg-dot {
  display: inline-block; width: 9px; height: 9px; border-radius: 50%;
}
.topo-legend .lg-note { color: var(--ink-3); }

/* ---------------------------------------------------------------- ★ V1.10 结构物采集状态
 *
 *   ★ 跟"测点在线/离线、DTU 在线/离线"用同一套语义：绿 = 在跑，灰 = 不跑。
 *   ★★ 但含义不同：测点/DTU 的灰是"坏了 / 联系不上"（故障），
 *      结构物的灰是"我们主动不问"（决定）—— 日志里必须分得开。
 *   ★ 未采集（策略从没应用过）用【虚线空心圈】：既不是绿也不是灰，是"还没开始"。
 */
.acq-dot {
  display: inline-block; width: 7px; height: 7px; border-radius: 50%;
  margin-right: 5px; vertical-align: middle; background: var(--na);
}
.acq-dot.running   { background: var(--ok); }
.acq-dot.stopped   { background: var(--na); }
.acq-dot.unapplied {
  background: transparent; border: 1.5px solid var(--na); box-sizing: border-box;
}
.acq-tag { font-size: 11.5px; opacity: .8; margin-left: 2px; }
.acq-t-running   { color: var(--ok); }
.acq-t-stopped   { color: var(--na-ink); }
.acq-t-unapplied { color: var(--na-ink); opacity: .75; }
