/* web_statistics · Components — 卡片(KPI/panel/trend) + 图表 + 表格 + bar + dialog + sites +
   toast + 工具类(visually-hidden / empty-state / shimmer 等)
*/

/* 视图模式切换的 [hidden] 修复（2026-10）：
   applyViewVisibility() 走 el.hidden = true/false 控制 data-view 元素显隐。
   但 UA 默认 [hidden] { display: none } 与作者 CSS 的 .overview-hero{display:flex}
   等同特异性（都是 0,1,0）—— 按级联顺序作者 CSS 胜出，hidden 失效。
   这里用更高特异性 [data-view][hidden] 强制 display:none !important-free 版本：
   把 [hidden] 提到作者 CSS 最高优先级（specificity 0,2,0），覆盖任何 display:flex/grid。
   末尾置顶（components 末尾）确保作者规则晚于其他 [data-view] 规则。
   2026-10 进一步加固：加全局兜底 `[hidden] { display: none !important; }`，
   覆盖任何作者后加的 display:flex/grid 容器（如 .login-error / 未来加的卡片）。
   不影响合法用法：
   - .login-input:autofill 的 -webkit-box-shadow 是合法用法，与 display 无关
   - [hidden] 选择器特异性 0,1,0 + !important 强制胜出所有作者规则。 */
[data-view][hidden] { display: none !important; }
[hidden] { display: none !important; }
/* ====================================================================
   Card base — 共享给 .kpi / .panel / .trend
   子类只保留布局差异(padding / min-height / display / flex-direction)
   hover 行为留给子类,避免 .panel/.trend 出现意外 background 变化
   ==================================================================== */
.card {
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  transition:
    border-color 0.2s var(--ease),
    background 0.2s var(--ease),
    box-shadow 0.2s var(--ease),
    transform 0.2s var(--ease);
}
/* 2026-10 视觉润色：card 在 hover 时除了 border / background 变化，还加一层极轻
   的 shadow + 0.5px 上浮，让「这块可交互」的信号更强（鼠标 hover 数据卡片时有
   「卡片在响应你」的物理感）。KPI / panel / trend 各自有 :hover border-color
   覆盖，这里补充通用 shadow + transform，叠加不冲突。 */
.card:hover {
  box-shadow: 0 2px 8px var(--button-shadow);
  transform: translateY(-0.5px);
}

/* ====================================================================
   KPI strip — 大数字 + sparkline, 4 列
   ==================================================================== */
.kpis {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 14px;
}
.kpi {
  padding: 18px 20px 16px;          /* was 20/22/18 — slight trim to reduce empty feel */
  position: relative;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  /* 2026-10 视觉润色：min-height 从 148 → 132. 之前 sparkline 区（34px）+ kpi-sub
     margin-top:auto 把 sub 推到底部，内容不满时卡底到 sub 出现大段空白。改
     132 + 收紧 padding（20→18）后，卡片视觉重量更紧凑，sparkline 与 sub 距离
     也从 30+px 降到 12-16px，避免"上重下空"的视觉不平衡。空 sparkline
     仍由 :empty 规则塌缩到 4px 兜底（数据完全无时）。 */
  min-height: 132px;
}
.kpi:hover { border-color: var(--border-strong); background: var(--surface-2); }

.kpi-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  min-height: 20px;
}
.kpi-label {
  color: var(--text-tertiary);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  letter-spacing: var(--tracking-body);
  /* sentence case */
}
.kpi-value {
  font-size: var(--fs-display);
  font-weight: var(--fw-bold);
  line-height: 1.05;
  color: var(--text);
  letter-spacing: var(--tracking-display);
  font-variant-numeric: tabular-nums;
  margin: 6px 0 0;
}
.kpi-unit {
  font-size: var(--fs-lg);
  font-weight: var(--fw-medium);
  color: var(--text-tertiary);
  margin-left: 3px;
}
.kpi-sub {
  color: var(--text-secondary);
  font-size: var(--fs-base);
  font-variant-numeric: tabular-nums;
  /* 2026-10 视觉润色：margin-top:auto 保留（kpi-spark 渲染失败时空隙感更自然），
     但补 margin-top:6px 给 sparkline 显式下沉——之前 kpi-spark 的 margin-top:8px
     加上 sub 的 auto 一起跑，sparkline 紧贴 sub 看着"挤"，且 8px 在 132 卡片
     里显大。改 6px 平衡 sub 与 sparkline 间距。 */
  margin-top: auto;
}

/* Delta badge */
.kpi-delta {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 2px 8px;
  border-radius: var(--r-pill);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  font-variant-numeric: tabular-nums;
  background: var(--surface-2);
  border: 1px solid var(--border-subtle);
  color: var(--text-tertiary);
  transition: color 0.2s var(--ease), background 0.2s var(--ease), border-color 0.2s var(--ease);
}
.kpi-delta[hidden] { display: none; }
.kpi-delta[data-dir="up"]   { color: var(--success); background: var(--success-soft); border-color: var(--success-ring); }
.kpi-delta[data-dir="down"] { color: var(--danger);  background: var(--danger-soft);  border-color: var(--danger-ring); }
.kpi-delta-icon { width: 9px; height: 9px; }
.kpi-delta[data-dir="down"] .kpi-delta-icon { transform: rotate(180deg); }

.kpi-spark {
  width: 100%;
  height: 30px;                     /* was 34 — 略矮，与收紧后的 kpi 卡片配套 */
  display: block;
  /* 2026-10 视觉润色：margin-top 8 → 6 + padding-top 4 → 2，整体 sparkline 块
     高度从 42px 降到 32px。在数据稀疏（小时粒度 1 峰 + 多 0 点）的常见场景下，
     sparkline 自身就有"看起来空"的视觉特征，区块越紧凑越不显空。 */
  margin-top: 6px;
  padding-top: 2px;
}
/* 2026-10 视觉润色：空数据时 sparkline 自动塌缩到 4px，避免「迷你图区」出现
   大块空白让 KPI 卡片显得空洞。数据来后由 charts.js::renderSpark 重写 SVG，
   高度自动恢复。 */
.kpi-spark:empty { height: 4px; margin-top: 0; padding-top: 0; }

/* ====================================================================
   Trend chart section (full-bleed, no inner card)
   ==================================================================== */
.trend {
  padding: 22px 24px 24px;          /* was 18/20 */
}
.trend:hover { border-color: var(--border-strong); }

.trend-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin: 0 0 16px;
}
.trend-head-left {
  display: inline-flex;
  align-items: center;
  gap: 14px;
}
.trend-title {
  margin: 0;
  font-size: var(--fs-lg);
  font-weight: var(--fw-semibold);
  color: var(--text);
  letter-spacing: var(--tracking-snug);
}

.chart-wrap { position: relative; width: 100%; }
/* 2026-10 R17：aspect-ratio 让 chart 跟随卡片宽度自动加高，填满卡片。
   之前固定 260px → 宽屏 1500+ 卡片下图表看起来很"扁"（5:1+ 比例）。
   3/1 比例：1024px→342 / 1280px→427(cap) / 1920px→640(cap)。
   min/max 兜底：极窄屏不低于 240，超宽屏不高于 400（防 KPI 行被挤压）。 */
#chart-trend {
  width: 100%;
  aspect-ratio: 3 / 1;
  min-height: 240px;
  max-height: 400px;
  display: block;
}
.chart-trend-grid line {
  stroke: var(--border-subtle);
  stroke-width: 1;
  stroke-dasharray: 2 4;
}
/* 底部 0 轴线稍微加深（仅 dasharray 改为实线），让眼睛有"地面线"参考，
   配合左侧 Y 轴 0 标签 —— 这是图表最基本的视觉锚点。 */
.chart-trend-grid line:last-child {
  stroke: var(--border);
  stroke-dasharray: none;
  stroke-width: 1;
}
/* 2026-10 视觉优化：Y 轴左侧数值标签（lib/charts.js renderTrend 写）。
   与 X 轴的 mono 字体保持一致；小尺寸 + tertiary 文字色，避免抢主图视觉。 */
.chart-trend-yaxis text {
  fill: var(--text-tertiary);
  font-size: 10.5px;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}
.chart-trend-axis-label {
  fill: var(--text-muted);
  font-size: var(--fs-xs);
  font-family: var(--font-mono);
}

/* 2026-10 R17 移除：.trend-summary / .trend-stat-* 整套样式 ——
   配套 index.html 删掉的 4 stat 横排面板。 */

/* hover 十字线 + tooltip（保留，与 stat 面板无关） */
.chart-trend-hover-line {
  stroke: var(--border-strong);
  stroke-width: 1;
  stroke-dasharray: 3 3;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.12s var(--ease);
}
.chart-trend-hover-dot {
  fill: var(--accent-bright);
  stroke: var(--surface-1);
  stroke-width: 2;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.12s var(--ease);
}
.chart-trend-hover-uv {
  fill: var(--success);
  stroke: var(--surface-1);
  stroke-width: 2;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.12s var(--ease);
}
.chart-trend-hit-area { fill: transparent; cursor: crosshair; }

.chart-tooltip {
  position: absolute;
  pointer-events: none;
  /* 跟随主题：light 下不再"异形深色"浮在浅色卡片上 */
  background: var(--bg-elevated);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  border: 1px solid var(--border);
  border-radius: var(--r);
  padding: 10px 13px;
  font-size: var(--fs-base);
  color: var(--text);
  box-shadow: var(--shadow);
  min-width: 140px;
  opacity: 0;
  transform: translate(-50%, -8px) scale(0.96);
  transition: opacity 0.12s var(--ease), transform 0.12s var(--ease);
  z-index: 10;
}
.chart-tooltip[hidden] { display: none; }
.chart-tooltip-show { opacity: 1; transform: translate(-50%, -100%) scale(1); }
.chart-tooltip-date {
  color: var(--text-tertiary);
  font-size: var(--fs-xs);
  font-variant-numeric: tabular-nums;
  margin-bottom: 6px;
  font-family: var(--font-mono);
}
.chart-tooltip-row {
  display: flex;
  align-items: center;
  gap: 6px;
  font-variant-numeric: tabular-nums;
  line-height: 1.7;
}
.chart-tooltip-row .dot-pv,
.chart-tooltip-row .dot-uv {
  width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0;
}
.chart-tooltip-row .dot-pv { background: var(--accent-bright); box-shadow: 0 0 6px var(--accent-glow); }
.chart-tooltip-row .dot-uv { background: var(--chart-uv); box-shadow: 0 0 6px var(--chart-uv-soft); }
.chart-tooltip-row > span:nth-child(2) { color: var(--text-tertiary); }
.chart-tooltip-row b { margin-left: auto; font-weight: var(--fw-semibold); color: var(--text); }
/* 2026-10 视觉优化：tooltip PV 行尾部占比 %（"12.3%"），让用户一眼看到
   "这一点占区间总流量的多少"。紧凑的 tertiary 色 + mono 字体避免抢主数字。 */
.chart-tooltip-pct {
  font-size: 10.5px;
  font-family: var(--font-mono);
  color: var(--text-tertiary);
  font-variant-numeric: tabular-nums;
  margin-left: 4px;
  font-weight: var(--fw-regular);
}

/* Legend */
.legend { display: inline-flex; align-items: center; gap: 6px; }
/* 2026-10 视觉润色：legend-pill 加微弱 accent-tint 背景 + 1px accent ring，
   比原纯 surface-2 更明确「这是个图例」—— 同时与正文卡片边框区分，扫一眼
   就能定位到图例位置。原纯 surface-2 在 light 主题下与卡片底色太近。 */
.legend-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 10px;
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  color: var(--text-secondary);
  background: var(--accent-softer);
  border: 1px solid var(--border-subtle);
  border-radius: var(--r-pill);
  font-variant-numeric: tabular-nums;
  transition: background 0.15s var(--ease), border-color 0.15s var(--ease);
}
/* UV pill：用 chart-uv-soft 替代 accent-softer，让两条图例一眼能区分。
   2026-10：跟随 --chart-uv（不再写死 --success）—— 曲线本身用哪个色，图例 /
   tooltip 小圆点就得用同一个，否则「图例说绿、线是蓝」。 */
.legend-pill.legend-uv {
  background: var(--chart-uv-soft);
}
.legend-pill i {
  display: inline-block;
  width: 14px;
  height: 2px;
  background: var(--accent-bright);
  border-radius: 2px;
  flex-shrink: 0;
}
.legend-pill.legend-uv i {
  /* 2026-10：之前用 `height:0; border-top: 2px dashed` 实现虚线段，但 border
     的 dasharray 是按 border-width 比例渲染的 → 14px 容器内 dash 长度 ~5px、
     与 chart UV 实线（dasharray="4 3"）比例不一致，用户一眼觉得「图例和曲线
     形状对不上」。改成 linear-gradient 重复 7px 一个 dash+gap 周期，14px 内
     正好 2 个 dash，与 chart 视觉一致。 */
  background-image: linear-gradient(90deg, var(--chart-uv) 57%, transparent 57%);
  background-size: 7px 2px;
  background-repeat: repeat-x;
  background-color: transparent;
  border: 0;
  border-radius: 0;
  height: 2px;
  margin: 0;
}

/* 2026-10：legend pill 内嵌的「当前 PV / UV 数值」—— 趋势图空态时显示「—」，
   数据加载后由 charts.js renderTrend() 更新为最后一个数据点的 fmt 值。不需
   hover 即可一眼读到当前周期的 PV/UV 总数。 */
.legend-value {
  font-weight: var(--fw-semibold);
  color: var(--text);
  margin-left: 2px;
  font-variant-numeric: tabular-nums;
}
[data-loading="true"] .legend-value {
  color: transparent !important;
  background: linear-gradient(90deg, var(--shimmer-base) 0%, var(--shimmer-highlight) 50%, var(--shimmer-base) 100%);
  background-size: 200% 100%;
  border-radius: 3px;
  animation: shimmer 1.4s linear infinite;
  display: inline-block;
  min-width: 28px;
  height: 12px;
}

/* Card status (loading / error / retry) */
.card-status {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  padding: 3px 10px;
  border-radius: var(--r-pill);
  border: 1px solid transparent;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  animation: card-status-in 0.2s var(--ease);
}
.card-status[hidden] { display: none; }
.card-status-loading { color: var(--text-tertiary); background: var(--surface-2); border-color: var(--border-subtle); }
.card-status-loading::before {
  content: "";
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--text-tertiary);
  animation: status-blink 1.2s ease-in-out infinite;
}
.card-status-error { color: var(--danger); background: var(--danger-soft); border-color: var(--danger); }
.card-status-error::before {
  content: "";
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--danger);
  box-shadow: 0 0 6px var(--danger-ring);
}
/* 2026-10 R17：限流 429 不是真故障，用 warning 色调（区别于 error 的红），
   不显示重试按钮（dom.js 已对 'warn' kind 跳过 retry button 渲染）。
   用户看到「稍候」知道等几秒就恢复，避免把限流当成故障反复点重试继续撞桶。 */
.card-status-warn  { color: var(--warning); background: var(--warning-soft); border-color: var(--warning); }
.card-status-warn::before {
  content: "";
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--warning);
  box-shadow: 0 0 6px var(--warning-ring);
}
.card-status-retry {
  /* 2026-09 审计 BUG-19：原版硬编码 rgba(255,255,255,*) 在 light 主题下白字白底，
     a11y 失败。改用 token（var(--surface-2) + var(--text)）让 dark/light 都可见。 */
  background: var(--surface-2);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--r-xs);
  padding: 2px 7px;
  font-size: var(--fs-xs);
  font-family: inherit;
  font-weight: var(--fw-medium);
  cursor: pointer;
  transition: background 0.15s var(--ease);
}
.card-status-retry:hover { background: var(--surface-3); }

/* Realtime panel: 下次刷新倒计时（数字 + 进度条）
   - 5s 档看不出节奏，但 5→40s 退避档让用户直观感受「现在轮得慢」
   - 不抢眼：11px + text-tertiary + 2px 细条 + opacity 0.5 填充 */
.realtime-next-poll {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 11px;
  font-weight: var(--fw-medium);
  color: var(--text-tertiary);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.realtime-next-poll[hidden] { display: none; }
.realtime-next-poll-bar {
  position: relative;
  display: inline-block;
  width: 40px;
  height: 2px;
  background: var(--surface-2);
  border-radius: 1px;
  overflow: hidden;
}
.realtime-next-poll-bar::after {
  content: "";
  position: absolute;
  inset-block: 0;
  inset-inline-start: 0;
  width: var(--poll-progress, 0%);
  background: var(--text-tertiary);
  opacity: 0.5;
  transition: width 0.25s linear;
}

/* 紧凑版 select-wrap（用于 sidebar「自动刷新」meta-row 等内嵌紧凑场景；
   比 topbar select-wrap 更小、更贴 meta-row 视觉） */
.select-wrap-sm select {
  width: auto;
  padding: 3px 24px 3px 9px;
  font-size: 11px;
  font-weight: var(--fw-medium);
  color: var(--text-tertiary);
  background: var(--surface-2);
  border-color: var(--border-subtle);
  border-radius: var(--r-pill);
  line-height: 1.4;
}
.select-wrap-sm select:hover {
  color: var(--text);
  background: var(--surface-3);
  border-color: var(--border);
}
.select-wrap-sm .select-caret {
  right: 8px;
  width: 9px;
  height: 9px;
  opacity: 0.65;
}

@keyframes status-blink {
  0%, 100% { opacity: 0.35; }
  50%      { opacity: 1; }
}
@keyframes card-status-in {
  from { opacity: 0; transform: translateY(-2px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* Grids */
.grid-2 {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px;                        /* was 12 */
}
.grid-3 {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 14px;
}

/* ====================================================================
   Panel (通用面板: 表/条形图/事件流)
   ==================================================================== */
.panel {
  padding: 20px 22px 22px;          /* was 16/18/18 */
}
.panel:hover { border-color: var(--border-strong); }

.panel-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  margin: 0 0 14px;
}
.panel-head-row { align-items: center; }
.panel-head-main { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.panel-title {
  margin: 0;
  font-size: var(--fs-md);
  font-weight: var(--fw-semibold);
  color: var(--text);
  letter-spacing: var(--tracking-snug);
  line-height: 1.4;
  display: inline-flex;
  align-items: center;
  gap: 8px;
}
.panel-caption {
  margin: 0;
  color: var(--text-tertiary);
  font-size: var(--fs-base);
  line-height: 1.4;
}
/* 2026-10 视觉润色：trend 卡片 title 也加 accent 短竖条 —— 与 sidebar
   section-title 一致的「功能锚点」风格，让"访问趋势"在卡片列表里更有身份。
   用 trend-title::before 不动 .trend-head flex 布局（绝对定位 + padding-left 即可）。
   宽度 / 高度走 tokens.css 的 --stripe-w-md / --stripe-h（卡片级）。 */
.trend-title {
  position: relative;
  padding-left: 14px;
}
.trend-title::before {
  content: "";
  position: absolute;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
  width: var(--stripe-w-md);
  height: var(--stripe-h);
  background: var(--accent);
  border-radius: 2px;
}

/* ====================================================================
   Tables
   ==================================================================== */
.table-wrap { width: 100%; overflow-x: auto; }
.data-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-base);
  table-layout: fixed;
}
.data-table th,
.data-table td {
  padding: 13px 14px;
  text-align: left;
  border-bottom: 1px solid var(--border-subtle);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  font-variant-numeric: tabular-nums;
}
.data-table tbody td {
  color: var(--text);
}
.data-table thead th {
  color: var(--text-tertiary);
  font-weight: var(--fw-semibold);
  font-size: var(--fs-xs);
  letter-spacing: var(--tracking-body);
  /* sentence case column headers */
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: 0;
  background: var(--surface-1);
  z-index: 1;
}
.data-table tbody tr { transition: background 0.12s var(--ease); }
.data-table tbody tr:hover { background: var(--surface-2); }
.data-table tbody tr:hover td { color: var(--text); }
.data-table tbody tr:last-child td { border-bottom: none; }

.data-table th.num,
.data-table td.num {
  text-align: right;
  width: 104px;
  color: var(--text-secondary);
  font-weight: var(--fw-semibold);
}

.data-table-rt thead th:nth-child(1) { width: 88px; }
.data-table-rt thead th:nth-child(2) { width: 32%; }
.data-table-rt thead th:nth-child(3) { width: 18%; }
.data-table-rt thead th:nth-child(4),
.data-table-rt thead th:nth-child(5) { width: 14%; }
.data-table-rt thead th:nth-child(6) { width: 100px; }

/* ====================================================================
   Horizontal bar chart
   ==================================================================== */
.bar-chart {
  display: flex;
  flex-direction: column;
  gap: 11px;                        /* was 9 */
}
.bar-row {
  display: grid;
  grid-template-columns: 116px 1fr 84px;
  align-items: center;
  gap: 14px;
  font-size: var(--fs-base);
  animation: bar-row-in 0.5s var(--ease-out) backwards;
}
.bar-row:nth-child(1)  { animation-delay: 0.00s; }
.bar-row:nth-child(2)  { animation-delay: 0.03s; }
.bar-row:nth-child(3)  { animation-delay: 0.06s; }
.bar-row:nth-child(4)  { animation-delay: 0.09s; }
.bar-row:nth-child(5)  { animation-delay: 0.12s; }
.bar-row:nth-child(6)  { animation-delay: 0.15s; }
.bar-row:nth-child(7)  { animation-delay: 0.18s; }
.bar-row:nth-child(8)  { animation-delay: 0.21s; }
.bar-row:nth-child(9)  { animation-delay: 0.24s; }
.bar-row:nth-child(10) { animation-delay: 0.27s; }

@keyframes bar-row-in {
  from { opacity: 0; transform: translateX(-6px); }
  to   { opacity: 1; transform: translateX(0); }
}
.bar-row > span:first-child {
  color: var(--text-secondary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  font-weight: var(--fw-medium);
  transition: color 0.15s var(--ease);
}
.bar-row:hover > span:first-child { color: var(--text); }

.bar-track {
  background: var(--surface-2);
  border-radius: var(--r-pill);
  height: 8px;
  overflow: hidden;
  position: relative;
  box-shadow: inset 0 1px 2px var(--button-shadow);
}
.bar-fill {
  height: 100%;
  width: var(--w, 0%);
  background: var(--gradient-brand);
  border-radius: var(--r-pill);
  transition: width 0.6s var(--ease-out);
  box-shadow: 0 0 8px var(--accent-glow);
  position: relative;
}
.bar-fill::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, var(--highlight-line), transparent 50%);
  border-radius: inherit;
}
.bar-value {
  text-align: right;
  color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
  font-weight: var(--fw-semibold);
}
/* 2026-10 视觉优化：bar 行右侧两栏紧凑布局——主数字 + 占比 %。
   占比字号小 + tertiary 文字色，避免抢主数字的视觉。
   2026-10 字号提升：原 10.5px + tertiary 在 light 模式擦 WCAG AA 4.5:1（小字号
   阈值）。提升到 11.5px 仍比主数字小 1-2px（视觉层级），但满足 AA 对比度
   余量。chart-tooltip-pct 同期处理。 */
.bar-share {
  display: inline-block;
  margin-left: 6px;
  font-size: 11.5px;
  font-weight: var(--fw-regular);
  color: var(--text-secondary);
  font-family: var(--font-mono);
}

/* ====================================================================
   Utilities
   ==================================================================== */
.muted { color: var(--text-muted); font-weight: 400; font-size: var(--fs-base); }
.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0);
  white-space: nowrap; border: 0;
}

.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  padding: 44px 16px;
  color: var(--text-tertiary);
  font-size: var(--fs-base);
  text-align: center;
  position: relative;
}
/* 虚线圆：纯 ::before 实现，破折线圆 + 中心点用 box-shadow 嵌套
   （一个 18px 内阴影模拟"应有数据但没有"的暗示点）。
   比单层虚线圆更有视觉意图，但保持 CSS 简洁不依赖嵌套伪元素。 */
.empty-state::before {
  content: "";
  width: 36px;
  height: 36px;
  border-radius: 50%;
  border: 1.5px dashed var(--border-strong);
  background: transparent;
  opacity: 0.65;
  margin-bottom: 2px;
  flex-shrink: 0;
  /* radial-gradient 让中心有一个 4px 实心点：
     gradient 在 18/18 = 50% 处放一个小点（黑色/灰色），覆盖在虚线圆中心。 */
  background-image: radial-gradient(circle at 50% 50%, var(--text-muted) 0 2.5px, transparent 3px);
  background-repeat: no-repeat;
}
/* 把空态内文字 + 副提示分层：主文字稍亮，副提示更暗 */
.empty-state .empty-state-msg {
  color: var(--text-secondary);
  font-weight: var(--fw-medium);
}
.empty-state .empty-state-hint {
  font-size: var(--fs-sm);
  color: var(--text-muted);
  margin-top: -4px;
  max-width: 280px;
  line-height: 1.5;
}
.empty-state-row { text-align: center !important; padding: 24px 16px !important; }
.empty-state-row::before { display: none; }

/* bar-chart 卡片内的空状态：撑满卡片高度，图标略放大，避免在窄卡片里显得太弱 */
.bar-chart > .empty-state {
  min-height: 132px;
  padding: 40px 16px;
}
.bar-chart > .empty-state::before {
  width: 42px;
  height: 42px;
  border-width: 1.5px;
  background-image: radial-gradient(circle at 50% 50%, var(--text-muted) 0 2.5px, transparent 3px);
  background-size: 100% 100%;
}

[data-loading="true"] .kpi-value {
  display: inline-block;
  min-width: 60px;
  height: 28px;
  color: transparent !important;
  /* 2026-10 修复：原 gradient 用 --surface-2 → --surface-3，对 --surface-1 卡片底
     对比度仅 ~1.04:1，肉眼几乎不可见 —— shimmer 跑起来 KPI 槽位看起来像空值，
     用户以为是 bug。换 --shimmer-base/--shimmer-highlight 把对比度拉到 ~1.7x，
     加载中明显能看见，停下后也能识别为"加载占位"而不是数字。 */
  background: linear-gradient(90deg, var(--shimmer-base) 0%, var(--shimmer-highlight) 50%, var(--shimmer-base) 100%);
  background-size: 200% 100%;
  border-radius: 4px;
  animation: shimmer 1.4s linear infinite;
  vertical-align: middle;
}
[data-loading="true"] .kpi-spark { opacity: 0.3; }
[data-loading="true"] .chart-tooltip { display: none; }
@keyframes shimmer {
  0%   { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

/* Focus */
button:focus-visible,
select:focus-visible,
input:focus-visible,
textarea:focus-visible,
[role="tab"]:focus-visible,
a:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--accent-soft), 0 0 0 4px var(--bg);
  border-color: var(--accent) !important;
}

/* 2026-09 审计 BUG-37 LOW 修复：trend SVG 键盘可达，焦点环需要可见。
   chart-wrap 内层 SVG 本身有 viewBox 缩放，外层用 outline（不被 viewBox 影响）。 */
.chart-wrap > svg[role="img"]:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--accent-soft), 0 0 0 4px var(--bg);
  border-radius: 6px;
}

/* ====================================================================
   Page footer — 产品铭牌（工程图例条式）
   ==================================================================== */
.page-foot {
  margin-top: var(--space-4);
  padding: var(--space-5) 0 var(--space-3);
  border-top: 1px solid var(--border);
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-5);
  color: var(--text-muted);
}
.foot-spec {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, max-content));
  column-gap: var(--space-6);
  row-gap: var(--space-2);
  margin: 0;
  padding: 0;
  font-size: var(--fs-xs);
  font-variant-numeric: tabular-nums;
}
.foot-spec > div { display: flex; flex-direction: column; gap: 2px; }
.foot-spec dt {
  margin: 0;
  color: var(--text-secondary);
  font-weight: var(--fw-medium);
  letter-spacing: 0;
}
.foot-spec dd {
  margin: 0;
  color: var(--text-muted);
  font-size: var(--fs-xs);
  letter-spacing: 0;
  text-transform: lowercase;
}
.foot-divider {
  display: inline-block;
  width: 1px;
  height: 24px;
  background: var(--border);
  margin: 0 var(--space-2);
  vertical-align: middle;
}
.foot-brand {
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-3);
  font-size: var(--fs-sm);
  color: var(--text-secondary);
}
.foot-brand > span:first-child {
  font-weight: var(--fw-semibold);
  color: var(--text);
  letter-spacing: 0;
}
.foot-tag {
  color: var(--text-muted);
  font-size: var(--fs-xs);
  letter-spacing: 0;
}

/* ====================================================================
   Realtime event stream
   ==================================================================== */
.data-table tbody tr.row-new td {
  /* 2026-09 审计 BUG-21：用 var(--success-soft) 替代硬编码 rgba(16,185,129,0.14)，
     light 主题下 --success-soft 已调成 0.10 适配浅底，dark 主题 0.16 不动。 */
  background: var(--success-soft);
  animation: row-new-fade 2.4s var(--ease) forwards;
}
@keyframes row-new-fade {
  0%   { background: var(--success-soft); filter: brightness(1.5); }
  100% { background: transparent; filter: none; }
}

.url-cell {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  max-width: 100%;
}
.url-text {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}
.evt-tag {
  display: inline-block;
  flex-shrink: 0;
  padding: 1px 6px;
  border-radius: var(--r-xs);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  letter-spacing: 0;
  background: var(--surface-2);
  color: var(--text-tertiary);
  border: 1px solid transparent;
  font-variant-numeric: tabular-nums;
}
.evt-tag-pageview { color: var(--accent-bright); background: var(--accent-soft); border-color: var(--ring-medium); }
.evt-tag-event    { color: var(--success); background: var(--success-soft); border-color: var(--success-ring); }
.evt-tag-error    { color: var(--danger); background: var(--danger-soft); border-color: var(--danger-ring); }

.kpi-value.flash { animation: value-flash 0.6s var(--ease-out); }
@keyframes value-flash {
  0%   { opacity: 0.4; transform: translateY(2px); }
  100% { opacity: 1;   transform: translateY(0); }
}

/* ====================================================================
   Dialog (通用模态)
   ==================================================================== */
.dialog-overlay {
  position: fixed;
  inset: 0;
  background: var(--scrim);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  z-index: 100;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 60px 16px 16px;
  animation: dialog-fade-in 0.18s var(--ease-out);
  overflow-y: auto;
}
@keyframes dialog-fade-in { from { opacity: 0; } to { opacity: 1; } }
.dialog-card {
  width: 100%;
  max-width: var(--dialog-w, 520px);   /* 尊重 openDialog({width}) 传的 per-dialog 宽度；
                                          lib/dialog.js 通过 setProperty('--dialog-w') 注入，
                                          CSP 下不走内联 style 字符串赋值。此处之前只写了
                                          width:100% 而没读 --dialog-w，导致所有弹窗都撑满视口
                                          （在 1440 屏上看到 ~1340px 宽；站管理 / 删除确认 /
                                          审计 / 设置全部受影响。 */
  background: var(--surface-2);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-xl);
  box-shadow: var(--shadow-lg);
  display: flex;
  flex-direction: column;
  max-height: calc(100vh - 80px);
  animation: dialog-slide-in 0.22s var(--ease-out);
}
@keyframes dialog-slide-in {
  from { opacity: 0; transform: translateY(-8px) scale(0.98); }
  to   { opacity: 1; transform: translateY(0)    scale(1); }
}
.dialog-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 18px 24px;
  border-bottom: 1px solid var(--border-subtle);
}
.dialog-title {
  margin: 0;
  font-size: var(--fs-lg);
  font-weight: var(--fw-bold);
  color: var(--text);
  letter-spacing: -0.012em;
}
.dialog-close {
  background: transparent;
  border: none;
  color: var(--text-tertiary);
  font-size: 22px;
  line-height: 1;
  width: 28px;
  height: 28px;
  border-radius: var(--r);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: background 0.12s, color 0.12s;
}
.dialog-close:hover { background: var(--surface-hover); color: var(--text); }

.dialog-body {
  padding: 18px 24px;
  overflow-y: auto;
  color: var(--text-secondary);
  font-size: var(--fs-base);
  line-height: 1.6;
}
.dialog-foot {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  padding: 14px 24px 18px;
  border-top: 1px solid var(--border-subtle);
}
.dialog-foot .btn { width: auto; min-width: 80px; min-height: 34px; }

/* 2026-10 视觉润色：.btn-danger —— 红色实心按钮，与 .btn-primary / .btn-ghost 同级用法。
   复用 --danger / --danger-soft / --danger-ring / --highlight-line / --button-shadow tokens。
   仅在 sites 删除确认弹窗的 footer 中使用。 */
.btn-danger {
  background: var(--danger);
  color: #fff;
  border: 1px solid var(--danger);
  box-shadow: 0 1px 0 var(--highlight-line) inset, 0 1px 2px var(--button-shadow);
}
.btn-danger:hover {
  filter: brightness(1.08);
  box-shadow: 0 1px 0 var(--highlight-line) inset, 0 4px 14px var(--danger-ring);
}
.btn-danger:active {
  filter: brightness(0.96);
  box-shadow: 0 1px 0 var(--highlight-line) inset, 0 1px 2px var(--button-shadow);
}
.btn-danger:disabled { opacity: 0.6; cursor: not-allowed; }

/* ====================================================================
   Sites manager
   ==================================================================== */
.sites-manager {
  display: grid;
  grid-template-columns: minmax(0, 320px) minmax(0, 1fr);
  gap: 24px;
  align-items: start;
}
/* 编辑模式：隐藏 create pane，edit form 拿全宽。JS 在 enterEditMode/exitEditMode
   切换 is-editing 类（sites.js），最小侵入控制 layout 不动 DOM 数据流。 */
.sites-manager.is-editing { grid-template-columns: minmax(0, 1fr); }
.sites-manager.is-editing > .sites-pane[data-mode="create"] { display: none; }
/* 双列模式（>720px）：左列加细分隔线，让左右两个 pane 视觉上断开。
   padding-right: 12px 让 border 落在 24px gap 的正中间。 */
@media (min-width: 721px) {
  .sites-manager:not(.is-editing) > .sites-pane:first-child {
    padding-right: 12px;
    border-right: 1px solid var(--border);
  }
}
.sites-pane {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.sites-pane-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
}
.sites-pane-title {
  margin: 0 0 8px;
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  color: var(--text-tertiary);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  position: relative;             /* 给 ::before 绝对定位用 */
  padding-left: 14px;             /* 4px accent + 10px gap */
}
/* 不用 flex 避免在窄列（list 列 280px）里 flex-item 各自换行；
   用 absolute ::before 让 accent 方块浮动在标题左侧。 */
.sites-pane-title::before {
  content: "";
  position: absolute;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
  width: var(--stripe-w-md);
  height: var(--stripe-h);
  background: var(--accent);
  border-radius: 2px;
}
/* 嵌套元素（标题文本 + #sites-count）取消 uppercase/letter-spacing，
   保留 count 的 accent-bright 高亮。CJK 字符 uppercase 视觉无影响。 */
.sites-pane-title > * { color: var(--text); text-transform: none; letter-spacing: 0; }
.sites-pane-title #sites-count { color: var(--accent-bright); }
.sites-pane-hint { font-size: var(--fs-xs); color: var(--text-muted); font-weight: var(--fw-regular); }
.sites-list {
  display: flex;
  flex-direction: column;
  gap: 5px;
  max-height: 560px;                 /* 利用 1440+ 视口留白，可视区显示 4 条完整项 */
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: 5px;
  background: var(--surface-1);
}
/* 2026-10 视觉润色：站点卡片分层（name+tag 行 / pills 行 / 操作列），
   active 改用左侧 accent 竖条 + 圆点（与主按钮空间分离），空状态用虚线 + 下箭头引导。 */
.sites-empty {
  padding: 28px 16px 24px;
  text-align: center;
  color: var(--text-tertiary);
  font-size: var(--fs-base);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  border: 1px dashed var(--border);
  border-radius: var(--r);
  background: var(--surface-1);
}
.sites-empty-title { color: var(--text-secondary); font-weight: var(--fw-medium); }
.sites-empty-hint  { font-size: var(--fs-sm); }
/* 向下小箭头：用 CSS mask 画一个 chevron，指向下方"新建站点"表单 */
.sites-empty::after {
  content: "";
  width: 14px; height: 8px;
  background: currentColor;
  -webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 8'><path d='M1 1 L7 7 L13 1' fill='none' stroke='black' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/></svg>") center/contain no-repeat;
          mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 8'><path d='M1 1 L7 7 L13 1' fill='none' stroke='black' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/></svg>") center/contain no-repeat;
  opacity: 0.45;
  margin-top: 4px;
}
.sites-item {
  display: flex;
  align-items: flex-start;          /* 卡片变高（name+meta 两行），不再 center */
  justify-content: space-between;
  gap: 10px;
  padding: 10px 11px;
  border-radius: var(--r-sm);       /* 嵌套卡片更紧凑 */
  border: 1px solid transparent;
  position: relative;                /* 给 .sites-item-active::before 圆点定位 */
  transition: background 0.12s, border-color 0.12s;
}
.sites-item:hover { background: var(--surface-hover); }
/* active：左侧 3px accent 竖条 —— 与 accent 主按钮空间上分离，
   不再用大块 accent-softer 背景（之前与按钮同色系，辨别度低）。
   简化掉了 ::before 圆点（原本 4 层叠加），现在只 3 层：bg + border + 左竖条。 */
.sites-item-active {
  background: var(--accent-softer);
  border-color: var(--ring-medium);
  border-left: 3px solid var(--accent);
  padding-left: 11px;                /* 补偿更厚的左边缘 */
}
.sites-item-info { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 6px; }
.sites-item-name {
  font-size: var(--fs-base);
  font-weight: var(--fw-semibold);
  color: var(--text);
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  min-width: 0;
}
.sites-item-name > span:first-child {
  min-width: 0;
  overflow-wrap: anywhere;            /* 长名字断行而不是溢出 */
}
/* meta：原来一行 ellipsis，现在 flex-wrap pills —— 多行展示，窄屏不丢信息 */
.sites-item-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 3px 5px;
  align-items: center;
  font-size: var(--fs-xs);
  color: var(--text-tertiary);
}
.sites-meta-pill {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 1px 7px;
  background: var(--surface-3);     /* 比 form 容器 surface-2 高一档，pills 浮起可见 */
  border: 1px solid transparent;     /* 默认无边框 → 受限 pill 才走 ring-medium */
  border-radius: var(--r-pill);
  color: var(--text-secondary);
  font-family: var(--font-mono);
  font-size: 10.5px;
  white-space: nowrap;
  max-width: 130px;                  /* 长 site ID / domain 截断，避免单条占满一行 */
  overflow: hidden;
  text-overflow: ellipsis;
}
/* origin pill 在受限状态下用 accent 色突出 —— 视觉信号 "这个站点的 origin 有限制" */
.sites-meta-pill-origin-restricted {
  background: var(--accent-softer);
  border-color: var(--ring-medium);
  color: var(--accent-bright);
}
.sites-item-actions {
  display: flex;
  gap: 6px;
  flex-shrink: 0;
  align-self: flex-start;
}
.sites-item-actions .btn-icon { width: 13px; height: 13px; }
/* action 按钮里的文字 label：默认显示，窄屏 (≤480px) 下隐藏 → 只剩 icon */
.sites-action-label { display: inline; }
.sites-tag {
  display: inline-block;
  padding: 1px 7px;
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  letter-spacing: 0;
  border-radius: var(--r-xs);
  background: var(--accent-soft);
  color: var(--accent-bright);
  border: 1px solid var(--ring-medium);
}

/* 2026-10 v19：拖动排序。.sites-drag-handle 是行内 grip 列（draggable=true），
   拖动期间 .sites-item.is-dragging 半透明；目标行顶部/底部 2px accent 线
   指示落点（.is-drop-before / .is-drop-after）。*/
.sites-drag-handle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 32px;
  flex-shrink: 0;
  margin: -2px 0;
  color: var(--text-tertiary);
  border-radius: var(--r-xs);
  cursor: grab;
  user-select: none;            /* 防止拖动时 grip 内 SVG 被文字选中 */
  -webkit-user-drag: element;   /* Safari 提示这是可拖元素 */
  touch-action: none;           /* 移动端禁用浏览器原生滚动/缩放干扰 */
  transition: color 0.12s, background 0.12s;
}
.sites-drag-handle:hover { color: var(--text-secondary); background: var(--surface-hover); }
.sites-drag-handle:active { cursor: grabbing; color: var(--accent); }
.sites-drag-handle:focus-visible {
  outline: 2px solid var(--ring-medium);
  outline-offset: 1px;
}
/* 单站点时不显示拖动柄的 draggable 行为 —— 仍渲染占位以保持行高一致，但视觉
   淡化 + cursor: default 提示用户「无法拖动」。JS 侧 renderList 也不会给这种
   状态加 draggable=true / role="button"。*/
.sites-drag-handle-disabled {
  cursor: default;
  opacity: 0.35;
}
.sites-drag-handle-disabled:hover { background: transparent; }
.sites-drag-handle svg { width: 10px; height: 14px; pointer-events: none; }
/* 被拖动的行：半透明 + 轻微缩放反馈，与落点指示线配合形成清晰的拖动 UX */
.sites-item.is-dragging {
  opacity: 0.45;
  transform: scale(0.98);
  background: var(--surface-2);
  border-color: var(--border);
}
/* 落点指示：box-shadow inset 2px 不影响行内容布局；颜色用 accent 与
   sites-item-active 左竖条呼应。*/
.sites-item.is-drop-before {
  box-shadow: 0 -2px 0 0 var(--accent) inset;
  border-top-color: transparent;
}
.sites-item.is-drop-after {
  box-shadow: 0 2px 0 0 var(--accent) inset;
  border-bottom-color: transparent;
}
@media (prefers-reduced-motion: reduce) {
  .sites-item.is-dragging { transform: none; }
  .sites-drag-handle { transition: none; }
}

/* 复制按钮（编辑表单 + 删除确认弹窗共用） */
.sites-id-row {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.sites-id-copy {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 5px 10px;
  border-radius: var(--r);
  background: var(--surface-2);
  border: 1px solid var(--border);
  color: var(--text-secondary);
  font-size: var(--fs-xs);
  font-family: var(--font-sans);
  cursor: pointer;
  transition: background 0.15s var(--ease), border-color 0.15s var(--ease), color 0.15s var(--ease);
  min-height: 28px;
}
.sites-id-copy:hover { background: var(--surface-3); border-color: var(--border-strong); color: var(--text); }
.sites-id-copy:active { transform: scale(0.97); }
.sites-id-copy:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.sites-id-copy .btn-icon { width: 13px; height: 13px; }
.sites-id-copy.is-copied {
  background: var(--success-soft);
  border-color: var(--success-ring);
  color: var(--success);
}

/* 删除确认弹窗专用 */
.sites-confirm-name {
  margin: 0 0 4px;
  font-size: var(--fs-lg);
  font-weight: var(--fw-semibold);
  color: var(--text);
  letter-spacing: 0;
}
/* 编辑模式切换：list → 表单是 DOM 替换（无网络往返）。
   给 .sites-form-inline 加短淡入，让切换看起来更连贯，比直接冒出一坨不卡眼。
   注意：仅 fade 列表内容 fade-out / form-in-out；外层 pane 切换 grid 列会引发
   layout shift，无法仅靠 CSS 抹平，所以这一帧淡入仅作用于内容。 */
@keyframes sites-form-inline-in {
  from { opacity: 0; transform: translateY(-2px); }
  to   { opacity: 1; transform: translateY(0); }
}
.sites-form-inline { animation: sites-form-inline-in 0.18s var(--ease-out); }
.sites-confirm-id {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  padding: 3px 8px;
  background: var(--surface-1);
  border: 1px solid var(--border-subtle);
  border-radius: var(--r-xs);
  color: var(--text-secondary);
  margin-bottom: 14px;
}
.sites-confirm-warning {
  margin: 14px 0 0;
  padding: 10px 12px;
  background: var(--danger-soft);
  border: 1px solid var(--danger-ring);
  border-radius: var(--r);
  color: var(--danger);
  font-size: var(--fs-base);
  line-height: 1.55;
}

/* 响应式：dialog 在窄屏贴边 + 卡片按钮换下方 + 窄屏操作按钮只剩 icon */
@media (max-width: 720px) {
  .dialog-overlay { padding: 24px 12px 12px; }
  .dialog-head, .dialog-body, .dialog-foot { padding-left: 18px; padding-right: 18px; }
  /* 双列 → 单列堆叠 */
  .sites-manager { grid-template-columns: 1fr; gap: 18px; }
  /* 卡片操作换到 info 下方 */
  .sites-item { flex-direction: column; align-items: stretch; gap: 8px; }
  .sites-item-actions { justify-content: flex-end; }
  .sites-item-actions .btn { flex: 1 1 auto; min-width: 0; }
  /* 拖动柄在窄屏堆叠时与 info 同行左侧对齐；width auto 让 SVG 不留大空白 */
  .sites-drag-handle { align-self: flex-start; }
  /* 副提示（小灰字）隐藏 */
  .sites-pane-hint { display: none; }
  /* 解除防御性 max-width，窄屏单列让 input 占满 */
  .sites-form-row input,
  .sites-form-row textarea { max-width: none; }
}
@media (max-width: 480px) {
  .dialog-overlay { padding: 12px 8px 8px; }
  .dialog-head { padding: 14px 16px; }
  .dialog-body { padding: 14px 16px; }
  .dialog-foot { padding: 12px 16px 14px; flex-direction: column-reverse; gap: 6px; }
  .dialog-foot .btn { width: 100%; }
  .sites-form { padding: 12px; gap: 10px; }
  .sites-form-row input { padding: 8px 10px; }
  .sites-form-actions { flex-direction: column-reverse; }
  .sites-form-actions .btn { width: 100%; }
  .sites-action-label { display: none; }
}

.sites-form {
  display: flex;
  flex-direction: column;
  gap: 12px;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: 14px;
  background: var(--surface-1);
}
.sites-form-row {
  display: flex;
  flex-direction: column;
  gap: 5px;
}
.sites-form-row label {
  font-size: var(--fs-sm);
  color: var(--text-tertiary);
  font-weight: var(--fw-medium);
  letter-spacing: 0;
}
/* 必填星号：用 accent 主色而不是 danger —— 星号是 schema 信号，不是错误信号 */
.sites-form-row .required { color: var(--accent); }
.sites-form-hint {
  font-weight: var(--fw-regular);
  letter-spacing: 0;
  color: var(--text-muted);
  font-size: var(--fs-xs);
}
/* 2026-10 视觉润色：input / form-id 背景从 var(--bg) 改为 var(--surface-2)，
   比表单容器（--surface-1）高一档，使输入框在两种主题下都"浮起"可见。
   旧版在 light 主题下与表单容器几乎同色，input 看不见边界。 */
.sites-form-row input {
  background: var(--surface-2);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--r);
  padding: 9px 12px;
  font-size: var(--fs-base);
  font-family: inherit;
  outline: none;
  width: 100%;
  max-width: 460px;                 /* 即使 dialog 被拉大也不会跟着无限长 */
  transition: border-color 0.12s, background 0.12s, box-shadow 0.12s;
}
.sites-form-row textarea {
  background: var(--surface-2);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--r);
  padding: 9px 12px;
  font-size: var(--fs-base);
  font-family: inherit;
  outline: none;
  resize: vertical;
  width: 100%;
  max-width: 460px;
  transition: border-color 0.12s, background 0.12s, box-shadow 0.12s;
}
.sites-form-row input:hover { border-color: var(--border-strong); background: var(--surface-3); }
/* 焦点态加 3px accent ring，与全局按钮 / select 等 focus-visible 风格一致，
   比单纯 border-color 变化更易识别（键盘可达性 ↑）。 */
.sites-form-row input:focus,
.sites-form-row textarea:focus {
  border-color: var(--accent);
  background: var(--surface-3);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.sites-form-row input:focus-visible,
.sites-form-row textarea:focus-visible { outline: none; }
.sites-form-id {
  font-family: var(--font-mono);
  font-size: var(--fs-base);
  color: var(--text-secondary);
  background: var(--surface-2);
  padding: 7px 12px;
  border-radius: var(--r);
  border: 1px solid var(--border-subtle);
  display: inline-block;
  width: fit-content;
}
.sites-form-error {
  color: var(--danger);
  font-size: var(--fs-base);
  padding: 7px 10px;
  background: var(--danger-soft);
  border: 1px solid var(--danger-ring);
  border-radius: var(--r);
}
.sites-form-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 6px;
  padding-top: 14px;
  border-top: 1px solid var(--border-subtle);
}
.sites-form-actions .btn { width: auto; min-width: 100px; }

/* Table pager "查看更多" */
.pager-row td {
  text-align: center;
  padding: 10px 0 !important;
  background: var(--surface-1);
  border-top: 1px dashed var(--border);
}
.pager-row .pager-more { width: auto; min-width: 120px; }
.pager-row .pager-more:disabled { opacity: 0.6; }

/* ====================================================================
   Toast
   ==================================================================== */
.toast-region {
  position: fixed;
  right: 24px;
  bottom: 24px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  z-index: 100;
  pointer-events: none;
  max-width: 360px;
}
.toast {
  pointer-events: auto;
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 12px 16px;
  /* 2026-09 审计修复：toast 之前是硬编码 rgba(22,22,26,0.94)，
     浮在浅色卡片上像「异形深色」块。改为 var(--bg-elevated) + backdrop-filter，
     跟着主题走（dark 仍稳、light 自然融入）。 */
  background: var(--bg-elevated);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  font-size: var(--fs-base);
  box-shadow: var(--shadow);
  opacity: 0;
  transform: translateY(8px) scale(0.98);
  transition: opacity 0.18s var(--ease), transform 0.18s var(--ease);
}
.toast::before {
  content: "";
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--text-tertiary);
  flex-shrink: 0;
}
.toast-success::before { background: var(--success); box-shadow: 0 0 6px var(--success-ring); }
.toast-error::before   { background: var(--danger);  box-shadow: 0 0 6px var(--danger-ring); }
.toast-warn::before    { background: var(--warning); box-shadow: 0 0 6px var(--warning-ring); }
.toast-info::before    { background: var(--accent);  box-shadow: 0 0 6px var(--ring-medium); }
.toast-success { border-color: var(--success-ring); }
.toast-error   { border-color: var(--danger-ring); }
.toast-warn    { border-color: var(--warning-ring); }
.toast-info    { border-color: var(--ring-medium); }
.toast-show    { opacity: 1; transform: translateY(0) scale(1); }
.toast-hide    { opacity: 0; transform: translateY(8px) scale(0.98); }

/* Login page（独立 /dashboard/login，使用同一套主题 tokens）*/
.login-body {
  margin: 0;
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg);
  font-family: var(--font-sans);
  color: var(--text);
  padding: var(--space-6);
  position: relative;
  overflow: hidden;
}
/* 登录页背景：与 dashboard 同款的双 radial-gradient 暖色光晕，
   让登录页「像同一个产品的同一页」而不是一个空白表单。
   固定定位 + pointer-events:none，避免挡住 form 交互。 */
.login-bg {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  background:
    radial-gradient(900px 520px at 88% -8%, var(--accent-soft) 0%, transparent 60%),
    radial-gradient(720px 480px at 8% 102%, var(--accent-softer) 0%, transparent 60%);
  opacity: 0.95;
}
/* 网格纹理：极细的 1px 点阵（0.04 透明度），给登录页一点「工程 / 数据」气质；
   与 dashboard 主体保持视觉关联。 */
.login-bg::after {
  content: "";
  position: absolute;
  inset: 0;
  background-image:
    radial-gradient(circle at 1px 1px, var(--border-strong) 1px, transparent 0);
  background-size: 28px 28px;
  opacity: 0.18;
  mask-image: radial-gradient(circle at 50% 50%, black 0%, transparent 75%);
  -webkit-mask-image: radial-gradient(circle at 50% 50%, black 0%, transparent 75%);
}

.login-wrap {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: 420px;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--space-5);
}
/* 顶部品牌：图标 + 渐变文字名，与 dashboard topbar 视觉对齐。 */
.login-brand {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  margin: 0 auto var(--space-1);
  text-decoration: none;
  color: inherit;
  transition: opacity 0.15s var(--ease);
}
.login-brand:hover { opacity: 0.85; }
.login-brand-mark {
  width: 36px;
  height: 36px;
  border-radius: 9px;
  object-fit: cover;
  box-shadow: 0 4px 14px var(--ring-medium);
}
.login-brand-name {
  background: var(--gradient-brand);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
  font-size: var(--fs-xl);
  font-weight: var(--fw-bold);
  letter-spacing: -0.02em;
}

.login-card {
  background: var(--surface-1);
  border: 1px solid var(--border-subtle);
  border-radius: var(--r-xl);
  padding: 32px 28px 26px;
  display: flex;
  flex-direction: column;
  gap: 24px;
  /* 浮起感：浅色主题下也能看出与背景分层 */
  box-shadow:
    0 1px 0 var(--highlight-line) inset,
    0 16px 40px var(--button-shadow),
    0 2px 6px var(--button-shadow);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
}
.login-head { display: flex; flex-direction: column; gap: 8px; }
.login-title {
  margin: 0;
  font-size: 24px;
  font-weight: var(--fw-bold);
  color: var(--text);
  letter-spacing: var(--tracking-tight);
  line-height: 1.3;
  display: inline-flex;
  align-items: center;
  gap: 10px;
}
/* 标题左侧 accent 短竖条 — 与 dashboard panel/trend/sites 标题风格一致 */
.login-title::before {
  content: "";
  display: inline-block;
  width: var(--stripe-w-md);
  height: var(--stripe-h-page);
  background: var(--gradient-brand);
  border-radius: 2px;
  flex-shrink: 0;
}
.login-sub {
  margin: 0;
  font-size: var(--fs-base);
  color: var(--text-tertiary);
  line-height: 1.55;
}
.login-form { display: flex; flex-direction: column; gap: 18px; }
.login-row { display: flex; flex-direction: column; gap: 7px; }
.login-row label {
  font-size: var(--fs-sm);
  color: var(--text-secondary);
  font-weight: var(--fw-medium);
  letter-spacing: 0;
}
.login-input {
  appearance: none;
  width: 100%;
  box-sizing: border-box;
  padding: 12px 14px;
  background: var(--surface-2);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  font: inherit;
  font-size: var(--fs-md);
  font-family: var(--font-sans);
  outline: none;
  transition: border-color 120ms var(--ease), background 120ms var(--ease), box-shadow 120ms var(--ease);
}
.login-input::placeholder { color: var(--text-muted); }
.login-input:hover { border-color: var(--border-strong); background: var(--surface-3); }
.login-input:focus {
  border-color: var(--accent);
  background: var(--surface-1);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.login-input:autofill,
.login-input:-webkit-autofill {
  -webkit-text-fill-color: var(--text);
  -webkit-box-shadow: 0 0 0 1000px var(--surface-2) inset;
}
.login-input:disabled { opacity: 0.6; cursor: not-allowed; }
.login-error {
  background: var(--danger-soft);
  border: 1px solid var(--danger-ring);
  color: var(--danger);
  padding: 10px 12px;
  border-radius: var(--r-md);
  font-size: var(--fs-base);
  line-height: 1.5;
  display: flex;
  align-items: center;
  gap: 8px;
}
/* 2026-10：双层防御。即便顶部 [hidden] 全局兜底因为某些 edge case
   （如 IE/old WebView 解析器异常）失效，.login-error 自身 hidden 时也不该
   显示——避免无内容的红框在每次登录页加载时出现。 */
.login-error[hidden] { display: none; }
.login-error::before {
  content: "";
  flex-shrink: 0;
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--danger);
  box-shadow: 0 0 6px var(--danger-ring);
}
.login-submit {
  width: 100%;
  padding: 12px 16px;
  font-size: var(--fs-md);
  font-weight: var(--fw-semibold);
  margin-top: 6px;
  min-height: 44px;
  letter-spacing: 0.01em;
}
.login-foot {
  text-align: center;
  font-size: var(--fs-sm);
  color: var(--text-muted);
  letter-spacing: 0;
  padding: 4px 0;
}
@media (max-width: 480px) {
  .login-body { padding: var(--space-4); }
  .login-card { padding: 24px 20px 22px; }
  .login-title { font-size: 20px; }
  .login-wrap { max-width: 100%; }
}

/* ====================================================================
   Settings dialog (lib/settings.js)
   4 段：外观 / 交互 / 数据 / 重置。
   通用：swatch grid + segmented control + switch；不依赖 innerHTML。
   ==================================================================== */

/* 段分隔：每段标题 + 行；段间 1px 横线（视觉分组） */
.settings-section {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 14px 0 4px;
}
.settings-section + .settings-section { border-top: 1px solid var(--border-subtle); }
.settings-section-title {
  margin: 0;
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  color: var(--text-tertiary);
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

/* 一行：左标签 + 右控件 */
.settings-row {
  display: grid;
  grid-template-columns: 92px 1fr;
  align-items: center;
  gap: 14px;
  min-height: 32px;
}
.settings-row-label {
  font-size: var(--fs-base);
  color: var(--text-secondary);
  font-weight: var(--fw-medium);
  letter-spacing: 0;
  /* label 不换行；超长时截断 */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.settings-row-note {
  margin: 0;
  font-size: var(--fs-xs);
  color: var(--text-muted);
  grid-column: 1 / -1;
  margin-top: 6px;
}

/* 小型 segmented（用于 settings dialog 内，比顶栏时间范围略紧凑） */
.segmented.segmented-sm { padding: 2px; gap: 1px; }
.segmented.segmented-sm button { padding: 4px 11px; font-size: var(--fs-sm); }

/* ---------- Palette swatch grid ---------- */
.swatch-grid {
  display: inline-flex;
  gap: 10px;
  padding: 2px 0;
  flex-wrap: wrap;
}
.swatch {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: transparent;
  border: 2px solid transparent;
  cursor: pointer;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  outline: none;
  transition:
    border-color 0.15s var(--ease),
    transform 0.1s var(--ease),
    box-shadow 0.15s var(--ease);
}
.swatch-fill {
  display: block;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  /* background 由 JS 用 style.setProperty 注入（hex 来自 PALETTE_SWATCH_HEX） */
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.12);
}
.swatch:hover { transform: scale(1.06); }
.swatch:focus-visible {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.swatch[data-active="true"] {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

/* ---------- Switch (启用动画) ---------- */
.switch {
  position: relative;
  width: 40px;
  height: 22px;
  background: var(--surface-3);
  border: 1px solid var(--border);
  cursor: pointer;
  border-radius: var(--r-pill);
  padding: 0;
  outline: none;
  transition: background 0.15s var(--ease), border-color 0.15s var(--ease);
}
.switch:focus-visible {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.switch-thumb {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--text);
  box-shadow: 0 1px 2px var(--button-shadow);
  transition: left 0.18s var(--ease-out), background 0.15s var(--ease);
}
.switch[aria-checked="true"] {
  background: var(--accent);
  border-color: var(--accent);
}
.switch[aria-checked="true"] .switch-thumb {
  left: 20px;
  background: #ffffff;
}

/* ---------- 重置按钮 ---------- */
.settings-reset-btn { align-self: flex-start; }
.settings-confirm-text {
  margin: 0;
  color: var(--text-secondary);
  font-size: var(--fs-base);
  line-height: 1.6;
}

/* ====================================================================
   侧栏「刷新」手动按钮（仅图标可点击）
   状态文字保持静态指示（带 stale 颜色变化），只把刷新图标做成 button：
     · 点击立即触发 refresh()；
     · 刷新中图标持续旋转、按钮 disabled；
     · 旁边 .meta-fresh-label 维持 stale/fresh 文字色语义。
   ==================================================================== */
@keyframes spin { to { transform: rotate(360deg); } }

/* 紧凑图标按钮：22x22，比 topbar .icon-btn(32x32) 小一档，
   贴合 sidebar meta-row 的视觉密度。 */
.meta-fresh-icon {
  width: 22px;
  height: 22px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: none;
  padding: 0;
  margin: 0;
  border-radius: var(--r-sm);
  cursor: pointer;
  color: var(--text-tertiary);
  flex-shrink: 0;
  transition: color 0.15s var(--ease), background 0.15s var(--ease);
}
.meta-fresh-icon:hover {
  background: var(--ghost-tint-hover);
  color: var(--text);
}
.meta-fresh-icon:focus-visible {
  outline: 2px solid var(--ring-medium);
  outline-offset: 1px;
}
.meta-fresh-icon[disabled] {
  cursor: wait;
  opacity: 0.55;
}
.meta-fresh-icon[data-refreshing="true"] {
  color: var(--accent-bright);
}
.meta-fresh-icon[data-refreshing="true"] .refresh-icon {
  animation: spin 0.9s linear infinite;
}
.meta-fresh-icon .refresh-icon {
  width: 13px;
  height: 13px;
}

/* 锁定 .meta-fresh-label 宽度，避免每秒 textContent 变化（"刚刚更新"
   → "31 秒前更新" → "1 分钟前更新" 等）导致 .meta-row-value (inline-flex)
   整体宽度变化，进而让 button 在 .meta-row (justify-content: space-between)
   里左右抖动 —— 这是用户报告"刷新按钮抖"的根因。
     - display: inline-block 让 min-width 在 span 上生效
     - text-align: right 让短文字也右对齐到 value 容器右边缘
     - min-width: 6.5em 足够容纳"59 分钟前更新"（fs-base 13.5px ≈ 88px）
     - white-space: nowrap 防意外换行 */
.meta-fresh-label {
  display: inline-block;
  min-width: 6.5em;
  text-align: right;
  white-space: nowrap;
}

/* ====================================================================
   View mode switch (顶栏「综合/单站」分段)
   2026-10 新增：复用 .segmented 样式（layout.css 已定义），这里只补 .view-switch
   在 topbar 里的微调（更紧凑的 padding / 与站点选择器的间距）。
   ==================================================================== */
.view-switch {
  flex-shrink: 0;
}
.view-switch button {
  padding: 5px 13px;
  font-size: var(--fs-base);
}

/* ====================================================================
   Overview hero strip (综合页头部快速摘要)
   2026-10 优化：综合页与单站页区别于「综合页聚合所有站点 + 单站页专注 1 站」。
   在 KPI 行之前加一个紧凑横向 strip，把"总站点 / 在线 / 头部站点"几个无法
   直观塞进 4 个 KPI 卡的关键数字汇总到一行，让用户第一眼看到「全局」而不是
   「单个 KPI」。综合页专属：data-view="overview" 由 applyViewVisibility 控制。
   ==================================================================== */
.overview-hero {
  display: flex;
  align-items: stretch;
  gap: 0;
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: 0;
  overflow: hidden;
  position: relative;
}
/* 左侧 accent 竖条——与 sidebar section-title 一致的"功能锚点"风格，
   让综合页的 hero strip 在视觉层级里站得住，不被 4 张 KPI 卡比下去。
   宽度走 --stripe-w-sm（与 section-title 同级 = 安静副元素）；
   gradient-brand 让 hero 比纯 panel title 更显眼（hero = 页面级摘要）。 */
.overview-hero::before {
  content: "";
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: var(--stripe-w-sm);
  background: var(--gradient-brand);
  border-radius: 0 2px 2px 0;
  pointer-events: none;
}
.overview-hero-item {
  flex: 1 1 0;
  min-width: 0;
  padding: 16px 20px 16px 22px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  position: relative;
}
/* 中间分隔：1px 实线 + 两侧 surface 阶梯 → 视觉上是"几个独立统计块并排" */
.overview-hero-item + .overview-hero-item {
  border-left: 1px solid var(--border-subtle);
}
/* 0 站点时由 JS 加这个类把 4 列骨架隐藏掉，只剩 onboarding wrap 显示。
   不能用 [hidden] 属性 —— items 有 `display:flex` 作者规则（同特异性更晚定义）
   会覆盖 UA 默认的 [hidden]{display:none}，隐藏失效。 */
.overview-hero-item--hidden { display: none !important; }
.overview-hero-label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--text-tertiary);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.overview-hero-label svg { width: 12px; height: 12px; opacity: 0.7; }
.overview-hero-value {
  font-size: var(--fs-lg);
  font-weight: var(--fw-bold);
  color: var(--text);
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
  letter-spacing: var(--tracking-display);
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  min-width: 0;
}
.overview-hero-value-unit {
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  color: var(--text-tertiary);
}
/* hero 里的次级数字（如 "5 个有今日数据"）—— 紧凑，tertiary 文字色 */
.overview-hero-sub {
  color: var(--text-secondary);
  font-size: var(--fs-sm);
  font-variant-numeric: tabular-nums;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.overview-hero-top-site {
  color: var(--accent-bright);
  font-weight: var(--fw-semibold);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 100%;
}

/* 在线数字绿点：让 hero 比 KPI 卡多一个「活的」信号 */
.overview-hero-online-dot {
  display: inline-block;
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--success);
  box-shadow: 0 0 0 0 var(--success-soft);
  animation: pulse 1.8s var(--ease) infinite;
  flex-shrink: 0;
}

/* 空态：0 站点时整个 hero 显示一条 onboarding 提示，比 4 个 "0 / —" 占位更有用 */
.overview-hero-empty {
  flex: 1 1 100%;
  padding: 22px 24px 22px 26px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.overview-hero-empty-title {
  font-size: var(--fs-md);
  font-weight: var(--fw-semibold);
  color: var(--text);
}
.overview-hero-empty-hint {
  font-size: var(--fs-sm);
  color: var(--text-secondary);
}

/* ====================================================================
   Sites summary (综合页「站点列表」)
   2026-10 新增：默认 dashboard 改为综合页后展示的核心面板之一。
   每行 = 一个站点 + 完整 KPI + 元数据，点击跳转单站视图。
   ==================================================================== */
.sites-summary {
  padding: 20px 22px 22px;
}
/* 列宽（2026-10 加了排名列，所有列 nth-child 顺延 1）：
   - nth-child(1) = 排名（col-rank 类另外 width:44px，不在这规则里）
   - nth-child(2) = 站点名 → auto 占剩余空间
   - nth-child(3-5) = PV / 今日 / UV → 数字列紧凑 100px
   - nth-child(6) = 在线 → 84px（dot + 数字）
   - nth-child(7) = 平均时长 → 90px */
.data-table-sites thead th:nth-child(2) { width: auto; }  /* 站点名 自适应 */
.data-table-sites thead th:nth-child(3),
.data-table-sites thead th:nth-child(4),
.data-table-sites thead th:nth-child(5) { width: 100px; }
.data-table-sites thead th:nth-child(6) { width: 84px; }
.data-table-sites thead th:nth-child(7) { width: 90px; }

/* 排名列：综合页独有的 "#1" 列，给站点一个视觉等级。仅综合页生效。 */
.data-table-sites .col-rank {
  width: 44px;
  padding-left: 14px !important;
  padding-right: 4px !important;
}
.site-rank {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 24px;
  height: 22px;
  padding: 0 7px;
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  font-variant-numeric: tabular-nums;
  color: var(--text-tertiary);
  background: var(--surface-2);
  border: 1px solid var(--border-subtle);
  border-radius: var(--r-pill);
  letter-spacing: 0;
}
/* 前三名用 accent 调色 —— 用户进综合页第一眼看到的就是"最活跃 3 个站点" */
.site-rank-top {
  color: var(--accent-bright);
  background: var(--accent-softer);
  border-color: var(--ring-medium);
}
.site-rank-1 {
  background: var(--accent-soft);
  color: var(--accent-bright);
  border-color: var(--accent);
}
.site-summary-row {
  cursor: pointer;
  position: relative;
  transition: background 0.12s var(--ease);
}
.site-summary-row:focus-visible {
  /* 键盘焦点环：表行可聚焦（tabIndex=0）但默认 UA outline 在 border-collapse:collapse
     表格里视觉很弱。改用 inset box-shadow 保留 1px 边界 + 3px accent ring。
     inset 而非 outline 避免被相邻 td border 干扰视觉连续性。 */
  outline: none;
  background: var(--accent-softer);
  box-shadow: inset 0 0 0 2px var(--ring-medium);
}
.site-summary-row:focus-visible td {
  color: var(--text);
}
/* 整行 hover 状态由 .data-table tbody tr:hover 提供；
   这里加一个右侧 chevron 暗示「点击查看详情」，让「可点」信号更强 */
.site-summary-row::after {
  content: "›";
  position: absolute;
  right: 14px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--text-tertiary);
  font-size: 22px;
  line-height: 1;
  font-weight: var(--fw-regular);
  opacity: 0;
  transition: opacity 0.15s var(--ease);
  pointer-events: none;
}
.site-summary-row:hover::after { opacity: 0.6; }
.site-cell {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
  padding-right: 24px;  /* 给 ::after chevron 留位置，避免压到内容 */
}
/* 站点名 + id pill 同行：flex 容器让 pill 紧贴名字右侧，避免名字太长时
   pill 被挤到下一行（之前是 column 布局，长名字 + pill 会出现两行版式） */
.site-cell-head {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}
.site-name {
  font-weight: var(--fw-semibold);
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}
.site-id-pill-sm {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  padding: 1px 6px;
  border-radius: var(--r-xs);
  background: var(--surface-2);
  border: 1px solid var(--border-subtle);
  color: var(--text-secondary);
  display: inline-block;
  width: fit-content;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  flex-shrink: 0;
}
/* 域名行：与 ID pill 同行 → 整行 = 名字+pill / 域名，2 行结构 */
.site-domain {
  color: var(--text-tertiary);
  font-size: var(--fs-xs);
  font-family: var(--font-mono);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 100%;
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
.site-domain::before {
  content: "";
  display: inline-block;
  width: 8px;
  height: 1px;
  background: var(--border-strong);
  flex-shrink: 0;
}

/* PV 列：主数字 + 今日占比 %，让用户看到"这个站点流量在今日区间内的分布"。
   之前只看 PV 绝对值（无今日参考），表格信息密度低。 */
.site-pv-cell {
  display: inline-flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 1px;
  line-height: 1.2;
}
.site-pv-today-pct {
  font-size: 10.5px;
  font-family: var(--font-mono);
  color: var(--text-tertiary);
  font-variant-numeric: tabular-nums;
}

/* 在线列：彩色圆点 + 数字，比纯文本更直观 */
.online-cell {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  justify-content: flex-end;
}
.online-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--success);
  box-shadow: 0 0 0 0 var(--success-soft);
  flex-shrink: 0;
}
/* 在线 = 0 时把点改灰，避免误导「有在线但为 0」的视觉冲突。
   inline 在线数字会在 online-cell 里；空态特判由 JS 加 .online-dot--empty 类
   （不能纯靠 CSS 数文本节点数，::has textContent 检测浏览器兼容性差）。 */
.online-dot--empty { background: var(--text-muted); box-shadow: none; }

/* ====================================================================
   Overview page — empty state (0 sites / 全部无数据)
   综合页专属：当还没有任何站点或所有站点都无事件时，整张「站点列表」卡片
   显示一个友好的 onboarding 提示（不只是"暂无数据"），让首次登录用户知道
   下一步该做什么。
   ==================================================================== */
/* 0 站点时整行只有 1 个 td（colspan=7），需要去掉 td 默认 padding
   否则 onboarding 内容会被压到很窄 */
.sites-empty-row > td {
  padding: 0 !important;
}
.sites-empty-hero {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 10px;
  padding: 36px 24px 32px;
}
.sites-empty-hero-icon {
  width: 48px;
  height: 48px;
  border-radius: var(--r-md);
  background: var(--accent-soft);
  border: 1px solid var(--ring-medium);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--accent-bright);
}
.sites-empty-hero-icon svg { width: 22px; height: 22px; }
.sites-empty-hero-title {
  font-size: var(--fs-md);
  font-weight: var(--fw-semibold);
  color: var(--text);
  margin: 0;
}
.sites-empty-hero-hint {
  margin: 0;
  color: var(--text-secondary);
  font-size: var(--fs-base);
  line-height: 1.55;
  max-width: 480px;
}
.sites-empty-hero-steps {
  list-style: none;
  margin: 6px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: 100%;
  max-width: 480px;
}
.sites-empty-hero-step {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 10px 12px;
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: var(--r);
  text-align: left;
  font-size: var(--fs-sm);
  color: var(--text-secondary);
  line-height: 1.5;
}
.sites-empty-hero-step b {
  color: var(--text);
  font-weight: var(--fw-semibold);
}
.sites-empty-hero-step-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--accent-softer);
  border: 1px solid var(--ring-medium);
  color: var(--accent-bright);
  font-size: 11px;
  font-weight: var(--fw-semibold);
  font-variant-numeric: tabular-nums;
  margin-top: 1px;
}

/* 数据存在但全 0 的状态：保留表格骨架但 body 行内给提示，
   比整张表格切到 empty state 更连贯（用户能看到列名 → 理解含义） */
.sites-zero-row td {
  padding: 32px 16px !important;
  text-align: center !important;
  color: var(--text-tertiary);
}
.sites-zero-row-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  border: 1.5px dashed var(--border-strong);
  background: transparent;
  margin-bottom: 8px;
  color: var(--text-muted);
}
.sites-zero-row-icon svg { width: 16px; height: 16px; }
.sites-zero-row-msg {
  display: block;
  font-size: var(--fs-base);
  color: var(--text-secondary);
  margin-bottom: 4px;
}
.sites-zero-row-hint {
  display: block;
  font-size: var(--fs-sm);
  color: var(--text-tertiary);
}

