/* ============================================================
   DTvpn 分流中台 — 业务样式  (style_id: saas-dark-web)
   所有视觉值引用 tokens.css 的 var(--...)，零字面量。
   ============================================================ */
* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: var(--font-family-sans);
  font-size: var(--font-size-base);
  line-height: var(--line-height-normal);
  color: var(--color-text-primary);
  background: var(--color-app-bg);
  -webkit-font-smoothing: antialiased;
}

.hidden { display: none !important; }
.muted { color: var(--color-text-muted); }
.spacer { flex: 1; }

/* -------- 滚动条：统一暗色主题（垂直/横向） -------- */
* { scrollbar-width: thin; scrollbar-color: var(--color-border-strong) transparent; }
::-webkit-scrollbar { width: var(--space-lg); height: var(--space-lg); }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: var(--color-border-strong);
  border-radius: var(--radius-full);
  border: var(--space-2xs) solid transparent;
  background-clip: padding-box;
}
::-webkit-scrollbar-thumb:hover { background: var(--color-text-muted); background-clip: padding-box; }
::-webkit-scrollbar-corner { background: transparent; }

/* -------- 通用控件 -------- */
button {
  cursor: pointer;
  border: 0;
  border-radius: var(--radius-md);
  padding: var(--space-md) var(--space-2xl);
  min-height: var(--size-hit);
  background: var(--color-brand);
  color: var(--color-text-on-accent);
  font-family: inherit;
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-medium);
  transition: background var(--motion-fast) var(--motion-ease),
              box-shadow var(--motion-fast) var(--motion-ease),
              transform var(--motion-fast) var(--motion-ease);
}
button:hover { background: var(--color-brand-strong); }
button:active { transform: translateY(1px); }
button.ghost {
  background: transparent;
  border: 1px solid var(--color-border);
  color: var(--color-text-muted);
}
button.ghost:hover {
  background: var(--color-surface-3);
  border-color: var(--color-border-strong);
  color: var(--color-text-primary);
}

input, select {
  background: var(--color-surface-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-md) var(--space-lg);
  min-height: var(--size-hit);
  color: var(--color-text-primary);
  font-family: inherit;
  font-size: var(--font-size-base);
  outline: none;
  transition: border-color var(--motion-fast) var(--motion-ease);
}
input::placeholder { color: var(--color-text-muted); }
input:focus, select:focus { border-color: var(--color-brand); }

/* 焦点可见 (A11y) */
:focus-visible {
  outline: var(--focus-width) solid var(--color-focus-ring);
  outline-offset: var(--focus-width);
}

.err {
  color: var(--color-danger);
  min-height: var(--space-3xl);
  font-size: var(--font-size-sm);
  margin-top: var(--space-sm);
}

/* ============================================================
   登录
   ============================================================ */
.login-wrap {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--color-app-bg);
  background-image: var(--gradient-glow);
  padding: var(--space-4xl);
}
.login-card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-card);
  padding: var(--space-6xl);
  width: var(--width-login);
  display: flex;
  flex-direction: column;
  gap: var(--space-xl);
}
.login-logo {
  display: flex;
  align-items: center;
  gap: var(--space-xl);
  justify-content: center;
  margin-bottom: var(--space-md);
}
/* 品牌图片 logo(白色版,替换原文字徽章;暗色背景上显示)*/
.brand-img { height: auto; display: block; }
.login-logo .brand-img { width: 230px; max-width: 84%; }
.side-brand .brand-img { width: 100%; max-width: 176px; }
.login-card h1 {
  font-size: var(--font-size-xl);
  font-weight: var(--font-weight-semibold);
  margin: 0;
  text-align: center;
}

/* 品牌 logo 方块 */
.brand-logo {
  width: var(--size-logo);
  height: var(--size-logo);
  flex: 0 0 auto;
  border-radius: var(--radius-md);
  background: var(--gradient-brand);
  color: var(--color-text-on-accent);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-weight: var(--font-weight-bold);
  font-size: var(--font-size-sm);
  box-shadow: var(--shadow-glow);
}

/* ============================================================
   应用外壳：侧栏 + 内容
   ============================================================ */
.app {
  display: grid;
  grid-template-columns: var(--size-sidebar) 1fr;
  min-height: 100vh;
}

/* -------- 侧栏 -------- */
.sidebar {
  display: flex;
  flex-direction: column;
  background: var(--color-sidebar-bg);
  border-right: 1px solid var(--color-border);
  padding: var(--space-3xl) var(--space-xl);
  gap: var(--space-3xl);
  /* 页面整体滚动时，侧栏固定在视口，导航始终可见 */
  position: sticky;
  top: 0;
  align-self: start;
  height: 100vh;
  overflow-y: auto;
}
.side-brand {
  display: flex;
  align-items: center;
  gap: var(--space-xl);
  padding: 0 var(--space-md);
}
.brand-name {
  font-weight: var(--font-weight-bold);
  font-size: var(--font-size-md);
  line-height: var(--line-height-tight);
}
.brand-sub {
  color: var(--color-text-muted);
  font-size: var(--font-size-xs);
}

.side-nav {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  flex: 1;
}
/* 导航项（沿用 .tab data-tab 机制） */
.tab {
  display: flex;
  align-items: center;
  gap: var(--space-xl);
  width: 100%;
  text-align: left;
  background: transparent;
  color: var(--color-text-muted);
  border-radius: var(--radius-md);
  padding: var(--space-lg) var(--space-xl);
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-medium);
}
.tab:hover {
  background: var(--color-white-a06);
  color: var(--color-text-primary);
}
.tab.active {
  background: var(--color-brand-soft);
  color: var(--color-text-primary);
  box-shadow: inset var(--indicator-width) 0 0 0 var(--color-brand);
}
.tab svg {
  width: var(--size-nav-icon);
  height: var(--size-nav-icon);
  flex: 0 0 auto;
}
.tab.active svg { color: var(--color-brand); }

/* 区块内的子标签页（机架手机 USB/OTG 分页等）。和侧栏 .tab 无关：横排、下划线选中态。 */
.subtabs {
  display: flex;
  gap: var(--space-sm);
  border-bottom: 1px solid var(--color-white-a06);
}
.subtab {
  background: transparent;
  color: var(--color-text-muted);
  border: 0;
  border-bottom: 2px solid transparent;
  border-radius: 0;
  padding: var(--space-sm) var(--space-lg);
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-medium);
  cursor: pointer;
}
.subtab:hover { color: var(--color-text-primary); }
.subtab.on {
  color: var(--color-text-primary);
  border-bottom-color: var(--color-brand);
}

/* 侧栏底部 */
.side-foot {
  display: flex;
  flex-direction: column;
  gap: var(--space-xl);
}
.side-status {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  padding: var(--space-lg) var(--space-xl);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  font-size: var(--font-size-xs);
  color: var(--color-text-muted);
}
.side-status b {
  color: var(--color-success);
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-semibold);
}
.user-card {
  display: flex;
  align-items: center;
  gap: var(--space-lg);
  padding: var(--space-md);
  border-top: 1px solid var(--color-border);
}
.avatar {
  width: var(--size-avatar);
  height: var(--size-avatar);
  flex: 0 0 auto;
  border-radius: var(--radius-full);
  background: var(--gradient-brand);
  color: var(--color-text-on-accent);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-semibold);
}
.user-meta {
  display: flex;
  flex-direction: column;
  min-width: 0;
  flex: 1;
}
.me {
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.me-role { font-size: var(--font-size-2xs); }
.icon-btn {
  background: transparent;
  border: 0;
  color: var(--color-text-muted);
  padding: var(--space-sm);
  min-height: var(--size-hit);
  min-width: var(--size-hit);
  border-radius: var(--radius-md);
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.icon-btn:hover { background: var(--color-white-a06); color: var(--color-text-primary); }
.icon-btn svg { width: var(--size-nav-icon); height: var(--size-nav-icon); }

.side-version {
  text-align: center;
  color: var(--color-text-muted);
  font-size: var(--font-size-2xs);
  padding-top: var(--space-md);
}

/* 测试数据开关：开启态高亮 */
#testdata-btn.on {
  background: var(--color-brand-soft);
  border-color: var(--color-brand);
  color: var(--color-brand);
}

/* -------- 内容区 -------- */
.content { display: flex; flex-direction: column; min-width: 0; }

.topbar {
  display: flex;
  align-items: center;
  gap: var(--space-2xl);
  min-height: var(--size-topbar);
  padding: var(--space-2xl) var(--space-4xl);
  border-bottom: 1px solid var(--color-border);
}
.page-head { display: flex; flex-direction: column; gap: var(--space-2xs); }
#page-title {
  margin: 0;
  font-size: var(--font-size-xl);
  font-weight: var(--font-weight-bold);
}
.page-sub { color: var(--color-text-muted); font-size: var(--font-size-sm); }
.topbar-right {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: var(--space-xl);
}
.stat-pill {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  padding: var(--space-sm) var(--space-2xl);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-full);
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
}
.stat-pill b { color: var(--color-success); font-weight: var(--font-weight-semibold); }

.panes { padding: var(--space-4xl); }
.pane { animation: fade-in var(--motion-slow) var(--motion-ease); }
.pane h3 {
  margin: var(--space-4xl) 0 var(--space-xl);
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-semibold);
  color: var(--color-text-primary);
}
.pane h3:first-child { margin-top: 0; }

@keyframes fade-in {
  from { opacity: 0; transform: translateY(var(--space-md)); }
  to   { opacity: 1; transform: translateY(0); }
}

/* -------- 工具栏 -------- */
.toolbar {
  display: flex;
  align-items: center;
  gap: var(--space-2xl);
  margin-bottom: var(--space-2xl);
  flex-wrap: wrap;
}
.ops-cast {
  display: flex;
  align-items: center;
  gap: var(--space-lg);
  flex-wrap: wrap;
  margin-bottom: var(--space-2xl);
}
/* 标签行内排:文字 + 输入框 + 单位 同一行,整条控件横排一排(不再上下叠) */
.ops-cast label { display: inline-flex; flex-direction: row; align-items: center; gap: 6px; white-space: nowrap; font-size: var(--font-size-2xs); color: var(--color-text-muted); }
.ops-cast #cast-arg-wrap { flex-direction: row; align-items: flex-end; gap: var(--space-sm); }
/* 手机农场看板 */
.phone-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr)); gap: var(--space-2xl); margin-bottom: var(--space-2xl); }
/* 缩略图缩小一半:卡片列宽减半 + 间距收紧 */
/* 手机卡固定 119px 宽,高度自适应(截图区 9:16 + 下方信息自动撑高) */
.phone-grid.small { grid-template-columns: repeat(auto-fill, 119px); gap: var(--space-lg); justify-content: start; }
/* 节点类型图标(节点总览主机名前):扁平线性 + 按类型配色,一眼分清设备类型 */
.ntype { display: inline-flex; vertical-align: -3px; margin-right: 6px; }
.ntype svg { width: 15px; height: 15px; }
.nt-win { color: #4aa3ff; }   /* Windows 电脑=蓝 */
.nt-ubn { color: #e95420; }   /* Ubuntu=官方橙 */
.nt-r2s { color: #35c46a; }   /* R2S 盒子=绿 */
.nt-phone { color: #a78bfa; } /* 手机=紫 */
.phone-card { border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow: hidden; background: var(--color-surface); }
.ph-screen { aspect-ratio: 9 / 16; background: var(--color-app-bg); display: flex; align-items: center; justify-content: center; cursor: pointer; }
.ph-screen img { width: 100%; height: 100%; object-fit: cover; }
.phone-grid.small .ph-meta { padding: var(--space-sm); }
/* 群控舰队卡(无截图,显手机图标) */
.fleet-ph { cursor: default; }
.fleet-ico { font-size: 1.8rem; opacity: .5; }
.ph-meta { padding: var(--space-md); display: flex; flex-direction: column; gap: var(--space-xs); }
/* 机号标题:所有卡统一字号(不再随 .small 缩成11px)——CS006/cs005 一样大 */
.ph-id { font-size: 14px; font-weight: 600; letter-spacing: .3px; line-height: 1.3; }
/* 手机卡操作按钮:弹性换行 + 均匀间距 + 更小字号/内边距,窄卡片里也不挤成一团 */
.ph-acts { margin-top: var(--space-sm); display: flex; flex-wrap: wrap; gap: 5px; }
.ph-acts .mini { margin-right: 0; font-size: 11px; padding: 3px 9px; line-height: 1.5; min-height: auto; }
/* 卡片里的小字统一收一点,行距舒服些 */
.ph-meta .tiny, .ph-meta .muted.tiny { font-size: 11px; line-height: 1.5; }
.ph-modal { position: fixed; inset: 0; background: var(--color-app-bg); display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: var(--space-md); z-index: 1000; cursor: pointer; }
.ph-modal img { max-width: 92vw; max-height: 86vh; object-fit: contain; border-radius: var(--radius-md); }
.toolbar #q { width: var(--width-search); max-width: 100%; }
.toolbar label {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  color: var(--color-text-muted);
  font-size: var(--font-size-sm);
}

/* ============================================================
   表格（卡片式）
   ============================================================ */
.table-wrap {
  overflow-x: auto;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-card);
}
table {
  width: 100%;
  border-collapse: collapse;
  min-width: var(--table-min-width);
  font-size: var(--font-size-sm);
}
th, td {
  padding: var(--space-xl) var(--space-2xl);
  text-align: left;
  border-bottom: 1px solid var(--color-border);
  white-space: nowrap;
}
th {
  background: var(--color-surface-2);
  color: var(--color-text-muted);
  font-weight: var(--font-weight-semibold);
  font-size: var(--font-size-xs);
  text-transform: uppercase;
  letter-spacing: 0.02em;
}
tbody tr { transition: background var(--motion-fast) var(--motion-ease); }
tbody tr:last-child td { border-bottom: 0; }
tbody tr:hover { background: var(--color-white-a02); }
td.mono { font-family: var(--font-family-mono); font-size: var(--font-size-xs); }
.copy { cursor: pointer; color: var(--color-brand); }
.copy:hover { text-decoration: underline; }

/* 状态点 */
.dot {
  display: inline-block;
  width: var(--size-dot);
  height: var(--size-dot);
  border-radius: var(--radius-full);
  margin-right: var(--space-md);
  vertical-align: baseline;
}
.dot.on { background: var(--color-success); box-shadow: var(--shadow-glow); }
.dot.off { background: var(--color-danger); }
.dot.warn { background: var(--color-warning); }

/* 档位 / 状态 chip */
.badge {
  display: inline-block;
  padding: var(--space-2xs) var(--space-lg);
  border-radius: var(--radius-full);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-medium);
  border: 1px solid transparent;
}
.badge.res { color: var(--color-info); background: var(--color-info-soft); }
.badge.g4  { color: var(--color-warning); background: var(--color-warning-soft); }
.badge.danger { color: var(--color-danger); background: var(--color-danger-soft); }
.badge.p2p { color: var(--color-success); background: var(--color-success-soft); }
.badge.relay { color: var(--color-warning); background: var(--color-warning-soft); }
.badge.live { color: var(--color-brand); background: var(--color-brand-soft); }
.badge.cap { color: var(--color-warning); background: var(--color-warning-soft); }
.badge.self { color: var(--color-success); background: var(--color-success-soft); }
.badge.peer { color: var(--color-info); background: var(--color-info-soft); }
.chk-col { width: var(--size-hit); text-align: center; }
.batchbar { gap: var(--space-md); align-items: center; }
.batchbar input[type="number"] { width: var(--width-search); }

/* 线路自检结果 */
.lt-verdict { font-size: var(--font-size-lg); font-weight: var(--font-weight-bold); margin-bottom: var(--space-sm); }
.lt-verdict.ok { color: var(--color-success); }
.lt-verdict.bad { color: var(--color-danger); }
.lt-verdict.warn { color: var(--color-warning); }
.lt-reason { font-size: var(--font-size-sm); color: var(--color-text-muted); margin-bottom: var(--space-xl); }
.lt-table { width: 100%; border-collapse: collapse; font-size: var(--font-size-sm); }
.lt-table td { padding: var(--space-md) var(--space-sm); border-bottom: 1px solid var(--color-border); }
.lt-table tr:last-child td { border-bottom: 0; }
.lt-table td:first-child { width: 9ch; white-space: nowrap; }

.empty { padding: var(--space-6xl); text-align: center; color: var(--color-text-muted); }

/* IP来源就地编辑 */
.src-cell { cursor: text; border-radius: var(--radius-sm); }
.src-cell:hover { background: var(--color-white-a06); }
.src-in {
  width: 100%;
  padding: var(--space-xs) var(--space-sm);
  min-height: auto;
  font-size: var(--font-size-sm);
  border-color: var(--color-brand);
}

/* 操作列 & 小按钮 */
td.acts { white-space: nowrap; }
.mini {
  padding: var(--space-xs) var(--space-lg);
  min-height: auto;
  font-size: var(--font-size-xs);
  margin-right: var(--space-xs);
  border-radius: var(--radius-sm);
}
/* 删除=危险操作:实心红底+白字,别继承基础按钮的紫色渐变(红字压紫底看不清) */
.mini.danger { color: #fff; background: var(--color-danger); background-image: none; border-color: var(--color-danger); }
.mini.danger:hover { background: #dc2626; border-color: #dc2626; }

/* ============================================================
   弹窗
   ============================================================ */
.modal-wrap {
  position: fixed;
  inset: 0;
  background: var(--color-overlay-scrim);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 50;
  padding: var(--space-4xl);
  animation: fade-in var(--motion-base) var(--motion-ease);
}
.modal {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-pop);
  padding: var(--space-4xl);
  width: var(--width-modal);
  max-width: 100%;
  display: flex;
  flex-direction: column;
  gap: var(--space-xl);
}
.modal h3 {
  margin: 0 0 var(--space-sm);
  font-size: var(--font-size-md);
  font-weight: var(--font-weight-semibold);
}
.modal label {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
}
.modal label.ck { flex-direction: row; align-items: center; gap: var(--space-md); }
/* 只让文本类输入占满宽度——别把复选框/单选也撑成 100% 宽(那会渲染成一个大方块) */
.modal input:not([type="checkbox"]):not([type="radio"]), .modal select { width: 100%; }
/* 复选框:正常尺寸 + 站点主色(accent-color),和整站统一 */
.modal label.ck input[type="checkbox"] {
  width: 16px; height: 16px; flex: 0 0 auto; margin: 0;
  accent-color: var(--color-brand);
  cursor: pointer;
}
.modal-btns {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-xl);
  margin-top: var(--space-md);
}

/* ============================================================
   Toast
   ============================================================ */
.toast {
  position: fixed;
  bottom: var(--space-5xl);
  left: 50%;
  transform: translateX(-50%);
  background: var(--color-surface);
  border: 1px solid var(--color-brand);
  color: var(--color-text-primary);
  padding: var(--space-xl) var(--space-4xl);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-pop);
  z-index: 60;
  max-width: 80vw;
  font-size: var(--font-size-sm);
}

/* ============================================================
   首页：卡片 / KPI / 图表 / 中国地图
   ============================================================ */
.card-title {
  margin: 0 0 var(--space-xl);
  font-size: var(--font-size-md);
  font-weight: var(--font-weight-semibold);
  color: var(--color-text-primary);
}
.card-title.mt { margin-top: var(--space-4xl); }

/* KPI 指标卡 */
.kpi-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: var(--space-2xl);
  margin: var(--space-4xl) 0;
}
.kpi {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-card);
  padding: var(--space-3xl);
  border-left: var(--indicator-width) solid var(--color-brand);
}
.kpi-success { border-left-color: var(--color-success); }
.kpi-teal    { border-left-color: var(--color-teal); }
.kpi-info    { border-left-color: var(--color-info); }
.kpi-warning { border-left-color: var(--color-warning); }
.kpi-label { color: var(--color-text-muted); font-size: var(--font-size-sm); }
.kpi-value { font-size: var(--font-size-2xl); font-weight: var(--font-weight-bold); margin: var(--space-sm) 0; }
.kpi-sub { font-size: var(--font-size-xs); }

/* 图表网格 */
.chart-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-2xl);
}
.chart-card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-card);
  padding: var(--space-3xl);
  min-width: 0;
}
.chart-card.span2 { grid-column: span 2; }
.chart-card.span3 { grid-column: span 3; }
.chart-box { width: 100%; }
.chart-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 12ch;
  padding: var(--space-4xl);
  font-size: var(--font-size-sm);
}
.chart-svg { width: 100%; height: auto; display: block; }
.relay-chart { width: 100%; height: 15rem; }

/* 趋势折线 */
.chart-svg .grid { stroke: var(--color-border); stroke-width: 1; }
.chart-svg .ax { fill: var(--color-text-muted); font-size: var(--font-size-2xs); }
.chart-svg .area { fill: var(--color-brand); fill-opacity: 0.12; }
.chart-svg .line { fill: none; stroke: var(--color-brand); stroke-width: 2; stroke-linejoin: round; }
.chart-svg .pt { fill: var(--color-brand); stroke: var(--color-surface); stroke-width: 1.5; }

/* 环形图 */
.donut-wrap { display: flex; align-items: center; gap: var(--space-3xl); flex-wrap: wrap; }
.donut { width: 8.75rem; height: 8.75rem; flex: 0 0 auto; }
.dn-track { fill: none; stroke: var(--color-surface-3); stroke-width: 14; }
.dn-on { fill: none; stroke: var(--color-success); stroke-width: 14; stroke-linecap: round; }
.dn-self { fill: none; stroke: var(--color-success); stroke-width: 14; }
.dn-peer { fill: none; stroke: var(--color-info); stroke-width: 14; }
.dn-num { fill: var(--color-text-primary); font-size: var(--font-size-2xl); font-weight: var(--font-weight-bold); }
.dn-cap { fill: var(--color-text-muted); font-size: var(--font-size-xs); }
.legend { display: flex; flex-direction: column; gap: var(--space-md); }
.legend .lg, .map-legend .lg { display: flex; align-items: center; gap: var(--space-md); font-size: var(--font-size-sm); color: var(--color-text-muted); }
.legend b { color: var(--color-text-primary); }
.sw { width: var(--space-xl); height: var(--space-xl); border-radius: var(--radius-sm); flex: 0 0 auto; display: inline-block; }
.sw-on  { background: var(--color-success); }
.sw-off { background: var(--color-danger); }
.sw-res { background: var(--color-info); }
.sw-g4  { background: var(--color-warning); }
.sw-self { background: var(--color-success); }
.sw-peer { background: var(--color-info); }

/* Top 省份条形 */
.bar-row { display: flex; align-items: center; gap: var(--space-xl); margin-bottom: var(--space-xl); font-size: var(--font-size-sm); }
.bar-name { width: 5ch; flex: 0 0 auto; color: var(--color-text-primary); }
.bar-track { flex: 1; height: var(--space-lg); background: var(--color-surface-3); border-radius: var(--radius-full); overflow: hidden; }
.bar-fill { display: block; height: 100%; background: var(--gradient-brand); border-radius: var(--radius-full); transition: width var(--motion-slow) var(--motion-ease); }
.bar-val { flex: 0 0 auto; color: var(--color-text-muted); font-size: var(--font-size-xs); min-width: 12ch; text-align: right; }

/* ---------- 中国地图 ---------- */
.map-card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-card);
  padding: var(--space-3xl);
}
.map-head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-xl); margin-bottom: var(--space-xl); }
.map-body { position: relative; }
/* ECharts 画布容器（canvas 渲染，高度必须显式给） */
.map-svg { width: 100%; height: 26rem; }

/* 三列：地图 / 省市选择 / 节点详情 */
.geo-grid { display: grid; grid-template-columns: 1.6fr 0.9fr 1.25fr; gap: var(--space-2xl); align-items: start; }
.geo-map { min-width: 0; }
.geo-sel, .geo-detail { min-width: 0; max-height: 30rem; overflow-y: auto; border-left: 1px solid var(--color-border); padding-left: var(--space-2xl); }
.geo-col-h { font-size: var(--font-size-sm); font-weight: var(--font-weight-semibold); color: var(--color-text-muted); margin-bottom: var(--space-xl); position: sticky; top: 0; background: var(--color-surface); padding-bottom: var(--space-sm); }
.geo-detail .geo-col-h { color: var(--color-text-primary); }

/* 省市选择器 */
.prov-list { display: flex; flex-direction: column; gap: var(--space-2xs); }
.prov-item { display: flex; align-items: center; justify-content: space-between; gap: var(--space-md); padding: var(--space-md) var(--space-lg); border-radius: var(--radius-md); cursor: pointer; font-size: var(--font-size-sm); }
.prov-item:hover { background: var(--color-white-a06); }
.prov-item.on { background: var(--color-brand-soft); color: var(--color-text-primary); box-shadow: inset var(--indicator-width) 0 0 0 var(--color-brand); }
.prov-c { color: var(--color-text-primary); font-weight: var(--font-weight-semibold); flex: 0 0 auto; }
.city-sub { display: flex; flex-wrap: wrap; gap: var(--space-sm); padding: var(--space-md) var(--space-lg) var(--space-lg); }
.city-chip { font-size: var(--font-size-xs); padding: var(--space-2xs) var(--space-md); border-radius: var(--radius-full); background: var(--color-surface-2); border: 1px solid var(--color-border); color: var(--color-text-muted); cursor: pointer; }
.city-chip.on { background: var(--color-brand-soft); color: var(--color-brand); border-color: var(--color-brand); }

/* 节点详情卡片 */
.node-detail { display: flex; flex-direction: column; gap: var(--space-xl); }
.nd-hint { font-size: var(--font-size-xs); color: var(--color-text-muted); margin-bottom: var(--space-md); }
.nd-card { background: var(--color-surface-2); border: 1px solid var(--color-border); border-radius: var(--radius-md); padding: var(--space-xl); }
.nd-top { display: flex; align-items: center; justify-content: space-between; gap: var(--space-md); margin-bottom: var(--space-sm); }
.nd-host { font-size: var(--font-size-sm); font-weight: var(--font-weight-semibold); }
.nd-meta { font-size: var(--font-size-xs); color: var(--color-text-muted); margin-bottom: var(--space-md); }
.nd-rows { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-sm) var(--space-md); font-size: var(--font-size-xs); color: var(--color-text-muted); }
.nd-rows b { color: var(--color-text-primary); font-weight: var(--font-weight-medium); }
.nd-sumrow { display: flex; justify-content: space-between; gap: var(--space-md); font-size: var(--font-size-sm); padding: var(--space-md) 0; border-bottom: 1px solid var(--color-border); }
.nd-sumrow:last-child { border-bottom: 0; }
/* 下钻侧栏面板 */
.map-panel {
  position: absolute;
  top: var(--space-md);
  right: var(--space-md);
  width: var(--width-search);
  max-width: 60%;
  max-height: 90%;
  overflow: auto;
  background: var(--color-surface-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-pop);
  padding: var(--space-2xl);
}
.mp-title { font-size: var(--font-size-lg); font-weight: var(--font-weight-semibold); }
.mp-sub { font-size: var(--font-size-xs); margin: var(--space-sm) 0 var(--space-xl); }
.mp-list { display: flex; flex-direction: column; gap: var(--space-md); }
.mp-row { display: flex; justify-content: space-between; gap: var(--space-md); font-size: var(--font-size-sm); padding-bottom: var(--space-md); border-bottom: 1px solid var(--color-border); }
.mp-row:last-child { border-bottom: 0; }
.map-legend { display: flex; gap: var(--space-4xl); flex-wrap: wrap; margin-top: var(--space-xl); }
.mp-grad { width: var(--space-6xl); height: var(--space-xl); border-radius: var(--radius-sm); background: var(--gradient-brand); flex: 0 0 auto; display: inline-block; }

/* ============================================================
   响应式：窄屏折叠侧栏为顶部横排
   ============================================================ */
@media (max-width: 53.75em) {
  .app { grid-template-columns: 1fr; }
  .sidebar {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-xl);
    border-right: 0;
    border-bottom: 1px solid var(--color-border);
    /* 窄屏改为顶部横排：取消固定 */
    position: static;
    height: auto;
    overflow: visible;
  }
  .side-nav { flex-direction: row; flex-wrap: wrap; flex: 1 1 100%; }
  .tab.active { box-shadow: inset 0 calc(-1 * var(--focus-width)) 0 0 var(--color-brand); }
  .side-foot { flex-direction: row; align-items: center; }
  .topbar { flex-wrap: wrap; }
  .chart-grid { grid-template-columns: 1fr; }
  .chart-card.span2, .chart-card.span3 { grid-column: span 1; }
  .map-panel { position: static; width: auto; max-width: none; margin-top: var(--space-xl); }
  .map-svg { height: 22rem; }
  .geo-grid { grid-template-columns: 1fr; }
  .geo-sel, .geo-detail { border-left: 0; padding-left: 0; max-height: none; border-top: 1px solid var(--color-border); padding-top: var(--space-xl); }
}

/* ============================================================
   可访问性：尊重减少动效偏好
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}

/* 云手机舰队：分配手机勾选列表（仅布局，无颜色字面量） */
.fleet-assign-modal { width: min(600px, 94vw); }
.fleet-assign-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; margin: .5rem 0 .3rem; }
.fa-search { flex: 1 1 220px; min-width: 160px; padding: .4rem .6rem; border-radius: 8px;
  border: 1px solid var(--color-border, #26324a); background: var(--color-app-bg, #0b1220);
  color: var(--color-text-primary, #e2e8f0); font-size: .85rem; }
.fa-chips { display: flex; flex-wrap: wrap; align-items: center; gap: .25rem; }
.fa-chip { padding: .22rem .55rem; border-radius: 999px; font-size: .78rem; cursor: pointer;
  border: 1px solid var(--color-border, #26324a); background: transparent; color: var(--color-text-secondary, #94a3b8); }
.fa-chip.on { background: var(--color-brand, #8b5cf6); border-color: var(--color-brand, #8b5cf6); color: #fff; }
.fa-sep { width: 1px; height: 16px; background: var(--color-border, #26324a); margin: 0 .2rem; }
.fa-actions { display: flex; flex-wrap: wrap; align-items: center; gap: .4rem; margin-left: auto; }
.fleet-assign-list { max-height: 46vh; overflow-y: auto; margin: .3rem 0; }
/* 按盒子分组 */
.fa-group { border: 1px solid var(--color-border, #1e293b); border-radius: 8px; margin-bottom: .4rem; overflow: hidden; }
.fa-group-head { position: sticky; top: 0; z-index: 1; display: flex; align-items: center; gap: .45rem;
  padding: .4rem .55rem; background: var(--color-surface, #111827); border-bottom: 1px solid var(--color-border, #1e293b); }
.fa-group-toggle { cursor: pointer; color: var(--color-text-muted, #64748b); user-select: none; width: 1em; }
.fa-group-body { padding: .1rem .2rem; }
/* ★ .modal label 默认 flex-direction:column(0,1,1 特异度),必须用【两个类】选择器压过它,强制横排 */
.fleet-assign-list .fleet-assign-row { display: flex; flex-direction: row; align-items: center; gap: .4rem;
  padding: .35rem .45rem; font-size: .85rem; cursor: pointer; border-radius: 6px; }
.fleet-assign-list .fleet-assign-row:hover { background: var(--color-white-a02, rgba(255,255,255,.03)); }
/* ★ .modal input{width:100%} 会把勾选框撑满,必须收回;两个类压过它 */
.fleet-assign-list input, .fa-group-head input { width: auto; flex: 0 0 auto; margin: 0; }
.fleet-assign-list .badge { flex: 0 0 auto; white-space: nowrap; }
.fa-title { font-weight: 600; color: var(--color-text-primary, #e2e8f0); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 12rem; flex: 0 1 auto; }
.fa-loc { flex: 0 0 auto; }
.fa-model { flex: 1 1 auto; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.fa-serial { flex: 0 0 auto; opacity: .6; }

/* 手机 APK 管理:拖拽上传区 + 装失败原因 */
.apk-drop {
  border: 2px dashed var(--color-border, #2a2f3a);
  border-radius: var(--radius-md, 8px);
  padding: var(--space-xl, 18px);
  text-align: center;
  color: var(--color-text-muted, #94a3b8);
  font-size: var(--font-size-sm, 13px);
  transition: border-color .15s, background .15s;
}
.apk-drop.over { border-color: var(--color-info, #4f8cff); background: var(--color-info-soft, rgba(79,140,255,.08)); }
/* 文件选择框:原生太丑,给它的按钮套上站点风格(紫描边,悬停填充),与整站统一 */
.apk-drop input[type="file"] { color: var(--color-text-muted); font-size: var(--font-size-xs); max-width: 100%; }
.apk-drop input[type="file"]::file-selector-button {
  margin-right: 8px;
  padding: 5px 14px;
  border-radius: var(--radius-sm, 6px);
  border: 1px solid var(--color-brand, #8b5cf6);
  background: var(--color-brand-soft, rgba(139, 92, 246, 0.12));
  color: var(--color-brand, #8b5cf6);
  font-size: var(--font-size-xs);
  font-weight: 600;
  cursor: pointer;
  transition: background .15s, color .15s, border-color .15s;
}
.apk-drop input[type="file"]::file-selector-button:hover {
  background: var(--color-brand, #8b5cf6);
  color: #fff;
}
/* 装 APK 失败的人话原因(多为厂商守卫要在手机上登账号关)——必须完整可读,不省略号截断 */
.apk-err {
  color: var(--color-danger, #ef4444);
  font-size: var(--font-size-xs, 11.5px);
  line-height: 1.5;
  max-width: 26em;
  white-space: normal;
  margin-top: var(--space-2xs, 3px);
}

/* 两步验证(TOTP)设置 */
.totp-box { display: flex; gap: var(--space-xl, 12px); align-items: flex-start; flex-wrap: wrap; }
.totp-qr {
  width: 200px; height: 200px; padding: var(--space-sm, 8px);
  background: #fff;                 /* ★二维码必须白底黑块:深色底扫不出来 */
  border-radius: var(--radius-md, 10px);
}
.totp-side { flex: 1; min-width: 16em; }
.totp-side code {
  display: inline-block; padding: var(--space-2xs, 2px) var(--space-sm, 8px);
  background: var(--color-bg-soft, #0f1218); border: 1px solid var(--color-border, #1e293b);
  border-radius: var(--radius-sm, 6px); letter-spacing: .08em; word-break: break-all;
}
.warn-text { color: var(--color-warning, #f59e0b); }
.totp-codes-warn {
  border: 1px solid var(--color-warning, #f59e0b); background: var(--color-warning-soft, rgba(245,158,11,.14));
  border-radius: var(--radius-md, 10px); padding: var(--space-lg, 10px);
  font-size: var(--font-size-sm, 13px); line-height: 1.6; margin-bottom: var(--space-md, 10px);
}
.totp-codes-list {
  font-family: var(--font-mono, monospace); font-size: var(--font-size-md, 14px);
  letter-spacing: .1em; line-height: 2; columns: 2; margin: 0 0 var(--space-md, 10px);
  padding: var(--space-lg, 10px); background: var(--color-bg-soft, #0f1218);
  border: 1px solid var(--color-border, #1e293b); border-radius: var(--radius-md, 10px);
}

/* 案例素材库网格 */
.case-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: var(--space-md, 8px); margin-top: var(--space-sm, 6px); }
.case-item { position: relative; border: 1px solid var(--color-border, #1e293b); border-radius: var(--radius-md, 10px); overflow: hidden; background: var(--color-bg-soft, #0f1218); }
.case-item img { width: 100%; height: 120px; object-fit: cover; display: block; }
.case-cap { padding: 4px 6px; font-size: var(--font-size-xs, 12px); color: var(--color-text-muted, #64748b); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.case-del { position: absolute; top: 4px; right: 4px; width: 22px; height: 22px; border: 0; border-radius: 50%; background: rgba(0,0,0,.55); color: #fff; cursor: pointer; font-size: 12px; }
.case-del:hover { background: var(--color-danger, #ef4444); }

/* ============ 拓扑图:自绘 HTML + SVG ============
   为什么不用 ECharts:节点要做成"图标+名字+IP+状态灯+指标"的信息卡片、边要跑流动动画、
   同组节点要用泳道框圈起来 —— 这三件 ECharts graph 都做不好(label 富文本排不出卡片、
   graph 系列没有流动效果、graphic 矩形不跟随缩放平移一拖就错位)。
   自绘就是普通 DOM + CSS,想怎么排怎么排,还零新依赖。 */
.topo-wrap { position: relative; overflow: hidden; background:
  radial-gradient(circle at 20% 10%, rgba(139,92,246,.07), transparent 55%), var(--color-app-bg, #0a0e1a);
  border: 1px solid var(--color-border, #1e293b); border-radius: var(--radius-md, 10px); cursor: grab; }
.topo-wrap.dragging { cursor: grabbing; }
.topo-canvas { position: absolute; left: 0; top: 0; transform-origin: 0 0; }
.topo-edges { position: absolute; left: 0; top: 0; overflow: visible; pointer-events: none; }

/* 边:实线=网线/供电,紫虚线=控制通道(网络adb),蓝点线=走SK5出口。
   ★只有两端都在线的边才跑流动 —— 断了的静止变暗,免得"看着在流其实是断的"。 */
.topo-edge { fill: none; stroke-width: 1.8; opacity: .5; stroke-linejoin: round; }
.topo-edge.k-wire { stroke: var(--color-border-strong, #26324a); }
.topo-edge.k-ctrl { stroke: var(--color-brand, #8b5cf6); stroke-dasharray: 5 4; }
.topo-edge.k-sk5  { stroke: var(--color-info, #3b82f6); stroke-dasharray: 2 5; stroke-linecap: round; }
.topo-edge.live { opacity: .95; stroke-dasharray: 7 9; animation: topoflow 1.1s linear infinite; }
.topo-edge.live.k-ctrl { stroke-dasharray: 5 6; }
.topo-edge.dead { opacity: .22; stroke-dasharray: 3 5; animation: none; }
@keyframes topoflow { to { stroke-dashoffset: -32; } }
/* 手动接线的边:金色实线,一眼看出这条是人改的(不是平台推断的) */
.topo-edge.manual { stroke: var(--color-warning, #f59e0b) !important; stroke-dasharray: none; opacity: .95; }
.topo-edge.manual.dead { opacity: .5; }
/* 拉线时跟着鼠标走的"橡皮筋" */
.topo-rubber { fill: none; stroke: var(--color-brand, #8b5cf6); stroke-width: 2; stroke-dasharray: 6 5; pointer-events: none; }

/* 泳道:同一盒子下的手机、同一交换机下的口圈起来。400 台时这个比单纯连线清楚得多 */
/* 分区框:一个局域网一条横带。不同网段 = 不同物理空间,它们之间本来就没有线相连,
   画在一起会让人以为是同一张网、还去找那条并不存在的连线。 */
.topo-group { position: absolute; border: 1px solid var(--color-border-strong, #26324a);
  border-radius: 16px; background: rgba(255,255,255,.018); pointer-events: none; z-index: 0; }
.topo-group-title { position: absolute; top: -11px; left: 18px; padding: 1px 10px; font-size: 12px;
  font-weight: 600; border-radius: 999px; white-space: nowrap;
  background: var(--color-app-bg, #0b1220); color: var(--color-text-secondary, #94a3b8);
  border: 1px solid var(--color-border-strong, #26324a); }

.topo-lane { position: absolute; border: 1px dashed var(--color-border-strong, #26324a);
  border-radius: 14px; background: var(--color-white-a02, rgba(255,255,255,.02)); pointer-events: none; }
.topo-lane-title { position: absolute; top: -9px; left: 12px; padding: 0 6px; font-size: 11px;
  color: var(--color-text-muted, #64748b); background: var(--color-app-bg, #0a0e1a); }

/* 节点卡片:宽度随文字自适应(max-content),布局按测到的真实宽排间距。封顶 380 防超长文字撑爆。 */
.topo-node { position: absolute; display: flex; align-items: center; gap: 8px;
  width: max-content; min-width: 128px; max-width: 380px;
  padding: 8px 10px; box-sizing: border-box; border-radius: 10px; cursor: pointer;
  background: var(--color-surface, #111827); border: 1px solid var(--color-border, #1e293b);
  transition: transform .12s, box-shadow .12s, border-color .12s; }
.topo-node:hover { transform: translateY(-1px); border-color: var(--color-brand, #8b5cf6);
  box-shadow: 0 6px 18px rgba(0,0,0,.45); z-index: 5; }
.topo-node.on { border-color: var(--color-brand, #8b5cf6); box-shadow: 0 0 0 2px var(--color-brand-soft); }
/* 哑设备是**推断**出来的,不是平台真能看到的设备 → 视觉上就该"虚" */
.topo-node.t-dumb { border-style: dashed; background: transparent; opacity: .85; }
.topo-ico { flex: 0 0 auto; width: 22px; height: 22px; opacity: .9; }
.topo-ico svg { width: 22px; height: 22px; display: block; }
.topo-body { min-width: 0; flex: 1; display: flex; flex-direction: column; justify-content: center; }
.topo-name { font-size: 12px; font-weight: 600; color: var(--color-text-primary, #e2e8f0);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.topo-sub { font-size: 11px; color: var(--color-text-muted, #64748b);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.topo-dot { flex: 0 0 auto; width: 8px; height: 8px; border-radius: 50%; }
/* 手机卡四排:①备注(name加粗)②位置③型号(sub)④SK5落地到区。加宽让文字尽量不截断;
   图标/状态点顶对齐,别居中飘。宽度与 JS 里 TOPO_GRID.NWP 保持一致。 */
.topo-node.phone3 { align-items: flex-start; }
.topo-node.phone3 .topo-ico { margin-top: 1px; }
.topo-node.phone3 .topo-dot { margin-top: 2px; }
/* 第2排:机架位置(机架手机页"位置"列)。没标就空一行——用 min-height 撑住,排版不塌。 */
.topo-loc { font-size: 11px; line-height: 1.3; min-height: 14px; color: var(--color-text-secondary, #94a3b8);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.topo-sk5 { font-size: 10.5px; line-height: 1.35; color: var(--color-success, #22c55e);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.topo-sk5.warn { color: var(--color-warning, #f59e0b); font-weight: 600; }

/* 看口表拖动排序:行头「⠿」是拖动句柄 */
td.drag-h { cursor: grab; white-space: nowrap; user-select: none; color: var(--color-text-muted, #64748b); }
td.drag-h:active { cursor: grabbing; }
tr.dragging { opacity: .5; background: var(--color-brand-soft, rgba(139,92,246,.15)); }

/* 接线把手:卡片上/下缘的小圆点(竖向布局)。默认隐藏,hover 卡片(或接线模式)才显形。
   拖它到另一张卡片 = 指定上/下游。in=上缘(拉它的上游),out=下缘(拉它的下游)。 */
.topo-port { position: absolute; left: 50%; width: 13px; height: 13px; margin-left: -6.5px;
  border-radius: 50%; background: var(--color-brand, #8b5cf6); border: 2px solid var(--color-app-bg, #0b1220);
  cursor: crosshair; opacity: 0; transition: opacity .12s, transform .12s; z-index: 8; }
.topo-port.in { top: -7px; }
.topo-port.out { bottom: -7px; }
.topo-node.wireable:hover .topo-port,
.topo-canvas.connect .topo-node.wireable .topo-port { opacity: 1; }
.topo-port:hover { transform: scale(1.35); }
/* 接线模式:整块变十字光标提示可连;"待接"的源节点高亮 */
.topo-canvas.connect .topo-node { cursor: crosshair; }
.topo-node.armed { border-color: var(--color-warning, #f59e0b); box-shadow: 0 0 0 2px var(--color-warning, #f59e0b); }
#topo-connect-btn.on { background: var(--color-brand, #8b5cf6); color: #fff; border-color: var(--color-brand, #8b5cf6); }
.st-ok   .topo-dot { background: var(--color-success, #22c55e); box-shadow: 0 0 6px var(--color-success, #22c55e); }
.st-down .topo-dot { background: var(--color-danger, #ef4444); box-shadow: 0 0 6px var(--color-danger, #ef4444); }
.st-warn .topo-dot { background: var(--color-warning, #f59e0b); }
.st-idle .topo-dot { background: var(--color-text-muted, #64748b); }
/* 合并卡(交换机口+插在上面的手机)的第二个状态点 = 那个【口】通不通;实心点仍是手机在不在。
   做成空心环、和实心点区分开 —— 环灭=交换机/网线/软路由的事,点灭=手机的事。 */
.topo-node .topo-dot.port { width: 7px; height: 7px; margin-left: 4px; box-sizing: border-box;
  background: none; box-shadow: none; border: 2px solid var(--color-text-muted, #64748b); }
.topo-node .topo-dot.port.st-ok   { border-color: var(--color-success, #22c55e); }
.topo-node .topo-dot.port.st-down { border-color: var(--color-danger, #ef4444); }
.topo-node .topo-dot.port.st-idle { border-color: var(--color-text-muted, #64748b); }
.st-ok   .topo-ico { color: var(--color-success, #22c55e); }
.st-down .topo-ico { color: var(--color-danger, #ef4444); }
.st-warn .topo-ico { color: var(--color-warning, #f59e0b); }
.st-idle .topo-ico { color: var(--color-text-muted, #64748b); }
.st-down.topo-node { background: linear-gradient(0deg, var(--color-danger-soft), var(--color-danger-soft)), var(--color-surface, #111827); }

/* 聚焦模式:点中一个节点 → 它所在的【整条链路】高亮,其余压暗。
   排障时一眼就能把"这台手机 → 哪个口 → 哪台交换机 → 哪台软路由 → 哪个网段 → 哪个SK5出口"
   从一屏几十个节点里摘出来。点空白处或抽屉的 ✕ 取消。 */
.topo-canvas.focus .topo-node { opacity: .22; filter: saturate(.3); }
.topo-canvas.focus .topo-node.hl { opacity: 1; filter: none; }
/* 高亮卡片的描边色见下面【按点的是什么分紫/绿】那两条 —— 这里不再无条件描紫,
   否则会和那两条打架(点非盒子时描边仍是紫的,和绿色的连线对不上)。 */
.topo-canvas.focus .topo-lane { opacity: .3; }
.topo-canvas.focus .topo-edge { opacity: .08; animation: none; }
/* 选中的链路走一种醒目虚线,一眼跟其它边区分开。★颜色跟着"你点的是什么"走:
     · 点【群控盒子】→ 紫色 = 我在看"这台盒子控着哪些手机"(控制通道)
     · 点其它硬件    → 绿色 = 我在看物理链路(网线)
   仍然只有两端都在线才流动 —— 聚焦不能把断链画成活的。 */
.topo-canvas.focus .topo-edge.hl { stroke: var(--color-success, #22c55e) !important;
  stroke-dasharray: 7 6; stroke-width: 2.6; opacity: .4; }
.topo-canvas.focus.focus-ctrl .topo-edge.hl { stroke: var(--color-brand, #8b5cf6) !important; }
.topo-canvas.focus .topo-edge.hl.live { opacity: 1; animation: topoflow 1.1s linear infinite; }
/* 选中盒子时,链路上的卡片描边也跟着变紫,和绿色的"看网线"区分开 */
.topo-canvas.focus.focus-ctrl .topo-node.hl:not(.on) { border-color: var(--color-brand, #8b5cf6); }
.topo-canvas.focus:not(.focus-ctrl) .topo-node.hl:not(.on) { border-color: var(--color-success, #22c55e); }

/* 拖动中的节点:抬起来一层,不被别的卡片盖住 */
.topo-node.moving { z-index: 20; cursor: grabbing; box-shadow: 0 10px 26px rgba(0,0,0,.55);
  border-color: var(--color-brand, #8b5cf6); transition: none; }
.topo-node { cursor: grab; }
.topo-node.on { cursor: grab; }

/* 右侧抽屉:点节点看全部字段 + 直接操作 */
.topo-drawer { position: absolute; top: 0; right: 0; width: 320px; height: 100%; z-index: 20;
  background: var(--color-surface, #111827); border-left: 1px solid var(--color-border, #1e293b);
  box-shadow: -8px 0 24px rgba(0,0,0,.4); padding: 12px 14px; box-sizing: border-box;
  overflow: auto; transform: translateX(100%); transition: transform .18s; }
.topo-drawer.open { transform: none; }
.topo-drawer h4 { margin: 0 0 2px; font-size: 14px; color: var(--color-text-primary, #e2e8f0); }
.topo-drawer .kv { display: flex; gap: 8px; padding: 3px 0; font-size: 12px;
  border-bottom: 1px solid var(--color-white-a02, rgba(255,255,255,.02)); }
.topo-drawer .kv b { flex: 0 0 82px; font-weight: 400; color: var(--color-text-muted, #64748b); }
.topo-drawer .kv span { flex: 1; color: var(--color-text-primary, #e2e8f0); word-break: break-all; }
.topo-close { position: absolute; top: 8px; right: 10px; background: none; border: 0;
  color: var(--color-text-muted, #64748b); cursor: pointer; font-size: 16px; padding: 2px 6px; }
