/* xg-e2e-console 前端共享样式
   深色专业风为主，@media prefers-color-scheme 兼顾浅色。
   颜色/间距全走 CSS 变量，两套主题只改变量。 */

/* ---------- 主题变量：默认深色 ---------- */
:root {
  --bg:          #0e1116;
  --bg-elev:     #161b22;
  --bg-elev2:    #1c232c;
  --bg-hover:    #202832;
  --border:      #2a323d;
  --border-soft: #21272f;
  --fg:          #e6edf3;
  --fg-muted:    #9aa7b4;
  --fg-dim:      #838e9a;
  --accent:      #4c8dff;
  --accent-btn:  #2f6fe0;   /* 主按钮填充：白字达 4.7:1（--accent 亮蓝只用于链接/圆点/焦点） */
  --on-accent:   #ffffff;
  --accent-bg:   #12233f;
  --focus:       #4c8dff;

  /* 状态色（深色）：--st-* = 填充/进度条/圆点色；--ct-* = 徽章/旗标文字色（在自身 15% tint 上达 ≥4.5:1） */
  --st-passed:   #3fb950;
  --st-failed:   #f85149;
  --st-running:  #4c8dff;
  --st-queued:   #8b949e;
  --st-merging:  #a371f7;
  --st-canceled: #8b949e;
  --st-timeout:  #d29922;
  --st-infra:    #e8833a;   /* 橙：与 timeout 金色拉开（原 #db8b0b 深色下与 timeout 撞色） */
  --st-voided:   #7a828d;   /* 与 --fg-dim 脱钩 */

  --ct-passed:   #3caf4c;
  --ct-failed:   #f95f58;
  --ct-running:  #5794ff;
  --ct-queued:   #8d96a0;
  --ct-merging:  #ab7df8;
  --ct-canceled: #8d96a0;
  --ct-timeout:  #c69020;
  --ct-infra:    #e67828;
  --ct-voided:   #8c939c;

  --mono: ui-monospace, "SF Mono", "JetBrains Mono", "Menlo", "Consolas", monospace;
  --sans: -apple-system, "PingFang SC", "Microsoft YaHei", "Segoe UI", Roboto, sans-serif;

  /* 页宽单一来源：顶栏与主体必须同宽，否则两者左右边界会错开。
     1280 下运行列表 11 列会挤到每行折行（行高 61px→46px 差一倍），1600 后折行消失、再宽无收益。 */
  --page-w: 1600px;

  --radius: 6px;
  --shadow: 0 1px 3px rgba(0,0,0,.4);
  --shadow-lg: 0 24px 56px -16px rgba(0,0,0,.65), 0 2px 8px rgba(0,0,0,.25);
}

@media (prefers-color-scheme: light) {
  :root {
    --bg:          #f6f8fa;
    --bg-elev:     #ffffff;
    --bg-elev2:    #f0f3f6;
    --bg-hover:    #eef2f6;
    --border:      #d5dbe1;
    --border-soft: #e4e8ec;
    --fg:          #1f2328;
    --fg-muted:    #57606a;
    --fg-dim:      #646d77;
    --accent:      #0969da;
    --accent-btn:  #0969da;   /* 浅色下白字压 #0969da 已达 5.2:1，无需另设 */
    --on-accent:   #ffffff;
    --accent-bg:   #ebf3fc;   /* 更淡：accent 文字压在其上达 4.6:1（badge-env / banner-info） */
    --focus:       #0969da;

    --st-passed:   #1a7f37;
    --st-failed:   #cf222e;
    --st-running:  #0969da;
    --st-queued:   #6e7781;
    --st-merging:  #8250df;
    --st-canceled: #6e7781;
    --st-timeout:  #9a6700;
    --st-infra:    #bc4c00;
    --st-voided:   #7a828d;

    --ct-passed:   #187733;
    --ct-failed:   #c1202b;
    --ct-running:  #0861c9;
    --ct-queued:   #606871;
    --ct-merging:  #7741dc;
    --ct-canceled: #606871;
    --ct-timeout:  #8a5c00;
    --ct-infra:    #ad4600;
    --ct-voided:   #636a73;

    --shadow: 0 1px 3px rgba(140,149,159,.25);
    --shadow-lg: 0 20px 48px -16px rgba(31,35,40,.24), 0 2px 6px rgba(31,35,40,.08);
  }
}

/* ---------- 基础 ---------- */
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
  background: var(--bg);
  color: var(--fg);
  font-family: var(--sans);
  font-size: 15px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
code, .mono { font-family: var(--mono); font-size: .92em; }

:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }

/* ---------- 布局 ---------- */
/* 上内边距不能是 0：顶栏 sticky，内容会直接顶在它的下边框上（首页筛选栏最明显） */
.wrap { max-width: var(--page-w); margin: 0 auto; padding: 20px 20px 60px; }

.topbar {
  position: sticky; top: 0; z-index: 20;
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--border);
}
.topbar-inner {
  max-width: var(--page-w); margin: 0 auto; padding: 12px 20px;
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
}
.brand { display: flex; align-items: baseline; gap: 10px; }
.brand h1 { font-size: 23px; margin: 0; font-weight: 700; letter-spacing: .2px; }
.brand .sub { color: var(--fg-dim); font-size: 13px; }
.spacer { flex: 1 1 auto; }

/* ---------- 按钮 ---------- */
.btn {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 14px; border-radius: var(--radius);
  border: 1px solid var(--border); background: var(--bg-elev);
  color: var(--fg); font-size: 14px; font-family: inherit; cursor: pointer;
  transition: background .12s, border-color .12s;
  white-space: nowrap;
}
.btn:hover { background: var(--bg-hover); text-decoration: none; }
.btn:disabled { opacity: .45; cursor: not-allowed; }
.btn-primary { background: var(--accent-btn); border-color: var(--accent-btn); color: var(--on-accent); font-weight: 550; }
.btn-primary:hover { background: color-mix(in srgb, var(--accent-btn) 85%, #000); }
.btn-danger { border-color: color-mix(in srgb, var(--st-failed) 55%, var(--border)); color: var(--st-failed); }
.btn-danger:hover { background: color-mix(in srgb, var(--st-failed) 14%, transparent); }
.btn-sm { padding: 3px 9px; font-size: 13px; }

/* 分段按钮组：几个动作围绕同一件事时连成一段，比等距排开的独立按钮更像一组，
   也把它们和旁边改状态的动作（取消/重跑）区分开。
   等宽卡在「四个汉字」上（4em 字面 + 左右各 14px 内边距 + 两条 1px 边框，border-box 全算进 min-width）：
   分享按钮会切「生成中…／已复制／复制失败」，不定宽整组就跟着抽；
   而这组是 margin-left:auto 右对齐的，抽的是左边那两颗，最扎眼。 */
.btn-group { display: inline-flex; }
.btn-group > .btn {
  border-radius: 0; margin-left: -1px;   /* 负边距吃掉相邻的一条边框，分隔线只剩 1px */
  min-width: calc(4em + 30px); justify-content: center;
}
/* 用逻辑属性写首尾圆角：只有一颗按钮时它同时是 first 和 last，
   写成 border-radius 简写会被后一条整体覆盖，圆角掉一半 */
.btn-group > .btn:first-child {
  border-start-start-radius: var(--radius); border-end-start-radius: var(--radius); margin-left: 0;
}
.btn-group > .btn:last-child {
  border-start-end-radius: var(--radius); border-end-end-radius: var(--radius);
}
/* 抬到邻居之上，否则 hover 高亮的边框和焦点圈会被右邻的 1px 压掉一条 */
.btn-group > .btn:hover, .btn-group > .btn:focus-visible { position: relative; z-index: 1; }

/* ---------- 徽章 / 状态 ---------- */
.badge {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 2px 9px; border-radius: 100px;
  font-size: 13px; font-weight: 550; line-height: 1.6;
  border: 1px solid transparent; white-space: nowrap;
}
.badge-plain {
  background: var(--bg-elev2); color: var(--fg-muted);
  border-color: var(--border-soft); font-weight: 500;
}
.badge-env { background: var(--accent-bg); color: var(--accent); font-family: var(--mono); }

/* 状态徽章：--c = 填充/描边色；文字走 --ct（更亮/更深，保证 ≥4.5:1） */
.status-badge {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 2px 10px; border-radius: 100px; font-size: 13px; font-weight: 600;
  color: var(--ct, var(--c)); background: color-mix(in srgb, var(--c) 15%, transparent);
  border: 1px solid color-mix(in srgb, var(--c) 35%, transparent);
  white-space: nowrap;
}
.status-badge.lg { font-size: 15px; padding: 5px 14px; }
.status-badge .dot { width: 8px; height: 8px; border-radius: 50%; background: currentColor; flex: none; }

/* 状态色映射 */
.st-passed   { --c: var(--st-passed);   --ct: var(--ct-passed); }
.st-failed   { --c: var(--st-failed);   --ct: var(--ct-failed); }
.st-running  { --c: var(--st-running);  --ct: var(--ct-running); }
.st-queued   { --c: var(--st-queued);   --ct: var(--ct-queued); }
.st-merging  { --c: var(--st-merging);  --ct: var(--ct-merging); }
.st-canceled { --c: var(--st-canceled); --ct: var(--ct-canceled); }
.st-timeout  { --c: var(--st-timeout);  --ct: var(--ct-timeout); }
.st-infra_failed { --c: var(--st-infra); --ct: var(--ct-infra); }
.st-voided   { --c: var(--st-voided);   --ct: var(--ct-voided); }
/* shard 复用 */
.st-pending    { --c: var(--st-queued);  --ct: var(--ct-queued); }
.st-dispatched { --c: var(--st-merging); --ct: var(--ct-merging); }
.st-finished   { --c: var(--st-passed);  --ct: var(--ct-passed); }
/* case */
.st-flaky   { --c: var(--st-timeout); --ct: var(--ct-timeout); }
.st-skipped { --c: var(--st-queued);  --ct: var(--ct-queued); }

/* running/merging/dispatched 脉冲 */
.status-badge.st-running .dot,
.status-badge.st-merging .dot,
.status-badge.st-dispatched .dot { animation: pulse 1.4s ease-in-out infinite; }
@keyframes pulse {
  0%,100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--c) 60%, transparent); }
  50%     { box-shadow: 0 0 0 5px color-mix(in srgb, var(--c) 0%, transparent); }
}
@media (prefers-reduced-motion: reduce) {
  .status-badge .dot, .rdot.busy, .live-dot i, .spinner { animation: none !important; }
}

/* ---------- 卡片 / 面板 ---------- */
.card {
  background: var(--bg-elev); border: 1px solid var(--border);
  border-radius: var(--radius); box-shadow: var(--shadow);
}
.section { margin-top: 24px; }
.section-head {
  display: flex; align-items: center; gap: 10px; margin: 0 0 12px;
}
.section-head h2 { font-size: 16px; margin: 0; font-weight: 600; color: var(--fg); }
.section-head .count { color: var(--fg-dim); font-size: 13px; }

/* ---------- 表格 ---------- */
.table-scroll { overflow-x: auto; border-radius: var(--radius); }
table.grid { width: 100%; border-collapse: collapse; font-size: 14px; }
table.grid th {
  text-align: left; font-weight: 550; color: var(--fg-muted);
  padding: 9px 12px; border-bottom: 1px solid var(--border);
  background: var(--bg-elev2); white-space: nowrap; position: sticky; top: 0;
}
table.grid td { padding: 9px 12px; border-bottom: 1px solid var(--border-soft); vertical-align: middle; }
table.grid tbody tr { transition: background .1s; }
table.grid tbody tr.clickable { cursor: pointer; }
table.grid tbody tr.clickable:hover { background: var(--bg-hover); }
table.grid tbody tr:last-child td { border-bottom: none; }
.num { font-variant-numeric: tabular-nums; }

/* ---------- 通过率 ---------- */
.passrate { display: flex; align-items: center; gap: 8px; min-width: 120px; }
.passrate .bar {
  flex: 1 1 auto; height: 6px; border-radius: 3px; overflow: hidden;
  background: var(--bg-elev2); display: flex; min-width: 48px;
}
.passrate .bar i { height: 100%; display: block; }
.passrate .bar .p { background: var(--st-passed); }
.passrate .bar .f { background: var(--st-failed); }
.passrate .bar .k { background: var(--st-timeout); } /* flaky */
.passrate .bar .s { background: var(--st-queued); } /* skipped */
.passrate .txt { font-family: var(--mono); font-size: 13px; color: var(--fg-muted); white-space: nowrap; }

/* ---------- runner 状态条 ---------- */
.runnerbar { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.runnerbar .rlabel { font-size: 13px; color: var(--fg-dim); margin-right: 2px; }
.rdot {
  width: 11px; height: 11px; border-radius: 50%;
  border: 1px solid rgba(0,0,0,.25); cursor: default; position: relative;
}
.rdot.idle    { background: var(--st-passed); }
.rdot.busy    { background: var(--st-running); }
.rdot.busy    { animation: pulse 1.6s ease-in-out infinite; --c: var(--st-running); }
.rdot.offline { background: var(--st-voided); opacity: .6; }
.rdot.warn    { box-shadow: 0 0 0 2px color-mix(in srgb, var(--st-timeout) 55%, transparent); }

/* tooltip（纯 CSS，data-tip）；:focus-visible 让键盘用户也能看到 */
[data-tip] { position: relative; }
[data-tip]:hover::after,
[data-tip]:focus-visible::after {
  content: attr(data-tip);
  position: absolute; bottom: calc(100% + 8px); left: 50%; transform: translateX(-50%);
  background: var(--bg-elev2); color: var(--fg); border: 1px solid var(--border);
  padding: 7px 10px; border-radius: var(--radius); font-size: 13px; line-height: 1.5;
  white-space: pre-wrap; z-index: 60; box-shadow: var(--shadow);
  font-family: var(--mono); pointer-events: none;
  width: max-content; max-width: min(280px, calc(100vw - 24px));
}
/* 顶栏里的 tooltip（执行机圆点）必须向下弹——否则整块被裁在视口顶边之外 */
.topbar [data-tip]:hover::after,
.topbar [data-tip]:focus-visible::after { top: calc(100% + 8px); bottom: auto; }

/* ---------- 截断 + hover 全文 ---------- */
.truncate { max-width: 220px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: inline-block; vertical-align: bottom; }

/* ---------- 筛选栏 ---------- */
.filters { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-bottom: 16px; }
/* [hidden] 必须压过下面这些 display 规则：作者样式表整体优先于浏览器 UA 样式表里的
   [hidden]{display:none}，与选择器权重无关——`.field{display:flex}` 会让 <div class="field" hidden>
   照样显示（新建测试页的「测试账号」在 drill 环境上就这么漏出来过）。全局兜住，别逐个类去补。 */
[hidden] { display: none !important; }
.field { display: flex; flex-direction: column; gap: 5px; }
.field > label { font-size: 13px; color: var(--fg-muted); font-weight: 500; }
select, input[type=text], input[type=number], input[type=email],
input[type=password], input[type=search], textarea {
  background: var(--bg-elev); color: var(--fg);
  border: 1px solid var(--border); border-radius: var(--radius);
  padding: 7px 10px; font-size: 14px; font-family: inherit; min-width: 0;
}
select:focus, input:focus, textarea:focus { border-color: var(--accent); }
/* 只在指针聚焦时去掉外框；键盘聚焦仍走全局 :focus-visible 的焦点环 */
select:focus:not(:focus-visible), input:focus:not(:focus-visible), textarea:focus:not(:focus-visible) { outline: none; }
textarea { resize: vertical; font-family: var(--mono); }
.hint { font-size: 13px; color: var(--fg-dim); }
.hint.warn { color: var(--st-timeout); }

/* ---------- 表单 ---------- */
.form { max-width: 720px; }
.form .field { margin-bottom: 18px; }
.form .field.inline { flex-direction: row; align-items: center; gap: 10px; }
.form label.req::after { content: " *"; color: var(--st-failed); }
.checks { display: flex; flex-direction: column; gap: 8px; padding: 4px 0; }
.checks label { display: flex; align-items: center; gap: 8px; font-size: 14px; cursor: pointer; }
.checks .rmeta { color: var(--fg-dim); font-size: 13px; font-family: var(--mono); margin-left: auto; }
/* 新建测试：分支行 / 标签 chips / 模糊推荐下拉 / grep 预览 */
.branch-row { display: flex; gap: 8px; align-items: center; }
.branch-row select { flex: 1; }
/* 与 select 等高：矮一截的小按钮看着像悬空装饰，实测会被整个忽略 */
.branch-row .btn { flex: none; align-self: stretch; }
.branch-row .reload-icon { font-size: 16px; line-height: 1; }
/* 拉取中转起来：分支列表可能几十毫秒就回来，只靠 disabled 变淡等于没反馈 */
.branch-row .btn.is-loading .reload-icon { display: inline-block; animation: spin .7s linear infinite; }
.chips-row { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; min-height: 30px; }
.tag-chip { display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px; border: 1px solid var(--border);
  border-radius: 999px; font-size: 13px; font-family: var(--mono); cursor: pointer; user-select: none;
  background: var(--bg-elev); }
.tag-chip:hover { background: var(--bg-hover); }
.tag-chip:has(input:checked) { border-color: var(--accent); background: var(--accent-bg); color: var(--accent); }
.tag-chip input { margin: 0; accent-color: var(--accent); }
.suggest-wrap { position: relative; }
.suggest-wrap input { width: 100%; }
.suggest-box { position: absolute; top: calc(100% + 4px); left: 0; right: 0; z-index: 30;
  background: var(--bg-elev2); border: 1px solid var(--border); border-radius: var(--radius);
  max-height: 280px; overflow-y: auto; box-shadow: var(--shadow-lg); }
.suggest-item { display: flex; align-items: baseline; gap: 8px; padding: 7px 10px; font-size: 14px; cursor: pointer; }
.suggest-item:hover, .suggest-item.active { background: var(--bg-hover); }
.suggest-item .s-type { flex: none; font-size: 12px; color: var(--fg-dim); border: 1px solid var(--border-soft);
  border-radius: 4px; padding: 0 5px; }
.suggest-item .s-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.suggest-item .s-file { flex: none; margin-left: auto; font-size: 12px; color: var(--fg-dim); font-family: var(--mono); }
.grep-preview-row { display: flex; align-items: flex-start; gap: 8px; }
.grep-preview-row .badge { flex: none; margin-top: 3px; }
.grep-preview { flex: 1; min-width: 0; display: block; padding: 8px 12px; border: 1px dashed var(--border); border-radius: var(--radius);
  font-family: var(--mono); font-size: 13px; word-break: break-all; background: var(--bg-elev); }
.grep-preview.is-empty { color: var(--fg-dim); }
#hitCount { margin-top: 5px; }
.muted { opacity: .45; }
span.warn { color: var(--st-timeout); }

details.adv { border: 1px solid var(--border); border-radius: var(--radius); padding: 0 14px; margin-bottom: 18px; }
details.adv > summary { cursor: pointer; padding: 12px 0; font-weight: 550; font-size: 14px; user-select: none; }
details.adv[open] { padding-bottom: 14px; }
.row2 { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }

/* ---------- 详情头 ----------
   分两层：主行只放"哪次跑 / 结果如何 / 能做什么"，其余事实沉到元信息网格。
   原来 10 项 + 操作全挤一条 flex：横向密度过高扫不动，右侧还空一大块。 */
.run-head { padding: 0; }
.rh-top { display: flex; align-items: center; gap: 10px 14px; flex-wrap: wrap; padding: 16px 20px; }
.rh-title { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; min-width: 0; }
.run-id { font-family: var(--mono); font-size: 24px; font-weight: 650; }
.run-head .actions { margin-left: auto; display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }

/* 元信息网格：列固定，标签与值上下成对、跨行对齐——比 flex 挤一行好扫得多 */
.rh-meta {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 16px 24px;
  padding: 16px 20px; border-top: 1px solid var(--border);
  background: var(--bg-elev2); border-radius: 0 0 var(--radius) var(--radius);
}
.rh-meta .meta { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.rh-meta .k { font-size: 12px; color: var(--fg-dim); text-transform: uppercase; letter-spacing: .5px; }
.rh-meta .v { font-size: 14px; color: var(--fg); overflow-wrap: anywhere; }
.rh-meta .v .dim { color: var(--fg-dim); white-space: nowrap; }
.rh-meta .v .dim::before { content: "·"; margin: 0 5px; color: var(--border); }
/* 通过率条别撑满整格：条越长越难一眼读出比例，10/10 也被顶到格子最右边 */
.rh-meta .passrate { min-width: 0; }
.rh-meta .passrate .bar { max-width: 150px; }
/* 徽章与通过率文本拉齐到值的字号（状态徽章走 .lg，不参与） */
.run-head .badge, .run-head .passrate .txt { font-size: 14px; }

@media (max-width: 1020px) { .rh-meta { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
/* 手机上到 2 列为止：单列会把 7 项拉成一条很长的瀑布，比原来更难看完 */
@media (max-width: 760px) {
  .rh-top { padding: 14px 16px; }
  .rh-meta { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px 16px; padding: 14px 16px; }
}

/* ---------- shard 卡片 ---------- */
.shards { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 300px), 1fr)); gap: 12px; }
.shard { padding: 14px; }
.shard .sh-top { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
.shard .sh-idx { font-family: var(--mono); font-weight: 650; }
.shard .sh-grid { display: grid; grid-template-columns: auto 1fr; gap: 4px 12px; font-size: 13px; }
.shard .sh-grid .k { color: var(--fg-dim); }
.shard .sh-grid .v { color: var(--fg); font-family: var(--mono); word-break: break-all; }
.flag { display: inline-flex; align-items: center; gap: 4px; padding: 1px 7px; border-radius: 4px; font-size: 13px; font-weight: 600; }
.flag-lost   { background: color-mix(in srgb, var(--st-failed) 15%, transparent); color: var(--ct-failed); border: 1px solid color-mix(in srgb, var(--st-failed) 40%, transparent); }
.flag-zombie { background: color-mix(in srgb, var(--st-timeout) 15%, transparent); color: var(--ct-timeout); border: 1px solid color-mix(in srgb, var(--st-timeout) 40%, transparent); }
.upl { display: inline-block; width: 7px; height: 7px; border-radius: 50%; }
.upl.on { background: var(--st-passed); }
.upl.off { background: var(--border); }
.countdown { font-family: var(--mono); font-variant-numeric: tabular-nums; }
.countdown.over { color: var(--st-failed); }

/* ---------- error 摘要 ---------- */
.err-excerpt {
  font-family: var(--mono); font-size: 13px; color: var(--st-failed);
  background: color-mix(in srgb, var(--st-failed) 8%, transparent);
  border-left: 2px solid var(--st-failed); padding: 8px 10px; border-radius: 0 4px 4px 0;
  white-space: pre-wrap; word-break: break-word; max-height: 260px; overflow: auto; margin-top: 6px;
}
details.caseerr summary { cursor: pointer; color: var(--st-failed); font-size: 13px; }

/* ---------- 提示条 ---------- */
.banner {
  padding: 12px 16px; border-radius: var(--radius); font-size: 14px;
  display: flex; align-items: center; gap: 10px; margin-bottom: 16px;
}
.banner-info  { background: var(--accent-bg); color: var(--accent); border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent); }
.banner-error { background: color-mix(in srgb, var(--st-failed) 12%, transparent); color: var(--st-failed); border: 1px solid color-mix(in srgb, var(--st-failed) 35%, transparent); }
.banner-merge { background: color-mix(in srgb, var(--st-merging) 12%, transparent); color: var(--st-merging); border: 1px solid color-mix(in srgb, var(--st-merging) 35%, transparent); }
/* warn＝「能用但有事要交代」，比 error 轻一档（如：未配密钥，测试账号功能停用中） */
.banner-warn  { background: color-mix(in srgb, var(--st-timeout) 12%, transparent); color: var(--st-timeout); border: 1px solid color-mix(in srgb, var(--st-timeout) 35%, transparent); }

/* ---------- 空态 / 加载 ---------- */
.empty { text-align: center; padding: 60px 20px; color: var(--fg-dim); }
.empty .big { font-size: 16px; color: var(--fg-muted); margin-bottom: 6px; }
.loading { display: flex; align-items: center; gap: 10px; color: var(--fg-muted); padding: 40px 20px; justify-content: center; }
.spinner {
  width: 16px; height: 16px; border: 2px solid var(--border);
  border-top-color: var(--accent); border-radius: 50%; animation: spin .7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* 轮询指示 */
/* 点只有 7px，太难悬停——加 padding 把 hover/focus 命中区撑到 ~19px，圆点视觉大小不变 */
.live-dot { display: inline-flex; align-items: center; padding: 6px; font-size: 13px; color: var(--fg-dim); cursor: default; }
.live-dot i { width: 7px; height: 7px; border-radius: 50%; background: var(--st-running); animation: pulse 1.6s ease-in-out infinite; --c: var(--st-running); }
.live-dot.stopped i { background: var(--fg-muted); --c: var(--fg-muted); animation: pulse 2.8s ease-in-out infinite; }

/* ---------- 登录 ---------- */
.authwrap {
  min-height: 100vh; min-height: 100dvh;
  display: flex; align-items: center; justify-content: center;
  padding: 24px; position: relative; overflow: hidden;
}
/* 背景：两团柔光 + 中心渐隐的细网格，明暗两套都成立 */
.authwrap::before, .authwrap::after { content: ''; position: absolute; inset: 0; z-index: 0; pointer-events: none; }
.authwrap::before {
  background:
    radial-gradient(640px 440px at 50% -10%, color-mix(in srgb, var(--accent) 20%, transparent), transparent 70%),
    radial-gradient(520px 380px at 92% 108%, color-mix(in srgb, var(--st-merging) 13%, transparent), transparent 72%);
}
.authwrap::after {
  background-image:
    linear-gradient(to right, var(--border-soft) 1px, transparent 1px),
    linear-gradient(to bottom, var(--border-soft) 1px, transparent 1px);
  background-size: 44px 44px;
  -webkit-mask-image: radial-gradient(600px 420px at 50% 42%, #000, transparent 78%);
  mask-image: radial-gradient(600px 420px at 50% 42%, #000, transparent 78%);
  opacity: .55;
}

.authcard {
  position: relative; z-index: 1;
  width: 100%; max-width: 400px; padding: 32px 30px 24px;
  border-radius: 14px; box-shadow: var(--shadow-lg);
}

.authbrand { text-align: center; margin-bottom: 24px; }
.authlogo {
  display: block; width: 54px; height: 54px; margin: 0 auto 14px;
  /* 底板已是白色（原来是蓝色渐变块），辉光调淡一档，免得压过标志本身 */
  filter: drop-shadow(0 8px 18px color-mix(in srgb, var(--accent) 28%, transparent));
}
.authbrand h1 { font-size: 27px; margin: 0; font-weight: 700; letter-spacing: .3px; }

.authcard .form { max-width: none; }
.authcard .form .field { margin-bottom: 16px; }
.authcard .field > label { font-size: 14px; color: var(--fg-muted); font-weight: 600; }
.authcard input { width: 100%; padding: 10px 12px; font-size: 15px; border-radius: 8px; }
.authcard .btn-primary {
  width: 100%; justify-content: center; margin-top: 6px;
  padding: 10px 16px; font-size: 15px; font-weight: 600; border-radius: 8px;
}

/* 登录方式页签（验证码 / 密码） */
.authtabs {
  display: flex; gap: 4px; margin-bottom: 18px; padding: 3px;
  background: var(--bg-hover); border-radius: 9px;
}
.authtab {
  flex: 1; padding: 7px 0; font-size: 14px; font-weight: 550;
  border: 0; border-radius: 7px; background: transparent; color: var(--fg-muted); cursor: pointer;
}
.authtab.on { background: var(--bg); color: var(--fg); box-shadow: var(--shadow-sm, 0 1px 2px rgba(0,0,0,.08)); }

/* 验证码行：输入框 + 获取按钮 */
.code-row { display: flex; gap: 8px; }
.code-row input { flex: 1; min-width: 0; }
.code-row .btn { flex: none; white-space: nowrap; border-radius: 8px; }
.code-row .btn:disabled { font-variant-numeric: tabular-nums; }

/* 密码显隐切换 */
.pw-wrap { position: relative; display: flex; }
.pw-wrap input { padding-right: 44px; }
.pw-toggle {
  position: absolute; right: 5px; top: 50%; transform: translateY(-50%);
  display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 30px; padding: 0; border: none; border-radius: 6px;
  background: none; color: var(--fg-dim); cursor: pointer;
}
.pw-toggle:hover { color: var(--fg); background: var(--bg-elev2); }
.pw-toggle .i-off { display: none; }
.pw-toggle.on .i-on { display: none; }
.pw-toggle.on .i-off { display: block; }

.authfoot {
  margin-top: 20px; padding-top: 15px; border-top: 1px solid var(--border-soft);
  text-align: center; font-size: 13px; color: var(--fg-dim); line-height: 1.8;
}

@media (max-width: 480px) {
  .authcard { padding: 26px 20px 20px; border-radius: 12px; }
  .authwrap::after { display: none; }
}

/* 顶栏用户菜单：邮箱为触发器，悬浮 / 键盘聚焦展开下拉 */
.usermenu {
  position: relative; display: inline-flex; align-items: center; gap: 4px;
  font-size: 14px; padding: 4px 2px; cursor: default; outline: none; user-select: none;
}
.usermenu .um-email { color: var(--fg-muted); max-width: 220px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.usermenu .um-caret { color: var(--fg-dim); font-size: 11px; transition: transform .15s ease; }
.usermenu:hover .um-email, .usermenu:focus-within .um-email { color: var(--fg); }
.usermenu:hover .um-caret, .usermenu:focus-within .um-caret { transform: rotate(180deg); color: var(--fg-muted); }

.um-pop {
  position: absolute; top: 100%; right: 0; margin-top: 6px; min-width: 150px;
  display: flex; flex-direction: column; padding: 6px;
  background: var(--bg-elev); border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: 0 10px 28px rgba(0,0,0,.22);
  opacity: 0; visibility: hidden; transform: translateY(-4px);
  transition: opacity .12s ease, transform .12s ease, visibility .12s;
  z-index: 30;
}
.usermenu:hover .um-pop, .usermenu:focus-within .um-pop { opacity: 1; visibility: visible; transform: translateY(0); }
/* 触发器与面板间的 6px 缝隙加透明桥，鼠标下移不丢 hover */
.um-pop::before { content: ''; position: absolute; top: -6px; left: 0; right: 0; height: 6px; }

.um-item {
  display: block; box-sizing: border-box; width: 100%; text-align: left;
  padding: 7px 10px; border-radius: 6px; font-size: 14px; line-height: 1.2; white-space: nowrap;
  color: var(--fg-muted); background: none; border: none; cursor: pointer;
  font-family: inherit; text-decoration: none;
}
.um-item:hover, .um-item:focus-visible { background: var(--bg-elev2); color: var(--fg); outline: none; }
.um-pop .um-logout { color: var(--fg-dim); margin-top: 4px; padding-top: 9px; border-top: 1px solid var(--border); border-radius: 0 0 6px 6px; }
.um-pop .um-logout:hover, .um-pop .um-logout:focus-visible { color: var(--fg); }

/* 临时密码 / 分享链接展示 */
.secret-val {
  font-family: var(--mono); font-size: 15px; font-weight: 600; letter-spacing: .5px;
  background: var(--bg-elev2); color: var(--fg); padding: 4px 10px; border-radius: var(--radius);
  border: 1px solid var(--border); user-select: all;
}
.share-url {
  flex: 1 1 320px; min-width: 0; font-family: var(--mono); font-size: 13px;
  background: var(--bg-elev2); color: var(--fg); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 6px 10px;
}

/* ---------- 本地重放弹层 ---------- */
.replay-dlg {
  width: min(760px, calc(100vw - 32px)); padding: 0;
  border: 1px solid var(--border); border-radius: 10px;
  background: var(--bg-elev); color: var(--fg); box-shadow: var(--shadow-lg);
}
.replay-dlg::backdrop { background: rgba(0, 0, 0, .55); }
.replay-head { display: flex; align-items: center; gap: 12px; padding: 14px 18px; border-bottom: 1px solid var(--border-soft); }
.replay-head h2 { margin: 0; font-size: 16px; flex: 1; }
.replay-body { padding: 2px 18px 18px; }
/* 弹层里的提示条是整段说明，不是一行标语：顶对齐 + 放宽行距，别让第二行贴着第一行 */
.replay-dlg .banner { margin: 14px 18px 0; align-items: flex-start; line-height: 1.6; }
.replay-step { margin-top: 16px; }
.replay-label { font-size: 13px; color: var(--fg-muted); margin-bottom: 6px; }
.replay-cmd {
  display: flex; align-items: flex-start; gap: 8px; padding: 9px 10px;
  background: var(--bg-elev2); border: 1px solid var(--border-soft); border-radius: var(--radius);
}
/* 整条摊开、不横向滚：这里的目的是「跑之前看清楚要跑什么」，横滚会把后半截藏起来，
   而且 auto 在部分环境下会给每个块都留 15px 滚动条沟槽（连不溢出的也留），一排灰杠很脏。
   精确复制交给右边按钮；user-select:all＝单击选中整条，clipboard 被拒时还能手动 ⌘C。 */
.replay-cmd code { flex: 1; min-width: 0; white-space: pre-wrap; overflow-wrap: anywhere; user-select: all; line-height: 1.6; }
.replay-cmd .btn { flex: none; }
.replay-step .hint { display: block; margin-top: 6px; line-height: 1.6; }
.case-actions { display: flex; gap: 6px; }

/* ---------- 响应式 ---------- */
@media (max-width: 720px) {
  /* 上内边距同样不能是 0——顶栏 sticky，否则手机上内容照样顶在它下边框上（桌面已在 .wrap 修过） */
  .wrap { padding: 16px 12px 40px; }
  .replay-head, .replay-body, .replay-dlg .banner { padding-left: 12px; padding-right: 12px; }
  .replay-dlg .banner { margin-left: 12px; margin-right: 12px; }
  .topbar-inner { padding: 10px 12px; }
  .row2 { grid-template-columns: 1fr; }
  .run-head .actions { margin-left: 0; width: 100%; }
  .hide-sm { display: none !important; }
}

/* ---------- 执行机页 ---------- */

/* 顶栏圆点条整体是一个通往执行机页的链接：圆点只有 11px，逐点做小链接的命中区太小，
   还会让每台机多出一个 Tab 停靠点。下划线得关掉——这里不是正文里的链接。 */
.rbar-link {
  display: inline-flex; align-items: center; gap: 6px; flex-wrap: wrap;
  color: inherit; padding: 2px 5px; border-radius: var(--radius);
}
.rbar-link:hover { text-decoration: none; background: var(--bg-hover); }

/* 四态徽章。走 .status-badge 的 --c/--ct 机制，只多一组颜色映射。
   失联用琥珀不用红：机器关机过夜也会落进这一档（>120s 无心跳即失联），
   红色天天亮着就没人再看红色了；真正伤到某次 run 的是分片上的 .flag-lost 红标。 */
.st-runner-idle    { --c: var(--st-passed);  --ct: var(--ct-passed); }
.st-runner-busy    { --c: var(--st-running); --ct: var(--ct-running); }
.st-runner-offline { --c: var(--st-voided);  --ct: var(--ct-voided); }
.st-runner-lost    { --c: var(--st-timeout); --ct: var(--ct-timeout); }
/* 等待心跳＝在线但状态未知（调度台此时也不会派活）：与离线同色，别用绿的让人以为可用 */
.st-runner-unknown { --c: var(--st-queued);  --ct: var(--ct-queued); }
.status-badge.st-runner-busy .dot { animation: pulse 1.4s ease-in-out infinite; }

.rn-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 400px), 1fr)); gap: 12px; }
/* 竖向 flex ＋ 页脚 margin-top:auto：同一行的卡片内容长短不一，页脚不对齐会显得每张卡都在漂 */
.rn-card { display: flex; flex-direction: column; }

.rn-head {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 12px 14px; border-bottom: 1px solid var(--border-soft);
}
.rn-head .rn-id { font-size: 16px; font-weight: 650; }
.rn-dim { color: var(--fg-dim); }

/* 陈旧读数的免责声明放在读数**上方**：把断连前的最后一帧当实时展示，比不展示更坏 */
.rn-stale-note {
  padding: 8px 14px; font-size: 13px; line-height: 1.5;
  color: var(--ct-timeout); background: color-mix(in srgb, var(--st-timeout) 10%, transparent);
  border-bottom: 1px solid var(--border-soft);
}

.rn-metrics { padding: 12px 14px; display: flex; flex-direction: column; gap: 12px; }
/* 历史值：左侧虚线 + 文字降一档 + 进度条压暗，扫一眼就知道这几行不是此刻的事实。
   刻意**不给整块加 opacity**——那会把标签文字一起压到对比度不达标（明暗双主题都是），
   而「这数是旧的」不该以「这数看不清」为代价。 */
.rn-metrics.is-stale { border-left: 2px dashed var(--border); }
.rn-metrics.is-stale .k, .rn-metrics.is-stale .v { color: var(--fg-muted); }
.rn-metrics.is-stale .meter { opacity: .55; }
.rn-metric-top { display: flex; align-items: baseline; gap: 8px; font-size: 13px; }
.rn-metric-top .k { color: var(--fg-dim); flex: none; }
.rn-metric-top .v { color: var(--fg); text-align: right; }
/* 口径/单位注解：虚框 + help 光标表示"这里有话说"，正文由 [data-tip] 弹出 */
.rn-note {
  flex: none; font-size: 12px; color: var(--fg-dim); cursor: help;
  border: 1px dashed var(--border); border-radius: 4px; padding: 0 5px;
}

.meter { height: 6px; border-radius: 3px; background: var(--bg-elev2); overflow: hidden; margin-top: 6px; }
.meter > i { display: block; height: 100%; background: var(--st-running); }
.meter.lv-warn > i { background: var(--st-timeout); }
.meter.lv-danger > i { background: var(--st-failed); }
/* 未知 ≠ 0%：0% 的意思是"闲得很"。斜纹空槽把"没数据"与"真的很空"区分开 */
.meter.is-unknown {
  background: repeating-linear-gradient(-45deg, var(--bg-elev2) 0 4px, var(--border-soft) 4px 8px);
}

.rn-sparks { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; padding: 0 14px 12px; }
.rn-spark {
  min-width: 0; padding: 6px 8px 4px;
  background: var(--bg-elev2); border: 1px solid var(--border-soft); border-radius: var(--radius);
}
.rn-spark-top { display: flex; align-items: baseline; gap: 6px; font-size: 12px; }
.rn-spark-top .k { color: var(--fg-dim); flex: none; }
.rn-spark-top .hint { font-size: 12px; text-align: right; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rn-spark-empty { height: 34px; display: flex; align-items: center; justify-content: center; }
/* viewBox 只有 100 单位宽、按容器横向拉伸（preserveAspectRatio=none）：
   描边必须 non-scaling，否则线宽会跟着被拉成粗细不均的一条 */
.spark { display: block; width: 100%; height: 34px; }
.spark .spark-line {
  fill: none; stroke-width: 1.5; vector-effect: non-scaling-stroke;
  stroke-linejoin: round; stroke-linecap: round;
}
.spark-mem  .spark-line { stroke: var(--st-merging); }
.spark-load .spark-line { stroke: var(--st-running); }
.spark .spark-busy { fill: color-mix(in srgb, var(--st-running) 14%, transparent); }

.rn-sec-title {
  padding: 10px 14px 4px; font-size: 12px; letter-spacing: .5px;
  color: var(--fg-dim); border-top: 1px solid var(--border-soft);
}
.rn-job { padding: 0 14px 12px; }
.rn-job-top { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 8px; }
.rn-kv { display: grid; grid-template-columns: auto 1fr; gap: 4px 12px; font-size: 13px; }
.rn-kv .k { color: var(--fg-dim); }
.rn-kv .v { color: var(--fg); overflow-wrap: anywhere; }
/* 磁盘超水位 / run 已终态这类"能用但有事要交代"的旗标，比 .flag-lost 轻一档 */
.flag-warn {
  background: color-mix(in srgb, var(--st-timeout) 15%, transparent); color: var(--ct-timeout);
  border: 1px solid color-mix(in srgb, var(--st-timeout) 40%, transparent);
}

.rn-recent { padding: 0 14px 12px; display: flex; flex-direction: column; gap: 6px; }
.rn-recent-row { display: flex; align-items: center; gap: 8px; font-size: 13px; min-width: 0; }

.rn-foot {
  margin-top: auto; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px 12px;
  padding: 10px 14px; border-top: 1px solid var(--border-soft); background: var(--bg-elev2);
  border-radius: 0 0 var(--radius) var(--radius);
}
.rn-foot-item { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.rn-foot .k { font-size: 12px; color: var(--fg-dim); }
.rn-foot .v { font-size: 13px; overflow-wrap: anywhere; }

/* 空态里的登记命令：user-select:all ＝ 单击整条选中，直接 ⌘C */
.rn-cmd {
  display: inline-block; margin-top: 10px; padding: 8px 12px; text-align: left;
  background: var(--bg-elev2); border: 1px solid var(--border); border-radius: var(--radius);
  font-size: 13px; user-select: all; overflow-wrap: anywhere;
}

/* 卡片窄到一定程度，两张并排的趋势图就只剩几十像素宽，读不出形状——改上下排 */
@media (max-width: 560px) {
  .rn-sparks { grid-template-columns: 1fr; }
  .rn-foot { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ---------- App 失败截图（run.html mediaSection） ---------- */
/* 手机竖屏截图是 9:19.5 的长条，等宽网格 + 固定高度裁切顶端——失败现场的关键信息
   （对话框/报错文案）大多在屏幕上半部，裁底不裁顶。点开新标签看完整图。 */
.media-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 180px), 1fr)); gap: 12px; }
.media-item {
  display: flex; flex-direction: column; gap: 0; text-decoration: none; color: inherit;
  border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; background: var(--bg-elev2);
}
.media-item:hover { border-color: var(--border-strong, var(--border)); }
.media-item img { width: 100%; height: 240px; object-fit: cover; object-position: top; display: block; background: #000; }
.media-video {
  height: 240px; display: flex; align-items: center; justify-content: center;
  font-size: 15px; color: var(--fg-dim); background: var(--bg-elev2);
}
.media-label { padding: 6px 10px; font-size: 12px; color: var(--fg-dim); border-top: 1px solid var(--border-soft); }
