/* RC Shadow Display — 展台大屏样式。
 *
 * 尺寸全部用 vh/vw：这块屏可能是 1920×1080 也可能是 4K，观众在三米外。
 * 正文最小 1.9vh（50 寸屏上约 1.2cm 字高），标题和那个倍数远大于此。
 * 深底高反差，不用任何外部字体 —— 现场可能没网。
 */

:root {
  --bg: #060b14;
  --panel: #0b1220;
  --panel-2: #0a101c;
  --line: rgba(148, 163, 184, 0.18);
  --text: #e6edf6;
  --muted: #93a4bb;
  --human: #22d3ee;
  --ghost: #fbbf24;
  --hot: #fb7185;
  --cool: #7dd3fc;
  --accent: #38bdf8;
  --set-edge: #93a4bb;          /* 布景线框:和 taskscene.js 的 COLORS.setEdge 同一个值 */
  --font: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial,
          "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
  --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

* { box-sizing: border-box; }

html, body {
  margin: 0; padding: 0; height: 100%; width: 100%;
  background: var(--bg); color: var(--text);
  font-family: var(--font);
  overflow: hidden;
  -webkit-font-smoothing: antialiased;
}

body {
  display: flex; flex-direction: column;
  height: 100vh;
}

/* ------------------------------------------------------------- header --- */

#top {
  flex: 0 0 auto;
  display: flex; align-items: baseline; gap: 2.4vw;
  padding: 0.8vh 1.6vw 0.7vh;
  border-bottom: 1px solid var(--line);
}

.brand {
  font-size: 3.0vh; font-weight: 900; letter-spacing: 0.06em;
  color: #fff; white-space: nowrap;
}
.brand-sub {
  margin-left: 0.7vw; font-size: 1.9vh; font-weight: 700;
  letter-spacing: 0.22em; color: var(--muted);
}

.meta {
  display: flex; gap: 1.5vw; flex-wrap: nowrap; overflow: hidden;
  font-size: 1.7vh; color: var(--muted); letter-spacing: 0.02em;
  min-width: 0;
}
/* 策略名可以很长；让它省略号而不是把页头挤成三行、把画面往下推 */
.meta > span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.meta > span:nth-child(2) { flex: 0 1 auto; min-width: 0; }
.meta b { color: var(--text); font-weight: 700; font-family: var(--mono); }
.meta .ro {
  margin-left: auto; color: #6b7f99; font-style: italic; white-space: nowrap;
}

/* --------------------------------------------------- synthetic banner --- */

.synthetic {
  display: none;
  flex: 0 0 auto;
  align-items: center; gap: 1.2vw;
  padding: 0.6vh 1.6vw;
  background: repeating-linear-gradient(135deg,
      rgba(251, 191, 36, 0.14) 0 14px, rgba(251, 191, 36, 0.07) 14px 28px);
  border-bottom: 2px solid rgba(251, 191, 36, 0.55);
  font-size: 1.7vh; min-width: 0;
}
.synthetic.show { display: flex; }
.synthetic .tag {
  font-weight: 900; letter-spacing: 0.14em; font-size: 2.1vh;
  color: #0b1220; background: var(--ghost);
  padding: 0.25vh 0.7vw; border-radius: 3px; white-space: nowrap;
}
.synthetic .en { color: #fde68a; font-weight: 600;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
.synthetic .zh { color: #d4b872; margin-left: auto; text-align: right;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; flex: 0 1 auto; }

.warnbar {
  display: none; flex: 0 0 auto;
  padding: 0.6vh 1.6vw; background: rgba(244, 63, 94, 0.2);
  border-bottom: 2px solid #fb7185;
  font-size: 2.0vh; font-weight: 800; color: #fecdd3;
}
.warnbar.show { display: block; }

/* ----------------------------------------------------------- headline --- */

#headline-wrap {
  flex: 0 0 auto;
  padding: 0.25vh 1.6vw 0.2vh;
  text-align: center;
}
#headline {
  font-size: 2.25vh; font-weight: 900; letter-spacing: -0.01em; line-height: 1.05;
  color: #fff;
}
#headline-zh {
  margin-top: 0.2vh; font-size: 1.8vh; color: var(--muted); font-weight: 600;
}
.task-events {
  display: none; margin-top: 0.1vh; font-size: 1.45vh; color: #6b7f99; font-weight: 600;
}
.task-events.show { display: block; }

/* --------------------------------------------------------------- main --- */

/* 矩阵是主角，占左边；三维画面在右边。founder 要的是「虚拟画面」，
 * lead 要的是「矩阵才是产品」—— 两个都得在，所以并排而不是二选一。 */
#main {
  flex: 1 1 auto;
  display: grid; grid-template-columns: 44fr 56fr; gap: 1.0vw;
  padding: 0 1.6vw; min-height: 0;
}

.sec-title {
  flex: 0 0 auto;
  font-size: 1.7vh; font-weight: 900; letter-spacing: 0.1em; text-transform: uppercase;
  color: #b9c8db; padding: 0 0 0.35vh 0.2vw;
}
.sec-title span { color: var(--muted); font-weight: 700; }
.sec-title .hint b { color: #93a4bb; font-family: var(--mono); font-weight: 700; }
.sec-title .hint {
  float: right; text-transform: none; letter-spacing: 0.01em;
  font-size: 1.5vh; font-weight: 600; color: #6b7f99;
}

#matrix-wrap {
  display: flex; flex-direction: column; min-height: 0; min-width: 0;
}
#matrix { flex: 1 1 auto; min-height: 0; overflow: hidden; display: flex; flex-direction: column; }

/* --------------------------------------------------------------- 矩阵 --- */

.mx-warn {
  flex: 0 0 auto;
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.5vw;
  background: rgba(244, 63, 94, 0.14);
  border: 2px solid rgba(244, 63, 94, 0.55); border-radius: 5px;
  padding: 0.45vh 0.7vw; margin-bottom: 0.45vh;
}
.mx-warn-tag {
  font-size: 1.75vh; font-weight: 900; letter-spacing: 0.1em;
  color: #0b1220; background: #fb7185; padding: 0.15vh 0.5vw; border-radius: 3px;
}
.mx-warn-text { font-size: 1.6vh; font-weight: 800; color: #fecdd3; }
.mx-warn-sub { flex: 1 0 100%; font-size: 1.35vh; color: #f3a8b4; line-height: 1.35; }

.mx { flex: 1 1 auto; display: flex; flex-direction: column; gap: 0.25vh; min-height: 0; }
.mx-row {
  display: grid;
  grid-template-columns: 15.5vw repeat(var(--mx-cols, 2), 1fr) 7vw;
  gap: 0.4vw; align-items: center;
  background: rgba(148, 163, 184, 0.045);
  border-left: 3px solid transparent;
  border-radius: 4px; padding: 0.2vh 0.4vw;
  flex: 1 1 0; min-height: 0; overflow: hidden;   /* 行数多少都塞得下 */
}
.mx-row.mx-head { background: none; flex: 0 0 auto; padding-bottom: 0; }
.mx-row.baseline { background: rgba(148, 163, 184, 0.13); border-left-color: #94a3b8; }
.mx-row.primary { background: rgba(56, 189, 248, 0.11); border-left-color: var(--cool); }
.mx-row.ghosted { box-shadow: inset 0 0 0 1px rgba(226, 232, 240, 0.18); }

.mx-rh {
  display: grid; grid-template-columns: 1.3vh 1.1vw 1fr; grid-auto-rows: min-content;
  align-items: center; gap: 0 0.3vw; min-width: 0;
  font-size: 1.6vh; color: var(--text);
}
/* 第二行：型号名 + 标签，跨过圆点和序号那两列 */
.mx-rh .mx-model, .mx-rh .mx-tag { grid-column: 3 / -1; }
.mx-rh .mx-tag { justify-self: start; margin-top: 0.1vh; }
.mx-head .mx-rh { display: block; }
.mx-head .mx-rh, .mx-bh {
  font-size: 1.5vh; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase;
  color: #70819a;
}
.mx-bh { color: var(--accent, #93a4bb); text-align: center; }
.mx-dot { width: 1.2vh; height: 1.2vh; border-radius: 50%; }
.mx-dot.off { opacity: 0.25; }
.mx-num { font-family: var(--mono); font-size: 1.35vh; color: #64748b; text-align: right; }
.mx-pid {
  font-family: var(--mono); font-size: 1.6vh; font-weight: 800; color: #fff;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.mx-model {
  font-size: 1.25vh; color: #7f90a8; white-space: nowrap; overflow: hidden;
  text-overflow: ellipsis; min-width: 0;
}
.mx-tag {
  font-size: 1.2vh; font-weight: 900; letter-spacing: 0.08em; flex: 0 0 auto;
  color: #0b1220; background: #94a3b8; padding: 0 0.3vw; border-radius: 2px;
}
.mx-tag.legacy { background: #475569; color: #cbd5e1; }

.mx-cell { display: flex; flex-direction: column; justify-content: center; min-width: 0; }
.mx-v {
  font-family: var(--mono); font-size: 2.2vh; font-weight: 900; color: var(--text);
  line-height: 1.0; text-align: center;
}
.mx-v.none { color: #4d5c73; }
.mx-bar {
  height: 0.55vh; background: rgba(148, 163, 184, 0.14); border-radius: 2px;
  margin: 0.25vh 0; overflow: hidden;
}
.mx-bar i { display: block; height: 100%; }
/* 度量不具区分力时条形图整个撤掉：条形图是在邀请横向比大小，
 * 而那正是此刻不成立的动作。斜纹代表「这里本来有编码，被撤了」。 */
.mx-bar.withheld {
  background: repeating-linear-gradient(135deg,
    rgba(148,163,184,0.18) 0 4px, rgba(148,163,184,0.05) 4px 8px);
}
.mx-meta.censored { color: #fb7185; font-weight: 700; }
.mx-meta { font-size: 1.1vh; color: #6b7f99; text-align: center; white-space: nowrap;
           overflow: hidden; text-overflow: ellipsis; }

.mx-spread { display: flex; flex-direction: column; justify-content: center; align-items: center; }
.mx-sv { font-family: var(--mono); font-size: 2.0vh; font-weight: 900; color: #e2e8f0; }
.mx-sv.big { color: #fbbf24; font-size: 2.5vh; }
.mx-sv.none { color: #4d5c73; }
.mx-rt { font-size: 1.1vh; font-weight: 700; white-space: nowrap; }
.mx-rt.ok { color: #34d399; }
.mx-rt.part { color: var(--ghost); }
.mx-rt.bad { color: #fb7185; }
.mx-rt.unknown { color: #5c6d86; }

.mx-foot {
  flex: 0 0 auto; padding-top: 0.3vh;
  font-size: 1.15vh; color: #64748b; line-height: 1.2;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* -------------------------------------------------------------- stage --- */

#stage {
  display: grid; gap: 0.8vw; min-height: 0; min-width: 0;
}
/* 本体数量由数据决定：两块并排是常态，三块也撑得住（brief 说 2–3 个本体） */
#stage.cols-1 { grid-template-columns: 1fr; }
#stage { grid-auto-rows: 1fr; }
#stage.cols-2 { grid-template-columns: 1fr 1fr; }
#stage.cols-3 { grid-template-columns: 1fr 1fr 1fr; }
#stage.cols-3 .p-title { font-size: 2.3vh; }
#stage.cols-3 .p-sub { font-size: 1.6vh; }
#stage.cols-3 .big-num { font-size: 3.8vh; }
#stage.cols-3 .num { font-size: 2.0vh; }
#stage.cols-3 .stat label { font-size: 1.25vh; }
#stage.cols-3 .panel-foot { gap: 0.75vw; }
#stage.cols-3 .stat label { white-space: nowrap; font-size: 1.15vh; letter-spacing: 0.04em; }
#stage.cols-3 .stat.wide { min-width: 5.2vw; }
#stage.cols-3 .rcsn { font-size: 1.2vh; }
#stage.cols-3 .scene-legend { font-size: 1.35vh; }
#stage.cols-3 .pill { font-size: 1.4vh; }
.panel.unused { display: none; }
.legend .leg2 { display: none; }
.legend.has3 .leg2 { display: inline; }

.panel {
  --accent: #38bdf8;
  position: relative;
  display: flex; flex-direction: column; min-height: 0;
  background: var(--panel);
  border: 1px solid var(--line);
  border-top: 4px solid var(--accent);
  border-radius: 6px;
  overflow: hidden;
  transition: opacity 180ms ease;
}
#panel-1 { --accent: #fb7185; }
#panel-2 { --accent: #a78bfa; }
.panel.dim { opacity: 0.28; }

.panel-head {
  flex: 0 0 auto;
  display: flex; align-items: flex-start; justify-content: space-between; gap: 0.5vw;
  padding: 0.55vh 0.8vw 0.4vh; min-width: 0;
}
.panel-head > div:first-child { min-width: 0; }
.p-title {
  font-size: 2.3vh; font-weight: 900; color: #fff;
  font-family: var(--mono); letter-spacing: -0.02em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.p-sub { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 1.5vh; color: var(--muted); font-weight: 600; margin-top: 0.15vh; }

.pill {
  flex: 0 0 auto;
  font-size: 1.35vh; font-weight: 800; letter-spacing: 0.1em;
  padding: 0.3vh 0.5vw; border-radius: 999px; white-space: nowrap;
  border: 2px solid currentColor; font-family: var(--mono);
}
.pill.live { color: #34d399; background: rgba(52, 211, 153, 0.12); }
.pill.lagging { color: var(--ghost); background: rgba(251, 191, 36, 0.12); }
.pill.offline { color: #7b8ca6; background: rgba(123, 140, 166, 0.10); }

/* 证据视口固定高度。原来是 flex:1 1 auto，于是它吸收面板里所有其他块的高度差 ——
   实测两块面板的 jspace 文字换行差 25px，画布就差 24px（246 vs 222）。
   两个并排比较的本体，视口大小不同本身就是一个仪器差异。 */
.canvas-wrap {
  flex: 0 0 auto; height: var(--canvas-h); min-height: 0; position: relative;
  background: #0b1220;
  border-top: 1px solid var(--line); border-bottom: 1px solid var(--line);
}
.canvas-wrap canvas { display: block; width: 100% !important; height: 100% !important; }

.scene-legend {
  position: absolute; left: 0.7vw; top: 0.7vh; z-index: 5;
  display: flex; flex-direction: column; gap: 0.25vh;
  font-size: 1.55vh; font-weight: 700; color: #b9c8db;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.9); pointer-events: none;
}
.scene-legend i {
  display: inline-block; width: 1.8vh; height: 0.7vh; border-radius: 2px;
  margin-right: 0.4vw; vertical-align: middle;
}
.scene-legend .h { background: var(--human); }
.scene-legend .g { background: var(--ghost); }

/* ------------------------------------------------- 布景 / 测量 两层标注 --- */

/* 常驻徽标。三维画面里也有一条一样的字(sprite),那条跟着相机走、
 * 截图裁掉边框也还在;这条保证在任何缩放下都清楚可读。两条都要。 */
/* 「这幅画里什么是真的」——**画布之外**的一条固定高度的带子。
 *
 * 它曾经是画布右上角的浮层。挪出来的理由不是排版洁癖:画面上半部分正是
 * 肘部,6 自由度和 7 自由度的区别就长在那儿,而那是这块屏要证明的东西。
 * 一条解释画面的说明,盖住画面里承载结论的那一块,和一条自己把读者劝走的
 * 警告是同一类错误 —— 正确的东西把自己打败了。
 *
 * 固定三行,高度可预测;整块只有 .truthbar 一个包裹元素,要挪到别处
 * 改这一条规则就够,不用碰 index.html。 */
.truthbar {
  flex: 0 0 auto;
  padding: 0.25vh 0.8vw 0.3vh;
  border-bottom: 1px solid var(--line);
  background: rgba(7, 12, 21, 0.75);
  min-width: 0; overflow: hidden;
}

.setbadge {
  font-size: 1.15vh; font-weight: 800; letter-spacing: 0.05em;
  color: #b8c6d8; line-height: 1.4;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.setbadge:empty { display: none; }
.setbadge.off { opacity: 0.4; border-style: dotted; }

/* 物体数据那一行。没有物体位姿是**常态**,所以默认色不是报警红 ——
 * 红色留给自相矛盾(manifest 说能算结果,obs 里却一个物体都没有)。 */
.objects { max-width: 100%; }
.objects b {
  display: block; font-size: 1.3vh; font-weight: 800; color: #93a4bb;
  line-height: 1.4; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.objects span {
  display: block; font-size: 1.15vh; font-weight: 600; color: #6b7e96;
  line-height: 1.4; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.objects.present b { color: #34d399; }
.objects.partial b { color: var(--ghost); }
.objects.undeclared b { color: var(--ghost); }
.objects.conflict b { color: var(--hot); }
.objects.conflict span { color: #fca5a5; }
.objects.undeclared span { color: #d4b872; }

.p-fallback {
  display: none;
  position: absolute; left: 0; right: 0; top: 42%;
  text-align: center; font-size: 2.2vh; font-weight: 700; color: var(--ghost);
  padding: 0 2vw;
}
.p-fallback.show { display: block; }

.panel-foot {
  flex: 0 0 auto;
  display: flex; align-items: flex-end; gap: 0.9vw;
  padding: 0.5vh 0.8vw 0.1vh; min-width: 0; overflow: hidden;
}
.stat { display: flex; flex-direction: column; gap: 0.1vh; }
.stat.wide { min-width: 0; }
.stat label {
  font-size: 1.25vh; font-weight: 700; letter-spacing: 0.06em;
  text-transform: uppercase; color: #70819a;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.big-num {
  font-family: var(--mono); font-size: 4.0vh; font-weight: 900; line-height: 0.95;
  letter-spacing: -0.03em;
}
.big-num.hot { color: var(--hot); }
.big-num.cool { color: var(--cool); }
.big-num.idle { color: #4d5c73; }
.big-num.stale { color: #8b7a4a; }   /* 旧值：暗下来，标签里写着几秒前 */
.num {
  font-family: var(--mono); font-size: 1.9vh; font-weight: 800; color: var(--text);
  line-height: 1.05; white-space: nowrap;
}
/* hold 基线：永远贴在主数字旁边。规则不是装饰。 */
.holdref {
  font-family: var(--mono); font-size: 1.9vh; font-weight: 800; color: #94a3b8;
  line-height: 1.05; white-space: nowrap;
}
.holdref.flat { color: #fb7185; }
.mech {
  position: absolute; right: 0.6vw; bottom: 0.5vh; z-index: 5;
  font-size: 1.3vh; font-weight: 700; color: #5c6d86;
  text-shadow: 0 1px 3px rgba(0,0,0,0.9); pointer-events: none;
  max-width: 70%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.mech.articulated { color: #34d399; }
.mech.bf { color: #b39a5e; }

/* 末端那一块 —— **在画面下方，不压在画面上**。
 *
 * 两次都试过，这是第二次改位置，记下为什么：
 *  · 第一版做成 canvas 上的浮层 → 和 .mech / .setbadge / .objects 叠成一团。
 *  · 第二版做成占高度的 flex 行 → .canvas-wrap 是 `flex:1 1 auto; min-height:0`，
 *    面板一挤它就被压到 0 高，整块三维画面消失。
 *  · 现在：仍然是画面下方的 flex 行，但**行数固定为 2**（head 1 行 + sub 1 行），
 *    高度是常量,不随文本长度增长。布局契约由 team-lead 统一持有：
 *    画面保住高度，解释性文字放在画面下方且行数固定。
 *
 * **chrome 不许遮住它所描述的证据。** 一行「这只手是推出来的」如果画在手指上，
 * 它就把自己要说明的东西盖掉了。
 *
 * 颜色是一条判据，不是装饰：
 *   modelled 琥珀 —— 真几何体，但手指是从标量 gripper 插值出来的
 *   static   灰   —— 真几何体，摆不出开/合（没关键帧）
 *   blocked  红   —— 审计判定这份数据画出来会说假话，一个形状都没画
 *   none     灰   —— 没有肯定的身份断言，所以没画
 * head 是那句不可省的话，**固定 1 行、允许省略号**（内容本身写得足够短）；
 * sub 是限定语，1 行。 */
.eef {
  flex: 0 0 auto;
  margin: 0.25vh 1.1vw 0;
  padding: 0.18vh 0.55vw;
  border-radius: 4px;
  border-left: 3px solid currentColor;
  background: rgba(148, 163, 184, 0.07);
}
/* 换行、不省略号 —— 由下面 team-lead 2026-09-27 那条规则统一规定，
 * 这里不再声明 white-space，避免两条规则互相打架。
 * 代价是行数随文案长度变，所以**文案本身要短**：head 和 sub 都压在
 * 两行以内（实测 505 px 面板约 60 字符一行）。 */
.eef b, .eef span { display: block; }
.eef b    { font-size: 1.35vh; font-weight: 800; line-height: 1.24; }
.eef span { font-size: 1.15vh; font-weight: 600; line-height: 1.24; color: #7f90a8; }
.eef.modelled { color: #e0b055; background: rgba(224, 176, 85, 0.10); }
.eef.static   { color: #93a4bb; }
.eef.none     { color: #6b7f99; }
.eef.blocked  { color: #fda4af; background: rgba(244, 63, 94, 0.16); }

.gate {
  flex: 0 0 auto;
  margin: 0.25vh 1.1vw 0;
  padding: 0.25vh 0.6vw;
  border-radius: 4px;
  font-size: 1.55vh; font-weight: 800; letter-spacing: 0.03em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.gate.pass { color: #34d399; background: rgba(52, 211, 153, 0.12); }
.gate.fail { color: #fda4af; background: rgba(244, 63, 94, 0.16); }
.gate.base { color: #93a4bb; background: rgba(148, 163, 184, 0.12); }
.gate.none { color: #6b7f99; background: transparent; }
#main:has(#stage.cols-3) .mx-row { grid-template-columns: 12vw repeat(var(--mx-cols,3), 1fr) 6vw; }
.pill.slow { border-style: dashed; }

.rcsn {
  flex: 0 0 auto;
  padding: 0.1vh 0.8vw 0.4vh;
  font-family: var(--mono); font-size: 1.15vh; color: #5c6d86; letter-spacing: 0.02em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* ------------------------------------------------------------- bottom --- */

#bottom {
  flex: 0 0 12.5vh;
  display: grid; grid-template-columns: 1fr 20vw; gap: 1.0vw;
  padding: 1.0vh 1.6vw 0.4vh; min-height: 0;
}

#strip-wrap {
  display: flex; flex-direction: column; min-height: 0;
  background: var(--panel); border: 1px solid var(--line); border-radius: 6px;
  padding: 0.5vh 0.6vw 0.3vh;
}
#strip { flex: 1 1 auto; width: 100%; min-height: 0; display: block; }

.legend {
  flex: 0 0 auto;
  display: flex; align-items: center; gap: 1.0vw;
  white-space: nowrap; overflow: hidden;
  padding: 0.25vh 0.4vw 0.05vh;
  font-size: 1.4vh; color: var(--muted); font-weight: 600;
}
.legend .spacer { flex: 1 1 auto; }
.legend .keys { color: #5c6d86; font-family: var(--mono); font-size: 1.45vh; }
.legend b { color: var(--text); font-family: var(--mono); font-weight: 800; }
.sw {
  display: inline-block; width: 2.0vh; height: 0.75vh; border-radius: 2px;
  margin-right: 0.45vw; vertical-align: middle;
}
.sw.human { background: var(--human); }
.sw.ghost { background: var(--ghost); }
.sw.gap { background: var(--hot); }
.sw.a { background: #38bdf8; }
.sw.b { background: #fb7185; }
.sw.hold { background: #94a3b8; }
.sw.set { background: transparent; border: 1px dashed var(--set-edge); }
.sw.meas { background: #f8fafc; border-radius: 50%; }
.sw.c { background: #a78bfa; }

#ratio {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  overflow: hidden;
  background: linear-gradient(160deg, #10203a 0%, #0a1322 100%);
  border: 1px solid rgba(56, 189, 248, 0.35); border-radius: 6px;
  padding: 0.6vh 0.8vw; min-height: 0;
}
.ratio-label {
  font-size: 1.3vh; font-weight: 800; letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--muted); text-align: center; line-height: 1.25;
}
#ratio-num {
  font-family: var(--mono); font-size: 7.2vh; font-weight: 900; line-height: 0.92;
  letter-spacing: -0.05em; color: #fff;
  text-shadow: 0 0 6vh rgba(56, 189, 248, 0.35);
}
#ratio-detail {
  font-family: var(--mono); font-size: 1.8vh; font-weight: 800; color: var(--muted);
  letter-spacing: 0.02em;
}
.ratio-policy {
  font-family: var(--mono); font-size: 1.5vh; font-weight: 800; color: var(--cool);
  margin-top: 0.2vh;
}
.ratio-control {
  font-family: var(--mono); font-size: 1.5vh; font-weight: 800; color: #94a3b8;
  margin-top: 0.15vh;
}
.ratio-verdict {
  margin-top: 0.3vh; padding: 0.15vh 0.5vw; border-radius: 3px;
  font-size: 1.25vh; font-weight: 800; line-height: 1.25; text-align: center;
  max-width: 100%; overflow: hidden;
}
.ratio-verdict.est { color: #34d399; background: rgba(52, 211, 153, 0.13); }
.ratio-verdict.weak { color: #fda4af; background: rgba(244, 63, 94, 0.15); }

.mx-subhead {
  flex: 0 0 auto; display: flex; align-items: baseline; gap: 0.4vw;
  padding: 0 0.4vw 0.2vh; font-size: 1.1vh; color: #6b7f99; line-height: 1.2;
  white-space: nowrap; overflow: hidden;
}
.mx-subhead > span:last-child { overflow: hidden; text-overflow: ellipsis; }
.mx-diag {
  flex: 0 0 auto; font-weight: 900; letter-spacing: 0.06em; text-transform: uppercase;
  color: #0b1220; background: #7f90a8; padding: 0 0.3vw; border-radius: 2px;
}
.mx-over { font-size: 1.1vh; font-weight: 800; white-space: nowrap; }
.mx-over.est { color: #34d399; }
.mx-over.weak { color: #fb7185; }
.mx-sv.est { color: #e2e8f0; }
.mx-sv.weak { color: #7b8ca6; }

.rcsn.mount-unknown { color: #8b7a4a; }

.ratio-note {
  display: none; margin-top: 0.3vh; padding: 0 0.4vw;
  font-size: 1.2vh; line-height: 1.25; color: #fda4af; text-align: center;
  overflow: hidden; max-height: 4.6vh;
}
.ratio-note.show { display: block; }

/* ------------------------------------------------------------- footer --- */

#foot {
  flex: 0 0 auto;
  display: flex; align-items: center; gap: 1.3vw; flex-wrap: nowrap; overflow: hidden;
  padding: 0.4vh 1.6vw 0.55vh;
  border-top: 1px solid var(--line);
  font-family: var(--mono); font-size: 1.4vh; color: #6f8099; white-space: nowrap;
}
#foot > span { flex: 0 0 auto; }
#foot .warn { color: #fbbf24; font-weight: 700; }
#foot .keys { margin-left: auto; color: #55647c; text-overflow: ellipsis; overflow: hidden; }

/* --------------------------------------------------------------- help --- */

.help {
  display: none;
  /* 靠右下角、压在页脚上方：开屏那几秒不许挡住两块 3D 画面 */
  position: fixed; right: 1.6vw; bottom: 3.2vh; z-index: 40;
  width: 30vw; max-width: 620px;
  background: rgba(8, 14, 25, 0.97);
  border: 1px solid rgba(56, 189, 248, 0.4); border-radius: 8px;
  padding: 1.4vh 1.4vw; box-shadow: 0 2vh 6vh rgba(0, 0, 0, 0.6);
}
.help.show { display: block; }
.help h2 {
  margin: 0 0 0.9vh; font-size: 2.2vh; font-weight: 900; color: #fff;
  letter-spacing: 0.02em;
}
.help table { width: 100%; border-collapse: collapse; font-size: 1.55vh; }
.help td { padding: 0.35vh 0; color: var(--text); vertical-align: top; }
.help td:first-child {
  width: 7vw; font-family: var(--mono); font-weight: 800; color: var(--cool); white-space: nowrap;
}
.help .note { font-size: 1.45vh; color: var(--muted); line-height: 1.45; margin: 1.2vh 0 0; }
.help code {
  font-family: var(--mono); background: rgba(148, 163, 184, 0.14);
  padding: 0.1vh 0.35vw; border-radius: 3px; color: #cfe4f7;
}

/* -------------------------------------------------------------- fatal --- */

.fatal {
  display: none;
  position: fixed; inset: 0; z-index: 90;
  align-items: center; justify-content: center; text-align: center;
  background: #1a0d12; color: #ffd9de;
  font-size: 3.0vh; font-weight: 800; line-height: 1.5; padding: 6vw;
}
.fatal.show { display: flex; }

/* 窄屏（笔记本自带屏预览用）—— 现场是横屏大屏，这只是别让预览完全没法看 */
@media (max-aspect-ratio: 5/4) {
  #stage { grid-template-columns: 1fr; }
  #bottom { grid-template-columns: 1fr 30vw; }
}


/* --- 布局所有权：team-lead，2026-09-27 --- */
/* 画布高度是唯一的真值来源；jspace 吸收剩余，所以文字长短不再影响证据视口。 */
:root { --canvas-h: 280px; }
#stage.cols-3 { --canvas-h: 250px; }
.panel .jspace { flex: 1 1 auto !important; min-height: 0; }

/* 浮层让开证据区。肘部（xArm6 单套环 vs xArm7 双套环）投影在画布左中偏上，
   x≈150..250 / y≈40..90 —— 那是这个展项唯一看得见的本体差异，不许被任何字盖住。
   图例移到左下（那里是基座，不是证据），机构说明移到右上。 */
.canvas-wrap > .scene-legend { top: auto !important; bottom: 0.7vh !important; }
/* .mech 已移出 canvas-wrap（见 index.html）：它是元数据，不是浮层。
   放在画布里会随机器人姿态变化而遮住不同部位 —— 遮到哪取决于那一帧，
   这正是「装饰不许遮挡证据」最难守的一种情形：它有时候不遮。 */
.panel > .mech { flex: 0 0 auto; position: static; padding: 0.3vh 0.8vw 0;
   text-align: left; color: #6b7e96; }

/* --- 免责与来源声明绝不许被省略号截断 --- 2026-09-27, team-lead
   实测截断后的样子：「fingers INTERPOLATED from ...」——
   原文是「fingers INTERPOLATED from the scalar obs.gripper, NOT measured」。
   「NOT measured」被吃掉之后，剩下的半句读起来像一句陈述而不是一句警告。
   这和今晚其他几处是同一个形状：保护措施在，但被打败了。
   所以这些行一律换行，绝不 ellipsis；宁可占两行，不可只说半句。 */
.canvas-wrap .objects,
.canvas-wrap .objects *,
.canvas-wrap .setbadge,
.canvas-wrap .mech,
.canvas-wrap .eef,
.canvas-wrap .eef *,
.panel > .mech,
.panel > .mech *,
.panel > .truthbar,
.panel > .truthbar *,
.panel > .eef,
.panel > .eef * {
  white-space: normal !important;
  overflow: visible !important;
  text-overflow: clip !important;
  overflow-wrap: anywhere;
}

/* 字号分级的依据是**观看距离**，不是重要性。
   机械臂要在 2 米外可辨（arms-3d 用 20/40 降采样验过）；
   免责与来源声明是凑近了读的，所以可以更小 —— 但绝不可以被截断。
   先缩字号，再还高度给证据视口。 */
.panel > .truthbar, .panel > .truthbar * { font-size: 1.05vh !important; line-height: 1.32 !important; }
.panel > .eef,      .panel > .eef *      { font-size: 1.05vh !important; line-height: 1.32 !important; }
.panel > .eef b,    .panel > .eef .ehead { font-size: 1.15vh !important; }
.panel > .mech,     .panel > .mech *     { font-size: 1.0vh  !important; line-height: 1.3  !important; }

/* --- 两个视图：booth（默认，三米外的观众）/ operator（今天的全部诊断）--- 2026-09-27
   同一个页面、同一份数据、同一个共享 rig。这里只管显隐和字号；
   取景方式和套环比例在 app.js _configureRig()。为什么藏这些，见 DISPLAY-RULES.md「五」。 */
.p-name, .p-status, #headline-booth, #headline-booth-zh, .synthetic .short { display: none; }

body.view-booth #top .meta,
body.view-booth .synthetic .en,
body.view-booth .synthetic .zh,
body.view-booth #headline,
body.view-booth #headline-zh,
body.view-booth .task-events,
body.view-booth #matrix-wrap,
body.view-booth .p-title,
body.view-booth .p-sub,
body.view-booth .pill,
body.view-booth .panel > .truthbar,
body.view-booth .panel > .eef,
body.view-booth .panel > .mech,
body.view-booth .panel-foot,
body.view-booth .rcsn,
body.view-booth .panel .jspace,
body.view-booth #bottom,
body.view-booth #foot { display: none !important; }

body.view-booth {
  /* 证据视口仍然是页面统一给的一个固定值（规矩四）；只是在 booth 视图里它拿走
     矩阵和底栏让出来的全部高度。面板里其余的块（标题、状态行）去吸收差额。 */
  --canvas-h: calc(100vh - 33vh);
}
body.view-booth #main { grid-template-columns: 1fr; padding: 0 1.6vw 1.2vh; }
body.view-booth #stage { gap: 1.2vw; }

body.view-booth .synthetic { font-size: 2.1vh; }
body.view-booth .synthetic .short {
  display: inline; color: #fde68a; font-weight: 700;
  white-space: normal; overflow: visible; text-overflow: clip; min-width: 0;
}

body.view-booth #headline-wrap { padding: 1.0vh 1.6vw 1.0vh; }
body.view-booth #headline-booth {
  display: block; font-size: 3.4vh; font-weight: 900; line-height: 1.15; color: #fff;
  letter-spacing: -0.01em;
}
body.view-booth #headline-booth-zh {
  display: block; margin-top: 0.4vh; font-size: 2.2vh; font-weight: 600; color: var(--muted);
}

body.view-booth .panel-head { padding: 0.8vh 1.0vw 0.7vh; }
body.view-booth .p-name {
  display: block; font-size: 3.0vh; font-weight: 900; color: #fff; line-height: 1.15;
  white-space: normal; overflow-wrap: anywhere;
}
/* 每块面板唯一的一行说明。它是免责类文字：换行，永不省略号（规矩二）。
   它吸收面板里剩下的高度，画布不吸收（规矩四）。 */
body.view-booth .p-status {
  display: block; flex: 1 1 auto; min-height: 0;
  padding: 0.8vh 1.0vw 0.6vh;
  font-size: 2.1vh; font-weight: 700; line-height: 1.3; color: #c9d6e6;
  white-space: normal !important; overflow: visible !important; text-overflow: clip !important;
}
body.view-booth .canvas-wrap > .scene-legend { font-size: 1.9vh; }
