/* ============================================================
   智能家居控制台 · 样式（2026-10-17 重写 · 精雕PPT暗玻璃霓虹体系）
   ------------------------------------------------------------
   视觉纪律（229 篇研究沉淀 + 大王钦定「花里胡哨精雕、严禁克制」）：
   1. 暗色为主舞台：深空黑底 + 青紫玻璃主调 + 霓虹点睛（粉/绿状态色），配比 60-30-10。
   2. 玻璃拟态四要素：透明磨砂(backdrop-filter) + 悬浮多层阴影 + 鲜艳背景透出 + 1px 高光渐变描边。
   3. 霓虹只点缀：同屏霓虹≤3 种、面积≤10-15%；开关/图标/状态徽标发光，大面积不发光。
   4. 质感层次：SVG 噪点 + 点阵几何 + 镜面 1-2px 上缘高光 + 背景光斑 blur。
   5. 字体金字塔：标题极粗+微字距、正文细灰、全篇≤2 种字、留白≥20%。
   6. 动效克制：只动 transform/opacity、非线性曲线、hover 发光、单页点缀。
   7. 亮色兜底：跟随系统；浅色模式也走玻璃质感，绝不退回「上个世纪」平板。
   8. 尊重「减少动态效果」与触屏 hover 隔离。
   ============================================================ */

/* ---------- 1. 设计令牌（暗色 = 精雕主舞台） ---------- */
:root,
[data-theme="dark"] {
  color-scheme: dark;

  --font-sans: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
               "Helvetica Neue", Arial,
               "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei",
               "Noto Sans SC", "Noto Sans CJK SC", "Source Han Sans SC",
               "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, "Cascadia Code", Menlo, Consolas, monospace;

  --fs-2xs: 11px; --fs-xs: 12px; --fs-sm: 13px; --fs-md: 14px;
  --fs-lg: 16px; --fs-xl: 20px; --fs-2xl: 24px;

  --lh-tight: 1.3; --lh-snug: 1.5; --lh-base: 1.6;

  --s-1: 4px; --s-2: 8px; --s-3: 12px; --s-4: 16px;
  --s-5: 20px; --s-6: 24px; --s-8: 32px;
  /* 圆角分级（对标小米品牌语言规范）：按钮 8 / 卡片 16 / 密集列表 12 / 胶囊 999 / 表格 0 */
  --r-xs: 6px; --r-sm: 8px; --r-md: 12px; --r-lg: 16px; --r-xl: 16px; --r-full: 999px;

  --ease-std: cubic-bezier(.4, 0, .2, 1);
  --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
  --dur-1: 150ms; --dur-2: 200ms; --dur-3: 250ms;

  --tap: 44px; --maxw: 1120px;

  /* 深空底色 + 玻璃表面（半透明，靠 backdrop-filter 磨砂） */
  --bg: #0A0A0F;
  --surface: rgba(255,255,255,.06);
  --surface-2: rgba(255,255,255,.04);
  --surface-3: rgba(255,255,255,.03);
  --line: rgba(255,255,255,.12);
  --line-2: rgba(255,255,255,.18);
  --text: #f2f4ff;
  --muted: #9aa3c7;
  --faint: #6b73a0;

  /* 霓虹主色 */
  --cyan: #00f0ff; --magenta: #ff2d95; --violet: #a855f7;
  --green: #39ff14; --amber: #ffb020;

  --brand: #22d3ee;          /* 主操作色（青），hover 渐变 */
  --brand-2: #a855f7;        /* 副紫，用于渐变 */
  --brand-ink: #06121a;
  --on-brand: #02121a;

  /* ══ 色彩角色（color roles）——组件只准引用角色，不准写裸色 ══
     品牌/操作 = 青→紫（--brand/--cyan/--violet）：开关 on、主按钮、焦点环、渐变点缀
     语义状态  = --ok 翠绿(在线/正常/成功) · --warn 琥珀(警告) · --bad 玫红(错误/失败)
     表面层级  = --surface(-2/-3) 玻璃面 · --line(-2) 描边 · --text/--muted/--faint 前景
     品牌标识  = --tag-* 只表「哪家」（米家/海尔/优智/涂鸦），不承载操作含义 */
  --ok: #34d399; --ok-weak: rgba(52,211,153,.13);
  --warn: #ffb020; --warn-weak: rgba(255,176,32,.12);
  --bad: #ff5a7a; --bad-weak: rgba(255,90,122,.12);
  /* 品牌标识色：只编码「哪家」，不承载操作含义，压低饱和但保留色相 */
  --tag-mijia-fg: #ffb066; --tag-mijia-bg: rgba(255,140,40,.16);
  --tag-haier-fg: #6cb8ff; --tag-haier-bg: rgba(20,140,255,.16);
  --tag-kiwik-fg: #c3a6ef; --tag-kiwik-bg: rgba(168,85,247,.18);
  --tag-tuya-fg:  #74f0a0; --tag-tuya-bg:  rgba(57,255,90,.16);

  /* 阴影层级（对标小米品牌语言规范：阴影要"几乎察觉不到"才算对；不使用光效/装饰图案）
     暗底主要靠「表面色 + 1px 描边」分层次，阴影只做极轻的抬起暗示，不再大范围发黑 */
  --glass-shadow: 0 2px 8px rgba(0,0,0,.32), inset 0 1px 0 rgba(255,255,255,.08);
  --shadow-pop: 0 8px 24px rgba(0,0,0,.5);
}

/* 亮色：精雕玻璃白，但不再灰平——冷调蛋白底 + 极淡三色晕，玻璃透出有层次 */
[data-theme="light"] {
  color-scheme: light;
  /* 大背景：浅浅灰（大王 2026-10-18 钦定）。原来偏蓝的 #e9edf6 和卡片白对比发脏，
     换成中性浅灰后，白卡自动"浮"出来，主次不用刻意调就分明。 */
  --bg: #f2f3f5;
  --surface: rgba(255,255,255,.80);
  --surface-2: rgba(255,255,255,.64);
  --surface-3: rgba(255,255,255,.5);
  --line: rgba(20,30,60,.12);
  --line-2: rgba(20,30,60,.22);
  --text: #141a2e;
  --muted: #515a78;
  --faint: #7c849e;

  --cyan: #0892b0; --magenta: #d11f7e; --violet: #7e2fe0;
  --green: #149a36; --amber: #c2760a;

  --brand: #0b9fc0; --brand-2: #7e2fe0; --brand-ink: #03242b; --on-brand: #ffffff;
  --ok: #149a36; --ok-weak: rgba(20,154,54,.14);
  --warn: #c2760a; --warn-weak: rgba(194,118,10,.14);
  --bad: #d62f4f; --bad-weak: rgba(214,47,79,.12);

  --tag-mijia-fg: #8a4e00; --tag-mijia-bg: rgba(255,140,40,.18);
  --tag-haier-fg: #11509a; --tag-haier-bg: rgba(20,140,255,.16);
  --tag-kiwik-fg: #5e2eb0; --tag-kiwik-bg: rgba(168,85,247,.16);
  --tag-tuya-fg:  #16924a; --tag-tuya-bg:  rgba(57,255,90,.16);

  /* 浅色同档（小米 low/high 两档，几乎察觉不到） */
  --glass-shadow: 0 2px 4px rgba(30,40,90,.06), inset 0 1px 0 rgba(255,255,255,.75);
  --shadow-pop: 0 8px 24px rgba(30,40,90,.12);
}

/* 浅色底：三色晕更淡、更靠后，别跟白卡片抢戏 */
[data-theme="light"] body {
  background:
    radial-gradient(1200px 620px at 10% -10%, rgba(8,146,176,.08), transparent 60%),
    radial-gradient(1000px 560px at 100% 4%, rgba(209,31,126,.06), transparent 58%),
    radial-gradient(1100px 720px at 48% 122%, rgba(126,47,224,.08), transparent 60%),
    var(--bg);
}
/* 点阵改成深蓝灰才看得见（原白点在浅底上隐身） */
[data-theme="light"] body::before { background-image: radial-gradient(rgba(20,30,60,.08) 1px, transparent 1.4px); }
[data-theme="light"] .glow.a { opacity: .05; }
[data-theme="light"] .glow.b { opacity: .04; }
[data-theme="light"] .glow.c { opacity: .035; }
[data-theme="light"] .glow { filter: blur(110px); }

/* 浅色：玻璃边收薄、hover 抬起更克制，避免卡片像浮贴纸 */
[data-theme="light"] .card::before,
[data-theme="light"] .dev::before,
[data-theme="light"] .login-card::before,
[data-theme="light"] .hero::before { opacity: .28; }
@media (hover: hover) and (pointer: fine) {
  [data-theme="light"] .card:hover,
  [data-theme="light"] .dev:hover,
  [data-theme="light"] .login-card:hover,
  [data-theme="light"] .hero:hover {
    transform: translateY(-3px);
    box-shadow: 0 12px 30px rgba(30,40,90,.12), inset 0 1px 0 rgba(255,255,255,.8);
  }
  [data-theme="light"] .card:hover::after,
  [data-theme="light"] .dev:hover::after,
  [data-theme="light"] .hero:hover::after { opacity: .65; }
}

/* 浅色：修正所有写死白色文字（暗色里 #fff 正常，浅色里必须转深，否则看不见） */
[data-theme="light"] .dev-name,
[data-theme="light"] .dev-head .dev-name,
[data-theme="light"] .kpi b,
[data-theme="light"] .kpi.accent b,
[data-theme="light"] .ring .val,
[data-theme="light"] .hero-chip b,
[data-theme="light"] .hero-chip .ok,
[data-theme="light"] .hero-chip .no { color: var(--text); text-shadow: none; }
[data-theme="light"] .hero h1 { background: linear-gradient(95deg, #2a3350 6%, var(--cyan) 50%, var(--violet) 96%);
  -webkit-background-clip: text; background-clip: text; }
[data-theme="light"] .ico { border-color: rgba(20,30,60,.16); }
/* 浅色：开关轨道从近乎隐身的白染改成深灰，白旋钮才看得清（暗色保留霓虹轨道） */
[data-theme="light"] .toggle { background: rgba(20,30,60,.14); border-color: rgba(20,30,60,.22); }
/* 大王 2026-10-18：未纳入的设备颜色要**浅于背景色**，原来是深色叠加形成灰补丁。
   实测亮度：页面底 237 / 旧未纳入 228(比底还暗) / 已纳入 251 → 改后约 245，
   介于两者之间：退后、不抢戏，但仍然看得见轮廓。 */
[data-theme="light"] .dev.excluded { background: rgba(255,255,255,.45); }
[data-theme="light"] .dev.excluded .dev-name { color: #6b7193; }
[data-theme="light"] .dev.excluded .dev-room { color: #828aa6; }

/* 浅色：图标 24px 彩色外发光改极淡投影——白卡片上彩光像贴纸荧光，收成内高光+轻落影 */
[data-theme="light"] .ico.ico-mijia { box-shadow: inset 0 1px 0 rgba(255,255,255,.5), 0 2px 9px rgba(255,140,40,.14); }
[data-theme="light"] .ico.ico-haier { box-shadow: inset 0 1px 0 rgba(255,255,255,.5), 0 2px 9px rgba(20,140,255,.14); }
[data-theme="light"] .ico.ico-kiwik { box-shadow: inset 0 1px 0 rgba(255,255,255,.5), 0 2px 9px rgba(168,85,247,.14); }
[data-theme="light"] .ico.ico-tuya  { box-shadow: inset 0 1px 0 rgba(255,255,255,.5), 0 2px 9px rgba(57,255,90,.14); }

/* 浅色：开关/在线点绿光收薄——浅底上强发光抢戏，改为克制描边+极淡投影 */
[data-theme="light"] .toggle.on { box-shadow: none; }
[data-theme="light"] .dot.on { box-shadow: 0 0 0 1.5px var(--ok) inset; }

/* ---------- 2. 基础 ---------- */
* { box-sizing: border-box; }
html { font-size: 100%; scrollbar-gutter: stable; -webkit-text-size-adjust: 100%; }

body {
  margin: 0; min-height: 100%;
  background-color: var(--bg);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: var(--fs-md); line-height: var(--lh-base);
  -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
  -webkit-tap-highlight-color: transparent;
  text-autospace: normal; text-spacing-trim: trim-start; line-break: strict;
  overflow-wrap: break-word; position: relative; overflow-x: hidden;
}
/* 点阵几何层 */
body::before {
  content: ""; position: fixed; inset: 0; z-index: 0; pointer-events: none; opacity: .5;
  background-image: radial-gradient(rgba(255,255,255,.10) 1px, transparent 1.4px);
  background-size: 22px 22px;
}
/* 噪点质感层 */
body::after {
  content: ""; position: fixed; inset: 0; z-index: 0; pointer-events: none; opacity: .05;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='2'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}
/* 装饰光带（3 条极淡霓虹横扫线） */
/* 装饰扫描线：小米规范「不使用装饰图案」→ 停用（DOM 保留，样式置 none 以免误删引用） */
.bg-lines { display: none; }
.bg-lines i { position: absolute; left: 0; right: 0; height: 1px;
  background: linear-gradient(90deg, transparent, var(--cyan), transparent); opacity: .12; }
.bg-lines i:nth-child(1) { top: 18%; }
.bg-lines i:nth-child(2) { top: 52%; background: linear-gradient(90deg, transparent, var(--violet), transparent); }
.bg-lines i:nth-child(3) { top: 84%; background: linear-gradient(90deg, transparent, var(--magenta), transparent); }

/* 背景氛围色（对标小米品牌语言规范「不使用光效/装饰图案」：
   不再是可辨识的霓虹光球，只留一层几乎察觉不到的品牌色晕，让暗底不发死。
   opacity 压到 .10 以下 + blur 拉到 130px，肉眼只见"底色微偏色"、不见形状。 */
.glow { position: fixed; border-radius: 50%; filter: blur(130px); pointer-events: none; z-index: 0; }
.glow.a { width: 560px; height: 560px; background: radial-gradient(circle, var(--cyan), transparent 70%); top: -180px; left: -120px; opacity: .10; }
.glow.b { width: 480px; height: 480px; background: radial-gradient(circle, var(--magenta), transparent 70%); top: 160px; right: -160px; opacity: .08; }
.glow.c { width: 520px; height: 520px; background: radial-gradient(circle, var(--violet), transparent 70%); bottom: -200px; left: 32%; opacity: .07; }

h1, h2, h3, h4 { margin: 0; font-weight: 700; line-height: var(--lh-tight);
  letter-spacing: -.01em; text-wrap: balance; }
h1 { font-size: var(--fs-2xl); } h2 { font-size: var(--fs-xl); }
h3 { font-size: var(--fs-lg); } h4 { font-size: var(--fs-md); }
p { margin: 0 0 var(--s-3); max-width: 38em; }
/* 卡内的说明文字要占满卡片宽度：38em 会把「当…就…」这类一句话挤成两行、
   把安全/守望说明挤成四行。米家/TDesign 的做法是说明文字跟容器同宽。 */
.card > p.muted, .card > p:not([class]) { max-width: none; }
a { color: var(--brand); }
:focus { outline: none; }
:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; border-radius: var(--r-xs); }
.hidden { display: none !important; }
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }

/* ---------- 3. 工具类 ---------- */
.muted { color: var(--muted); font-size: var(--fs-sm); line-height: var(--lh-snug); }
.faint { color: var(--faint); font-size: var(--fs-xs); }
.grow { flex: 1 1 180px; min-width: 0; }
.mt { margin-top: var(--s-3); }
.pad { padding: var(--s-3) var(--s-1); }
.err { min-height: 20px; margin: var(--s-2) 0 0; font-size: var(--fs-sm); color: var(--bad); font-weight: 500; }
.msg { min-height: 20px; margin: var(--s-2) 0 0; font-size: var(--fs-sm); color: var(--ok); font-weight: 500; }

.num, .gcount, .lc-t, .logline .t, .ctl .val, .badge, .kbadge, .cred-badge, .ok-badge,
.gapchip i, input[type="time"], input[type="date"], .dt-line, .rawlog, .stat-num, .kpi b, .ring .val {
  font-variant-numeric: tabular-nums lining-nums;
  font-feature-settings: "tnum" 1, "lnum" 1;
}
.sect { margin: var(--s-6) 0 var(--s-3); font-size: var(--fs-2xs); font-weight: 600;
  color: var(--faint); letter-spacing: .04em; }

/* ---------- 4. 共享表面卡（对标小米品牌语言规范：不使用光效、不使用装饰图案） ----------
   层次只由「表面色 + 1px 实线描边 + 极轻阴影」表达，不再有彩虹渐变描边/光标聚光/外发光。 */
.card, .dev, .login-card, .hero {
  position: relative;
  background: var(--surface);
  backdrop-filter: blur(16px) saturate(180%);
  -webkit-backdrop-filter: blur(16px) saturate(180%);
  border: 1px solid var(--line);
  border-radius: var(--r-xl);
  box-shadow: var(--glass-shadow);
  transition: transform .25s var(--ease-std), box-shadow .25s var(--ease-std);
}
@media (hover: hover) and (pointer: fine) {
  .card:hover, .dev:hover, .login-card:hover, .hero:hover {
    transform: translateY(-2px);
    box-shadow: 0 4px 12px rgba(0,0,0,.42);
  }
}

/* ---------- 5. 登录 ---------- */
.login-wrap { min-height: 100svh; display: flex; align-items: center; justify-content: center; padding: var(--s-6); position: relative; z-index: 1; }
.login-card { width: 100%; max-width: 380px; margin: 0; padding: var(--s-8) var(--s-6) var(--s-6); }
.login-card h1 { text-align: center; font-size: var(--fs-2xl); }
.login-card .muted { text-align: center; margin-bottom: var(--s-5); max-width: none; }
.login-card input { width: 100%; margin-bottom: var(--s-2); }
.login-card button.wide { margin-top: var(--s-2); }
.is-framed .login-card { display: none; }
.framed-card { text-align: left; position: relative; z-index: 1; }
.framed-card p { text-align: left; }
.framed-card a { display: inline-block; margin-top: var(--s-2); padding: 10px var(--s-4);
  min-height: var(--tap); border-radius: var(--r-sm); background: var(--brand); color: var(--on-brand);
  text-decoration: none; font-size: var(--fs-md); line-height: 24px; }

/* ---------- 6. 顶栏 ---------- */
header { position: sticky; top: 0; z-index: 20; display: flex; align-items: center; gap: var(--s-3);
  padding: var(--s-3) var(--s-4);
  background: color-mix(in srgb, var(--bg) 80%, transparent);
  backdrop-filter: saturate(180%) blur(14px); -webkit-backdrop-filter: saturate(180%) blur(14px);
  border-bottom: 1px solid var(--line); }
.brand { font-weight: 700; font-size: var(--fs-md); white-space: nowrap; display: flex; align-items: center; gap: 10px; }
.brand::before { content: "🏠"; font-size: 20px; }
.brand small { display: block; font-weight: 400; font-size: var(--fs-2xs); color: var(--faint); line-height: 1.3; letter-spacing: 0; }

/* 顶部状态条：只放实时状态徽标（数字 KPI 已移至 hero），对齐 V4 样稿的清爽顶栏 */
.status { display: flex; align-items: center; gap: var(--s-1); flex: 1; min-width: 0; flex-wrap: wrap; }
.status .sflag { flex: 0 0 auto; font-size: var(--fs-xs); font-weight: 600; padding: 4px var(--s-3);
  border-radius: var(--r-full); white-space: nowrap; background: var(--surface-2); color: var(--muted);
  box-shadow: 0 0 0 1px var(--line) inset; }
.status .sflag.ok { background: var(--ok-weak); color: var(--ok); box-shadow: 0 0 0 1px color-mix(in srgb, var(--ok) 40%, transparent) inset; }
.status .sflag.no { background: var(--bad-weak); color: var(--bad); box-shadow: 0 0 0 1px color-mix(in srgb, var(--bad) 40%, transparent) inset; }
.head-actions { display: flex; gap: var(--s-1); align-items: center; }

/* ---------- 7. 页签 ---------- */
/* 顶栏 tab：**四等分 4 个豆腐块 + 文字居中对齐**（大王 2026-10-18 钦定，v41~v43 绕了一圈回到这里）。
   等分网格 + 每块同宽 + 文字居中，就是"豆腐块"；不要再玩两端对齐/左簇那套。 */
.tabs { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--s-2);
  max-width: var(--maxw); margin: 0 auto; width: 100%; padding: var(--s-2) var(--s-4);
  border-bottom: 1px solid var(--line); }
.tab { position: relative; border: 0; background: transparent; color: var(--muted);
  padding: var(--s-3) var(--s-1);
  cursor: pointer; font-family: inherit; font-size: var(--fs-md); font-weight: 500; min-height: var(--tap);
  white-space: nowrap; transition: color var(--dur-1) var(--ease-std);
  text-align: center; min-width: 0; border-radius: var(--r-sm); }
.tab::after { content: ""; position: absolute; left: 50%; bottom: -1px; width: 56px; max-width: 70%; height: 2px;
  border-radius: 2px; background: linear-gradient(90deg, var(--cyan), var(--violet)); transform: translateX(-50%) scaleX(0);
  transform-origin: center; transition: transform var(--dur-2) var(--ease-std);  }
.tab.active { color: var(--text); font-weight: 700; }
.tab.active::after { transform: translateX(-50%) scaleX(1); }

main { padding: var(--s-4) var(--s-4) var(--s-10); max-width: var(--maxw); margin: 0 auto; width: 100%; position: relative; z-index: 1; }

/* ---------- 8. 卡片内边距 ---------- */
.card { padding: var(--s-6); margin: 0 0 var(--s-4); }
.card > h2:first-child { margin: 0 0 var(--s-2); }
.card > h2:first-child + .muted { margin-bottom: var(--s-3); text-align: left; line-height: 1.65; }
.logtips { margin: 0 0 var(--s-4); padding-left: var(--s-5); color: var(--muted); font-size: var(--fs-sm); line-height: 1.7; }
.logtips li { margin-bottom: var(--s-2); }
.logtips li::marker { color: var(--brand); }
.card > h2:first-child + .muted + button.primary { display: inline-flex; margin: 0; }
.card .card { border-radius: var(--r-sm); }
#page-settings { max-width: var(--maxw); }
.dv-sum { max-width: 46em; margin: 0 auto var(--s-2); text-align: left; line-height: 1.65; }
#lg-sum { margin: 0 0 var(--s-3); }
.statbar { display: flex; flex-wrap: wrap; gap: var(--s-2); margin: 0 0 var(--s-4); }
.stitem { display: inline-flex; align-items: center; gap: 6px; padding: 6px 12px; border-radius: var(--r-sm); font-size: var(--fs-xs); color: var(--text);
  background: var(--surface-2); border: 1px solid var(--line); }
.stitem .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--muted); flex: none; }
.stitem.ext .dot { background: var(--warn); }
.stitem.bad .dot { background: var(--bad); }
.stitem.sys .dot { background: var(--cyan); }
.hero-strip { display: flex; flex-wrap: wrap; gap: var(--s-2); margin: 0; padding: 0; list-style: none; }
.hero-strip li { display: inline-flex; align-items: center; gap: 6px; padding: 5px 10px; border-radius: var(--r-sm); font-size: var(--fs-xs);
  background: var(--surface-2); border: 1px solid var(--line); color: var(--text); }
.hero-strip li .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--muted); flex: none; }
.hero-strip li.ok .dot { background: var(--ok); }
.hero-strip li.no .dot { background: var(--bad); }

/* ---------- 9. 按钮 ---------- */
button, input, select, textarea { font: inherit; }
button { font-size: var(--fs-md); font-weight: 500; cursor: pointer; white-space: nowrap; min-height: var(--tap);
  padding: 0 var(--s-4); border-radius: var(--r-sm); border: 1px solid var(--line-2);
  background: var(--surface); color: var(--text); transition: background var(--dur-1), border-color var(--dur-1), color var(--dur-1), transform var(--dur-1); }
button:active { transform: translateY(1px); background: var(--surface-3); }
button:disabled { opacity: .45; color: var(--faint); cursor: not-allowed; transform: none; box-shadow: none; }
button.primary { background: linear-gradient(135deg, var(--brand), var(--brand-2)); border-color: transparent; color: var(--on-brand);
  box-shadow: 0 1px 2px rgba(0,0,0,.2); font-weight: 600; }
button.primary:active { filter: brightness(.92); }
button.ghost { background: var(--surface-2); border-color: var(--line-2); color: var(--muted); }
button.ghost.active { background: color-mix(in srgb, var(--brand) 18%, transparent); color: var(--brand); border-color: var(--brand); font-weight: 600; }
button.danger { color: var(--bad); border-color: color-mix(in srgb, var(--bad) 35%, transparent); }
button.wide { width: 100%; }
button.mini { min-height: 32px; padding: 0 var(--s-3); font-size: var(--fs-sm); border-radius: var(--r-xs); }
button.icon { padding: 0; width: var(--tap); min-height: var(--tap); display: inline-grid; place-items: center; }
#theme-toggle svg { grid-area: 1 / 1; }
#theme-toggle .ic-sun { display: none; }
[data-theme="dark"] #theme-toggle .ic-sun { display: block; }
[data-theme="dark"] #theme-toggle .ic-moon { display: none; }

@media (hover: hover) and (pointer: fine) {
  button:hover { background: var(--surface-3); border-color: var(--line-2); }
  button.primary:hover { filter: brightness(1.08); }
  button.ghost:hover { background: var(--surface-3); color: var(--text); }
  button.danger:hover { background: var(--bad-weak); border-color: var(--bad); }
  .tab:hover { color: var(--text); }
  .dev-head:hover { background: var(--surface-2); }
  .spk:hover { background: var(--surface-3); }
  .fchip:hover, .gchip:hover, .lchip:hover, .dchip:hover, .chip:hover, .pill:hover { background: var(--surface-3); color: var(--text); }
  details.card.cred > summary:hover h2 { color: var(--brand); }
  .dev.off:hover { transform: none; box-shadow: var(--glass-shadow); }
}

/* ---------- 10. 表单控件 ---------- */
input, select, textarea { font-size: var(--fs-md); color: var(--text); padding: 0 var(--s-3);
  border: 1px solid var(--line-2); border-radius: var(--r-sm); background: var(--surface-2);
  max-width: 100%; min-height: var(--tap); transition: border-color var(--dur-1), box-shadow var(--dur-1); }
input::placeholder, textarea::placeholder { color: var(--faint); }
input:focus, select:focus, textarea:focus { outline: none; border-color: var(--brand);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand) 22%, transparent); }
input[type="range"] { padding: 0; border: 0; background: transparent; min-height: 32px; flex: 1; accent-color: var(--brand); }
input[type="checkbox"] { min-height: 0; width: 20px; height: 20px; accent-color: var(--brand); }
.autochk { display: inline-flex; align-items: center; gap: var(--s-2); font-size: var(--fs-sm); color: var(--muted); }
.autochk input { width: auto; }

/* ---------- 11. 胶囊 / 徽标 ---------- */
/* 分组/品牌方块的容器：让里面的方块**等分撑满**这一行（豆腐块对齐的关键） */
.filters { display: flex; gap: var(--s-2); flex-wrap: wrap; flex: 1 1 auto; min-width: 0; }
.filters > .dv-link { flex: 1 1 0; }
.fchip, .gchip, .lchip, .dchip, .chip, .pill { font-family: inherit; font-weight: 500;
  border: 1px solid var(--line-2); border-radius: var(--r-full); cursor: pointer; white-space: nowrap; transition: background var(--dur-1), color var(--dur-1), border-color var(--dur-1); }
.fchip, .gchip { padding: 0 var(--s-3); min-height: 36px; font-size: var(--fs-sm); background: var(--surface); color: var(--muted); }
.lchip { padding: 0 var(--s-3); min-height: 32px; font-size: var(--fs-xs); background: var(--surface); color: var(--muted); }
.dchip { padding: 0 var(--s-2); min-height: 30px; font-size: var(--fs-xs); background: var(--surface); color: var(--muted); }
.chip  { padding: 0 var(--s-3); min-height: 36px; font-size: var(--fs-sm); background: var(--surface); color: var(--brand); border-color: color-mix(in srgb, var(--brand) 40%, transparent); }
.pill  { padding: 0 var(--s-4); min-height: 40px; font-size: var(--fs-md); background: var(--surface); color: var(--text); }
.fchip.active, .gchip.active, .lchip.active, .dchip.active, .pill.active { background: color-mix(in srgb, var(--brand) 20%, transparent); color: var(--brand); border-color: var(--brand); font-weight: 600; }
.chip:hover, .chip:active { background: color-mix(in srgb, var(--brand) 16%, transparent); }
.flabel { font-size: var(--fs-xs); color: var(--muted); }

.badge, .kbadge, .ok-badge { display: inline-flex; align-items: center; font-size: var(--fs-2xs); font-weight: 700;
  padding: 3px 9px; border-radius: var(--r-full); white-space: nowrap; border: 1px solid transparent; box-shadow: inset 0 1px 0 rgba(255,255,255,.12); }
.badge { background: var(--surface-2); color: var(--muted); }
.badge.mijia { background: var(--tag-mijia-bg); color: var(--tag-mijia-fg); border-color: color-mix(in srgb, var(--tag-mijia-fg) 20%, transparent); }
.badge.haier { background: var(--tag-haier-bg); color: var(--tag-haier-fg); border-color: color-mix(in srgb, var(--tag-haier-fg) 20%, transparent); }
.badge.kiwik { background: var(--tag-kiwik-bg); color: var(--tag-kiwik-fg); border-color: color-mix(in srgb, var(--tag-kiwik-fg) 20%, transparent); }
.badge.tuya  { background: var(--tag-tuya-bg);  color: var(--tag-tuya-fg);  border-color: color-mix(in srgb, var(--tag-tuya-fg)  20%, transparent); }
.badge.cond  { background: var(--warn-weak); color: var(--warn); border-color: color-mix(in srgb, var(--warn) 25%, transparent); }
.kbadge { background: var(--surface-2); color: var(--text); }
.kbadge.exec  { background: var(--ok-weak);   color: var(--ok);   border-color: color-mix(in srgb, var(--ok)   25%, transparent); box-shadow: inset 0 1px 0 rgba(255,255,255,.15); }
.kbadge.voice { background: color-mix(in srgb, var(--brand) 16%, transparent); color: var(--brand); border-color: color-mix(in srgb, var(--brand) 25%, transparent); }
.kbadge.parse  { background: color-mix(in srgb, var(--violet) 18%, transparent); color: var(--violet); border-color: color-mix(in srgb, var(--violet) 25%, transparent); }
.kbadge.speak { background: var(--warn-weak); color: var(--warn); border-color: color-mix(in srgb, var(--warn) 25%, transparent); }
.kbadge.event { background: color-mix(in srgb, var(--brand) 18%, transparent); color: var(--brand); border-color: color-mix(in srgb, var(--brand) 25%, transparent); }
.kbadge.state, .kbadge.ext { background: var(--bad-weak); color: var(--bad); border-color: color-mix(in srgb, var(--bad) 25%, transparent); }
.kbadge.rule  { background: color-mix(in srgb, var(--amber) 18%, transparent); color: var(--amber); border-color: color-mix(in srgb, var(--amber) 25%, transparent); }
.kbadge.sys, .kbadge.api, .kbadge.watch { background: var(--surface-3); color: var(--muted); border-color: var(--line-2); }
.ok-badge.ok { background: var(--ok-weak); color: var(--ok); border-color: color-mix(in srgb, var(--ok) 25%, transparent); }
.ok-badge.no { background: var(--bad-weak); color: var(--bad); border-color: color-mix(in srgb, var(--bad) 25%, transparent); }

.cred-badge { font-size: var(--fs-2xs); font-weight: 600; padding: 3px var(--s-3); border-radius: var(--r-full); }
.cred-badge.on  { background: var(--ok-weak); color: var(--ok); }
.cred-badge.off { background: var(--surface-3); color: var(--faint); }

/* ---------- 12. 工具栏 ---------- */
.hintbar { display: flex; gap: var(--s-2); align-items: center; margin: var(--s-3) 0; flex-wrap: wrap; }
.logbar  { display: flex; gap: var(--s-2); align-items: center; margin: var(--s-3) 0; flex-wrap: wrap; }
.logbar .grow { flex: 1 1 200px; min-width: 160px; }
.dv-sum { margin: 0 0 var(--s-2); color: var(--muted); font-size: var(--fs-sm); }

/* ---------- 13. HERO（一句话状态 + 安静 KPI + 单一在线率环 + 品牌状态条） ---------- */
.hero { padding: var(--s-5) var(--s-6); margin-bottom: var(--s-5); border-radius: var(--r-lg);
  /* ★ 2026-10-18 大王截图点名「左右结构改成上下结构好很多，后期有新品牌进来也好处理」：
     原来四块横排，左边问候块高、右边品牌状态一条，头重脚轻。
     改成上下两行：上行 = 问候 + KPI + 在线率环（横着排，flex-wrap 抗窄屏）；
     下行 = 品牌状态**横向一排、自动换行** —— 品牌再多也是往下长，不会把 Hero 撑变形。 */
  display: flex; flex-direction: column; gap: var(--s-4); overflow: hidden; }
.hero-top { display: flex; align-items: center; gap: var(--s-6); flex-wrap: wrap; }
.hero-top .hero-main { flex: 1 1 240px; min-width: 0; }
.hero-top .hero-kpi { flex: none; }
.hero-top .hero-ringcell { flex: none; margin-left: auto; }
/* 下行：品牌状态，与上行之间一条细线分层 */
.hero-status { border-top: 1px solid var(--line); padding-top: var(--s-3); }
.hero-cell { min-width: 0; }
.hero-main h1 { font-size: clamp(19px, 2vw, 26px); font-weight: 800; letter-spacing: -.02em;
  line-height: 1.25; margin: 0 0 var(--s-1); color: var(--text); }
.hero-main p { color: var(--muted); font-size: var(--fs-sm); line-height: 1.5; margin: 0; }
.hero-main p b { color: var(--text); font-weight: 700; }
/* KPI 三数：横排，数字大、标签小 */
.hero-kpi { display: flex; gap: var(--s-5); }
.kpi { background: none; border: 0; border-radius: 0; padding: 0; text-align: left; box-shadow: none; }
.kpi b { display: block; font-size: 24px; font-weight: 800; letter-spacing: -.03em; color: var(--text); line-height: 1.1; }
.kpi.accent b { color: var(--brand); }
.kpi span { display: block; font-size: var(--fs-xs); color: var(--muted); margin-top: 3px; letter-spacing: .03em; }
.hero-ringcell { display: grid; place-items: center; }
.ring { width: 104px; height: 104px; flex: none; display: grid; place-items: center; position: relative; }
.ring svg { position: absolute; inset: 0; transform: rotate(-90deg); }
.ring .val { font-size: 22px; font-weight: 800; color: var(--text); }
.ring .lab { position: absolute; bottom: 16px; font-size: var(--fs-2xs); color: var(--muted); }
/* 品牌状态：**横向一排、自动换行**（原来是竖排一长条，把 Hero 拉成头重脚轻） */
.hero-strip { display: flex; flex-direction: row; flex-wrap: wrap; gap: var(--s-2) var(--s-5);
  margin: 0; padding: 0; list-style: none; }
.hero-strip li { display: inline-flex; align-items: center; gap: 7px; font-size: var(--fs-xs);
  color: var(--muted); white-space: nowrap; }
.hero-strip li b { color: var(--text); font-weight: 600; }
.hero-strip li .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--muted); flex: none; }
.hero-strip li.ok .dot { background: var(--ok); }
.hero-strip li.no .dot { background: var(--bad); }
.strip { display: flex; gap: var(--s-2); flex-wrap: wrap; margin: 0; }
.hero-chip { display: inline-flex; align-items: center; gap: 6px; padding: 5px 10px; border-radius: var(--r-sm); font-size: var(--fs-xs);
  background: var(--surface-2); border: 1px solid var(--line); box-shadow: none; }
.hero-chip b { font-weight: 600; color: var(--text); }
.hero-chip .ok { color: var(--ok); }
.hero-chip .no { color: var(--bad); }

/* ---------- 14. 设备 ---------- */
/* 组头只保留「上方」间距来分隔上一组；下方间距交给 #dv-list 的 gap，
   避免元素自带 margin 与容器 gap 叠加（Every Layout：间距是上下文的事，不是元素的） */
/* ---------- 表面层级（浅色，明度单调递增，一眼分主次） ----------
   页面底 237 → 未纳入设备 243（退后）→ 组头分区条 248 → 已纳入设备 253（主角）
   暗色同理由 rgba 白的比例撑开：10 → 12 → 15 → 26。
   主次不再靠"谁更亮谁更花"，靠**明度台阶 + 边框 + 色条**三件套分工。 */
#dv-list .dev:not(.excluded) { background: rgba(255,255,255,.07); }
[data-theme="light"] #dv-list .dev:not(.excluded) { background: rgba(255,255,255,.90); }
.dev.excluded { background: rgba(255,255,255,.012); }
[data-theme="light"] .dev.excluded { background: rgba(255,255,255,.38); }
/* 组头：不再单独一张"分区条"，而是**分组大框里的标题栏**（见下面 .dv-group）。
   大王 2026-10-18：分类要整体框选、和背景做区分 —— 所以框是主体，组头只是框的第一行。 */
.ghead { display: flex; align-items: center; gap: var(--s-2); margin: 0;
  padding: var(--s-2) var(--s-4); flex-wrap: wrap; cursor: pointer; user-select: none; }
.ghead::before { content: ""; width: 4px; height: 18px; border-radius: 2px; margin-right: 4px;
  background: var(--brand); flex: none; }
.gname  { font-weight: 700; font-size: var(--fs-md); letter-spacing: -.01em; }
.gcount { color: var(--muted); font-size: var(--fs-xs); padding: 2px var(--s-2); background: var(--surface-2); border-radius: var(--r-full); }
.ghead .grow { flex: 1; }
.ghead .gfold { width: 18px; text-align: center; color: var(--faint); font-size: var(--fs-xs); }
.ghead.folded .gname, .ghead.folded .gcount { opacity: .7; }

/* ---------- 分组整体框选（大王 2026-10-18：分类可以整体框选和背景做区分） ----------
   以前只给组头一条淡底，组头归组头、设备行归设备行，看起来是一盘散沙。
   现在每个房间/分类是一个**整体的框**：一个 border 把组头 + 所有设备行包起来，
   组头只做框内的标题栏，设备行之间用细线分隔 —— 米家的分组就是这个样子。 */
.dv-group { border: 1px solid var(--line); border-radius: var(--r-md); overflow: hidden;
  background: rgba(255,255,255,.95); }
[data-theme="dark"] .dv-group { background: rgba(255,255,255,.04); }
.dv-group > .ghead { background: rgba(20,30,60,.035); border-bottom: 1px solid var(--line); }
[data-theme="dark"] .dv-group > .ghead { background: rgba(255,255,255,.05); }
/* 框内设备行：不再各自一张独立卡，改成横向细线分隔；圆角由 overflow:hidden 切掉 */
.dv-group > .dev { border-radius: 0; border-bottom: 1px solid var(--line); background: transparent; }
.dv-group > .dev:last-child { border-bottom: 0; }
/* 已纳入 = 跟框同色（白/深），未纳入也**不加深底色** ——
   v40 时"未纳入浅于背景"是针对"独立卡片"定的；现在设备行在分组框**内部**，
   再加深就变成白框里一条灰带（实测 light 240 vs 框 254，正是大王截图点名过的"突兀"）。
   改用米家的办法：底色与框一致，弱化全靠**灰度图标 + 淡化文字 + 半透明开关**，
   一眼看得出"这台没纳入"，但绝不挖坑。
   层级：浅色 底243 → 框/行 254；暗色 底10 → 框/未纳入 20 → 已纳入 27。 */
.dv-group > .dev:not(.excluded) { background: transparent; }
[data-theme="dark"] .dv-group > .dev:not(.excluded) { background: rgba(255,255,255,.06); }
.dv-group > .dev.excluded { background: transparent; }
/* 框和框之间留一点呼吸（原来是 margin 在组头自己身上） */
#dv-list { gap: 0; }
#dv-list > .dv-group { margin-bottom: var(--s-3); }

/* ---------- 设备页工具栏（大王 2026-10-18：「这些个也统一大小 豆腐块对齐啊」） ----------
   分组 / 品牌 / 筛选全部统一成**等宽等高的方块**（同一套尺寸、同一套圆角、文字居中），
   像顶栏 tab 的豆腐块那样排列。品牌再多也是继续等分换行，不会大小不一。 */
.dv-tool { display: flex; flex-direction: column; gap: var(--s-2); margin: 0 0 var(--s-3); }
.dv-row { display: flex; align-items: stretch; gap: var(--s-2); flex-wrap: wrap; }
.dv-label { font-size: var(--fs-xs); color: var(--faint); flex: none; letter-spacing: .02em;
  align-self: center; margin-right: var(--s-1); }
/* 方块本体：分组/品牌/筛选共用，尺寸完全一致（注意 #dv-filter 现在是 .dv-row，不是 .dvw-filter） */
.dv-link, .dv-row .lchip { flex: 1 1 0; min-width: 68px; min-height: 34px;
  display: inline-flex; align-items: center; justify-content: center; text-align: center;
  border: 1px solid var(--line-2); border-radius: var(--r-sm); background: var(--surface);
  color: var(--muted); cursor: pointer; font-family: inherit; font-size: var(--fs-sm); font-weight: 500;
  padding: 0 var(--s-2); white-space: nowrap; transition: background var(--dur-1), color var(--dur-1), border-color var(--dur-1); }
.dv-link:hover, .dv-row .lchip:hover { background: var(--surface-3); color: var(--text); }
.dv-link.active, .dv-row .lchip.active { background: color-mix(in srgb, var(--brand) 16%, transparent);
  color: var(--brand); border-color: var(--brand); font-weight: 700; }
.dv-link .n, .dv-row .lchip b { opacity: .7; font-weight: 600; margin-left: 4px; font-size: var(--fs-xs); }

/* 统计纯文字：弱、只读，和"能点的方块"拉开主次 */
.dvstat { font-size: var(--fs-sm); color: var(--muted); white-space: nowrap; align-self: center; }
.dvstat b { color: var(--text); font-weight: 700; }
/* 说明文字拉直：一行放下，不折行（大王 2026-10-18 截图点名） */
p.muted.oneline { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* 竖分隔线：把「统计」和「筛选方块」隔开 */
.vsep { display: inline-block; width: 1px; align-self: stretch; min-height: 24px;
  margin: 0 var(--s-1); background: var(--line); flex: none; }
/* 批量操作：与方块同高，收在每行右端 */
button.mini { min-height: 34px; padding: 0 var(--s-3); font-size: var(--fs-sm);
  border-radius: var(--r-sm); flex: none; }

/* ---------- 「允许语音直接控制」设备级授权 ---------- */
.directchk { margin: var(--s-2) 0 var(--s-2); padding: var(--s-2) var(--s-3);
  background: var(--surface-2); border-radius: var(--r-sm);
  border: 1px solid var(--line); font-size: var(--fs-sm); }
.directchk input { width: 18px; height: 18px; accent-color: var(--brand); flex: none; }
.bigchk { font-size: var(--fs-md); font-weight: 600; padding: var(--s-2) 0; }
.bigchk input { width: 20px; height: 20px; }

/* 设备列表：列表形式（大王要求）——每行一台，纵向排列，整齐不参差 */
#dv-list { display: flex; flex-direction: column; gap: var(--s-2); margin-top: var(--s-4); }

/* 单行布局：左=身份，右=状态+开关；展开明细跨整行落在下方。
   采用 Every Layout 的 Sidebar 原语：主内容抢空间、窄于 50% 自动换行，
   无需 @media 断点即可内在响应（intrinsic layout）。间距一律用 gap，不留外沿冗余。 */
/* 设备行属于「密集列表」：米家规范 12px（比卡片的 16px 更收，列表才显整齐） */
#dv-list .dev { border-radius: var(--r-md); display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--s-2) var(--s-4); padding: var(--s-3) var(--s-4); min-height: 0; overflow: hidden; }
/* 主内容（身份区）：尽量抢空间 */
#dv-list .dev > .dev-id { flex-basis: 0; flex-grow: 999; min-inline-size: 50%; }
/* 侧栏（状态+开关）：按内容宽度，靠右 */
#dv-list .dev > .dev-foot { flex-grow: 1; flex-basis: auto; }

/* 设备行：身份区（图标 + 名称/房间）横向铺开，文字一律左对齐 */
.dev-id { display: flex; align-items: center; gap: var(--s-3); cursor: pointer; min-width: 0; }
.ico { width: 40px; height: 40px; border-radius: 10px; display: grid; place-items: center; font-size: 20px; flex: none;
  border: 1px solid rgba(255,255,255,.14); }
.ico.ico-mijia { background: radial-gradient(circle at 30% 25%, rgba(255,140,40,.32), rgba(255,140,40,.06)); box-shadow: inset 0 1px 0 rgba(255,255,255,.25); }
.ico.ico-haier { background: radial-gradient(circle at 30% 25%, rgba(20,140,255,.32), rgba(20,140,255,.06)); box-shadow: inset 0 1px 0 rgba(255,255,255,.25); }
.ico.ico-kiwik { background: radial-gradient(circle at 30% 25%, rgba(168,85,247,.36), rgba(168,85,247,.06)); box-shadow: inset 0 1px 0 rgba(255,255,255,.25); }
.ico.ico-tuya  { background: radial-gradient(circle at 30% 25%, rgba(57,255,90,.32), rgba(57,255,90,.06)); box-shadow: inset 0 1px 0 rgba(255,255,255,.25); }
.dev-meta { min-width: 0; flex: 1; }
.dev-name  { font-weight: 700; font-size: var(--fs-md); color: #fff; letter-spacing: -.01em; line-height: 1.3; overflow-wrap: break-word; }
.dev-room  { font-size: var(--fs-xs); color: var(--muted); margin-top: 3px; line-height: 1.4; overflow-wrap: break-word; }
/* 列表行右侧：只剩开关。NN/g toggle 规范——位置移动 + 高对比色本身就是状态，
   「已纳入/已排除」文字徽标与开关语义重复，已合并删除（大王指示）。
   当前状态：on=接管中（青绿渐变）；off=已排除；整行另有幽灵玻璃降饱和。 */
.dev-foot { display: flex; align-items: center; justify-content: flex-end; margin-top: 0; padding-top: 0; border-top: 0; }

/* 在线/离线点（全局通用）：在线=实心绿光，离线=空心环。
   设备名旁的 inline 小点用 .dev-name .dot 上下文覆盖，不动全局（铁律⑦）。 */
.dot { width: 10px; height: 10px; border-radius: 50%; flex: 0 0 auto; background: transparent; box-shadow: 0 0 0 1.5px var(--line-2) inset; }
.dot.on { background: var(--ok); box-shadow: 0 0 0 1.5px var(--ok) inset; }
.dot.off { background: transparent; }
/* 设备名旁的在线点：inline 小号，贴着名字走 */
.dev-name .dot { display: inline-block; width: 8px; height: 8px; margin-left: var(--s-2); vertical-align: 2px; flex: none; }

/* 开关（NN/g 规范：滑块造型 + 位置移动 + 高对比色；点击立即生效）。
   ★ min-height:0 必须显式压掉全局 button 的 44px 触控高——不然 26px 轨道被撑成
   44px 大圆球、旋钮居中悬空，完全不像滑块（这正是大王截图里那个怪开关的根因）。 */
.toggle { appearance: none; -webkit-appearance: none; width: 44px; height: 26px; min-height: 0; border-radius: var(--r-full);
  background: var(--surface-2); border: 1px solid var(--line); position: relative; flex: none;
  cursor: pointer; transition: background var(--dur-2) var(--ease-std), border-color var(--dur-2) var(--ease-std), box-shadow var(--dur-2) var(--ease-std); font-size: 0; }
.toggle::after { content: ""; position: absolute; top: 2px; left: 2px; width: 20px; height: 20px; border-radius: 50%;
  background: #fff; box-shadow: 0 1px 4px rgba(0,0,0,.4); transition: transform .22s var(--ease-spring), box-shadow var(--dur-2); }
.toggle:hover { border-color: color-mix(in srgb, var(--cyan) 55%, var(--line)); }
.toggle:hover::after { transform: scale(1.08); box-shadow: 0 2px 7px rgba(0,0,0,.45); }
/* 开 → 纯色品牌胶囊（对标米家/TDesign：开关是功能控件，用纯色不用渐变霓虹） */
.toggle.on { background: var(--brand); border-color: transparent; }
.toggle.on::after { transform: translateX(20px); }
.toggle.on:hover::after { transform: translateX(20px) scale(1.08); }
.toggle:active::after { transform: scale(.94); }
.toggle.on:active::after { transform: translateX(20px) scale(.94); }
.toggle:focus-visible { outline: 2px solid var(--cyan); outline-offset: 2px; }
.toggle:disabled { opacity: .5; cursor: default; }

.chev { color: var(--faint); font-size: var(--fs-xs); transition: transform var(--dur-2); }
.dev.open .chev { transform: rotate(180deg); }

/* 已排除：幽灵玻璃（暗化 + 图标灰度 + 文字降饱和 + 渐变描边减弱），绝不虚线框。
   在线点保持真实在线状态——「排除」和「离线」是两回事，不混为一谈。 */
.dev.excluded { background: rgba(255,255,255,.022); }
.dev.excluded .ico { filter: grayscale(1) brightness(.92); opacity: .5; box-shadow: none; border-color: rgba(255,255,255,.1); }
.dev.excluded .dev-name { color: #8b91b5; }
.dev.excluded .dev-room { color: #5e648a; }
.dev.excluded .toggle { opacity: .45; filter: grayscale(.6); }
/* 未纳入整行再降一点存在感：文字和图标都淡，但底色不动（米家做法） */
.dev.excluded .dev-name, .dev.excluded .dev-room { opacity: .82; }

/* 展开明细：始终独占整行，落在列表行下方（而不是挤到右侧） */
.dev-body { flex-basis: 100%; padding: var(--s-3) 0 0; margin-top: var(--s-2); background: transparent; border-top: 1px solid var(--line); }
.devmeta { display: flex; align-items: center; gap: var(--s-2); flex-wrap: wrap; margin-top: var(--s-3); padding-top: var(--s-3); border-top: 1px dashed var(--line-2); }
.devmeta input { max-width: 132px; min-height: 32px; font-size: var(--fs-sm); padding: 0 var(--s-2); }
.devmeta .muted { font-size: var(--fs-xs); }

/* ---------- 自动化页（#au-list 复用 .dev 玻璃卡，但不在 #dv-list 内，需补齐布局）
   ★ 教训：v27 把 .dev 的全局 padding 收进 #dv-list .dev，导致 #au-list 里的卡片失去内边距；
   又删了 .dev-head / .dev-model 基础样式，自动化卡整排裸奔 → 大王反馈"自动化乱掉"。
   这里把这两类补齐，与设备卡同属一套玻璃体系。 ---------- */
#au-list { display: flex; flex-direction: column; gap: var(--s-4); margin-top: var(--s-4); }
#au-list .dev { margin-bottom: 0; padding: var(--s-4) var(--s-5); overflow: hidden; display: flex; flex-direction: column; gap: var(--s-2); }
.au-row { display: flex; align-items: center; gap: var(--s-3); }
.au-row .dev-name { flex: 1; min-width: 0; }
.au-acts { display: flex; gap: var(--s-2); flex: none; }
.au-line { display: flex; gap: var(--s-2); align-items: baseline; font-size: var(--fs-sm); color: var(--muted); line-height: 1.6; }
.au-line .au-k { flex: none; color: var(--faint); font-size: var(--fs-2xs); font-weight: 700; letter-spacing: .04em; padding: 1px 8px; border-radius: var(--r-full); background: var(--surface-2); border: 1px solid var(--line); }
.au-cond .au-k { color: var(--warn); border-color: color-mix(in srgb, var(--warn) 30%, transparent); background: var(--warn-weak); }
.au-warn .au-k { color: var(--bad); border-color: color-mix(in srgb, var(--bad) 30%, transparent); background: var(--bad-weak); }
.au-warn { color: var(--bad); }

.ctl { display: flex; align-items: center; gap: var(--s-3); padding: var(--s-3) 0; border-bottom: 1px solid var(--line); flex-wrap: wrap; }
.ctl:last-child { border-bottom: 0; }
.ctl-name { flex: 1 1 140px; font-size: var(--fs-md); }
.ctl select { flex: 1 1 150px; }
.ctl .val { min-width: 62px; text-align: right; color: var(--muted); font-size: var(--fs-sm); }
.switch { min-width: 88px; background: var(--surface); color: var(--muted); border: 1px solid var(--line-2); font-weight: 600; }
.switch.on { background: var(--ok-weak); color: var(--ok); border-color: color-mix(in srgb, var(--ok) 45%, transparent); }
.cap-group { font-size: var(--fs-2xs); color: var(--faint); margin: var(--s-4) 0 0; font-weight: 600; letter-spacing: .04em; }

/* ---------- 15. 自动化向导 ---------- */
.choice { display: flex; gap: var(--s-2); margin: var(--s-3) 0; flex-wrap: wrap; }
.wizard-steps { display: flex; gap: var(--s-2); margin-bottom: var(--s-4); }
.wizard-steps .step { flex: 1; text-align: center; font-size: var(--fs-sm); color: var(--muted); padding: var(--s-2) var(--s-1);
  border-radius: var(--r-xs); background: var(--surface-2); box-shadow: 0 0 0 1px var(--line) inset; transition: background var(--dur-2), color var(--dur-2); }
.wizard-steps .step.active { color: var(--brand); font-weight: 700; background: color-mix(in srgb, var(--brand) 16%, transparent); box-shadow: 0 0 0 1px var(--brand) inset; }
.wizard-foot { display: flex; gap: var(--s-2); margin-top: var(--s-5); flex-wrap: wrap; }
.wizard-foot button { flex: 1 1 auto; }
#wizard label { display: block; font-size: var(--fs-sm); color: var(--muted); margin: var(--s-4) 0 var(--s-2); font-weight: 600; }
#wizard input, #wizard select { width: 100%; }
.action-row, .cond-row, .trig-row { border-radius: var(--r-xs); padding: var(--s-3); margin-bottom: var(--s-2);
  background: var(--surface-2); box-shadow: 0 0 0 1px var(--line) inset; }
.ar-top, .ar-bottom, .cr-top, .tr-top { display: flex; gap: var(--s-2); align-items: center; flex-wrap: wrap; }
.ar-bottom { margin-top: var(--s-2); }
.ar-top select, .ar-bottom select, .cr-top select, .tr-top select { flex: 1 1 130px; }
.cr-kind, .tr-kind { font-size: var(--fs-xs); color: var(--muted); flex: 0 0 auto; }
.cr-top input { flex: 1 1 120px; min-height: 36px; }
.cr-top input[type="time"] { width: 116px; flex: 0 0 auto; }
.a-kind { flex: 0 0 auto; min-width: 120px; }
.ar-body { margin-top: var(--s-2); }
.ar-body .a-dev, .ar-body .a-text { width: 100%; }
.ar-body input[type="number"] { width: 96px; }
.c-days, .t-days { display: flex; gap: var(--s-1); align-items: center; flex-wrap: wrap; margin-top: var(--s-2); }
.cond-box { margin-top: var(--s-4); padding-top: var(--s-3); border-top: 1px dashed var(--line-2); }
.cond-box h4 { margin: 0 0 var(--s-1); font-size: var(--fs-sm); }
.cond-add, .trig-add { display: flex; gap: var(--s-2); align-items: center; margin-top: var(--s-2); flex-wrap: wrap; }
.cond-add select { flex: 1 1 180px; }

/* ---------- 16. 音箱 ---------- */
.speakers { display: flex; flex-direction: column; gap: var(--s-2); margin-bottom: var(--s-4); }
.spk { display: flex; align-items: center; gap: var(--s-3); padding: var(--s-3) var(--s-4); border-radius: var(--r-sm);
  background: var(--surface-2); box-shadow: 0 0 0 1px var(--line) inset; cursor: pointer; min-height: var(--tap); transition: background var(--dur-1); }
.spk span:first-of-type { flex: 1; font-size: var(--fs-md); }

/* ---------- 17. 运行日志 ---------- */
/* 运行日志 / 最近执行记录：带滑块的滚动容器（大王指令②：不再一拉到底） */
.logs { background: var(--surface); border-radius: var(--r-lg); padding: var(--s-1) var(--s-4); box-shadow: var(--glass-shadow);
  max-height: 68vh; overflow-y: auto; overflow-x: hidden; scrollbar-gutter: stable; contain: layout style paint; }
/* 主题化自定义滚动条（玻璃质感，WebKit + Firefox 双写，靶区≥12px 满足 WCAG 非文本对比） */
.logs::-webkit-scrollbar { width: 12px; height: 12px; }
.logs::-webkit-scrollbar-track { background: color-mix(in srgb, var(--text) 7%, transparent); border-radius: var(--r-full); }
.logs::-webkit-scrollbar-thumb { background: linear-gradient(180deg, var(--brand), var(--brand-2)); border-radius: var(--r-full);
  border: 3px solid transparent; background-clip: padding-box; box-shadow: none; }
.logs::-webkit-scrollbar-thumb:hover { background: linear-gradient(180deg, var(--brand-2), var(--brand)); background-clip: padding-box; }
.logs { scrollbar-width: thin; scrollbar-color: var(--brand) color-mix(in srgb, var(--text) 7%, transparent); }
.logline { display: flex; gap: var(--s-2); align-items: center; padding: var(--s-2) 0; border-bottom: 1px solid var(--line); font-size: var(--fs-sm); flex-wrap: wrap; }
.logline:last-child { border-bottom: 0; }
.logline .t { color: var(--faint); font-size: var(--fs-xs); min-width: 62px; }
.logline .body { flex: 1 1 260px; min-width: 180px; word-break: break-word; }
.logline .why { color: var(--muted); font-size: var(--fs-xs); }
.logline.ext { background: var(--bad-weak); margin: 0 calc(-1 * var(--s-4)); padding-left: var(--s-4); padding-right: var(--s-4); }
.logline.ext .body { font-weight: 600; }

.logcard { border-bottom: 1px solid var(--line); }
.logcard:last-child { border-bottom: 0; }
.logcard > summary { display: flex; gap: var(--s-2); align-items: center; padding: var(--s-2) 0; cursor: pointer; list-style: none; font-size: var(--fs-sm); flex-wrap: wrap; transition: background var(--dur-1); }
.logcard > summary::-webkit-details-marker { display: none; }
.logcard.ext > summary { background: var(--warn-weak); }
.logcard.bad > summary { background: var(--bad-weak); }
.lc-t { color: var(--faint); font-size: var(--fs-xs); min-width: 62px; }
.lc-say { flex: 1; min-width: 180px; }
.lc-chev { color: var(--faint); font-size: var(--fs-2xs); transition: transform var(--dur-2); }
.logcard[open] .lc-chev { transform: rotate(180deg); }
.lc-body { margin: var(--s-1) 0 var(--s-3); padding: var(--s-2) var(--s-3); border-radius: var(--r-xs); background: var(--surface-2); font-size: var(--fs-xs); line-height: 1.8; }
.lc-row { display: flex; gap: var(--s-2); }
.lc-row > span { color: var(--faint); min-width: 58px; }
.lc-row > b { font-weight: 600; word-break: break-all; }
.lc-count { display: inline-flex; align-items: center; justify-content: center; min-width: 20px; height: 20px; padding: 0 6px;
  border-radius: var(--r-full); background: var(--surface-3); color: var(--muted); font-size: var(--fs-2xs); font-weight: 700; }

.alertbox { border-radius: var(--r-sm); padding: var(--s-3) var(--s-4); margin-bottom: var(--s-4); font-size: var(--fs-sm); line-height: var(--lh-snug); }
.alertbox.warn { background: var(--warn-weak); color: var(--warn); }
.alertbox.bad  { background: var(--bad-weak);  color: var(--bad); }
.alertbox.info { background: color-mix(in srgb, var(--brand) 14%, transparent); color: var(--brand); }
.gaplist { display: flex; flex-wrap: wrap; gap: var(--s-1); margin-top: var(--s-2); }
.gapchip { display: inline-flex; align-items: center; gap: 5px; background: var(--surface); box-shadow: 0 0 0 1px var(--line) inset; border-radius: var(--r-full); padding: 3px var(--s-3); font-size: var(--fs-xs); color: var(--text); }
.gapchip i { font-style: normal; font-size: var(--fs-2xs); color: var(--muted); }
.rawlog { margin: var(--s-3) 0 0; max-height: 340px; overflow: auto; font-size: var(--fs-xs); line-height: 1.6; white-space: pre-wrap; word-break: break-all; background: var(--surface-2); color: var(--text); border-radius: var(--r-xs); padding: var(--s-3); }

/* ---------- 18. 试说一句 / 凭据折叠卡 ---------- */
.dt-out { margin-top: var(--s-4); }
.dt-hit  { background: var(--ok-weak);   border-radius: var(--r-sm); padding: var(--s-4); }
.dt-miss { background: var(--warn-weak); color: var(--warn); border-radius: var(--r-sm); padding: var(--s-4); font-size: var(--fs-sm); }
.dt-title { font-size: var(--fs-md); font-weight: 700; color: var(--ok); margin-bottom: var(--s-2); }
.dt-line { font-size: var(--fs-sm); line-height: 1.9; }
#dt-text { width: 100%; margin-bottom: var(--s-2); }
.dt-btns { display: flex; gap: var(--s-2); flex-wrap: wrap; margin-bottom: var(--s-3); }
.chips { display: flex; gap: var(--s-2); flex-wrap: wrap; }

details.card.cred > summary { display: flex; align-items: center; gap: var(--s-2); cursor: pointer; list-style: none; flex-wrap: wrap; }
details.card.cred > summary::-webkit-details-marker { display: none; }
details.card.cred > summary h2 { margin: 0; flex: 1; min-width: 120px; font-size: var(--fs-lg); }
details.card.cred > summary .cred-badge { font-size: var(--fs-xs); padding: 4px var(--s-3); }
details.card.cred[open] > summary .chev { transform: rotate(90deg); }
details.card.cred > summary + * { margin-top: var(--s-2); }
details.card.cred:not([open]) { padding-top: var(--s-4); padding-bottom: var(--s-4); }

/* ---------- 19. 空状态 / toast ---------- */
/* 对齐规矩：内容文字一律左对齐，居中只留给登录页/徽标/图标内部等容器级元素 */
.empty { padding: var(--s-8) var(--s-4); text-align: left; color: var(--muted); font-size: var(--fs-md); background: var(--surface); border-radius: var(--r-lg); box-shadow: var(--glass-shadow); }
.empty::before { content: "∅"; display: block; font-size: 30px; line-height: 1; color: var(--faint); margin-bottom: var(--s-3); opacity: .55; }

.toast { position: fixed; bottom: var(--s-6); left: 50%; transform: translateX(-50%); background: var(--text); color: var(--bg);
  padding: var(--s-3) var(--s-5); border-radius: var(--r-sm); font-size: var(--fs-md); font-weight: 500; z-index: 99; box-shadow: var(--shadow-pop); max-width: 88vw; text-align: center; }
.toast.bad { background: var(--bad); color: #fff; }

/* ---------- 20. 响应式 ---------- */
@media (max-width: 720px) {
  main { padding: var(--s-3) var(--s-3) 96px; }
  header { padding: var(--s-2) var(--s-3); gap: var(--s-2); flex-wrap: wrap; }
  .brand small { display: none; }
  .status { order: 3; flex-basis: 100%; padding-top: var(--s-2); flex-wrap: wrap; overflow: visible; row-gap: var(--s-2); }
  .status .sflag { padding: 3px var(--s-2); }
  .tabs { position: fixed; bottom: 0; left: 0; right: 0; z-index: 30; border-top: 1px solid var(--line); border-bottom: 0; padding: var(--s-1) var(--s-2) calc(var(--s-1) + env(safe-area-inset-bottom, 0px)); gap: var(--s-1); max-width: none; }
  .tab { flex: 1; padding: var(--s-2) var(--s-1); font-size: var(--fs-sm); border-radius: var(--r-sm); }
  .tab::after { display: none; }
  .tab.active { background: color-mix(in srgb, var(--brand) 18%, transparent); }
  .card { padding: var(--s-4); }
  .hero { padding: var(--s-4) var(--s-4); }
  h1 { font-size: var(--fs-xl); }
  .ctl { align-items: flex-start; flex-direction: column; gap: var(--s-2); }
  .ctl-name { flex: none; }
  .ctl select, .ctl .val { width: 100%; text-align: left; }
  .ctl .val { text-align: right; }
  .switch { width: 100%; }
  .ar-top, .ar-bottom, .cr-top, .tr-top { flex-wrap: wrap; }
  input, select, textarea { font-size: 16px; }
}
@media (max-width: 640px) {
  .logbar .grow { flex: 1 1 100%; }
  .logline .t, .lc-t { min-width: 54px; }
}

/* ---------- 21. 尊重"减少动态效果" ---------- */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .01ms !important; animation-iteration-count: 1 !important; transition-duration: .01ms !important; scroll-behavior: auto !important; }
}

/* 移动端设备列表：仍是「一行一台」，只收紧内边距、缩小图标，避免行被撑高 */
@media (max-width: 720px) {
  #dv-list { gap: var(--s-2); }
  #dv-list .dev { padding: var(--s-3); gap: var(--s-2) var(--s-3); min-height: 0; }
  #dv-list .ico { width: 32px; height: 32px; font-size: 17px; }
  #dv-list .dev-room { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  /* 移动端 Hero：在线率环收进右上角，不再独占一行留大片空白 */
  .hero { position: relative; padding-right: calc(var(--s-4) + 104px); }
  .ring { position: absolute; top: var(--s-4); right: var(--s-4); width: 88px; height: 88px; }
  .ring .val { font-size: 20px; }
}
