@font-face{font-family:Inter;src:url(vendor/fonts/Inter-latin.woff2) format("woff2");font-weight:400 700;font-display:swap}
:root{--bg:#09090b;--card:#18181b;--line:#27272a;--fg:#f4f4f5;--muted:#a1a1aa;--accent:#3b82f6;--ok:#22c55e;--loss:#ef4444;--r:8px}
*{box-sizing:border-box}
html,body{margin:0;height:100%}
body{background:var(--bg);color:var(--fg);font:14px/1.5 Inter,system-ui,-apple-system,"Noto Sans SC",sans-serif}
.app{display:grid;grid-template-columns:280px 1fr;min-height:100vh}
.mono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}
.muted{color:var(--muted)}
.sidebar{border-right:1px solid var(--line);padding:16px;display:flex;flex-direction:column;gap:16px;min-height:100vh}
.brand{display:flex;justify-content:space-between;align-items:flex-start;gap:8px}
.lang{flex:0 0 auto}
.lang .btn.small{padding:3px 8px;min-width:34px}
.brand-title{font-size:16px;font-weight:600}
.brand-sub{font-size:12px;color:var(--muted);letter-spacing:.06em;margin-top:2px}
.actions{display:flex;flex-direction:column;gap:8px}
.btn{background:var(--card);color:var(--fg);border:1px solid var(--line);border-radius:var(--r);padding:8px 12px;font:inherit;cursor:pointer}
.btn:hover{border-color:#3f3f46}
.btn:disabled{opacity:.45;cursor:not-allowed}
.btn.primary{background:var(--accent);border-color:var(--accent);color:#fff;font-weight:600}
.btn.danger{border-color:var(--loss);color:var(--loss)}
.btn.small{padding:4px 8px;font-size:12px}
.row{display:flex;gap:8px}.row .btn{flex:1}
.seg{display:flex;gap:4px}
.seg .btn.small{flex:1;padding:6px 4px}
.seg .btn.active{border-color:var(--accent);color:var(--accent)}
.hint{font-size:12px}
.who{display:grid;grid-template-columns:1fr 1fr;gap:8px;font-size:12px;color:var(--muted)}
.who label{display:flex;flex-direction:column;gap:4px;min-width:0}
.who input{background:var(--bg);color:var(--fg);border:1px solid var(--line);border-radius:var(--r);padding:5px 8px;font:inherit;font-size:13px;min-width:0}
.who input:focus{outline:none;border-color:var(--accent)}
.hist-head{display:flex;justify-content:space-between;color:var(--muted);font-size:12px;border-top:1px solid var(--line);padding-top:12px}
.hist-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:8px;overflow:auto;flex:1}
.hist-item{background:var(--card);border:1px solid var(--line);border-radius:var(--r);padding:8px 10px;cursor:pointer}
.hist-item:hover{border-color:#3f3f46}
.hist-item .t{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--muted)}
.hist-item .t .t-time{margin-right:auto}
.hist-item .pick{display:flex;align-items:center;cursor:pointer}
.hist-item .pick input{accent-color:var(--accent);width:13px;height:13px;margin:0;cursor:pointer}
.hist-item .del{padding:2px 6px;line-height:0;display:inline-flex;align-items:center}  /* 沿用 .tag 胶囊外观，只放图标 */
.hist-item .del:hover{color:var(--loss);border-color:var(--loss)}
.hist-item .v{font-size:13px;margin-top:4px}
.hist-empty{color:var(--muted);font-size:12px;padding:8px 2px}
.hist-foot{display:flex;gap:8px;border-top:1px solid var(--line);padding-top:12px}
.hist-foot .btn{flex:1;padding:6px 4px}
.main{padding:24px;display:flex;flex-direction:column;gap:16px}
.topbar{display:flex;justify-content:space-between;align-items:center}
.status{color:var(--muted)}
.node{font-size:12px;color:var(--muted)}
.cards{display:grid;grid-template-columns:1fr 1fr;gap:16px}
.card{background:var(--card);border:1px solid var(--line);border-radius:var(--r);padding:16px;display:flex;flex-direction:column;gap:16px}
.card header{display:flex;justify-content:space-between;align-items:center}
.card h2{margin:0;font-size:15px;font-weight:600}
.tag{font-size:12px;color:var(--muted);border:1px solid var(--line);border-radius:999px;padding:2px 8px}
.tag.on{color:var(--accent);border-color:var(--accent)}
.tag.bad{color:var(--loss);border-color:var(--loss)}
.gauges{display:grid;grid-template-columns:1fr 1fr;gap:12px}
.gauge{display:flex;flex-direction:column;align-items:center;gap:2px}
.dial-wrap{width:100%;max-width:170px}
.dial{display:block;width:100%}
.dial-track{fill:none;stroke:var(--line);stroke-width:7;stroke-linecap:round}
.dial-fill{fill:none;stroke:var(--accent);stroke-width:7;stroke-linecap:round}  /* 不要在这里写 dasharray：CSS 会压过 JS 更新的同名属性 */
.dial-needle{stroke:var(--fg);stroke-width:2.2;stroke-linecap:round}
.dial-hub{fill:var(--fg)}
.dial-value{margin-top:-10px;font-size:19px;font-weight:600;font-variant-numeric:tabular-nums}
.dial-unit{font-size:10px;font-weight:400;color:var(--muted);margin-left:3px}
.dial-label{font-size:12px;color:var(--muted)}
.metrics{display:grid;grid-template-columns:repeat(auto-fit,minmax(96px,1fr));gap:12px}
.metric .value{font-size:26px;font-weight:600;font-variant-numeric:tabular-nums}
.metric.big .value{font-size:34px}
.metric .value.bad{color:var(--loss)}
.metric .value.good{color:var(--ok)}
.metric .unit{font-size:12px;color:var(--muted)}
.bar{height:6px;background:var(--line);border-radius:3px;overflow:hidden}
.bar-fill{height:100%;width:0;background:var(--accent);transition:width .2s}
.loss-meta{font-size:12px;color:var(--muted)}
canvas{width:100%;height:120px;display:block}
dialog{background:var(--card);color:var(--fg);border:1px solid var(--line);border-radius:var(--r);max-width:640px;width:90%;padding:16px}
dialog[open]{display:flex;flex-direction:column;gap:12px;max-height:85vh}   /* 关闭按钮固定在滚动区之外 */
dialog[open] .dialog-body{flex:1 1 auto;min-height:0}      /* 必须 basis:auto：flex:1 是 basis:0，WebKit 上会把正文压成 0 高（内容看不见） */
dialog[open] > .btn{align-self:flex-end}
dialog::backdrop{background:rgba(0,0,0,.6)}
dialog h3{margin:0 0 12px;font-size:14px}
.dialog-body{overflow:auto}       /* 高度上限交给 dialog 的 max-height，正文自身不再设限 */
.dialog-body pre{white-space:pre-wrap;font-size:12px;color:var(--muted);margin:0}
#compare{max-width:940px}
.cmp{overflow-x:auto}
.cmp table{border-collapse:collapse;font-size:12px;width:100%}
.cmp th,.cmp td{border-bottom:1px solid var(--line);padding:6px 8px;text-align:right;white-space:nowrap}
.cmp thead th{color:var(--muted);font-weight:500;vertical-align:top;position:sticky;top:0;background:var(--bg)}
.cmp tbody th{text-align:left;font-weight:500;color:var(--fg);position:sticky;left:0;background:var(--bg)}
.cmp .th-sub{font-size:11px;color:var(--muted);font-weight:400;margin-top:2px}
.cmp .na{color:var(--muted)}
.cmp .muted{color:var(--muted)}
.cmp-val{font-variant-numeric:tabular-nums}
.cmp-bar{height:3px;background:var(--line);border-radius:2px;margin-top:3px}
.cmp-bar i{display:block;height:100%;background:var(--accent);border-radius:2px}
.cmp .vd{font-size:11px}
.cmp .vd.good{color:var(--ok)}
.cmp .vd.bad{color:var(--loss)}

@media (max-width:900px){
  /* 手机/窄屏：单列，且把"操作区"留在结果上方、历史记录挪到结果下方
     （display:contents 让侧栏的子元素直接参与 .app 的排序，不必改 DOM） */
  .app{display:flex;flex-direction:column;gap:12px;padding:12px;min-height:0}
  .sidebar{display:contents}
  .sidebar > .brand{order:1}
  .sidebar > .actions{order:2}
  .main{order:3;padding:0;gap:12px}
  .sidebar > .hist-head{order:4}
  .sidebar > .hist-list{order:5}
  .sidebar > .hist-foot{order:6}
  .main,.sidebar > *{min-width:0}
  .hist-list{flex:none;overflow:visible}      /* 整页滚动即可，手机上别套二次滚动 */
  .cards{grid-template-columns:1fr}
  .topbar{flex-wrap:wrap;gap:4px 12px}
  .metric .value{font-size:24px}
  .metric.big .value{font-size:30px}
  .hist-item .del{padding:5px 9px}            /* 图标按钮在手机上给足点击面积 */
  .hist-item .pick input{width:16px;height:16px}
  dialog{width:calc(100vw - 16px);max-width:none;max-height:88vh}
}

@media (max-width:560px){
  /* 窄屏用固定列数：auto-fit 在 304px 内容宽下只排得下 3 列，第 4 个指标会被甩到第二行留空洞；
     而且英文标签比中文宽——同样 360px，中文 4 个放得下、英文放不下 */
  #card-speed .metrics{grid-template-columns:repeat(2,1fr)}
  #card-loss .metrics{grid-template-columns:repeat(3,1fr)}
}
