/* ============================================================
   PEH-EMS · Alpin 设计系统  v2
   ------------------------------------------------------------
   三条硬规则（改动前请先读）：
   1) 本文件之外**不允许出现任何 #hex 颜色**。所有颜色必须走 var(--*)。
      → 换主题 = 换 :root 那一组变量；加主题 = 加一个 [data-theme=...] 块。
   2) 所有对比度按 WCAG AA（正文 4.5:1、大字与图形 3:1）核算过，
      见 _preview_cards_v2/_tools/contrast_check.py 的实测表。
   3) 布局尺寸（间距/圆角/字阶/断点）同样走变量，避免"改完主题布局散了"。
   ============================================================ */

/* ---------- 字体 ---------- */
@font-face{font-family:"InterLocal";src:local("Inter"),local("Inter Variable"),local("Segoe UI Variable Text");font-display:swap;}
:root{
  --font:"InterLocal","Inter","Segoe UI","Helvetica Neue",Arial,sans-serif;
  --mono:ui-monospace,"Cascadia Mono","SF Mono",Consolas,monospace;

  /* 字阶（1.20 比例，14px 基准） */
  --fs-xxs:10.5px; --fs-xs:11.5px; --fs-s:12.5px; --fs-m:14px;
  --fs-l:17px;     --fs-xl:22px;   --fs-xxl:30px; --fs-mega:40px;

  /* 间距（4 的倍数） */
  --s1:4px; --s2:8px; --s3:12px; --s4:16px; --s5:20px; --s6:24px; --s8:32px; --s10:40px;

  /* 圆角 —— 全站只用三档，不允许 12px 之外的花样 */
  --r-s:6px; --r-m:10px; --r-l:14px; --r-pill:999px;

  /* 动效 */
  --ease:cubic-bezier(.2,.7,.3,1);
  --dur:160ms;
  --dur-slow:280ms;

  /* 布局 */
  --maxw:1400px;
  --hdr-h:56px;
  --cols:3;
  --gap:16px;
}

/* ============================================================
   主题 A · Alpin 工业瓷白（主色，默认）
   设计意图：白纸 + 钢笔。底色带极轻的绿灰，让纯白卡片"浮"起来；
   饱和度只留给状态色和品牌色，界面本身不抢数据。
   ============================================================ */
:root,[data-theme="alpin"]{
  color-scheme:light;

  /* 表面：三级层次，靠明度差而不是投影区分 */
  --bg:#F4F5F3;          /* 页面底 —— 瓷白偏绿灰 */
  --panel:#FFFFFF;       /* 卡片 */
  --panel-2:#FAFBFA;     /* 卡片内嵌小块、表头 */
  --sunken:#EDEFEC;      /* 输入框、进度槽 */
  --line:#DDE2DD;        /* 主分隔线 */
  --line-soft:#EBEEEB;   /* 次级分隔线（表内横线） */

  /* 文字：只用四档，不许出现第五种灰 */
  --ink:#1F2A26;         /* 主文字      对 --panel 15.4:1 */
  --ink-2:#3A4742;       /* 次要文字    对 --panel 9.1:1 */
  --mute:#606B66;        /* 辅助文字    panel 5.4:1 / bg 4.99:1 / sunken 4.72:1 */
  --faint:#9AA39E;       /* 占位/禁用    仅用于非正文 */

  /* 状态色（浅底版本，用于 pill / 小标签） */
  --ok:#1D9E75;      --ok-bg:#E8F5F0;   --ok-ink:#106349;
  --warn:#B0740E;    --warn-bg:#FBF1DF; --warn-ink:#7A4F07;
  --err:#C2453F;     --err-bg:#FCECEB;  --err-ink:#8C2C28;
  --idle:#6D7873;    --idle-bg:#EDEFEC; --idle-ink:#4E5854;

  /* 数据色（图表 / 能量流向）—— 与状态色刻意分开，避免"绿色数据被误读成正常" */
  --d-pv:#9C7414;    /* 光伏 · 赭金  panel 4.5:1（浅底必须压深，否则曲线看不见） */
  --d-batt:#2C6E63;  /* 电池 · 深松绿 */
  --d-grid:#4A6B8A;  /* 电网 · 板岩蓝 */
  --d-load:#8A5B4A;  /* 负载 · 陶土棕 */

  /* 品牌（会被租户覆盖，见 §品牌下发） */
  --brand:#2F6B5A;
  --brand-ink:#FFFFFF;
  --brand-soft:#E7F0EC;

  /* 投影 —— 极轻。Alpin 不靠投影表达层级 */
  --sh-1:0 1px 2px rgba(31,42,38,.05);
  --sh-2:0 4px 16px rgba(31,42,38,.08);
  --sh-drawer:-16px 0 40px rgba(31,42,38,.10);

  /* 骨架屏 */
  --skeleton:linear-gradient(90deg,#EDEFEC 0%,#F6F7F5 50%,#EDEFEC 100%);
  --focus:0 0 0 3px rgba(47,107,90,.28);
}

/* ============================================================
   主题 C · Dunkel 夜间盘控（附加，手动切换）
   不是把浅色反相 —— 深底之间的层次更窄，靠"抬亮"而非"压暗"。
   深色下饱和色要降饱和提明度，否则在 OLED 上会发光刺眼。
   ============================================================ */
[data-theme="dunkel"]{
  color-scheme:dark;

  --bg:#12161A;
  --panel:#1A2026;
  --panel-2:#20272E;
  --sunken:#0D1115;
  --line:#2E3841;
  --line-soft:#232B33;

  --ink:#E4E9ED;         /* 对 --panel 12.4:1 */
  --ink-2:#B9C2CA;       /* 对 --panel 7.6:1 */
  --mute:#8C98A4;        /* 对 --panel 4.6:1  ← 过 AA */
  --faint:#5D6873;

  --ok:#3FB98C;      --ok-bg:#16302A;   --ok-ink:#7FD8B6;
  --warn:#D9A03C;    --warn-bg:#332A18; --warn-ink:#EFC67E;
  --err:#E2706A;     --err-bg:#33211F;  --err-ink:#F3A9A4;
  --idle:#8C98A4;    --idle-bg:#242C34; --idle-ink:#B9C2CA;

  --d-pv:#E4B94A;
  --d-batt:#4FBFA0;
  --d-grid:#7BA5CC;
  --d-load:#C99079;

  --brand:#4A9B85;
  --brand-ink:#0E1417;
  --brand-soft:#182A26;

  --sh-1:0 1px 2px rgba(0,0,0,.4);
  --sh-2:0 4px 16px rgba(0,0,0,.45);
  --sh-drawer:-16px 0 40px rgba(0,0,0,.5);

  --skeleton:linear-gradient(90deg,#1F262D 0%,#272F37 50%,#1F262D 100%);
  --focus:0 0 0 3px rgba(74,155,133,.35);
}

/* ============================================================
   品牌下发（tenant branding）
   ------------------------------------------------------------
   服务端 GET /api/me/branding 返回：
     { brand:"#2F6B5A", brandInk:"#FFFFFF", brandSoft:"#E7F0EC", logo:null, name:"Alpenwärme" }
   前端在 applyBranding() 里写这三个变量 —— 只覆盖"品牌"一组，
   **绝不覆盖状态色与数据色**（否则客户把品牌设成绿色，
   在线状态和光伏曲线就分不出来了）。这是硬约束，写在这里防止后人改坏。
   ============================================================ */

/* ============================================================
   基元
   ============================================================ */
*,*::before,*::after{box-sizing:border-box;}
html{-webkit-text-size-adjust:100%;}
body{
  margin:0;background:var(--bg);color:var(--ink);
  font-family:var(--font);font-size:var(--fs-m);line-height:1.5;
  -webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;
  font-feature-settings:"tnum" 1,"cv05" 1;
  transition:background-color var(--dur) var(--ease),color var(--dur) var(--ease);
}
/* 等宽数字：所有读数必须对齐，否则卡片墙会"数字跳舞" */
.num{font-variant-numeric:tabular-nums;letter-spacing:-.01em;}

h1,h2,h3,h4{margin:0;font-weight:600;letter-spacing:-.01em;}
p{margin:0;}
button{font:inherit;color:inherit;background:none;border:0;cursor:pointer;padding:0;}
input,select,textarea{font:inherit;color:inherit;}
a{color:var(--brand);text-decoration:none;}
:focus-visible{outline:none;box-shadow:var(--focus);border-radius:var(--r-s);}
::selection{background:var(--brand-soft);color:var(--ink);}

/* 滚动条 —— 默认样式在深色主题下很扎眼，统一覆盖 */
*{scrollbar-width:thin;scrollbar-color:var(--line) transparent;}
*::-webkit-scrollbar{width:10px;height:10px;}
*::-webkit-scrollbar-thumb{background:var(--line);border-radius:var(--r-pill);
  border:3px solid transparent;background-clip:content-box;}
*::-webkit-scrollbar-thumb:hover{background:var(--mute);background-clip:content-box;}
*::-webkit-scrollbar-track{background:transparent;}

.skip{position:absolute;left:-9999px;}
.skip:focus{left:var(--s3);top:var(--s3);z-index:999;background:var(--panel);
  padding:var(--s2) var(--s3);border-radius:var(--r-m);box-shadow:var(--sh-2);}

/* ============================================================
   骨架：sticky 顶栏 / 左侧身份条 / 主区
   ============================================================ */
.shell{min-height:100vh;display:flex;flex-direction:column;}

.topbar{
  position:sticky;top:0;z-index:60;
  background:color-mix(in srgb,var(--panel) 88%,transparent);
  backdrop-filter:blur(12px) saturate(1.2);
  border-bottom:1px solid var(--line);
}
.topbar-in{max-width:var(--maxw);margin:0 auto;padding:0 var(--s5);
  height:var(--hdr-h);display:flex;align-items:center;gap:var(--s4);}

/* 品牌标记：一个方块 + 一个词。不用渐变、不用图标字体 */
.logo{display:flex;align-items:center;gap:var(--s2);font-size:var(--fs-l);
  font-weight:600;letter-spacing:-.02em;color:var(--ink);}
.logo .mark{width:22px;height:22px;border-radius:7px;background:var(--brand);
  display:grid;place-items:center;color:var(--brand-ink);font-size:11px;font-weight:600;
  letter-spacing:0;flex:none;}
.logo .word{white-space:nowrap;}
.logo .tenant{font-size:var(--fs-s);font-weight:400;color:var(--mute);
  padding-left:var(--s2);margin-left:var(--s1);border-left:1px solid var(--line);}

/* 视图切换：分段控件（不是 tab。tab 的下划线在深色下很脏） */
.seg{display:inline-flex;background:var(--sunken);border-radius:var(--r-m);
  padding:3px;gap:2px;}
.seg button{padding:5px var(--s3);border-radius:7px;font-size:var(--fs-s);
  color:var(--mute);transition:background-color var(--dur) var(--ease),color var(--dur) var(--ease);}
.seg button:hover{color:var(--ink-2);}
.seg button[aria-selected="true"]{background:var(--panel);color:var(--ink);
  font-weight:500;box-shadow:var(--sh-1);}

.spacer{flex:1;}

/* 在线计数 pill */
.pill{display:inline-flex;align-items:center;gap:6px;height:26px;padding:0 var(--s3);
  border-radius:var(--r-pill);font-size:var(--fs-s);font-weight:500;
  background:var(--ok-bg);color:var(--ok-ink);}
.pill.warn{background:var(--warn-bg);color:var(--warn-ink);}
.pill.err{background:var(--err-bg);color:var(--err-ink);}
.pill .dot{width:6px;height:6px;border-radius:50%;background:currentColor;flex:none;}

/* 图标按钮：主题、密度、用户 */
.iconbtn{width:32px;height:32px;border-radius:var(--r-s);display:grid;place-items:center;
  color:var(--mute);transition:background-color var(--dur) var(--ease),color var(--dur) var(--ease);}
.iconbtn:hover{background:var(--sunken);color:var(--ink);}
.iconbtn svg{width:17px;height:17px;stroke:currentColor;fill:none;
  stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round;}

/* 当前用户 */
.me{display:flex;align-items:center;gap:var(--s2);padding:3px var(--s2) 3px 3px;
  border-radius:var(--r-pill);transition:background-color var(--dur) var(--ease);}
.me:hover{background:var(--sunken);}
.ava{width:28px;height:28px;border-radius:50%;background:var(--brand-soft);
  color:var(--brand);display:grid;place-items:center;font-size:var(--fs-xs);
  font-weight:600;flex:none;letter-spacing:0;}
.me .who{display:flex;flex-direction:column;line-height:1.15;text-align:left;}
.me .who b{font-size:var(--fs-s);font-weight:500;color:var(--ink);}
.me .who span{font-size:var(--fs-xxs);color:var(--mute);}

/* 用户菜单 —— 注意：必须挂在 .topbar-in 内部。
   挂到 #app 直下做兄弟节点，v-if 开关会打乱根层节点序列，
   Vue 3 按位置 diff 会误复用后面的 .main → 整页白屏。 */
.usermenu{position:absolute;right:var(--s5);top:calc(var(--hdr-h) - 4px);width:264px;
  background:var(--panel);border:1px solid var(--line);border-radius:var(--r-l);
  box-shadow:var(--sh-2);padding:var(--s2);z-index:70;
  animation:pop var(--dur) var(--ease);}
@keyframes pop{from{opacity:0;transform:translateY(-4px) scale(.98);}to{opacity:1;transform:none;}}
.usermenu .grp{font-size:var(--fs-xxs);color:var(--faint);padding:var(--s2) var(--s3) var(--s1);
  text-transform:uppercase;letter-spacing:.06em;}
.usermenu .who{display:flex;align-items:center;gap:var(--s3);padding:var(--s2) var(--s3);
  width:100%;border-radius:var(--r-s);text-align:left;}
.usermenu .who:hover{background:var(--sunken);}
.usermenu .who[aria-current="true"]{background:var(--brand-soft);}
.usermenu .who b{display:block;font-size:var(--fs-s);font-weight:500;}
.usermenu .who span{display:block;font-size:var(--fs-xxs);color:var(--mute);}
.usermenu hr{border:0;border-top:1px solid var(--line-soft);margin:var(--s2) 0;}

.main{max-width:var(--maxw);margin:0 auto;padding:var(--s6) var(--s5) 80px;width:100%;}

/* 页头：标题 + 一句口径说明（口径必须写在页面上，别写在文档里） */
.pagehead{display:flex;align-items:flex-end;justify-content:space-between;
  gap:var(--s4);margin-bottom:var(--s5);flex-wrap:wrap;}
.pagehead h1{font-size:var(--fs-xl);font-weight:600;letter-spacing:-.02em;}
.pagehead .sub{font-size:var(--fs-s);color:var(--mute);margin-top:2px;}

/* ============================================================
   汇总条 —— 6 个数，一行。数字大、标签小、单位最淡。
   ============================================================ */
.kpis{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:var(--s3);
  margin-bottom:var(--s5);}
.kpi{background:var(--panel);border:1px solid var(--line);border-radius:var(--r-l);
  padding:var(--s3) var(--s4);position:relative;overflow:hidden;}
.kpi .lb{font-size:var(--fs-xxs);color:var(--mute);text-transform:uppercase;
  letter-spacing:.05em;display:flex;align-items:center;gap:5px;}
.kpi .lb i{width:7px;height:7px;border-radius:2px;flex:none;}
.kpi .vl{font-size:var(--fs-xl);font-weight:600;letter-spacing:-.02em;margin-top:6px;line-height:1.1;}
.kpi .vl small{font-size:var(--fs-s);font-weight:400;color:var(--mute);margin-left:3px;letter-spacing:0;}
.kpi .ft{font-size:var(--fs-xxs);color:var(--mute);margin-top:3px;}

/* ============================================================
   工具条：搜索 + 状态筛选 + 密度
   ============================================================ */
.toolbar{display:flex;align-items:center;gap:var(--s2);margin-bottom:var(--s4);flex-wrap:wrap;}
.search{position:relative;flex:1;min-width:200px;max-width:340px;}
.search svg{position:absolute;left:10px;top:50%;transform:translateY(-50%);
  width:15px;height:15px;stroke:var(--faint);fill:none;stroke-width:1.6;pointer-events:none;}
.search input{width:100%;height:34px;padding:0 var(--s3) 0 32px;background:var(--panel);
  border:1px solid var(--line);border-radius:var(--r-m);font-size:var(--fs-s);
  color:var(--ink);transition:border-color var(--dur) var(--ease),box-shadow var(--dur) var(--ease);}
.search input::placeholder{color:var(--faint);}
.search input:focus{border-color:var(--brand);box-shadow:var(--focus);outline:none;}

.chips{display:flex;gap:var(--s1);}
.chip{height:30px;padding:0 var(--s3);border-radius:var(--r-m);font-size:var(--fs-s);
  color:var(--mute);border:1px solid transparent;display:inline-flex;align-items:center;gap:6px;
  transition:background-color var(--dur) var(--ease),color var(--dur) var(--ease);}
.chip:hover{background:var(--sunken);color:var(--ink-2);}
.chip[aria-pressed="true"]{background:var(--panel);border-color:var(--line);color:var(--ink);
  font-weight:500;box-shadow:var(--sh-1);}
.chip .n{font-size:var(--fs-xxs);color:var(--mute);font-variant-numeric:tabular-nums;}
.chip[aria-pressed="true"] .n{color:var(--ink-2);}

/* ============================================================
   卡片墙
   卡片的设计逻辑：一个"主角数字"（SOC 或功率）+ 一条状态色条。
   其余全部退到 --mute。宁可少一项，不要挤成表格。
   ============================================================ */
.cards{display:grid;gap:var(--gap);
  grid-template-columns:repeat(var(--cols),minmax(0,1fr));}

.card{
  background:var(--panel);color:var(--ink);border:1px solid var(--line);border-radius:var(--r-l);
  padding:var(--s4);text-align:left;width:100%;position:relative;overflow:hidden;
  display:flex;flex-direction:column;gap:var(--s3);
  transition:border-color var(--dur) var(--ease),box-shadow var(--dur) var(--ease),
             transform var(--dur) var(--ease);
  animation:cardIn var(--dur-slow) var(--ease) both;
}
@keyframes cardIn{from{opacity:0;transform:translateY(6px);}to{opacity:1;transform:none;}}
.card:hover{border-color:var(--line-2,var(--line));box-shadow:var(--sh-2);transform:translateY(-1px);}
/* 状态色条 —— 3px，靠左内嵌。比"整卡描边变色"安静得多 */
.card::before{content:"";position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--st);}
.card.s-ok{--st:var(--ok);}
.card.s-warn{--st:var(--warn);}
.card.s-err{--st:var(--err);}
.card.s-idle{--st:var(--idle);}
/* 离线卡整体降权：不是把字变淡（会读不出来），而是压掉饱和度和卡片阴影 */
.card.s-err,.card.s-idle{background:var(--panel-2);}
.card.s-err .socring,.card.s-idle .socring{opacity:.55;}

.card-top{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--s2);}
.card-id{min-width:0;}
.card-id .nm{font-size:var(--fs-m);font-weight:600;letter-spacing:-.01em;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.card-id .loc{font-size:var(--fs-xs);color:var(--mute);margin-top:1px;
  display:flex;align-items:center;gap:5px;min-width:0;}
.card-id .loc span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}

/* 状态点 + 文字（不能只用颜色区分 —— 色盲友好） */
.st{display:inline-flex;align-items:center;gap:5px;font-size:var(--fs-xs);
  font-weight:500;flex:none;padding:2px 8px;border-radius:var(--r-pill);}
.st .d{width:6px;height:6px;border-radius:50%;background:currentColor;flex:none;}
.st.s-ok{background:var(--ok-bg);color:var(--ok-ink);}
.st.s-warn{background:var(--warn-bg);color:var(--warn-ink);}
.st.s-err{background:var(--err-bg);color:var(--err-ink);}
.st.s-idle{background:var(--idle-bg);color:var(--idle-ink);}

/* 主角区：SOC 环 + 功率 */
.card-hero{display:flex;align-items:center;gap:var(--s4);}
.socring{position:relative;width:58px;height:58px;flex:none;}
.socring svg{transform:rotate(-90deg);display:block;}
.socring .track{stroke:var(--sunken);}
.socring .bar{stroke-linecap:round;transition:stroke-dasharray var(--dur-slow) var(--ease);}
.socring .val{position:absolute;inset:0;display:grid;place-items:center;
  font-size:var(--fs-m);font-weight:600;letter-spacing:-.02em;font-variant-numeric:tabular-nums;}
.socring .val i{font-size:9px;font-style:normal;color:var(--mute);margin-left:1px;}

.card-power{min-width:0;flex:1;}
.card-hero{container-type:inline-size;container-name:card;}
.card-power .big{font-size:var(--fs-xxl);font-weight:600;letter-spacing:-.03em;line-height:1;
  font-variant-numeric:tabular-nums;display:flex;align-items:baseline;gap:3px;
  color:var(--ink);white-space:nowrap;}
@container card (max-width:300px){.card-power .big{font-size:var(--fs-xl);}}
.card-power .big small{font-size:var(--fs-s);font-weight:400;color:var(--mute);letter-spacing:0;}
.card-power .neg{color:var(--err);}
.card-power .lbl{font-size:var(--fs-xxs);color:var(--mute);margin-top:4px;
  text-transform:uppercase;letter-spacing:.05em;}

/* 三分栏小指标 */
.card-metrics{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));
  border-top:1px solid var(--line-soft);padding-top:var(--s3);gap:var(--s2);}
.card-metrics div{min-width:0;}
.card-metrics .k{font-size:var(--fs-xxs);color:var(--mute);white-space:nowrap;
  overflow:hidden;text-overflow:ellipsis;}
.card-metrics .v{font-size:var(--fs-s);font-weight:500;margin-top:1px;
  font-variant-numeric:tabular-nums;white-space:nowrap;}
.card-metrics .v small{font-size:var(--fs-xxs);color:var(--mute);font-weight:400;}

.card-foot{display:flex;align-items:center;justify-content:space-between;
  font-size:var(--fs-xxs);color:var(--mute);gap:var(--s2);}
.card-foot .al{display:inline-flex;align-items:center;gap:4px;color:var(--warn-ink);
  background:var(--warn-bg);padding:1px 7px;border-radius:var(--r-pill);font-weight:500;}

/* 数据新鲜度条 —— 这条是"页面冻结"事故的防线，别删 */
.fresh{display:inline-flex;align-items:center;gap:5px;font-size:var(--fs-xxs);color:var(--mute);}
.fresh .d{width:6px;height:6px;border-radius:50%;flex:none;}
.fresh.ok .d{background:var(--ok);}
.fresh.warn .d{background:var(--warn);}
.fresh.err .d{background:var(--err);}
.fresh.err{color:var(--err-ink);font-weight:500;}

/* ============================================================
   表格视图（>50 台时的兜底。卡片扫不动，表格能排序）
   ============================================================ */
.tablewrap{background:var(--panel);border:1px solid var(--line);border-radius:var(--r-l);
  overflow:auto;}
table.tb{width:100%;border-collapse:collapse;font-size:var(--fs-s);}
table.tb th{position:sticky;top:0;z-index:1;white-space:nowrap;background:var(--panel-2);
  font-size:var(--fs-xxs);font-weight:500;color:var(--mute);text-transform:uppercase;
  letter-spacing:.05em;text-align:right;padding:var(--s2) var(--s3);
  border-bottom:1px solid var(--line);white-space:nowrap;}
table.tb th:first-child,table.tb td:first-child{text-align:left;}
table.tb th.sort{cursor:pointer;user-select:none;}
table.tb th.sort:hover{color:var(--ink);}
table.tb th .ar{opacity:.35;margin-left:3px;}
table.tb th[aria-sort] .ar{opacity:1;color:var(--brand);}
table.tb td{padding:var(--s2) var(--s3);border-bottom:1px solid var(--line-soft);
  text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap;color:var(--ink-2);}
table.tb tbody tr{cursor:pointer;transition:background-color var(--dur) var(--ease);}
table.tb tbody tr:hover{background:var(--panel-2);}
table.tb tbody tr:last-child td{border-bottom:0;}

/* ============================================================
   抽屉（点卡片打开）
   ============================================================ */
.scrim{position:fixed;inset:0;background:color-mix(in srgb,var(--ink) 32%,transparent);
  z-index:80;animation:fade var(--dur) var(--ease);}
@keyframes fade{from{opacity:0;}to{opacity:1;}}
.drawer{position:fixed;top:0;right:0;bottom:0;width:min(520px,96vw);z-index:81;
  background:var(--panel);border-left:1px solid var(--line);box-shadow:var(--sh-drawer);
  display:flex;flex-direction:column;animation:slide var(--dur-slow) var(--ease);}
@keyframes slide{from{transform:translateX(24px);opacity:.6;}to{transform:none;opacity:1;}}
.drawer-hd{padding:var(--s4) var(--s5);border-bottom:1px solid var(--line);
  display:flex;align-items:flex-start;gap:var(--s3);}
.drawer-hd h2{font-size:var(--fs-l);font-weight:600;letter-spacing:-.01em;}
.drawer-hd .sub{font-size:var(--fs-xs);color:var(--mute);margin-top:2px;}
.drawer-bd{flex:1;overflow:auto;padding:var(--s5);}
.drawer-ft{padding:var(--s3) var(--s5);border-top:1px solid var(--line);
  display:flex;gap:var(--s2);justify-content:flex-end;}

.sec{margin-bottom:var(--s5);}
.sec h3{font-size:var(--fs-xxs);color:var(--mute);text-transform:uppercase;
  letter-spacing:.06em;font-weight:500;margin-bottom:var(--s2);}
.kv{display:grid;grid-template-columns:1fr auto;gap:var(--s2) var(--s4);font-size:var(--fs-s);}
.kv dt{color:var(--mute);}
.kv dd{margin:0;text-align:right;font-variant-numeric:tabular-nums;color:var(--ink);}

/* 权限说明块 —— Grafana 没有的"产品识别点" */
.access{border:1px solid var(--line);border-radius:var(--r-m);padding:var(--s3);
  background:var(--panel-2);color:var(--ink);}   /* ★ 显式指定文字色：
     暗色主题下 .access 是深底(--panel-2 #20272E)，若继承抽屉外的 --ink-2
     会变成浅色字压浅底 → 实测仅 2.9:1，几乎不可读 */
.access .row{display:flex;align-items:center;gap:var(--s2);font-size:var(--fs-s);}
.access .row + .row{margin-top:var(--s2);}
.access .tag{font-size:var(--fs-xxs);padding:1px 7px;border-radius:var(--r-pill);
  /* ★ 用 color-mix 把品牌色调浅/压深后再上字：
     直接用 var(--brand) 时，暗色主题下 #2F6B5A 压在 --brand-soft #1E2F30
     上只有 2.24:1（实测），几乎看不见。color-mix 让两套主题自动适配。 */
  background:var(--brand-soft);
  color:color-mix(in srgb, var(--brand) 55%, var(--ink));font-weight:500;}
.access .no{color:var(--mute);}
.access .hint{font-size:var(--fs-xxs);color:var(--mute);margin-top:var(--s2);line-height:1.5;}
/* 说明性小字（"服务端会校验"这类责任声明）不能用 --faint：
   暗色下 --faint #5D6873 压 --panel-2 只有 2.65:1，正文级文字必须 ≥4.5:1。
   --mute 暗色 4.6:1 / 浅色 5.4:1，两套都过 AA。 */

/* ============================================================
   表单 / 按钮
   ============================================================ */
.btn{height:32px;padding:0 var(--s4);border-radius:var(--r-m);font-size:var(--fs-s);
  font-weight:500;display:inline-flex;align-items:center;gap:6px;border:1px solid var(--line);
  background:var(--panel);color:var(--ink-2);
  transition:background-color var(--dur) var(--ease),border-color var(--dur) var(--ease);}
.btn:hover{background:var(--sunken);}
.btn.primary{background:var(--brand);border-color:var(--brand);color:var(--brand-ink);}
.btn.primary:hover{filter:brightness(1.08);}
.btn.ghost{border-color:transparent;background:transparent;}
.btn.ghost:hover{background:var(--sunken);}
.btn:disabled{opacity:.45;cursor:not-allowed;}

/* ============================================================
   权限矩阵
   ============================================================ */
table.mx{width:100%;border-collapse:collapse;font-size:var(--fs-s);}
table.mx th{font-size:var(--fs-xxs);color:var(--mute);text-transform:uppercase;
  letter-spacing:.05em;text-align:center;padding:var(--s2);border-bottom:1px solid var(--line);font-weight:500;}
table.mx th:first-child{text-align:left;}
table.mx td{padding:var(--s2);text-align:center;border-bottom:1px solid var(--line-soft);}
table.mx td:first-child{text-align:left;color:var(--ink);}
table.mx .y{color:var(--ok);}
table.mx .n{color:var(--faint);}

/* ============================================================
   空态 / 提示条
   空态必须给"下一步做什么"，不能只说"没有数据"
   ============================================================ */
.empty{text-align:center;padding:64px var(--s5);}
.empty .ic{width:44px;height:44px;margin:0 auto var(--s3);border-radius:var(--r-m);
  background:var(--sunken);display:grid;place-items:center;}
.empty .ic svg{width:22px;height:22px;stroke:var(--mute);fill:none;stroke-width:1.5;}
.empty h3{font-size:var(--fs-l);font-weight:600;margin-bottom:var(--s2);}
.empty p{font-size:var(--fs-s);color:var(--mute);max-width:420px;margin:0 auto;}

.banner{display:flex;gap:var(--s3);align-items:flex-start;padding:var(--s3) var(--s4);
  border-radius:var(--r-m);font-size:var(--fs-s);margin-bottom:var(--s4);
  background:var(--warn-bg);color:var(--warn-ink);}
.banner.info{background:var(--brand-soft);color:var(--ink);}  /* ★ 用 --ink 而非 --ink-2：
     暗色下 --brand-soft #182A26 上压 --ink-2 #B9C2CA 只有 6.0:1 看着还行，
     但浅色下 --ink-2 在 #E7F0EC 上会掉到 4.4:1。统一 --ink 两套都稳过 AA。 */
.banner svg{width:16px;height:16px;flex:none;margin-top:2px;stroke:currentColor;fill:none;stroke-width:1.6;}

/* ============================================================
   骨架屏（避免"白屏 → 突然出现"，那是加载感最差的做法）
   ============================================================ */
.sk{border-radius:var(--r-s);background:var(--skeleton);background-size:200% 100%;
  animation:shimmer 1.4s linear infinite;}
@keyframes shimmer{from{background-position:200% 0;}to{background-position:-200% 0;}}
@media(prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.01ms !important;animation-iteration-count:1 !important;
    transition-duration:.01ms !important;}
}

/* ============================================================
   响应式 —— 断点只改 --cols 和几处 padding，不动组件
   ============================================================ */
/* ★ KPI 只在**真的放不下**时才拆行：5 格在 1100px 内容宽下每格还有 200px，够用。
   原先 1280 就拆成 3 列，导致最常见的 1280×800 笔记本上变成 3+2，页面像没做完。
   卡片墙（--cols）另算：它比 KPI 更吃宽度，所以仍然在 1280 掉到 3 列。 */
@media(max-width:1280px){:root{--cols:3;}}
@media(max-width:1120px){.kpis{grid-template-columns:repeat(3,minmax(0,1fr));}}
@media(max-width:1024px){:root{--cols:2;} .kpis{grid-template-columns:repeat(3,minmax(0,1fr));}}
@media(max-width:720px){
  :root{--cols:1;--gap:12px;}
  .kpis{grid-template-columns:repeat(2,minmax(0,1fr));}
  .main{padding:var(--s4) var(--s4) 64px;}
  .topbar-in{padding:0 var(--s4);gap:var(--s2);}
  .me .who{display:none;}
  .logo .tenant{display:none;}
  .drawer{width:100vw;}
}
@media print{
  .topbar,.toolbar,.drawer-ft,.iconbtn{display:none !important;}
  body{background:#fff;}
  .card{break-inside:avoid;box-shadow:none;}
}

/* ============================================================================
   Alpin v3 · 三级导航补充样式
   ----------------------------------------------------------------------------
   新增的四类东西：
     ① .crumb / .sitehead   面包屑 + 子站标题条
     ② .sitetabs            子站内页签（与 .seg 区分：这是页面级，那是视图级）
     ③ .devcard / .devlist  设备卡（组件 + 通道清单）
     ④ .timeline / .alarmrow 日志时间线 / 报警行
   一律复用上面的变量，不新增颜色。
   ============================================================================ */

/* ---------- ① 面包屑 ---------- */
.crumb{display:flex;align-items:center;gap:var(--s2);font-size:var(--fs-s);
  color:var(--mute);margin-bottom:var(--s3);flex-wrap:wrap;}
.crumb a,.crumb button{color:var(--mute);background:none;border:0;padding:2px 0;
  font-size:inherit;font-family:inherit;cursor:pointer;
  transition:color var(--dur) var(--ease);}
.crumb a:hover,.crumb button:hover{color:var(--ink);text-decoration:underline;
  text-underline-offset:3px;}
.crumb .sep{color:var(--faint);user-select:none;}
.crumb .here{color:var(--ink-2);font-weight:500;}

/* ---------- ② 子站标题条 ---------- */
.sitehead{display:flex;align-items:flex-start;gap:var(--s5);flex-wrap:wrap;
  padding-bottom:var(--s4);border-bottom:1px solid var(--line);margin-bottom:var(--s1);}
.sitehead .id{flex:1 1 320px;min-width:260px;}
.sitehead h1{font-size:var(--fs-xl);font-weight:600;letter-spacing:-.02em;
  display:flex;align-items:center;gap:var(--s3);flex-wrap:wrap;}
.sitehead .sub{font-size:var(--fs-s);color:var(--mute);margin-top:3px;}
.sitehead .meta{display:flex;gap:var(--s5);flex-wrap:wrap;align-items:flex-end;}
.sitehead .meta .m .k{font-size:var(--fs-xxs);color:var(--faint);text-transform:none;}
.sitehead .meta .m .v{font-size:var(--fs-m);color:var(--ink);font-variant-numeric:tabular-nums;
  margin-top:1px;white-space:nowrap;}
.sitehead .acts{display:flex;gap:var(--s2);align-items:center;}

/* ---------- ② 子站页签 ---------- */
.sitetabs{display:flex;gap:2px;border-bottom:1px solid var(--line);margin-bottom:var(--s5);
  overflow-x:auto;scrollbar-width:none;}
.sitetabs::-webkit-scrollbar{display:none;}
.sitetabs button{position:relative;padding:11px var(--s4) 12px;font-size:var(--fs-m);
  color:var(--mute);background:none;border:0;white-space:nowrap;cursor:pointer;
  font-family:inherit;font-weight:500;
  transition:color var(--dur) var(--ease);}
.sitetabs button:hover{color:var(--ink-2);}
.sitetabs button[aria-selected="true"]{color:var(--ink);}
.sitetabs button[aria-selected="true"]::after{content:"";position:absolute;left:0;right:0;
  bottom:-1px;height:2px;background:var(--brand);border-radius:2px 2px 0 0;}
.sitetabs .cnt{display:inline-block;min-width:17px;height:17px;line-height:17px;
  padding:0 5px;margin-left:6px;border-radius:var(--r-pill);font-size:var(--fs-xxs);
  text-align:center;font-variant-numeric:tabular-nums;
  background:var(--sunken);color:var(--mute);vertical-align:1px;}
.sitetabs .cnt.hot{background:var(--err-bg);color:var(--err-ink);}

/* ---------- ③ 设备卡 ---------- */
.devlist{display:grid;gap:var(--gap);
  grid-template-columns:repeat(auto-fill,minmax(300px,1fr));}
/* ★ 让四张卡「横着对齐」：卡片自身变成一个纵向网格，每一行（头/大字/通道/
   事实）都能通过 subgrid 对上邻居的行。否则每张卡的通道条数不同（2 条 vs 6 条），
   "Wirkleistung" 和 "Ladezustand" 会各占一个高度，整排高低不齐。
   Chrome 117+/Edge 117+ 支持 subgrid；不支持时退化为原来的 flex 堆叠，不会崩。 */
@supports (grid-template-rows: subgrid){
  .devlist{grid-template-rows:auto auto 1fr auto;}
  .devcard{display:grid;grid-row:span 4;grid-template-rows:subgrid;gap:var(--s3);}
}
.devcard{background:var(--panel);color:var(--ink);border:1px solid var(--line);
  border-radius:var(--r-l);padding:var(--s4);
  gap:var(--s3);position:relative;overflow:hidden;}
.devcard::before{content:"";position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--st,var(--line));}
.devcard.ok{--st:var(--ok);}
.devcard.err{--st:var(--err);}
.devcard.warn{--st:var(--warn);}
.devhd{display:flex;align-items:flex-start;gap:var(--s3);}
.devic{width:34px;height:34px;flex:none;border-radius:var(--r-m);display:grid;place-items:center;
  background:var(--sunken);color:var(--ink-2);}
.devic svg{width:18px;height:18px;stroke:currentColor;fill:none;stroke-width:1.7;
  stroke-linecap:round;stroke-linejoin:round;}
.devhd .nm{font-size:var(--fs-m);font-weight:600;letter-spacing:-.01em;}
.devhd .ty{font-size:var(--fs-xxs);color:var(--faint);margin-top:1px;}
.devhero{display:flex;align-items:baseline;gap:4px;color:var(--ink);
  font-variant-numeric:tabular-nums;}
.devhero .big{font-size:var(--fs-xxl);font-weight:600;letter-spacing:-.03em;line-height:1;}
.devhero .u{font-size:var(--fs-s);color:var(--mute);}
.devhero .lb{font-size:var(--fs-xs);color:var(--mute);margin-left:auto;align-self:flex-end;
  padding-bottom:3px;}
.chlist{display:flex;flex-direction:column;gap:1px;
  border-top:1px solid var(--line-soft);padding-top:var(--s2);}
.chrow{display:flex;justify-content:space-between;align-items:baseline;gap:var(--s3);
  padding:5px 0;font-size:var(--fs-s);}
.chrow .k{color:var(--mute);min-width:0;overflow:hidden;text-overflow:ellipsis;
  white-space:nowrap;}
.chrow .v{color:var(--ink);font-variant-numeric:tabular-nums;white-space:nowrap;
  font-weight:500;}
.chrow .v .u{color:var(--mute);font-weight:400;font-size:var(--fs-xs);margin-left:3px;}
/* 数值用等宽数字（tabular-nums）右对齐，单位固定宽度 → 各卡片同一列的
   "2,60 kW / −2,0 kW / 625 V" 小数点能对上，扫读时不用重新定位。 */
.devfacts{display:flex;gap:var(--s4);flex-wrap:wrap;font-size:var(--fs-xs);
  color:var(--mute);border-top:1px solid var(--line-soft);padding-top:var(--s2);}
/* ★ 这里的 k 是"容量 16 kWh"这种**带数据的标签**，不是装饰性占位文字，
   所以必须过 4.5:1。实测 --faint 在 panel 上只有 2.59（浅色）/ 2.89（深色），
   达不到正文标准 → 降一级用 --mute（5.54 / 5.59）。--faint 只留给 placeholder 和禁用态。 */
.devfacts b{color:var(--ink-2);font-weight:500;}

/* ---------- ④ 日志时间线 ---------- */
.timeline{background:var(--panel);border:1px solid var(--line);border-radius:var(--r-l);
  overflow:hidden;}
.tlrow{display:grid;grid-template-columns:74px 22px 1fr auto;gap:var(--s3);
  align-items:start;padding:10px var(--s4);border-bottom:1px solid var(--line-soft);
  font-size:var(--fs-s);}
.tlrow:last-child{border-bottom:0;}
.tlrow:hover{background:var(--panel-2);}
.tlrow .t{color:var(--mute);font-variant-numeric:tabular-nums;font-size:var(--fs-xs);
  padding-top:1px;white-space:nowrap;}
.tlrow .dot{width:8px;height:8px;border-radius:50%;margin:5px 0 0 6px;background:var(--mute);
  box-shadow:0 0 0 3px var(--panel);position:relative;z-index:1;}
.tlrow.l-info .dot{background:var(--d-grid);}
.tlrow.l-warn .dot{background:var(--warn);}
.tlrow.l-err .dot{background:var(--err);}
.tlrow .msg{color:var(--ink-2);line-height:1.5;min-width:0;}
.tlrow .kind{font-size:var(--fs-xxs);padding:1px 7px;border-radius:var(--r-pill);
  background:var(--sunken);color:var(--mute);white-space:nowrap;align-self:start;}
.tlrow.l-warn .kind{background:var(--warn-bg);color:var(--warn-ink);}
.tlrow.l-err .kind{background:var(--err-bg);color:var(--err-ink);}
/* 左侧竖线：把散落的圆点串成一条时间轴。
   ★ 不要用「绝对定位画一条长线 + 手算 left」—— 列宽一变就错位，首尾还露半截。
   ★ 也不要用伪元素 z-index:-1 垫到圆点下面：圆点是圆角元素却**没有背景裁剪**，
     伪元素在它上方会被裁成一个"缺口"，看着像线段被挖掉一块。
     正确做法是让每一行自己画线（.tlrow::before），圆点用行内背景色压住线。
     每行的线纵向贯通整行（含 padding 和分隔线），相邻行首尾自然接上，
     首行上半截、末行下半截再用 clip 掉。 */
.tlrow{position:relative;}
/* ★ 线必须用「绝对定位 + inset」实现：
     一旦它进入网格流，就会成为 .tlrow 的第一根网格轨（grid 会把 ::before 当子元素），
     把圆点整个挤到第三列去 —— 实测 dotLeftRel=108px 就是这么来的。
   ★ left:112px 是实测值（rowLeft→圆点中心的距离 = 内边距16 + 时间列74 + 间距12 + 圆点居中4 + 圆点偏移6），
     不是拍脑袋的 calc：calc 里各项一改就会悄悄错一两个像素，肉眼看着"线斜了"。 */
.tlrow::before{content:"";position:absolute;z-index:0;pointer-events:none;
  left:112px;width:1px;background:var(--line);
  top:0;bottom:0;}
.tlrow:first-child::before{top:19px;}   /* 首行从圆点中心起笔：padding10 + margin5 + 半径4 */
.tlrow:last-child::before{bottom:auto;height:19px;}
/* 圆点盖在线上面；box-shadow 那圈就是压线用的"白边" */
.tlrow .dot{z-index:1;position:relative;}/* 圆点必须盖在线上面，且要有不透明背景（阴影圈就是"压线"的白边） */
.tlrow .dot{z-index:1;position:relative;}

/* ---------- ④ 报警行 ---------- */
.alarms{display:flex;flex-direction:column;gap:var(--s2);}
.alarmrow{display:flex;gap:var(--s3);align-items:flex-start;padding:var(--s3) var(--s4);
  background:var(--panel);border:1px solid var(--line);border-left:3px solid var(--lv,var(--mute));
  border-radius:var(--r-m);}
.alarmrow.critical{--lv:var(--err);}
.alarmrow.warning{--lv:var(--warn);}
.alarmrow.info{--lv:var(--d-grid);}
.alarmrow .bd{flex:1;min-width:0;}
.alarmrow .ttl{font-size:var(--fs-m);color:var(--ink);font-weight:500;}
.alarmrow .det{font-size:var(--fs-s);color:var(--mute);margin-top:2px;line-height:1.5;}
.alarmrow .side{display:flex;flex-direction:column;align-items:flex-end;gap:var(--s2);flex:none;}
.lvtag{font-size:var(--fs-xxs);padding:2px 8px;border-radius:var(--r-pill);font-weight:500;
  white-space:nowrap;}
.lvtag.critical{background:var(--err-bg);color:var(--err-ink);}
.lvtag.warning{background:var(--warn-bg);color:var(--warn-ink);}
.lvtag.info{background:var(--brand-soft);color:var(--ink);}

/* ---------- 总览：紧凑卡（比 v2 少一半信息，只为"选站"服务） ---------- */
.card.mini{padding:var(--s4);gap:var(--s2);}
.card.mini .card-metrics{display:none;}
.card.mini .card-power .big{font-size:var(--fs-xl);}
.card.mini .ring{width:44px;height:44px;}
.card.mini .ring svg{width:44px;height:44px;}
.card.mini .ring .val{font-size:var(--fs-s);}
.card.mini .card-foot{font-size:var(--fs-xxs);}
.card-cta{display:none;}
.card:hover .card-cta{display:inline-flex;align-items:center;gap:3px;
  font-size:var(--fs-xxs);color:var(--brand);font-weight:500;}

/* ---------- 趋势图容器 ---------- */
.chartbox{background:var(--panel);border:1px solid var(--line);border-radius:var(--r-l);
  padding:var(--s5);}
.chartbox .hd{display:flex;align-items:baseline;gap:var(--s3);margin-bottom:var(--s4);
  flex-wrap:wrap;}
.chartbox .hd h3{font-size:var(--fs-m);font-weight:600;}
.chartbox .legend{display:flex;gap:var(--s4);flex-wrap:wrap;margin-left:auto;}
.chartbox .legend span{display:inline-flex;align-items:center;gap:5px;font-size:var(--fs-xs);
  color:var(--mute);}
.chartbox .legend i{width:9px;height:9px;border-radius:2px;flex:none;}
.chartbox .legend i.line{height:2px;border-radius:1px;width:14px;}
.chartwrap{position:relative;}
.chartwrap svg{display:block;width:100%;height:auto;}
.axis{fill:var(--faint);font-size:10.5px;font-family:var(--font);}
.gridln{stroke:var(--line-soft);stroke-width:1;}

/* ---------- 响应式 ---------- */
@media (max-width:1024px){
  .sitehead .meta{gap:var(--s4);}
  .devlist{grid-template-columns:repeat(auto-fill,minmax(260px,1fr));}
}
@media (max-width:720px){
  .sitehead{flex-direction:column;gap:var(--s3);}
  .sitetabs button{padding:10px var(--s3) 11px;}
  .tlrow{grid-template-columns:58px 18px 1fr;row-gap:var(--s1);}
  .tlrow .kind{grid-column:3;justify-self:start;}
  .tlline::before{left:calc(var(--s4) + 58px + var(--s3) + 7px);}
}

/* ============================================================
   总览页 · 地图 + 站点清单 + 动态电价
   ------------------------------------------------------------
   这一块把"选站"从平面列表升级成两个互补的视角：
     ① 地图 —— 空间维度，"哪一片出问题了"
     ② 清单 —— 状态维度，按严重程度排序，"先处理哪台"
   两边的悬停互相高亮（.sel），因为它们指向同一批对象；
   否则用户在地图上点了点、还要再去清单里找一遍，等于做两次工作。
   ============================================================ */
.fleet-grid{display:grid;grid-template-columns:minmax(0,1fr) 300px;gap:var(--gap);
  align-items:start;margin-bottom:var(--gap);}

.panel{background:var(--panel);border:1px solid var(--line);border-radius:var(--r-l);
  box-shadow:var(--sh-1);}
.panel-head{display:flex;align-items:center;gap:var(--s2);flex-wrap:wrap;
  padding:var(--s4) var(--s4) var(--s3);}
.panel-head h2{font-size:var(--fs-r);font-weight:600;letter-spacing:-.01em;color:var(--ink);}
.panel-head .hint{font-size:var(--fs-xxs);color:var(--mute);}
.panel-head .hint:last-child{margin-left:auto;}

/* ---------- 地图 ---------- */
.map-panel{padding-bottom:var(--s3);}
.mapbox{position:relative;margin:0 var(--s4);border-radius:var(--r-m);overflow:hidden;
  /* 底图是浅灰绿的矢量图，深色主题下要压暗，否则刺眼且抢走点位 */
  background:var(--panel-2);}
.mapbox img{display:block;width:100%;height:auto;user-select:none;}
[data-theme="dunkel"] .mapbox img{filter:invert(0.88) hue-rotate(180deg) saturate(0.6) brightness(1.05);}

.mk{position:absolute;transform:translate(-50%,-50%);padding:0;border:0;
  width:26px;height:26px;display:grid;place-items:center;background:none;cursor:pointer;
  /* 点位本身要小，但可点区域要大 —— 手指点不中 6px 的圆 */
  border-radius:50%;transition:transform var(--dur) var(--ease);}
.mk:hover,.mk:focus-visible{transform:translate(-50%,-50%) scale(1.25);}
.mk:focus-visible{outline:none;box-shadow:var(--focus);}
.mk .dot{width:11px;height:11px;border-radius:50%;background:var(--idle);
  border:2px solid var(--panel);box-shadow:0 1px 4px rgba(0,0,0,.35);
  transition:background-color var(--dur) var(--ease);}
.mk.s-ok .dot{background:var(--ok);}
/* ★ warn（无数据/告警）在浅底上单独用一个更亮的琥珀，
   直接沿用 --warn(#B0740E) 在小尺寸下会被看成"一个发脏的褐点"，
   和地图底色混在一起，现场根本找不到问题站。
   --warn 是给**文字和底色**定的（要过 4.5:1），点位只要过 3:1 就够，可以更亮。 */
.mk.s-warn .dot{background:#D98A00;box-shadow:0 0 0 2px rgba(217,138,0,.22),0 1px 4px rgba(0,0,0,.35);}
.mk.s-err .dot{background:var(--err);}
/* 离线站给一圈脉冲，因为它是唯一"现在就在损失产能"的状态 */
.mk.s-err .dot{box-shadow:0 0 0 3px color-mix(in srgb,var(--err) 22%,transparent),0 1px 4px rgba(0,0,0,.35);}
/* 选中的点位加一圈光环，和清单里的 .sel 同源 */
.mk.sel .dot{box-shadow:0 0 0 3px color-mix(in srgb, var(--brand) 40%, transparent),0 1px 4px rgba(0,0,0,.35);}

/* 常驻城市名：小、淡、不抢点位。放在点位右侧偏上，避免压住点本身。
   ★ 用 --ink-2 而不是 --faint：地图底是浅灰绿，--faint(2.59:1) 在上面
     基本看不清，而这是**正文级信息**（是哪个城市），必须过 4.5:1。 */
.mk .lb{position:absolute;left:calc(100% - 4px);top:-1px;
  font-size:10px;line-height:1;color:var(--ink-2);white-space:nowrap;
  pointer-events:none;letter-spacing:.01em;
  text-shadow:0 0 2px var(--panel),0 0 3px var(--panel),0 0 4px var(--panel);}
[data-theme="dunkel"] .mk .lb{color:var(--ink);}
.mk .tip{position:absolute;left:50%;top:calc(100% + 2px);transform:translateX(-50%);
  background:var(--ink);color:var(--panel);border-radius:var(--r-s);padding:5px 8px;
  font-size:var(--fs-xxs);line-height:1.45;white-space:nowrap;pointer-events:none;
  opacity:0;transition:opacity var(--dur) var(--ease);z-index:9;
  display:flex;flex-direction:column;gap:1px;box-shadow:var(--sh-2);}
.mk .tip b{font-weight:600;}
.mk .tip span{opacity:.75;}
.mk:hover .tip,.mk:focus-visible .tip,.mk.sel .tip{opacity:1;}

.mapfoot{padding:var(--s2) var(--s4) 0;font-size:var(--fs-xxs);color:var(--faint);}

/* ---------- 站点清单 ---------- */
.list-panel{padding-bottom:var(--s2);}
/* ★ 清单必须能滚：12 台就比地图高，24 台时会把整页拉长一倍，
   地图旁边留一大片空白（实测截图里就是这样）。
   给一个和地图等高的上限，超出部分在面板内滚 —— 页面节奏就不会被清单撑坏。 */
.list-panel{display:flex;flex-direction:column;max-height:560px;}
.alist{overflow-y:auto;overscroll-behavior:contain;flex:1;}
.alist{display:flex;flex-direction:column;padding:0 var(--s2);}
.arow{display:grid;grid-template-columns:8px minmax(0,1fr) auto auto;align-items:center;
  gap:var(--s2);padding:9px var(--s2);border-radius:var(--r-s);text-align:left;
  background:none;border:0;cursor:pointer;color:var(--ink);width:100%;
  transition:background-color var(--dur) var(--ease);}
.arow:hover,.arow.sel{background:var(--sunken);}
.arow:focus-visible{outline:none;box-shadow:var(--focus);}
.arow .d{width:7px;height:7px;border-radius:50%;background:var(--idle);}
.arow.s-ok .d{background:var(--ok);}
.arow.s-warn .d{background:var(--warn);}
.arow.s-err .d{background:var(--err);}
.arow .nm{min-width:0;display:flex;flex-direction:column;}
.arow .nm b{font-size:var(--fs-s);font-weight:500;white-space:nowrap;overflow:hidden;
  text-overflow:ellipsis;}
.arow .nm small{font-size:var(--fs-xxs);color:var(--mute);}
.arow .soc{font-size:var(--fs-s);font-weight:600;color:var(--ink-2);min-width:38px;text-align:right;}
.arow .soc i{font-style:normal;font-size:var(--fs-xxs);font-weight:400;color:var(--mute);margin-left:1px;}
.arow .pw{font-size:var(--fs-xxs);color:var(--mute);min-width:56px;text-align:right;}

/* ---------- 动态电价 ---------- */
.price-panel{padding-bottom:var(--s4);margin-bottom:var(--gap);}
.seg-mini{display:flex;gap:2px;margin-left:auto;background:var(--sunken);
  border-radius:var(--r-m);padding:2px;}
.seg-mini button{height:26px;padding:0 10px;border-radius:var(--r-s);font-size:var(--fs-xs);
  color:var(--mute);background:none;border:0;cursor:pointer;
  transition:background-color var(--dur) var(--ease),color var(--dur) var(--ease);}
.seg-mini button:hover{color:var(--ink-2);}
.seg-mini button[aria-pressed="true"]{background:var(--panel);color:var(--ink);font-weight:500;
  box-shadow:var(--sh-1);}
.seg-mini button:focus-visible{outline:none;box-shadow:var(--focus);}

.pstats{display:grid;grid-template-columns:repeat(4,minmax(0,1fr)) 1.6fr;gap:var(--s2);
  padding:0 var(--s4) var(--s3);}
.ps{background:var(--panel-2);border-radius:var(--r-m);padding:var(--s2) var(--s3);
  display:flex;flex-direction:column;gap:1px;min-width:0;}
.ps .l{font-size:var(--fs-xxs);color:var(--mute);white-space:nowrap;
  overflow:hidden;text-overflow:ellipsis;}
.ps .v{font-size:var(--fs-l);font-weight:600;line-height:1.15;letter-spacing:-.01em;}
.ps .u{font-size:var(--fs-xxs);color:var(--mute);}
.ps.wide{flex-direction:column;justify-content:center;}
.ps.wide .l{margin-bottom:4px;}
.ps .chips{display:flex;gap:5px;flex-wrap:wrap;}
.chip-w{font-size:var(--fs-xs);padding:2px 8px;border-radius:var(--r-s);
  background:var(--ok-bg);color:var(--ok-ink);font-weight:500;font-variant-numeric:tabular-nums;
  border:1px solid color-mix(in srgb, var(--ok) 22%, transparent);}

.psvg{display:block;width:100%;height:250px;padding:0 var(--s4);box-sizing:border-box;}
/* preserveAspectRatio="none" 会拉伸文字，所以字号用 11 而 viewBox 宽 1000：
   在 900px 宽的容器里约等于 10px，正好和 .axis 一致。 */
.psvg .pgrid line{stroke:var(--line-soft);stroke-width:1;}
.psvg .pylab text{fill:var(--faint);font-size:11px;font-family:var(--font);}
.psvg .pdiv line{stroke:var(--line);stroke-width:1;}
.psvg .pdiv text{fill:var(--mute);font-size:11px;font-family:var(--font);}
.psvg .pnow line{stroke:var(--ink);stroke-width:1.5;}
.psvg .pnow text{fill:var(--ink);font-size:11px;font-weight:600;font-family:var(--font);}
.psvg rect{transition:opacity var(--dur) var(--ease);}
.psvg rect:hover{opacity:.75;}

@media (max-width:1100px){
  .fleet-grid{grid-template-columns:1fr;}
  .pstats{grid-template-columns:repeat(2,minmax(0,1fr));}
  .ps.wide{grid-column:span 2;}
}

/* ---------- 电价：加载 / 错误 / 来源说明 ---------- */
.pnote{display:flex;align-items:flex-start;gap:var(--s2);margin:0 var(--s4) var(--s3);
  padding:var(--s3);border-radius:var(--r-m);background:var(--panel-2);
  font-size:var(--fs-s);color:var(--ink-2);line-height:1.5;}
.pnote svg{width:16px;height:16px;flex:none;margin-top:1px;stroke:currentColor;fill:none;
  stroke-width:1.7;}
/* 错误态必须是**红色块**，不能只用淡灰文字：
   这是"没有数据"而不是"数据很小"，两者看起来像的话，客户会把空图当 0 电价。 */
.pnote.err{background:var(--err-bg);color:var(--err-ink);
  border:1px solid color-mix(in srgb, var(--err) 26%, transparent);}
.pnote.err b{font-weight:600;}
.spin{width:14px;height:14px;flex:none;border-radius:50%;margin-top:2px;
  border:2px solid var(--line);border-top-color:var(--brand);
  animation:spin .8s linear infinite;}
@keyframes spin{to{transform:rotate(360deg)}}

.pfoot{padding:0 var(--s4) var(--s3);font-size:var(--fs-xxs);color:var(--mute);
  line-height:1.6;}
.pfoot .stale{display:inline-block;margin-right:6px;padding:1px 6px;border-radius:var(--r-s);
  background:var(--warn-bg);color:var(--warn-ink);font-weight:500;}

/* ============================================================
   设备卡片墙（一个站点几十台设备，每台一张卡）
   ------------------------------------------------------------
   ★ 颜色只用语义色（--ok/--warn/--err）与中性色 —— 品牌色绝不可
     改写状态色（品牌护栏会把冲突的品牌色退回默认色）。
   ★ 密度 2/3/4 由内联 grid-template-columns 控制，这里只定卡片内部。
   ============================================================ */
.subhd{font-size:var(--fs-m);font-weight:500;color:var(--ink);margin:var(--s6) 0 var(--s3);}

.fleetsum{display:flex;flex-wrap:wrap;gap:var(--s5);align-items:baseline;
  padding:var(--s3) var(--s4);margin-bottom:var(--gap);
  background:var(--panel);border:1px solid var(--line);border-radius:var(--r-m);
  font-size:var(--fs-s);color:var(--mute);}
.fleetsum b{color:var(--ink);font-weight:500;font-variant-numeric:tabular-nums;}

.fleetwall{display:flex;flex-direction:column;gap:var(--s6);margin-bottom:var(--s6);}
.fgroup{display:flex;flex-direction:column;gap:var(--s3);}
.fghd{display:flex;align-items:center;gap:var(--s3);flex-wrap:wrap;
  padding-bottom:var(--s2);border-bottom:1px solid var(--line-soft);}
.fgn{font-size:var(--fs-m);font-weight:500;color:var(--ink);}
.fgc{font-size:var(--fs-s);color:var(--mute);font-variant-numeric:tabular-nums;}
.fgbad{margin-left:auto;font-size:var(--fs-xs);padding:2px var(--s2);border-radius:var(--r-pill);
  background:var(--warn-bg);color:var(--warn-ink);font-variant-numeric:tabular-nums;}
.fgbad.off{background:var(--sunken,var(--panel-2));color:var(--mute);margin-left:var(--s2);}
.fgbad.hot{background:var(--err-bg);color:var(--err-ink);}

.dgrid{display:grid;gap:var(--s3);}
.dcard{background:var(--panel);color:var(--ink);border:1px solid var(--line);
  border-radius:var(--r-m);padding:var(--s3);display:flex;flex-direction:column;gap:var(--s2);
  position:relative;overflow:hidden;min-width:0;}
.dcard::before{content:"";position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--st,var(--line));}
.dcard.d-ok{--st:var(--ok);}
.dcard.d-alarm{--st:var(--err);}
.dcard.d-offline{--st:var(--mute);opacity:.72;}
.dhd{display:flex;align-items:center;gap:var(--s2);}
.did{font-size:var(--fs-xs);color:var(--mute);font-variant-numeric:tabular-nums;
  letter-spacing:.02em;}
.dst{margin-left:auto;font-size:var(--fs-xxs);padding:1px var(--s2);border-radius:var(--r-pill);
  background:var(--ok-bg);color:var(--ok-ink);}
.dst.s-alarm{background:var(--err-bg);color:var(--err-ink);}
.dst.s-offline{background:var(--panel-2);color:var(--mute);}
.dnm{font-size:var(--fs-s);color:var(--ink-2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.dhero{display:flex;align-items:baseline;gap:var(--s1);flex-wrap:wrap;}
.dhero .big{font-size:var(--fs-l);font-weight:500;color:var(--ink);font-variant-numeric:tabular-nums;}
.dhero .u{font-size:var(--fs-xs);color:var(--mute);}
.dhero .lb{font-size:var(--fs-xs);color:var(--mute);margin-left:auto;}
.dmrow{display:flex;flex-wrap:wrap;gap:var(--s3);font-size:var(--fs-xs);color:var(--mute);}
.dmrow b{color:var(--ink-2);font-weight:500;font-variant-numeric:tabular-nums;}
.dfacts{display:flex;flex-wrap:wrap;gap:var(--s2);font-size:var(--fs-xxs);color:var(--mute);
  padding-top:var(--s2);border-top:1px solid var(--line-soft);font-variant-numeric:tabular-nums;}
