Onboarding 链路已走完（L-core 四份 + 命中的 L-ref：`code-conventions` 起手必读段 + R0/R7/R9/R14/R16/R19/R23、`domain-tvu` 全文、`design-process` 头段、`figma-component-catalog` 相关条目），并对活源做了库实证（`variables.css` / `canonical/*` / `components/*` / `icons/raw.ts`）。

**起手判定**：`intent classified as 场景 3.5`（文字 brief 起步、无 Figma 输入、显式"用 TVU"）× **US-2**（Greenfield 单页）。

**R0 Phase 0 元素→组件映射**（全部对活源核过）：

| Element | Figma component_set | Code 真源 | 状态 |
|---|---|---|---|
| 顶栏 | `Top bar` `918b928e…` (Tag / Menu·Right_content SLOT) | `components/TopBar/TopBar.vue` | ✅ M1 强制 |
| 顶栏导航 | 无独立 set → 真源是 `Top bar` 内 `Menu` `4771:7173` | `components/MenuList` horizontal | ✅ 已登记 code-first divergence |
| 列表 | `Table` | `components/Table/Table.vue` | ✅ |
| 状态徽章 | `Pill Status` `5672:140617`（Status×Active 8 变体） | `canonical/PillStatus.vue` + `components/Pill/Pill.vue` | ⚠️ 见下方 gap |
| 行内操作 | `Button/dark M` `12b38c8b…` | `components/Button/Button.vue` | ✅ |
| 二次确认 | `Popup Box` `1939:43682` | `components/PopupBox/PopupBox.vue` | ✅ |
| 页面告警 | `Notification` SET `23535120…` | `components/Notification/Notification.vue` | ✅ |
| 搜索/筛选 | `input box/filled`·`select box/filled` | `components/Input`·`Select` | ✅ |
| 截断提示 | `Tooltips` | `components/Tooltip/Tooltip.vue` | ✅ |
| 协议标 / 计数 | `Badge` `4db5246d…` (Type×Color×Tag) | `canonical/Badge.vue` | ✅ |
| 图标 | icon registry | `src/icons/raw.ts` 具名常量 | ✅ 零自画 |

**唯一 gap（如实登记，未自创轴）**：`Pill Status` 的 Status 轴只有 `On-air / Preview / Analyzing / Inactive`，**没有 warning 档**。我没有给它加第五个 status 值（那会撞 AGENTS 硬规则 #3 + FIGMA_AS_SOURCE_OF_TRUTH 反模式表）；改为直接用底层 `Pill` 原语——它的 `dotColor` 枚举**本来就含 `orange`**（code-first 家族 BRIDGE-MOCKUP-003），构造规则逐字沿用 PillStatus 的 CANONICAL-028 方案（`bg = hue@18%` / border·dot·text = hue）。这条属 `BRIDGE-CODE-CANONICAL` sync 候选。

**落到设计上的域规则**：M3（红=On air，**不**用 SaaS 的绿=正常）· M4（运营控制台 dark only）· M5（不给创建入口）· M7（action 用正规 32px 按钮、status pill 用 18% 低饱和最安静）· M9（literal 动词）· M10（离线分 Idle / NeverStarted 两子型）· M11（丢包 trend = icon + 数值双层染色）· M12+M13（掉线不静默 no-op，给告知 + 前进路径）· R16（产品 UI 单语走 const map，中文只在注释）· R23（截断三件套：CSS + `scrollWidth` 检测 + tooltip/a11y）· R9（补齐 hover/focus/disabled）· R14（全 flex/grid，仅浮层用 fixed 且注明理由）。

```html
<title>TVU Live Console</title>

<style>
/* ============================================================================
 * 1 · TOKEN 层 —— 逐字取自 src/tokens/variables.css（Figma-synced，硬规则 #4：
 *     零 hex 硬编码）。单文件交付无法 <link> tvu-tokens.css，故内联；值未改动。
 *     M4：运营控制台仅交付 dark，页面不提供 theme 切换；light 块保留以忠实真源。
 * ==========================================================================*/
:root {
  /* Neutrals */
  --color-white:#ffffff; --color-grey-2:#f8f8f8; --color-grey-3:#f0f0f0;
  --color-grey-4:#dbdbdb; --color-grey-5:#cccccc; --color-grey-6:#9e9e9e;
  --color-grey-7:#7b7b7b; --color-grey-8:#595959; --color-grey-9:#434343;
  --color-grey-10:#353535; --color-grey-11:#262626; --color-grey-12:#1f1f1f;
  --color-grey-13:#141414; --color-grey-14:#000000;
  /* Brand & status */
  --brand:#2fb54e; --brand-hover:#41c760; --brand-match:#0b2b13; --brand-disable:#1a652c;
  --red:#ea4233; --red-hover:#ed5b4e; --red-bg:#fff1f0;
  --orange:#f68512; --orange-hover:#f89c3e; --orange-bg:#fff3db; --orange-tz:#d39f39;
  --blue:#3892f3; --blue-hover:#62a9f6; --blue-bg:#e1eefd;
  /* Message soft backgrounds */
  --msg-bg-red:#3b1c1a; --msg-bg-brand:#19311e; --msg-bg-blue:#1a2b3c; --msg-bg-grey:#353535;
  /* Text */
  --text-heading:#ffffff; --text-body:#f8f8f8; --text-2:#cccccc; --text-tips:#9e9e9e;
  --text-placeholder:#7b7b7b; --text-disabled:#595959; --text-grey-en:#cccccc;
  --text-grey-dis:#7b7b7b; --text-grey-hv:#cccccc; --text-primary-btn:#f8f8f8;
  /* Icon */
  --icon-active:#f8f8f8; --icon-default:#9e9e9e; --icon-disabled:#595959;
  --icon-placeholder:#7b7b7b; --icon-grey-dis:#7b7b7b;
  /* Surface */
  --bg-layer1:#141414; --bg-layer2:#1f1f1f; --bg-layer3:#262626; --bg-layer4:#353535;
  --bg-topbar:#000000; --bg-grey-btn-hv:#595959; --bg-grey-btn-en:#434343; --bg-grey-btn-dis:#434343;
  /* Line */
  --line-border:#595959; --line-deep:#353535; --line-light:#434343;
  /* Input / select aliases */
  --input-line-border:var(--color-grey-8); --input-disabled-border:var(--color-grey-8);
  --select-line-border:var(--color-grey-8); --select-disabled-border:var(--color-grey-8);
  /* Spacing */
  --sp-xxs:4px; --sp-xs:8px; --sp-s:12px; --sp-m:16px;
  --sp-l:24px; --sp-xl:32px; --sp-xxl:40px; --sp-xxxl:56px;
  /* Radius */
  --r-xs:2px; --r-s:4px; --r-m:8px; --r-l:12px; --r-xl:16px; --r-xxl:20px;
  /* Z-Index / Elevation */
  --z-dropdown:1000; --z-tooltip:1000; --z-modal:1000; --z-select-portal:1100;
  /* Layout breakpoints (CSS @media 吃不了 var()，字面量必须 ∈ 本组值集) */
  --bp-sm:640px; --bp-md:960px; --bp-lg:1100px; --bp-xl:1280px;
  /* Layout containers */
  --container-narrow:720px; --container-prose:840px;
  --container-page:1600px; --container-wide:1800px;
  /* Typography —— 本系统刻意没有等宽字体 token；数字列走 tabular-nums */
  --font-family-en:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,'Fira Sans','Droid Sans','Helvetica Neue',sans-serif;
  --font-family-zh:'PingFang SC','Microsoft YaHei',Helvetica,Arial,sans-serif;
  --font-family-base:var(--font-family-en),var(--font-family-zh);
  /* Component-variant tokens (theme-stable) */
  --notification-bg-dark:var(--color-grey-10); --notification-bg-light:var(--color-grey-2);
  --tooltip-bg-dark:var(--color-grey-10); --tooltip-border-dark:var(--color-grey-8);
  --tooltip-text-dark:var(--color-grey-2); --tooltip-max-width:320px;
  --notification-width-default:480px; --notification-width-side-pop:320px;
  --topbar-section-gap:80px; --topbar-height:64px;
  --badge-circle-size:24px; --control-height-xxs:32px;
  /* Effect Styles —— figma 真源 3-layered drop shadows */
  --shadow-l1:0 5px 12px 4px #00000017,0 3px 6px 0 #0000001f,0 1px 2px -2px #00000029;
  --shadow-l2:0 9px 28px 8px #0000000d,0 6px 16px 0 #00000014,0 3px 6px -4px #0000001f;
  --shadow-l3:0 12px 48px 16px #00000008,0 9px 28px 0 #0000000d,0 6px 16px -8px #00000014;
  --mask-overlay:rgba(0,0,0,.6); --mask-overlay-blur:8px;
  /* Text Styles —— figma 真源 7 Text Styles（family 段走系统栈，非 Roboto） */
  --text-style-tips:400 12px/16px var(--font-family-base);
  --text-style-body:400 14px/21px var(--font-family-base);
  --text-style-pop-title:600 16px/24px var(--font-family-base);
  --text-style-headline-2:600 18px/27px var(--font-family-base);
  --text-style-headline-1:600 20px/30px var(--font-family-base);
  --text-style-large-title-22:600 22px/33px var(--font-family-base);
  --text-style-large-title-24:600 24px/36px var(--font-family-base);
  --font-size-tips:12px; --font-size-body:14px; --font-size-pop-title:16px;
  --line-height-tips:16px; --line-height-body:21px;
  --font-weight-regular:400; --font-weight-semibold:600;
  /* Derived aliases */
  --input-filled-bg:var(--bg-layer4); --green-bg:var(--brand-match);
  --line-divider-deep:var(--line-deep); --line-divider-light:var(--line-light);
  --control-disabled-bg:var(--line-deep); --control-disabled-border:var(--line-border);
  --control-disabled-text:var(--text-disabled);
}

/* ============================================================================
 * 2 · ELEMENT BASE 层 —— 语义 HTML 的 correct-by-construction 兜底
 *     （variables.css 尾部 Element base layer，token-only / 无 layout / 无 !important）
 * ==========================================================================*/
*,*::before,*::after { box-sizing: border-box; }
body {
  margin: 0;
  font: var(--text-style-body);
  font-family: var(--font-family-base);
  background: var(--bg-layer1);
  color: var(--text-body);
  -webkit-font-smoothing: antialiased;
}
h1 { font: var(--text-style-headline-1); color: var(--text-body); margin: 0; }
h2 { font: var(--text-style-headline-2); color: var(--text-body); margin: 0; }
h3 { font: var(--text-style-pop-title); color: var(--text-body); margin: 0; }
label { font: var(--text-style-body); color: var(--text-body); }
a { color: var(--brand); }
button { font: var(--text-style-body); color: inherit; border-radius: var(--r-s); }
input, select {
  font: var(--text-style-body); color: var(--text-body);
  background: var(--input-filled-bg); border: 1px solid var(--line-border);
  border-radius: var(--r-s); padding: var(--sp-xs) var(--sp-s);
}
input::placeholder { color: var(--text-placeholder); }
table { font: var(--text-style-body); color: var(--text-body); border-collapse: collapse; }
th { font-weight: 600; color: var(--text-body); text-align: left; padding: var(--sp-xs) var(--sp-s); }
td { padding: var(--sp-xs) var(--sp-s); border-top: 1px solid var(--line-border); }
[role="dialog"] { font: var(--text-style-body); color: var(--text-body); background: var(--bg-layer2); border-radius: var(--r-l); }

/* ============================================================================
 * 3 · 组件层 —— 逐个 1:1 mirror canonical / base 实现的类名与几何
 * ==========================================================================*/

/* ---- TopBar (components/TopBar/TopBar.vue) —— M1：TVU 页面必须用它 ---- */
.topbar {
  display: flex; align-items: center;
  padding: var(--sp-xs) var(--sp-m);
  background: var(--bg-topbar);
  border: 1px solid var(--line-deep);
  gap: var(--topbar-section-gap);
  flex-shrink: 0; width: 100%; height: var(--topbar-height);
  position: sticky; top: 0; z-index: 5; /* overlay: 顶栏常驻，随页面滚动吸顶 */
}
.topbar-brand { display: flex; align-items: center; gap: var(--sp-m); flex-shrink: 0; }
.topbar-logo { display: inline-flex; width: 28px; height: 28px; }
.topbar-logo svg { width: 100%; height: 100%; }
.topbar-title { font: var(--text-style-pop-title); color: var(--text-heading); white-space: nowrap; }
.topbar-menu { display: flex; align-items: center; flex: 1; }
.topbar-right { display: flex; align-items: center; gap: var(--sp-xs); flex-shrink: 0; margin-left: auto; }
.topbar-user {
  display: flex; align-items: center; gap: var(--sp-xs);
  padding: var(--sp-xxs) var(--sp-xs);
  border: 0; background: transparent; color: var(--text-2);
  font: var(--text-style-body); cursor: pointer; border-radius: var(--r-s);
}
.topbar-user:hover { background: var(--bg-grey-btn-en); color: var(--text-heading); }
.topbar-user:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.topbar-user .ico { color: var(--icon-default); }
.topbar-user:hover .ico { color: var(--icon-active); }

/* ---- MenuList horizontal (components/MenuList/MenuList.vue)
       真源 = Top bar 内 Menu 子件 4771:7173。owner 2026-08-14 裁定：
       active 与 normal 只差文字色，⛔ 没有 brand 下划线，别再加回来。 ---- */
.menu-list { list-style: none; margin: 0; padding: 0; display: flex; }
.menu-list--horizontal { flex-direction: row; align-items: center; gap: var(--sp-m); }
.menu-list--horizontal .menu-list__item {
  display: flex; align-items: center; gap: var(--sp-xxs);
  padding: 0 var(--sp-xs);
  font: var(--text-style-headline-2);
  color: var(--text-tips);
  cursor: pointer; user-select: none; white-space: nowrap;
}
.menu-list--horizontal .menu-list__item:hover:not(.is-disabled) { color: var(--text-heading); }
.menu-list--horizontal .menu-list__item.is-active { color: var(--text-body); }
.menu-list__item.is-disabled { color: var(--text-disabled); cursor: not-allowed; pointer-events: none; }
.menu-list__item:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }

/* MenuList vertical —— 行内 More 溢出菜单用 */
.menu-list--vertical { flex-direction: column; gap: var(--sp-xxs); min-width: 180px; }
.menu-list--vertical .menu-list__item {
  display: flex; align-items: center; gap: var(--sp-xs);
  padding: var(--sp-xs) var(--sp-s);
  border-radius: var(--r-s); font: var(--text-style-body);
  color: var(--text-2); cursor: pointer; user-select: none;
}
.menu-list--vertical .menu-list__label { flex: 1; white-space: nowrap; }
.menu-list--vertical .menu-list__item:hover:not(.is-disabled) { background: var(--bg-layer3); color: var(--text-heading); }

/* ---- Button (components/Button/Button.vue) —— SIZE_GEOMETRY + PALETTE_SPEC 逐字 ---- */
.btn {
  --btn-bg: transparent; --btn-color: var(--text-2); --btn-border: transparent;
  --btn-opacity: 1; --btn-height: 32px; --btn-font-size: 14px; --btn-line-height: 21px;
  --btn-gap: 0px; --btn-padding-inline: 16px; --btn-border-width: 1px; --btn-radius-square: 4px;
  display: inline-flex; align-items: center; justify-content: center;
  gap: var(--btn-gap); min-width: 0;
  padding-inline: var(--btn-padding-inline); height: var(--btn-height);
  border: var(--btn-border-width) solid var(--btn-border);
  background: var(--btn-bg); color: var(--btn-color); opacity: var(--btn-opacity);
  border-radius: var(--btn-radius-square);
  font-family: var(--font-family-base); font-size: var(--btn-font-size);
  font-weight: 400; line-height: var(--btn-line-height);
  cursor: pointer; white-space: nowrap; outline: none; user-select: none;
  transition: background .15s, color .15s, border-color .15s, opacity .15s;
}
.btn:hover:not(:disabled) { background: var(--btn-bg-hover); color: var(--btn-color-hover); border-color: var(--btn-border-hover); }
.btn:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.btn:disabled { cursor: not-allowed; }
/* size S / M —— M 是 operator console 行内动作的强制档（M7：⛔ 不许缩成 XS chip） */
.btn--s { --btn-height: 24px; --btn-padding-inline: 8px; --btn-gap: 4px; }
.btn--m { --btn-height: 32px; --btn-padding-inline: 16px; --btn-gap: 4px; }
.btn--icononly { --btn-padding-inline: 0px; width: 32px; }
/* ghost (border-width 1.2px) */
.btn--ghost { --btn-border-width: 1.2px; }
.btn--ghost-gray { --btn-bg: transparent; --btn-color: var(--text-2); --btn-border: var(--bg-grey-btn-en);
  --btn-bg-hover: var(--bg-grey-btn-en); --btn-color-hover: var(--text-2); --btn-border-hover: var(--bg-grey-btn-en); }
.btn--ghost-red { --btn-bg: transparent; --btn-color: var(--red); --btn-border: var(--red);
  --btn-bg-hover: var(--red-bg); --btn-color-hover: var(--red); --btn-border-hover: var(--red); }
.btn--ghost-green { --btn-bg: transparent; --btn-color: var(--brand); --btn-border: var(--brand);
  --btn-bg-hover: var(--green-bg); --btn-color-hover: var(--brand); --btn-border-hover: var(--brand); }
.btn--ghost-red:disabled { --btn-color: var(--red); --btn-border: var(--red); --btn-opacity: .4; }
.btn--ghost-gray:disabled { --btn-color: var(--text-placeholder); --btn-border: var(--bg-grey-btn-en); }
/* filling */
.btn--fill-red { --btn-bg: var(--red); --btn-color: var(--text-primary-btn); --btn-border: transparent;
  --btn-bg-hover: var(--red-hover); --btn-color-hover: var(--text-primary-btn); --btn-border-hover: transparent; }
.btn--fill-green { --btn-bg: var(--brand); --btn-color: var(--text-primary-btn); --btn-border: transparent;
  --btn-bg-hover: var(--brand-hover); --btn-color-hover: var(--text-primary-btn); --btn-border-hover: transparent; }
/* rimless */
.btn--text-gray { --btn-bg: transparent; --btn-color: var(--text-2); --btn-border: transparent;
  --btn-bg-hover: var(--bg-grey-btn-en); --btn-color-hover: var(--text-2); --btn-border-hover: transparent; }
.btn .ico { flex-shrink: 0; }

/* ---- Pill (components/Pill/Pill.vue) —— geometry 1:1 与 Figma `Pill Status`
       h28 · padding 4/12/4/10 · gap 8 · dot 6 · radius 999 ---- */
.pill {
  display: inline-flex; align-items: center; box-sizing: border-box;
  min-height: 28px; gap: var(--sp-xs);
  padding: var(--sp-xxs) var(--sp-s) var(--sp-xxs) 10px;
  border-radius: 999px;
  background: var(--pill-bg); border: 1px solid var(--pill-border, transparent);
  color: var(--pill-text, var(--text-body));
  font-size: 12px; font-weight: 500; line-height: 1.4;
  font-family: var(--font-family-base); white-space: nowrap;
}
.pill__dot { flex-shrink: 0; width: 6px; height: 6px; border-radius: 50%; background: var(--pill-dot); }
.pill__label { font-variant-numeric: tabular-nums; }
/* PillStatus CANONICAL-028 配方：Active=True → bg = hue@18%，border/dot/text = hue。
   ON AIR 走 --red 是 M3 广播 ON AIR 惯例（⛔ 不是 SaaS 的「红=危险」）。 */
.pill--onair   { --pill-bg: color-mix(in srgb, var(--red) 18%, transparent);
                 --pill-border: var(--red);    --pill-dot: var(--red);    --pill-text: var(--red); }
/* WARNING：`Pill Status` 的 Status 轴无 warning 档 ⇒ 不给它加第五个 status 值，
   改用底层 Pill 原语 + --orange（Pill.dotColor 枚举本就含 orange）。同一配方换 hue。
   sync 候选：BRIDGE-CODE-CANONICAL。 */
.pill--warning { --pill-bg: color-mix(in srgb, var(--orange) 18%, transparent);
                 --pill-border: var(--orange); --pill-dot: var(--orange); --pill-text: var(--orange); }
/* OFFLINE：逐字取 PillStatus 的 Active=False muted 方案 */
.pill--offline { --pill-bg: var(--bg-layer3); --pill-border: var(--line-deep);
                 --pill-dot: var(--color-grey-7); --pill-text: var(--text-tips); }

/* ---- Badge (canonical/Badge.vue) ---- */
.badge {
  display: inline-flex; align-items: center; justify-content: center;
  width: auto; height: var(--badge-circle-size);
  padding-block: var(--sp-xxs); padding-inline: var(--sp-s);
  border: 1px solid var(--badge-border); border-radius: var(--r-xl);
  background: var(--badge-bg); color: var(--badge-text);
  font: var(--text-style-tips); white-space: nowrap; box-sizing: border-box;
}
.badge--line-neutral  { --badge-bg: transparent; --badge-border: var(--line-deep); --badge-text: var(--text-body); }
.badge--fill-red      { --badge-bg: var(--red);       --badge-border: transparent; --badge-text: var(--text-primary-btn); }
.badge--fill-orange   { --badge-bg: var(--orange);    --badge-border: transparent; --badge-text: var(--text-primary-btn); }
.badge--fill-neutral  { --badge-bg: var(--bg-layer4); --badge-border: transparent; --badge-text: var(--text-body); }

/* ---- Input filled (components/Input/Input.vue, variant=filled size=m) ---- */
.tvu-input-wrap { position: relative; display: block; }
.tvu-input {
  display: block; width: 100%; height: 32px;
  padding: var(--sp-xs) var(--sp-s);
  background: var(--input-filled-bg); border: 1px solid transparent;
  border-radius: var(--r-s); color: var(--text-body);
  font-size: 14px; font-family: var(--font-family-base);
  outline: none; transition: border-color .15s;
}
.tvu-input::placeholder { color: var(--text-placeholder); }
.tvu-input:hover:not(:disabled), .tvu-input:focus:not(:disabled) { border-color: var(--brand); }
.tvu-input--search { padding-left: calc(var(--sp-s) * 2 + 16px); }
.tvu-input-icon {
  position: absolute; /* overlay: 装饰性前导图标，锚在输入框内不参与流式布局 */
  left: var(--sp-s); top: 50%; transform: translateY(-50%);
  color: var(--icon-default); pointer-events: none; display: flex;
}

/* ---- Select filled (components/Select/Select.vue, variant=filled size=m) ---- */
.select-wrap { position: relative; display: inline-block; }
.select {
  display: block; width: 100%; height: 32px;
  background: var(--bg-layer4); border: 1px solid transparent;
  border-radius: var(--r-s); color: var(--text-body);
  font-size: 13px; font-family: var(--font-family-base);
  outline: none; appearance: none; -webkit-appearance: none;
  cursor: pointer; padding: 0 36px 0 var(--sp-s); transition: border-color .15s;
}
.select:hover, .select:focus { border-color: var(--brand); }
.select option { background: var(--bg-layer2); color: var(--text-body); }
.select-arrow {
  position: absolute; /* overlay: 原生 select 箭头已被 appearance:none 移除，此为替代指示 */
  right: 12px; top: 50%; transform: translateY(-50%);
  pointer-events: none; color: var(--text-tips); display: flex; align-items: center;
}

/* ---- Table (components/Table/Table.vue) ---- */
.tbl-wrap { position: relative; width: 100%; overflow-x: auto; }
.tbl {
  width: 100%; min-width: 1080px;
  border-collapse: collapse; table-layout: fixed;
  font: var(--text-style-body); color: var(--text-body); background: var(--bg-layer2);
}
/* INFRA-F129 ①：中和上面 element base 的 `td{border-top}`，否则它在 collapse
   冲突里会赢过组件自己的行分隔线，画出 Figma 里并不存在的 stroke。 */
.tbl td { border-top: 0; }
.tbl-head .tbl-cell {
  /* CANONICAL-029：Figma cell auto-layout = L0/R8/T8/B8，内容贴左网格线 */
  padding: var(--sp-xs) var(--sp-xs) var(--sp-xs) 0;
  font: var(--text-style-body); font-weight: 700; color: var(--text-tips);
  background: transparent; border-bottom: 1px solid var(--line-deep); white-space: nowrap;
}
.tbl-body .tbl-row { border-bottom: 1px solid var(--line-light); transition: background .12s; }
.tbl-body .tbl-row:last-child { border-bottom: none; }
.tbl-body .tbl-row:hover { background: var(--bg-layer3); }
.tbl-body .tbl-cell {
  padding: var(--sp-s) var(--sp-xs) var(--sp-s) 0;
  font: var(--text-style-body); color: var(--text-body); vertical-align: middle;
}
.tbl-cell__content { display: flex; align-items: center; gap: var(--sp-xxs); min-width: 0; }
.tbl-cell--right .tbl-cell__content { justify-content: flex-end; }
.tbl-empty { text-align: center; padding: var(--sp-xxl) var(--sp-xs); color: var(--text-tips); }

/* R23 截断三件套之 (a)：单行 tail-ellipsis。父级必须给宽度 + min-width:0，
   否则 flex 子项默认 min-width:auto，nowrap 会撑破 layout 而不截断。 */
.cell--ellipsis { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* ---- Tooltip (components/Tooltip/Tooltip.vue, theme=dark) ---- */
.tooltip-wrap { position: relative; display: inline-flex; isolation: isolate; min-width: 0; }
.tooltip-box {
  position: absolute; /* overlay: tooltip 无 layout 语义，锚在触发器上方 */
  z-index: var(--z-tooltip);
  bottom: calc(100% + 6px); left: 0;
  white-space: normal; max-width: var(--tooltip-max-width); width: max-content;
  padding: var(--sp-xxs) var(--sp-xs); border-radius: var(--r-s);
  background: var(--tooltip-bg-dark); border: 1px solid var(--tooltip-border-dark);
  color: var(--tooltip-text-dark); font: var(--text-style-tips);
  pointer-events: none; box-shadow: var(--shadow-l3);
  word-break: break-all;
  opacity: 0; visibility: hidden; transition: opacity .12s;
}
.tooltip-wrap:hover .tooltip-box, .tooltip-wrap:focus-within .tooltip-box { opacity: 1; visibility: visible; }
.tooltip-arrow {
  position: absolute; /* overlay: tooltip 指向箭头 */
  bottom: -5px; left: var(--sp-s); width: 8.5px; height: 8.5px;
  background: var(--tooltip-bg-dark); transform: rotate(45deg);
  border-right: 1px solid var(--tooltip-border-dark); border-bottom: 1px solid var(--tooltip-border-dark);
}

/* ---- Notification (components/Notification/Notification.vue) ---- */
.notif {
  display: grid; box-sizing: border-box; gap: var(--sp-xl);
  width: var(--notification-width-default);
  padding: var(--sp-m) var(--sp-l);
  border-radius: 6px; border: 1.2px solid var(--line-light);
  box-shadow: var(--shadow-l3);
  background: var(--notification-bg-dark); color: var(--text-body);
}
.notif--slide { width: var(--notification-width-side-pop); gap: var(--sp-s); }
.notif__main { display: flex; align-items: flex-start; gap: var(--sp-m); }
.notif__icon { flex: 0 0 auto; width: 24px; height: 24px; }
.notif__icon svg { display: block; width: 100%; height: 100%; }
.notif__body { flex: 1 1 auto; min-width: 0; display: grid; gap: var(--sp-s); }
.notif__header { display: flex; align-items: center; gap: var(--sp-m); }
.notif__title { flex: 1 1 auto; min-width: 0; margin: 0; font: var(--text-style-pop-title); }
.notif__desc { margin: 0; font: var(--text-style-body); color: var(--text-tips); }
.notif__actions { display: flex; justify-content: flex-end; gap: var(--sp-m); }
.notif__close {
  flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
  width: 16px; height: 16px; padding: 0; border: 0; background: transparent;
  color: var(--icon-default); cursor: pointer;
}
.notif__close:hover { color: var(--icon-active); }

/* ---- PopupBox (components/PopupBox/PopupBox.vue) ---- */
.popup-overlay {
  position: fixed; /* overlay: modal backdrop 覆盖 viewport，无 layout 语义 */
  inset: 0; z-index: var(--z-modal);
  display: flex; align-items: center; justify-content: center;
  background: var(--mask-overlay); backdrop-filter: blur(var(--mask-overlay-blur));
}
.popup-overlay[hidden] { display: none; }
.popup-box {
  display: flex; flex-direction: column;
  width: 480px; max-width: 90vw; max-height: 90vh; overflow: hidden;
  padding: 0 var(--sp-m);
  border: 1px solid var(--line-border); border-radius: var(--r-s);
  background: var(--bg-layer2); color: var(--text-body);
  outline: none; box-shadow: var(--shadow-l1);
}
.popup-box__header {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--sp-xs); padding: var(--sp-xs) 0;
  border-bottom: 1px solid var(--line-border);
}
.popup-box__title { flex: 1; min-width: 0; margin: 0; color: var(--text-body); font: var(--text-style-pop-title); }
.popup-box__close {
  display: inline-flex; flex: 0 0 auto; align-items: center; justify-content: center;
  width: 20px; height: 20px; padding: var(--sp-xxs);
  border: 0; background: transparent; color: var(--icon-default); cursor: pointer;
}
.popup-box__close:hover { color: var(--icon-active); }
.popup-box__body {
  flex: 1; min-height: 0; overflow: auto;
  padding: var(--sp-l) var(--sp-xs) 0;
  color: var(--text-body); font: var(--text-style-body);
}
.popup-box__footer { display: flex; justify-content: flex-end; gap: var(--sp-m); padding: var(--sp-l) var(--sp-xs); }

/* ============================================================================
 * 4 · 页面层（本 mockup 自己的编排；R14：一律 flex / grid）
 * ==========================================================================*/
.page { padding: var(--sp-l) var(--sp-m) var(--sp-xxxl); }
.page__inner { max-width: var(--container-page); margin: 0 auto; display: grid; gap: var(--sp-l); }

/* page-level strip —— Top bar 的 SLOT ⛔ 不许塞 counters / filter，必须落在它下方 */
.page-head { display: grid; gap: var(--sp-m); }
.page-head__row { display: flex; align-items: center; gap: var(--sp-m); flex-wrap: wrap; }
.page-head__titles { display: flex; align-items: baseline; gap: var(--sp-s); }
.page-head__sub { font: var(--text-style-body); color: var(--text-tips); font-variant-numeric: tabular-nums; }
.page-head__counters { display: flex; align-items: center; gap: var(--sp-xs); margin-left: auto; }

.toolbar { display: flex; align-items: center; gap: var(--sp-xs); flex-wrap: wrap; }
.toolbar__search { flex: 0 1 320px; min-width: 200px; }
.toolbar__spacer { flex: 1 1 auto; }
.toolbar__result { font: var(--text-style-tips); color: var(--text-tips); font-variant-numeric: tabular-nums; }

.panel {
  background: var(--bg-layer2);
  border: 1px solid var(--line-deep); border-radius: var(--r-m);
  padding: 0 var(--sp-m);
}

/* Source 单元格：主标题 + 副标题（M10 两个 Inactive 子型靠副标题措辞区分） */
.cell-stack { display: grid; gap: 2px; min-width: 0; }
.cell-stack__main { font: var(--text-style-body); color: var(--text-body); }
.cell-stack__meta { font: var(--text-style-tips); color: var(--text-tips); font-variant-numeric: tabular-nums; }
.cell-stack__meta--cta { color: var(--text-2); }

.uri { display: flex; align-items: center; gap: var(--sp-xs); min-width: 0; }
.uri__text { font-variant-numeric: tabular-nums; } /* ⛔ 本系统刻意无 monospace token */
.dash { color: var(--text-disabled); }

/* M11 trend：icon + 数值颜色双层信号，缺一不可 */
.metric { display: inline-flex; align-items: center; gap: var(--sp-xxs); font-variant-numeric: tabular-nums; }
.metric--diverging { color: var(--orange); }   /* ↑ 恶化 */
.metric--converging { color: var(--brand); }   /* ↓ 改善 */
.metric--stable { color: var(--text-body); }   /* 阈值内，无 icon */
.metric .ico { width: 12px; height: 12px; }

.row-actions { display: flex; align-items: center; justify-content: flex-end; gap: var(--sp-xs); }

/* 行内 More 溢出菜单 */
.overflow { position: relative; }
.overflow__panel {
  position: absolute; /* overlay: 下拉浮层锚定在触发按钮上，无 layout 语义 */
  top: calc(100% + 4px); right: 0; z-index: var(--z-dropdown);
  padding: var(--sp-xxs);
  background: var(--bg-layer3); border: 1px solid var(--line-light);
  border-radius: var(--r-s); box-shadow: var(--shadow-l2);
}
.overflow__panel[hidden] { display: none; }

/* toast 区 */
.toast-stack {
  position: fixed; /* overlay: toast 锚定 viewport 右上角 */
  top: calc(var(--topbar-height) + var(--sp-m)); right: var(--sp-m);
  z-index: var(--z-select-portal);
  display: grid; gap: var(--sp-xs);
}

.confirm-lead { display: flex; align-items: flex-start; gap: var(--sp-m); }
.confirm-lead__icon { flex: 0 0 auto; width: 24px; height: 24px; color: var(--red); }
.confirm-lead__icon svg { width: 100%; height: 100%; }
.confirm-body { display: grid; gap: var(--sp-s); min-width: 0; }
.confirm-body p { margin: 0; }
.confirm-impact {
  display: grid; gap: var(--sp-xs);
  padding: var(--sp-s); border-radius: var(--r-s);
  background: var(--bg-layer3); border: 1px solid var(--line-deep);
}
.confirm-impact__row { display: flex; gap: var(--sp-s); font: var(--text-style-tips); }
.confirm-impact__k { flex: 0 0 128px; color: var(--text-tips); }
.confirm-impact__v { flex: 1; min-width: 0; color: var(--text-body); font-variant-numeric: tabular-nums; word-break: break-all; }

.ico { display: inline-flex; flex: 0 0 auto; width: 16px; height: 16px; }
.ico svg { display: block; width: 100%; height: 100%; }
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}

/* 断点字面量必须 ∈ --bp-* 值集（audit:layout-tokens 机械核） */
@media (max-width: 1100px) {
  .topbar { gap: var(--sp-l); }
  .page-head__counters { margin-left: 0; }
}
@media (max-width: 960px) {
  .topbar-menu { display: none; }
}
</style>

<div class="app">
  <!-- ===== 页面级导航：Top bar（M1 硬规则）+ 内嵌 Menu ===== -->
  <header class="topbar" data-figma-component="Top bar" data-figma-tag="After Login">
    <div class="topbar-brand">
      <span class="topbar-logo" aria-hidden="true">
        <!-- icon registry `LogoTVU`；品牌绿是 Figma 导出的资产内嵌色，
             属已登记 divergence icon-variable-resolved-to-hex，非本页硬编码 -->
        <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M15.9933 8.68971V8.6898V8.6899V8.69001C15.9962 8.96317 16 9.31582 16 9.81499V13.9033C16 13.9033 16 16 13.6733 15.9997H7.99586L7.98791 15.9998L7.97996 16H7.97102H7.93922C7.93922 16 7.94419 16 7.9538 15.9997C3.55876 15.9857 0 12.4098 0 8.00017C0 3.5819 3.573 0 7.97996 0C12.1057 0 15.5005 3.13931 15.9165 7.16512L15.9182 7.16545L15.9185 7.18106C15.946 7.45033 15.99 7.97375 15.99 8.25C15.99 8.37606 15.9915 8.51693 15.9933 8.68953L15.9933 8.68971ZM1.19653 7.97028C1.19653 11.7388 4.24392 14.7937 8.00305 14.7937C11.7622 14.7937 14.8096 11.7388 14.8096 7.97028C14.8096 4.20178 11.7622 1.14681 8.00305 1.14681C4.24392 1.14681 1.19653 4.20178 1.19653 7.97028ZM13.4204 15.2121V13.212L15.0122 14.2386L13.4204 15.2121ZM12.8146 8.39867V5.86667H13.669V8.41467C13.669 8.90067 13.6164 9.27267 13.3229 9.53167C13.0294 9.79067 12.5968 9.92 12.0253 9.92C11.4538 9.92 11.022 9.79034 10.7291 9.53167C10.4366 9.273 10.3843 8.90134 10.3843 8.41734V5.86667H11.2394V8.39867C11.2394 8.67167 11.2963 8.875 11.4098 9.00867C11.5233 9.14233 11.7705 9.20934 12.0257 9.20934C12.2772 9.20934 12.524 9.14534 12.6402 9.01667C12.7564 8.888 12.8146 8.682 12.8146 8.39867ZM9.02194 5.86667L8.01441 8.853L7.00721 5.86667H6.03979L7.53984 9.92H8.46061L9.96033 5.86667H9.02194ZM3.48276 6.554H2.2251V5.86667H5.61583V6.554H4.35817V9.92H3.48276V6.554Z" fill="#56AF31"/></svg>
      </span>
      <span class="topbar-title" id="js-product-name"></span>
    </div>

    <nav class="topbar-menu" aria-label="Primary">
      <ul class="menu-list menu-list--horizontal" role="menubar" id="js-nav"></ul>
    </nav>

    <div class="topbar-right">
      <button type="button" class="topbar-user" id="js-user">
        <span class="ico" aria-hidden="true"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M11.7 4.7C11.7 6.74345 10.0434 8.4 7.99999 8.4C5.95653 8.4 4.29999 6.74345 4.29999 4.7C4.29999 2.65655 5.95653 1 7.99999 1C10.0434 1 11.7 2.65655 11.7 4.7ZM10.5 4.69922C10.5 3.31851 9.38071 2.19922 8 2.19922C6.61929 2.19922 5.5 3.31851 5.5 4.69922C5.5 6.07993 6.61929 7.19922 8 7.19922C9.38071 7.19922 10.5 6.07993 10.5 4.69922ZM8.00001 9.39844C6.24958 9.39844 4.57085 10.0938 3.33311 11.3315C2.52087 12.1438 1.94222 13.1459 1.64035 14.2335C1.55173 14.5528 1.73874 14.8835 2.05804 14.9721C2.37734 15.0607 2.70802 14.8737 2.79664 14.5544C3.04365 13.6645 3.5171 12.8446 4.18164 12.1801C5.19433 11.1674 6.56784 10.5984 8.00001 10.5984C9.43218 10.5984 10.8057 11.1674 11.8184 12.1801C12.4829 12.8446 12.9564 13.6645 13.2034 14.5544C13.292 14.8737 13.6227 15.0607 13.942 14.9721C14.2613 14.8835 14.4483 14.5528 14.3597 14.2335C14.0578 13.1459 13.4791 12.1438 12.6669 11.3315C11.4292 10.0938 9.75044 9.39844 8.00001 9.39844Z" fill="currentColor"/></svg></span>
        <span id="js-user-name"></span>
      </button>
    </div>
  </header>

  <main class="page">
    <div class="page__inner">

      <!-- ===== page-level strip ===== -->
      <section class="page-head">
        <div class="page-head__row">
          <div class="page-head__titles">
            <h1 id="js-page-title"></h1>
            <span class="page-head__sub" id="js-page-sub"></span>
          </div>
          <div class="page-head__counters" id="js-counters"></div>
        </div>

        <div class="toolbar" role="search">
          <div class="tvu-input-wrap toolbar__search">
            <span class="tvu-input-icon ico" aria-hidden="true"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M11.6529 12.5025C10.5267 13.4389 9.07914 14.002 7.50006 14.002C3.91021 14.002 1.00006 11.0918 1.00006 7.50195C1.00006 3.9121 3.91021 1.00195 7.50006 1.00195C11.0899 1.00195 14.0001 3.9121 14.0001 7.50195C14.0001 9.08062 13.4373 10.5278 12.5014 11.6539L14.8227 13.9753C15.057 14.2096 15.057 14.5895 14.8227 14.8238C14.5884 15.0581 14.2085 15.0581 13.9742 14.8238L11.6529 12.5025ZM12.8001 7.50117C12.8001 4.57406 10.4272 2.20117 7.50007 2.20117C4.57296 2.20117 2.20007 4.57406 2.20007 7.50117C2.20007 10.4283 4.57296 12.8012 7.50007 12.8012C10.4272 12.8012 12.8001 10.4283 12.8001 7.50117Z" fill="currentColor"/></svg></span>
            <input class="tvu-input tvu-input--search" type="search" id="js-search" autocomplete="off">
          </div>

          <div class="select-wrap">
            <select class="select" id="js-filter-status"></select>
            <span class="select-arrow ico" aria-hidden="true"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M3.05146 6.20128C3.27161 5.95374 3.65086 5.93145 3.89853 6.15148L7.86707 9.67727C7.94284 9.74458 8.05698 9.74458 8.13274 9.67727L12.1013 6.15148C12.349 5.93145 12.7282 5.95374 12.9484 6.20128C13.1685 6.44883 13.1462 6.82788 12.8985 7.04791L8.39841 11.046C8.17112 11.2479 7.8287 11.2479 7.60141 11.046L3.10129 7.04791C2.85362 6.82788 2.83131 6.44883 3.05146 6.20128Z" fill="currentColor"/></svg></span>
          </div>

          <div class="select-wrap">
            <select class="select" id="js-filter-region"></select>
            <span class="select-arrow ico" aria-hidden="true"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M3.05146 6.20128C3.27161 5.95374 3.65086 5.93145 3.89853 6.15148L7.86707 9.67727C7.94284 9.74458 8.05698 9.74458 8.13274 9.67727L12.1013 6.15148C12.349 5.93145 12.7282 5.95374 12.9484 6.20128C13.1685 6.44883 13.1462 6.82788 12.8985 7.04791L8.39841 11.046C8.17112 11.2479 7.8287 11.2479 7.60141 11.046L3.10129 7.04791C2.85362 6.82788 2.83131 6.44883 3.05146 6.20128Z" fill="currentColor"/></svg></span>
          </div>

          <span class="toolbar__spacer"></span>
          <span class="toolbar__result" id="js-result-count"></span>

          <button type="button" class="btn btn--m btn--ghost btn--ghost-gray" id="js-refresh">
            <span class="ico" aria-hidden="true"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M10.8367 1.60055C9.46757 0.993637 7.93986 0.84184 6.47805 1.16746C5.01623 1.49308 3.69739 2.27896 2.71528 3.40962C1.73317 4.54028 1.13958 5.95612 1.02172 7.44912C0.903855 8.94211 1.26794 10.4336 2.06047 11.7043C2.853 12.9751 4.0322 13.9581 5.42481 14.5091C6.81743 15.0601 8.35004 15.1499 9.79746 14.7653C11.2449 14.3807 12.5308 13.5421 13.4663 12.3726C13.6733 12.1138 13.6313 11.7362 13.3726 11.5292C13.1138 11.3222 12.7362 11.3642 12.5292 11.623C11.7541 12.592 10.6886 13.2869 9.48933 13.6055C8.29003 13.9242 7.02016 13.8498 5.86628 13.3933C4.71239 12.9367 3.73534 12.1222 3.07867 11.0693C2.422 10.0164 2.12034 8.78061 2.21799 7.54355C2.31565 6.3065 2.80748 5.13338 3.62123 4.19654C4.43498 3.25971 5.52773 2.60856 6.73895 2.33876C7.95017 2.06895 9.21599 2.19473 10.3504 2.6976C11.3659 3.14773 12.2282 3.87877 12.8379 4.80078H11.6C11.2686 4.80078 11 5.06941 11 5.40078C11 5.73215 11.2686 6.00078 11.6 6.00078H14.4C14.7314 6.00078 15 5.73215 15 5.40078L15 5.39692V2.6C15 2.26863 14.7314 2 14.4 2C14.0686 2 13.8 2.26863 13.8 2.6V4.08078C13.0666 2.99539 12.0409 2.13433 10.8367 1.60055Z" fill="currentColor"/></svg></span>
            <span id="js-refresh-label"></span>
          </button>
        </div>
      </section>

      <!-- ===== M12 / M13：外部异步事件不得静默 no-op —— 检测 → 告知 → 前进路径 ===== -->
      <div class="notif" role="alert" id="js-alert" data-figma-component="Notification">
        <div class="notif__main">
          <span class="notif__icon" style="color: var(--orange)" aria-hidden="true"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M8 1C4.13438 1 1 4.13438 1 8C1 11.8656 4.13438 15 8 15C11.8656 15 15 11.8656 15 8C15 4.13438 11.8656 1 8 1ZM7.9999 4C8.33127 4 8.5999 4.26863 8.5999 4.6V9C8.5999 9.33137 8.33127 9.6 7.9999 9.6C7.66853 9.6 7.3999 9.33137 7.3999 9V4.6C7.3999 4.26863 7.66853 4 7.9999 4ZM7.9999 12C7.66853 12 7.3999 11.7314 7.3999 11.4C7.3999 11.0686 7.66853 10.8 7.9999 10.8C8.33127 10.8 8.5999 11.0686 8.5999 11.4C8.5999 11.7314 8.33127 12 7.9999 12Z" fill="currentColor"/></svg></span>
          <div class="notif__body">
            <div class="notif__header">
              <h2 class="notif__title" id="js-alert-title"></h2>
              <button type="button" class="notif__close" id="js-alert-close">
                <span class="ico" aria-hidden="true"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M12.5758 13.4247C12.8101 13.659 13.19 13.659 13.4243 13.4247C13.6586 13.1903 13.6586 12.8104 13.4243 12.5761L8.84898 8.00082L13.4248 3.42543C13.6591 3.19113 13.6591 2.81123 13.4248 2.5769C13.1905 2.34258 12.8106 2.34256 12.5763 2.57687L8.00045 7.15229L3.42429 2.57613C3.18997 2.34181 2.81007 2.34181 2.57576 2.57613C2.34145 2.81044 2.34145 3.19034 2.57576 3.42465L7.15188 8.00078L2.57596 12.5763C2.34163 12.8106 2.34161 13.1905 2.57592 13.4248C2.81022 13.6592 3.19012 13.6592 3.42445 13.4249L8.00041 8.84931L12.5758 13.4247Z" fill="currentColor"/></svg></span>
              </button>
            </div>
            <p class="notif__desc" id="js-alert-desc"></p>
          </div>
        </div>
        <div class="notif__actions">
          <button type="button" class="btn btn--m btn--ghost btn--ghost-gray" id="js-alert-dismiss"></button>
          <button type="button" class="btn btn--m btn--ghost btn--ghost-green" id="js-alert-action"></button>
        </div>
      </div>

      <!-- ===== 直播源列表 ===== -->
      <section class="panel" aria-labelledby="js-page-title">
        <div class="tbl-wrap">
          <table class="tbl">
            <colgroup>
              <col style="width: 22%">
              <col style="width: 24%">
              <col style="width: 17%">
              <col style="width: 13%">
              <col style="width: 10%">
              <col style="width: 14%">
            </colgroup>
            <thead class="tbl-head"><tr id="js-thead"></tr></thead>
            <tbody class="tbl-body" id="js-tbody"></tbody>
          </table>
        </div>
      </section>

    </div>
  </main>
</div>

<!-- ===== 停止推流二次确认（Popup Box）===== -->
<div class="popup-overlay" id="js-popup-overlay" hidden>
  <div class="popup-box" role="dialog" aria-modal="true" aria-labelledby="js-popup-title" tabindex="-1" id="js-popup">
    <header class="popup-box__header">
      <h2 class="popup-box__title" id="js-popup-title"></h2>
      <button type="button" class="popup-box__close" id="js-popup-close">
        <span class="ico" style="width:12px;height:12px" aria-hidden="true"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M12.5758 13.4247C12.8101 13.659 13.19 13.659 13.4243 13.4247C13.6586 13.1903 13.6586 12.8104 13.4243 12.5761L8.84898 8.00082L13.4248 3.42543C13.6591 3.19113 13.6591 2.81123 13.4248 2.5769C13.1905 2.34258 12.8106 2.34256 12.5763 2.57687L8.00045 7.15229L3.42429 2.57613C3.18997 2.34181 2.81007 2.34181 2.57576 2.57613C2.34145 2.81044 2.34145 3.19034 2.57576 3.42465L7.15188 8.00078L2.57596 12.5763C2.34163 12.8106 2.34161 13.1905 2.57592 13.4248C2.81022 13.6592 3.19012 13.6592 3.42445 13.4249L8.00041 8.84931L12.5758 13.4247Z" fill="currentColor"/></svg></span>
      </button>
    </header>

    <div class="popup-box__body">
      <div class="confirm-lead">
        <span class="confirm-lead__icon" aria-hidden="true"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M8.69915 2.00682C8.39424 1.45866 7.60582 1.45867 7.30091 2.00682L1.10136 13.152C0.80475 13.6853 1.19031 14.3409 1.80048 14.3409H14.1996C14.8098 14.3409 15.1953 13.6853 14.8987 13.152L8.69915 2.00682ZM7.39999 6.60078C7.39999 6.26941 7.66862 6.00078 7.99999 6.00078C8.33136 6.00078 8.59999 6.26941 8.59999 6.60078V9.00078C8.59999 9.33215 8.33136 9.60078 7.99999 9.60078C7.66862 9.60078 7.39999 9.33215 7.39999 9.00078V6.60078ZM7.39999 11.4011C7.39999 11.0698 7.66862 10.8011 7.99999 10.8011C8.33136 10.8011 8.59999 11.0698 8.59999 11.4011C8.59999 11.7325 8.33136 12.0011 7.99999 12.0011C7.66862 12.0011 7.39999 11.7325 7.39999 11.4011Z" fill="currentColor"/></svg></span>
        <div class="confirm-body">
          <p id="js-popup-question"></p>
          <div class="confirm-impact" id="js-popup-impact"></div>
          <p class="notif__desc" id="js-popup-note"></p>
        </div>
      </div>
    </div>

    <footer class="popup-box__footer">
      <button type="button" class="btn btn--m btn--ghost btn--ghost-gray" id="js-popup-cancel"></button>
      <button type="button" class="btn btn--m btn--fill-red" id="js-popup-confirm"></button>
    </footer>
  </div>
</div>

<div class="toast-stack" id="js-toasts" aria-live="polite" aria-atomic="false"></div>

<script>
(function () {
  'use strict';

  /* ==========================================================================
   * R16 —— 产品 UI 字符串走 const map 单源、单语；⛔ 禁 inline 双语拼接。
   *        （代码注释不进 runtime，可任意语言。）
   * ========================================================================*/
  var T = {
    productName: 'TVU Live Console',
    userName: 'N. Zeng',
    nav: [
      { label: 'Sources',   value: 'sources',   active: true },
      { label: 'Receivers', value: 'receivers' },
      { label: 'Schedule',  value: 'schedule'  },
      { label: 'Analytics', value: 'analytics' }
    ],
    pageTitle: 'Sources',
    pageSub: function (n) { return n + ' sources · live monitoring'; },
    counters: { onair: 'On air', warning: 'Warning', offline: 'Offline' },
    searchPlaceholder: 'Search source, URI or destination',
    filterStatusAll: 'All statuses',
    filterRegionAll: 'All regions',
    refresh: 'Refresh',
    resultCount: function (shown, total) { return 'Showing ' + shown + ' of ' + total; },
    columns: {
      source: 'Source', input: 'Input URI', destination: 'Destination',
      status: 'Status', loss: 'Packet loss', actions: 'Actions'
    },
    status: { onair: 'ON AIR', warning: 'WARNING', offline: 'OFFLINE' },
    /* M9 —— 行内动作一律 literal 动词短语，⛔ 不用图标缩写或对象名代替动词 */
    actions: {
      stop: 'Stop Push', start: 'Start Push', details: 'Details',
      setup: 'Set Input and Destination', more: 'More actions',
      copyUri: 'Copy input URI', viewLog: 'View stream log', editDest: 'Edit destination'
    },
    emptyState: 'No source matches the current filter.',
    neverStarted: 'Never started',
    lastRan: function (from, to, mins) { return 'Last ran ' + from + ' → ' + to + ' · ' + mins + ' min'; },
    uptime: function (v) { return 'Uptime ' + v; },
    /* M12 / M13 —— 掉线不静默：告知发生了什么 + 给一键前进路径 */
    alert: {
      title: 'SRC-07 dropped at 10:32 and did not recover',
      desc: 'Auto-reconnect failed 3 times. The last recording segment was closed and stored on Dallas Receiver 3. The source is now offline and is not sending video.',
      dismiss: 'Dismiss',
      action: 'Reconnect SRC-07'
    },
    /* 二次确认 —— 停止推流会造成业务中断 */
    confirm: {
      title: function (id) { return 'Stop push for ' + id + '?'; },
      question: function (name) { return name + ' is currently on air. Stopping the push cuts the feed to its destination immediately.'; },
      k: { destination: 'Destination', uptime: 'On air for', uri: 'Input URI' },
      note: 'Restarting requires the encoder to re-establish the session, which typically takes 20–40 seconds.',
      cancel: 'Cancel',
      confirm: 'Stop Push'
    },
    toast: {
      stopped: function (id) { return id + ' stopped'; },
      stoppedDesc: 'The feed to its destination has been cut. The source is now offline.',
      started: function (id) { return id + ' push started'; },
      startedDesc: 'Waiting for the encoder to establish the session.',
      reconnect: function (id) { return 'Reconnecting ' + id + '…'; },
      reconnectDesc: 'A new session request was sent to the encoder.',
      copied: 'Input URI copied to clipboard.',
      copiedTitle: 'Copied'
    }
  };

  /* ==========================================================================
   * 图标 —— 全部取自 src/icons/raw.ts 具名常量，fill="currentColor"。
   *         R1 / R15：⛔ 零内联自画 SVG。
   * ========================================================================*/
  var ICON = {
    /* navigation/more —— tags: overflow / kebab / context-menu */
    more: '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M9.5 3.5C9.5 4.32843 8.82843 5 8 5C7.17157 5 6.5 4.32843 6.5 3.5C6.5 2.67157 7.17157 2 8 2C8.82843 2 9.5 2.67157 9.5 3.5ZM9.5 8C9.5 8.82843 8.82843 9.5 8 9.5C7.17157 9.5 6.5 8.82843 6.5 8C6.5 7.17157 7.17157 6.5 8 6.5C8.82843 6.5 9.5 7.17157 9.5 8ZM8 14C8.82843 14 9.5 13.3284 9.5 12.5C9.5 11.6716 8.82843 11 8 11C7.17157 11 6.5 11.6716 6.5 12.5C6.5 13.3284 7.17157 14 8 14Z" fill="currentColor"/></svg>',
    /* action/copy */
    copy: '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M2.2 1C1.53726 1 1 1.53726 1 2.2V11.4C1 12.0627 1.53726 12.6 2.2 12.6H3.40002V13.8004C3.40002 14.4631 3.93728 15.0004 4.60002 15.0004H13.8C14.4628 15.0004 15 14.4631 15 13.8004V4.60039C15 3.93765 14.4628 3.40039 13.8 3.40039H12.6V2.2C12.6 1.53726 12.0627 1 11.4 1H2.2ZM11.4 3.40039H4.60002C3.93728 3.40039 3.40002 3.93765 3.40002 4.60039V11.3992H2.60001C2.3791 11.3992 2.20001 11.2201 2.20001 10.9992V2.59922C2.20001 2.3783 2.3791 2.19922 2.60001 2.19922H11C11.2209 2.19922 11.4 2.3783 11.4 2.59922V3.40039ZM4.60004 4.99961C4.60004 4.77869 4.77912 4.59961 5.00004 4.59961H13.4C13.621 4.59961 13.8 4.7787 13.8 4.99961V13.3996C13.8 13.6205 13.621 13.7996 13.4 13.7996H5.00004C4.77912 13.7996 4.60004 13.6205 4.60004 13.3996V4.99961Z" fill="currentColor"/></svg>',
    /* navigation/timeline —— 用于「查看流日志」 */
    timeline: '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M2.2 1C1.53726 1 1 1.53726 1 2.2V3.8C1 4.46274 1.53726 5 2.2 5H10.8C11.4627 5 12 4.46274 12 3.8V2.2C12 1.53726 11.4627 1 10.8 1H2.2ZM2.40009 2.20002L10.6 2.20076C10.7104 2.20077 10.8 2.29031 10.8 2.40076V3.60147C10.8 3.71192 10.7104 3.80147 10.6 3.80147H2.4C2.28953 3.80147 2.19999 3.71191 2.19999 3.60145L2.20007 2.40001C2.20007 2.28955 2.28963 2.20001 2.40009 2.20002ZM5.2 6C4.53726 6 4 6.53726 4 7.2V8.8C4 9.46274 4.53726 10 5.2 10H13.8C14.4627 10 15 9.46274 15 8.8V7.2C15 6.53726 14.4627 6 13.8 6H5.2ZM5.40009 7.20002L13.6 7.20076C13.7104 7.20077 13.8 7.29031 13.8 7.40076V8.60147C13.8 8.71192 13.7104 8.80147 13.6 8.80147H5.4C5.28953 8.80147 5.19999 8.71192 5.2 8.60145L5.20007 7.40001C5.20007 7.28955 5.28963 7.20001 5.40009 7.20002ZM1 12.2C1 11.5373 1.53726 11 2.2 11H10.8C11.4627 11 12 11.5373 12 12.2V13.8C12 14.4627 11.4627 15 10.8 15H2.2C1.53726 15 1 14.4627 1 13.8V12.2ZM2.20007 12.4L2.19999 13.6015C2.19999 13.7119 2.28953 13.8015 2.4 13.8015H10.6C10.7104 13.8015 10.8 13.7119 10.8 13.6015V12.4008C10.8 12.2903 10.7104 12.2008 10.6 12.2008L2.40009 12.2C2.28963 12.2 2.20007 12.2895 2.20007 12.4Z" fill="currentColor"/></svg>',
    /* action/edit */
    edit: '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M11.7515 1.35147C11.5264 1.12643 11.2212 1 10.9029 1C10.5847 1 10.2794 1.12643 10.0544 1.35147L1.35147 10.0544C1.12643 10.2794 1 10.5847 1 10.9029V14C1 14.5523 1.44772 15 2 15H5.09706C5.41532 15 5.72055 14.8736 5.94559 14.6485L14.6485 5.94559C14.8736 5.72055 15 5.41532 15 5.09706C15 4.7788 14.8736 4.47357 14.6485 4.24853L11.7515 1.35147ZM10.9029 2.2L13.8 5.09706L11.9 6.99706L9.00294 4.1L10.9029 2.2ZM8.15441 4.94853L11.0515 7.84559L5.09706 13.8H2.2V10.9029L8.15441 4.94853Z" fill="currentColor"/></svg>',
    /* navigation/arrow-up · arrow-down —— M11 trend 的 icon 层 */
    arrowUp: '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M12.7387 7.18614C12.9731 7.42046 13.3529 7.42046 13.5873 7.18614C13.8216 6.95183 13.8216 6.57193 13.5873 6.33762L8.4417 1.19205C8.43644 1.18637 8.43105 1.18077 8.42553 1.17525C8.19122 0.940938 7.81132 0.940938 7.577 1.17525L2.41512 6.33713C2.18081 6.57145 2.18081 6.95135 2.41512 7.18566C2.64944 7.41997 3.02934 7.41997 3.26365 7.18566L7.40144 3.04787L7.40144 14.402C7.40144 14.7333 7.67007 15.002 8.00144 15.002C8.33281 15.002 8.60144 14.7333 8.60144 14.402L8.60144 3.04885L12.7387 7.18614Z" fill="currentColor"/></svg>',
    arrowDown: '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M3.26364 8.81581C3.02933 8.58149 2.64943 8.58149 2.41512 8.81581C2.1808 9.05012 2.1808 9.43002 2.41512 9.66434L7.56068 14.8099C7.56594 14.8156 7.57133 14.8212 7.57685 14.8267C7.81116 15.061 8.19106 15.061 8.42538 14.8267L13.5873 9.66482C13.8216 9.43051 13.8216 9.05061 13.5873 8.81629C13.3529 8.58198 12.973 8.58198 12.7387 8.81629L8.60094 12.9541L8.60094 1.6C8.60094 1.26863 8.33231 1 8.00094 1C7.66957 1 7.40094 1.26863 7.40094 1.6L7.40094 12.9531L3.26364 8.81581Z" fill="currentColor"/></svg>',
    /* status/success —— toast 用 */
    success: '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M8 15C11.866 15 15 11.866 15 8C15 4.13401 11.866 1 8 1C4.13401 1 1 4.13401 1 8C1 11.866 4.13401 15 8 15ZM5.22424 7.37564L7.03029 9.18159C7.10839 9.2597 7.23502 9.25969 7.31313 9.18159L10.6017 5.89303C10.836 5.65871 11.2159 5.65871 11.4502 5.89303C11.6846 6.12735 11.6846 6.50725 11.4502 6.74156L7.80811 10.3837C7.45664 10.7351 6.88681 10.7351 6.53533 10.3837L4.37573 8.22419C4.14141 7.98988 4.1414 7.60998 4.37571 7.37566C4.61002 7.14134 4.98991 7.14133 5.22424 7.37564Z" fill="currentColor"/></svg>',
    /* status/warning */
    warning: '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M8 1C4.13438 1 1 4.13438 1 8C1 11.8656 4.13438 15 8 15C11.8656 15 15 11.8656 15 8C15 4.13438 11.8656 1 8 1ZM7.9999 4C8.33127 4 8.5999 4.26863 8.5999 4.6V9C8.5999 9.33137 8.33127 9.6 7.9999 9.6C7.66853 9.6 7.3999 9.33137 7.3999 9V4.6C7.3999 4.26863 7.66853 4 7.9999 4ZM7.9999 12C7.66853 12 7.3999 11.7314 7.3999 11.4C7.3999 11.0686 7.66853 10.8 7.9999 10.8C8.33127 10.8 8.5999 11.0686 8.5999 11.4C8.5999 11.7314 8.33127 12 7.9999 12Z" fill="currentColor"/></svg>',
    /* action/close */
    close: '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M12.5758 13.4247C12.8101 13.659 13.19 13.659 13.4243 13.4247C13.6586 13.1903 13.6586 12.8104 13.4243 12.5761L8.84898 8.00082L13.4248 3.42543C13.6591 3.19113 13.6591 2.81123 13.4248 2.5769C13.1905 2.34258 12.8106 2.34256 12.5763 2.57687L8.00045 7.15229L3.42429 2.57613C3.18997 2.34181 2.81007 2.34181 2.57576 2.57613C2.34145 2.81044 2.34145 3.19034 2.57576 3.42465L7.15188 8.00078L2.57596 12.5763C2.34163 12.8106 2.34161 13.1905 2.57592 13.4248C2.81022 13.6592 3.19012 13.6592 3.42445 13.4249L8.00041 8.84931L12.5758 13.4247Z" fill="currentColor"/></svg>'
  };

  /* ==========================================================================
   * 数据 —— M5：monitoring 页面是 readonly 监控 + 控制台，⛔ 不暴露创建入口，
   *        源由编码器 / 后端接入后 push 进来。
   *        M10：off 状态必须分两子型（Idle 曾运行 vs NeverStarted 从未运行）。
   * ========================================================================*/
  var SOURCES = [
    { id:'SRC-01', name:'Studio A — Main PGM',        proto:'SRT',  uri:'srt://ingest-dal.tvunetworks.com:9001?streamid=studio-a-pgm&latency=120&passphrase=set', dest:'Dallas Receiver 1',   region:'us',   state:'onair',   loss:0.00, trend:'stable',     uptime:'04:12:37' },
    { id:'SRC-02', name:'Field Unit — NY Downtown',   proto:'SRT',  uri:'srt://ingest-nyc.tvunetworks.com:9012?streamid=field-ny-downtown',                     dest:'Cloud Transcoder EU', region:'eu',   state:'onair',   loss:0.02, trend:'converging', uptime:'01:48:02' },
    { id:'SRC-03', name:'Stadium Cam 3 — East Gate',  proto:'RTMP', uri:'rtmp://ingest-dal.tvunetworks.com/live/stadium-east-gate-3',                            dest:'Dallas Receiver 2',   region:'us',   state:'warning', loss:4.31, trend:'diverging',  uptime:'00:52:19' },
    { id:'SRC-04', name:'Mobile Backpack — LiveU Van',proto:'ZIXI', uri:'zixi://feeder-us-east.tvunetworks.com:2088/mobile-backpack-van-04',                     dest:'Cloud Transcoder US', region:'us',   state:'onair',   loss:0.01, trend:'converging', uptime:'02:31:55' },
    { id:'SRC-05', name:'Studio B — Backup Feed',     proto:'SRT',  uri:'srt://ingest-dal.tvunetworks.com:9002?streamid=studio-b-backup',                        dest:'Dallas Receiver 1',   region:'us',   state:'offline', sub:'idle',         lastFrom:'09:12', lastTo:'10:47', lastMins:95 },
    { id:'SRC-06', name:'Remote Guest — Tokyo',       proto:'RTMP', uri:'rtmp://ingest-apac.tvunetworks.com/live/remote-guest-tokyo-01',                         dest:'Cloud Transcoder APAC',region:'apac',state:'warning', loss:2.14, trend:'diverging',  uptime:'00:17:44' },
    { id:'SRC-07', name:'Roof Cam — Weather',         proto:'RTSP', uri:'rtsp://10.42.18.7:554/roofcam/stream1',                                                 dest:'Dallas Receiver 3',   region:'us',   state:'offline', sub:'idle',         lastFrom:'06:00', lastTo:'10:32', lastMins:272 },
    { id:'SRC-08', name:'Truck 2 — Unassigned',       proto:null,   uri:null,                                                                                    dest:null,                  region:'us',   state:'offline', sub:'never' }
  ];

  var REGIONS = [
    { value:'all',  label: T.filterRegionAll },
    { value:'us',   label:'US' },
    { value:'eu',   label:'EU' },
    { value:'apac', label:'APAC' }
  ];

  var $ = function (id) { return document.getElementById(id); };
  function el(tag, cls, text) {
    var n = document.createElement(tag);
    if (cls) n.className = cls;
    if (text != null) n.textContent = text;
    return n;
  }
  function icon(svg, cls) {
    var s = el('span', 'ico' + (cls ? ' ' + cls : ''));
    s.setAttribute('aria-hidden', 'true');
    s.innerHTML = svg;
    return s;
  }

  /* ---- 静态文案注入（R16：全部来自 T） ---- */
  $('js-product-name').textContent = T.productName;
  $('js-user-name').textContent = T.userName;
  $('js-page-title').textContent = T.pageTitle;
  $('js-page-sub').textContent = T.pageSub(SOURCES.length);
  $('js-search').placeholder = T.searchPlaceholder;
  $('js-search').setAttribute('aria-label', T.searchPlaceholder);
  $('js-refresh-label').textContent = T.refresh;
  $('js-alert-title').textContent = T.alert.title;
  $('js-alert-desc').textContent = T.alert.desc;
  $('js-alert-dismiss').textContent = T.alert.dismiss;
  $('js-alert-action').textContent = T.alert.action;
  $('js-alert-close').setAttribute('aria-label', T.alert.dismiss);
  $('js-popup-cancel').textContent = T.confirm.cancel;
  $('js-popup-confirm').textContent = T.confirm.confirm;
  $('js-popup-close').setAttribute('aria-label', T.confirm.cancel);

  /* ---- 顶栏导航（MenuList horizontal）---- */
  (function renderNav() {
    var ul = $('js-nav');
    T.nav.forEach(function (item) {
      var li = el('li', 'menu-list__item' + (item.active ? ' is-active' : ''), item.label);
      li.setAttribute('role', 'menuitem');
      li.tabIndex = 0;
      if (item.active) li.setAttribute('aria-current', 'true');
      li.addEventListener('click', function () { setActiveNav(li); });
      li.addEventListener('keydown', function (e) {
        if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); setActiveNav(li); }
      });
      ul.appendChild(li);
    });
    function setActiveNav(node) {
      Array.prototype.forEach.call(ul.children, function (c) {
        c.classList.remove('is-active');
        c.removeAttribute('aria-current');
      });
      node.classList.add('is-active');
      node.setAttribute('aria-current', 'true');
    }
  })();

  /* ---- 筛选器 ---- */
  (function renderFilters() {
    var s = $('js-filter-status');
    [{ value:'all', label: T.filterStatusAll },
     { value:'onair', label: T.status.onair },
     { value:'warning', label: T.status.warning },
     { value:'offline', label: T.status.offline }].forEach(function (o) {
      var opt = el('option', null, o.label); opt.value = o.value; s.appendChild(opt);
    });
    s.setAttribute('aria-label', T.filterStatusAll);
    var r = $('js-filter-region');
    REGIONS.forEach(function (o) { var opt = el('option', null, o.label); opt.value = o.value; r.appendChild(opt); });
    r.setAttribute('aria-label', T.filterRegionAll);
  })();

  /* ---- 表头 ---- */
  (function renderHead() {
    var tr = $('js-thead');
    [T.columns.source, T.columns.input, T.columns.destination,
     T.columns.status, T.columns.loss, T.columns.actions].forEach(function (title, i) {
      var th = el('th', 'tbl-cell' + (i === 5 ? ' tbl-cell--right' : ''));
      th.scope = 'col';
      var wrap = el('span', 'tbl-cell__content');
      wrap.appendChild(el('span', null, title));
      th.appendChild(wrap);
      tr.appendChild(th);
    });
  })();

  /* ==========================================================================
   * 状态徽章 —— Pill 原语。三态色：M3 广播惯例 红=ON AIR（⛔ 不是绿=正常），
   *            橙=WARNING，灰=OFFLINE。每态都带文字 + 色点，不只靠颜色区分。
   *            M7：低饱和 18% 底，视觉权重最安静，不与 action 抢焦点。
   * ========================================================================*/
  function statusPill(state) {
    var map = { onair:['pill--onair', T.status.onair], warning:['pill--warning', T.status.warning], offline:['pill--offline', T.status.offline] };
    var cfg = map[state];
    var p = el('span', 'pill ' + cfg[0]);
    p.setAttribute('data-figma-component', 'Pill Status');
    p.appendChild(el('span', 'pill__dot'));
    p.appendChild(el('span', 'pill__label', cfg[1]));
    return p;
  }

  /* Badge Type=Rectangle · Tag=Line · Color=Neutral —— outline 是只读信息标签的标准 primitive */
  function protoBadge(proto) {
    var b = el('span', 'badge badge--line-neutral', proto);
    b.setAttribute('data-figma-component', 'Badge');
    return b;
  }

  /* R23 —— 截断三件套：(a) CSS ellipsis / (b) scrollWidth 检测 / (c) tooltip + a11y 双保险。
     ⛔ 反模式是「所有 cell 一律挂 tooltip」：短文本 hover 也弹 = 噪音 + 屏幕阅读器读两遍。 */
  var tipSeq = 0;
  function truncatable(text) {
    var wrap = el('span', 'tooltip-wrap');
    var span = el('span', 'cell--ellipsis', text);
    wrap.appendChild(span);
    wrap._probe = span;
    wrap._text = text;
    return wrap;
  }
  function applyTruncationDetection(root) {
    Array.prototype.forEach.call(root.querySelectorAll('.tooltip-wrap'), function (wrap) {
      var probe = wrap._probe;
      if (!probe) return;
      var isTruncated = probe.scrollWidth > probe.clientWidth;
      var existing = wrap.querySelector('.tooltip-box');
      if (!isTruncated) {
        if (existing) existing.remove();
        probe.removeAttribute('title');
        probe.removeAttribute('aria-describedby');
        probe.removeAttribute('tabindex');
        return;
      }
      if (existing) return;
      var id = 'tip-' + (++tipSeq);
      var box = el('div', 'tooltip-box');
      box.id = id;
      box.setAttribute('role', 'tooltip');
      box.appendChild(el('span', 'tooltip-content', wrap._text));
      box.appendChild(el('span', 'tooltip-arrow'));
      wrap.appendChild(box);
      probe.setAttribute('aria-describedby', id); /* 富 tooltip 的 a11y 通道 */
      probe.setAttribute('title', wrap._text);    /* no-JS / 触屏 fallback */
      probe.tabIndex = 0;                          /* 键盘可达 */
    });
  }

  /* M11 —— trend 必须 icon + 数值颜色双层信号，只给 icon 不算完整 spec。
     阈值：Δ 恶化 → ↑ 橙；Δ 改善 → ↓ 绿；阈值内 → 无 icon，中性色。 */
  function lossMetric(row) {
    if (row.state === 'offline') { return el('span', 'dash', '—'); }
    var cls = row.trend === 'diverging' ? 'metric--diverging'
            : row.trend === 'converging' ? 'metric--converging' : 'metric--stable';
    var m = el('span', 'metric ' + cls);
    if (row.trend === 'diverging') m.appendChild(icon(ICON.arrowUp));
    else if (row.trend === 'converging') m.appendChild(icon(ICON.arrowDown));
    m.appendChild(el('span', null, row.loss.toFixed(2) + '%'));
    return m;
  }

  function sourceCell(row) {
    var stack = el('div', 'cell-stack');
    stack.appendChild(el('div', 'cell-stack__main', row.name));
    var meta;
    if (row.state === 'offline' && row.sub === 'never') {
      /* M10 Inactive-NeverStarted：无 route / 无 delta，只给 bootstrap 提示 */
      meta = el('div', 'cell-stack__meta cell-stack__meta--cta', row.id + ' · ' + T.neverStarted);
    } else if (row.state === 'offline') {
      /* M10 Inactive-Idle：曾运行有 history ⇒ 心智是「停了可重启」 */
      meta = el('div', 'cell-stack__meta', row.id + ' · ' + T.lastRan(row.lastFrom, row.lastTo, row.lastMins));
    } else {
      meta = el('div', 'cell-stack__meta', row.id + ' · ' + T.uptime(row.uptime));
    }
    stack.appendChild(meta);
    return stack;
  }

  function uriCell(row) {
    if (!row.uri) return el('span', 'dash', '—');
    var box = el('div', 'uri');
    box.appendChild(protoBadge(row.proto));
    var t = truncatable(row.uri);
    t.classList.add('uri__text');
    box.appendChild(t);
    return box;
  }

  /* ==========================================================================
   * 行内操作 —— M7：action 视觉权重最高，一律正规 M(32px) 尺寸 button，
   *            ⛔ 不许自制 XS chip（Fitts 定律）；列宽不够就让列宽。
   *            配色按 catalog：red=Stop/destructive · green=Start · gray 1=中性。
   * ========================================================================*/
  function actionsCell(row) {
    var box = el('div', 'row-actions');

    if (row.state === 'onair' || row.state === 'warning') {
      var stop = el('button', 'btn btn--m btn--ghost btn--ghost-red', T.actions.stop);
      stop.type = 'button';
      stop.addEventListener('click', function () { openStopConfirm(row); });
      box.appendChild(stop);
      box.appendChild(secondary(row));
    } else if (row.sub === 'never') {
      /* M10：NeverStarted 的 CTA 必须是 literal verb 的配置引导，不是「重启」 */
      var setup = el('button', 'btn btn--m btn--ghost btn--ghost-green', T.actions.setup);
      setup.type = 'button';
      setup.addEventListener('click', function () { toast('success', T.actions.setup, T.toast.startedDesc); });
      box.appendChild(setup);
    } else {
      var start = el('button', 'btn btn--m btn--ghost btn--ghost-green', T.actions.start);
      start.type = 'button';
      start.addEventListener('click', function () {
        row.state = 'onair'; row.loss = 0.00; row.trend = 'stable'; row.uptime = '00:00:04';
        delete row.sub; render();
        toast('success', T.toast.started(row.id), T.toast.startedDesc);
      });
      box.appendChild(start);
      box.appendChild(secondary(row));
    }
    return box;
  }

  /* 次要操作：Details（中性 ghost）+ More 溢出菜单 */
  function secondary(row) {
    var frag = document.createDocumentFragment();

    var details = el('button', 'btn btn--m btn--ghost btn--ghost-gray', T.actions.details);
    details.type = 'button';
    details.addEventListener('click', function () { toast('success', row.id, T.actions.details); });
    frag.appendChild(details);

    var wrap = el('div', 'overflow');
    var trigger = el('button', 'btn btn--m btn--text-gray btn--icononly');
    trigger.type = 'button';
    trigger.setAttribute('aria-label', T.actions.more);
    trigger.setAttribute('aria-haspopup', 'menu');
    trigger.setAttribute('aria-expanded', 'false');
    trigger.appendChild(icon(ICON.more));

    var panel = el('div', 'overflow__panel');
    panel.hidden = true;
    var list = el('ul', 'menu-list menu-list--vertical');
    list.setAttribute('role', 'menu');
    [{ label: T.actions.copyUri, ico: ICON.copy, disabled: !row.uri, run: function () { toast('success', T.toast.copiedTitle, T.toast.copied); } },
     { label: T.actions.viewLog, ico: ICON.timeline, run: function () { toast('success', row.id, T.actions.viewLog); } },
     { label: T.actions.editDest, ico: ICON.edit, run: function () { toast('success', row.id, T.actions.editDest); } }
    ].forEach(function (m) {
      var li = el('li', 'menu-list__item' + (m.disabled ? ' is-disabled' : ''));
      li.setAttribute('role', 'menuitem');
      li.tabIndex = m.disabled ? -1 : 0;
      if (m.disabled) li.setAttribute('aria-disabled', 'true');
      li.appendChild(icon(m.ico));
      li.appendChild(el('span', 'menu-list__label', m.label));
      if (!m.disabled) {
        li.addEventListener('click', function () { closePanel(); m.run(); });
        li.addEventListener('keydown', function (e) {
          if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); closePanel(); m.run(); }
        });
      }
      list.appendChild(li);
    });
    panel.appendChild(list);

    function closePanel() { panel.hidden = true; trigger.setAttribute('aria-expanded', 'false'); }
    trigger.addEventListener('click', function (e) {
      e.stopPropagation();
      var willOpen = panel.hidden;
      closeAllPanels();
      panel.hidden = !willOpen;
      trigger.setAttribute('aria-expanded', String(willOpen));
    });

    wrap.appendChild(trigger);
    wrap.appendChild(panel);
    frag.appendChild(wrap);
    return frag;
  }

  function closeAllPanels() {
    Array.prototype.forEach.call(document.querySelectorAll('.overflow__panel'), function (p) { p.hidden = true; });
    Array.prototype.forEach.call(document.querySelectorAll('.overflow .btn'), function (b) { b.setAttribute('aria-expanded', 'false'); });
  }
  document.addEventListener('click', closeAllPanels);

  /* ==========================================================================
   * 渲染
   * ========================================================================*/
  function visibleRows() {
    var q = ($('js-search').value || '').trim().toLowerCase();
    var st = $('js-filter-status').value;
    var rg = $('js-filter-region').value;
    return SOURCES.filter(function (r) {
      if (st !== 'all' && r.state !== st) return false;
      if (rg !== 'all' && r.region !== rg) return false;
      if (!q) return true;
      return [r.id, r.name, r.uri || '', r.dest || ''].join(' ').toLowerCase().indexOf(q) !== -1;
    });
  }

  function renderCounters() {
    var box = $('js-counters');
    box.innerHTML = '';
    var counts = { onair: 0, warning: 0, offline: 0 };
    SOURCES.forEach(function (r) { counts[r.state]++; });
    [['fill-red', counts.onair, T.counters.onair],
     ['fill-orange', counts.warning, T.counters.warning],
     ['fill-neutral', counts.offline, T.counters.offline]].forEach(function (c) {
      box.appendChild(el('span', 'badge badge--' + c[0], c[1] + ' ' + c[2]));
    });
  }

  function render() {
    var tbody = $('js-tbody');
    tbody.innerHTML = '';
    var rows = visibleRows();

    if (!rows.length) {
      var tr = el('tr', 'tbl-empty-row');
      var td = el('td', 'tbl-empty', T.emptyState);
      td.colSpan = 6;
      tr.appendChild(td);
      tbody.appendChild(tr);
    } else {
      rows.forEach(function (row) {
        var tr = el('tr', 'tbl-row');
        [ sourceCell(row), uriCell(row),
          row.dest ? truncatable(row.dest) : el('span', 'dash', '—'),
          statusPill(row.state), lossMetric(row), actionsCell(row)
        ].forEach(function (node, i) {
          var td = el('td', 'tbl-cell' + (i === 5 ? ' tbl-cell--right' : ''));
          var wrap = el('span', 'tbl-cell__content');
          wrap.appendChild(node);
          td.appendChild(wrap);
          tr.appendChild(td);
        });
        tbody.appendChild(tr);
      });
    }

    $('js-result-count').textContent = T.resultCount(rows.length, SOURCES.length);
    $('js-page-sub').textContent = T.pageSub(SOURCES.length);
    renderCounters();
    applyTruncationDetection(tbody);
  }

  /* 动态内容 / 容器 resize 后重判截断（R23：不能只在 mount 时判一次） */
  if (window.ResizeObserver) {
    new ResizeObserver(function () { applyTruncationDetection($('js-tbody')); })
      .observe(document.querySelector('.tbl-wrap'));
  }

  $('js-search').addEventListener('input', render);
  $('js-filter-status').addEventListener('change', render);
  $('js-filter-region').addEventListener('change', render);
  $('js-refresh').addEventListener('click', render);

  /* ==========================================================================
   * 停止推流二次确认（Popup Box）—— 破坏性动作走 modal，
   * footer 按 catalog：destructive = filling/red，cancel = ghost/gray 1。
   * ========================================================================*/
  var pendingRow = null;
  var lastFocused = null;
  var overlay = $('js-popup-overlay');
  var dialog = $('js-popup');
  var FOCUSABLE = 'button:not([disabled]),[href],input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])';

  function openStopConfirm(row) {
    pendingRow = row;
    $('js-popup-title').textContent = T.confirm.title(row.id);
    $('js-popup-question').textContent = T.confirm.question(row.name);
    $('js-popup-note').textContent = T.confirm.note;

    var impact = $('js-popup-impact');
    impact.innerHTML = '';
    [[T.confirm.k.destination, row.dest],
     [T.confirm.k.uptime, row.uptime],
     [T.confirm.k.uri, row.uri]].forEach(function (kv) {
      var r = el('div', 'confirm-impact__row');
      r.appendChild(el('span', 'confirm-impact__k', kv[0]));
      r.appendChild(el('span', 'confirm-impact__v', kv[1]));
      impact.appendChild(r);
    });

    lastFocused = document.activeElement;
    overlay.hidden = false;
    document.body.style.overflow = 'hidden';
    window.addEventListener('keydown', onDialogKeydown);
    requestAnimationFrame(function () {
      var f = dialog.querySelectorAll(FOCUSABLE);
      (f[0] || dialog).focus();
    });
  }

  function closeConfirm() {
    overlay.hidden = true;
    pendingRow = null;
    document.body.style.overflow = '';
    window.removeEventListener('keydown', onDialogKeydown);
    if (lastFocused) lastFocused.focus();
  }

  function onDialogKeydown(e) {
    if (e.key === 'Escape') { e.preventDefault(); closeConfirm(); return; }
    if (e.key !== 'Tab') return;
    var f = Array.prototype.slice.call(dialog.querySelectorAll(FOCUSABLE));
    if (!f.length) { e.preventDefault(); dialog.focus(); return; }
    var first = f[0], last = f[f.length - 1];
    if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); }
    else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); }
  }

  /* PopupBox 默认 closeOnBackdrop=false —— 破坏性确认不应被误点背景关掉 */
  $('js-popup-close').addEventListener('click', closeConfirm);
  $('js-popup-cancel').addEventListener('click', closeConfirm);
  $('js-popup-confirm').addEventListener('click', function () {
    var row = pendingRow;
    if (!row) return;
    row.state = 'offline';
    row.sub = 'idle';
    row.lastFrom = '08:41';
    row.lastTo = new Date().toTimeString().slice(0, 5);
    row.lastMins = 184;
    closeConfirm();
    render();
    /* M12：动作产生的状态变化必须有可见反馈，不静默 */
    toast('success', T.toast.stopped(row.id), T.toast.stoppedDesc);
  });

  /* ---- toast（Notification slide 形态：320px，被动告知，无 action 行）---- */
  function toast(kind, title, desc) {
    var n = el('div', 'notif notif--slide');
    n.setAttribute('role', 'status');
    var main = el('div', 'notif__main');
    main.appendChild(icon(kind === 'success' ? ICON.success : ICON.warning, 'notif__icon'));
    var body = el('div', 'notif__body');
    var head = el('div', 'notif__header');
    head.appendChild(el('h2', 'notif__title', title));
    var close = el('button', 'notif__close');
    close.type = 'button';
    close.setAttribute('aria-label', T.alert.dismiss);
    close.appendChild(icon(ICON.close));
    close.addEventListener('click', function () { n.remove(); });
    head.appendChild(close);
    body.appendChild(head);
    body.appendChild(el('p', 'notif__desc', desc));
    main.appendChild(body);
    n.appendChild(main);
    n.querySelector('.notif__icon').style.color = kind === 'success' ? 'var(--brand)' : 'var(--orange)';
    $('js-toasts').appendChild(n);
    setTimeout(function () { n.remove(); }, 5000);
  }

  /* ---- 页面级告警条的前进路径（M12 第 3 段：能一键操作就给一键）---- */
  $('js-alert-action').addEventListener('click', function () {
    var row = SOURCES.filter(function (r) { return r.id === 'SRC-07'; })[0];
    if (row) {
      row.state = 'onair'; row.loss = 0.00; row.trend = 'stable'; row.uptime = '00:00:02';
      delete row.sub;
      render();
    }
    $('js-alert').hidden = true;
    toast('success', T.toast.reconnect('SRC-07'), T.toast.reconnectDesc);
  });
  $('js-alert-dismiss').addEventListener('click', function () { $('js-alert').hidden = true; });
  $('js-alert-close').addEventListener('click', function () { $('js-alert').hidden = true; });

  render();
})();
</script>
```

几点交付说明：

**三态的颜色为什么是红/橙/灰而不是绿/黄/灰** —— `domain-tvu.md` M3 是硬规则：TVU 客户是广播工程师，**红 = on air 是肌肉记忆**，规则逐字写着"不要按 SaaS 默认（绿=活跃、红=危险）配色"。所以「正常推流中」= 红 ON AIR，告警 = 橙，离线 = 灰 muted。三态各带文字 label + 色点，不单靠颜色区分。

**几处按规则做的、可能和直觉不同的决定**：
- **没有「新建直播源」按钮** —— M5：监控/控制台页是 readonly 监控 + 控制台，源由编码器/后端接入后 push 进来，不在此页创建。
- **离线分了两个子型** —— M10 强制：`SRC-05/07` 是 Inactive-Idle（副标题 `Last ran 09:12 → 10:47 · 95 min`，心智=停了可重启），`SRC-08` 是 Inactive-NeverStarted（`Never started` + CTA `Set Input and Destination`）。合并成单一 Inactive 会丢掉 bootstrap 引导场景。
- **行内按钮是 32px 正规尺寸不是小 chip** —— M7 + catalog 的 `Don't use for` 逐字："不要自画 XS chip 替代它，column 不够宽就让 column 变宽"。
- **UI 文案全英文单语** —— R16/M33：产品 UI 字符串走 const map 单源，禁中英混排；双语只允许在代码注释里（页面里的中文注释即按此规则）。
- **顶栏导航 active 态只变文字色、没有品牌色下划线** —— owner 2026-08-14 已就此裁定"以 Figma 为准"，`MenuList.vue` 里逐字标着 "⛔ Do not re-add it"。
- **URI 列没用等宽字体** —— `variables.css` 明写本系统"deliberately NO separate monospace font"，数据列走 base 字族 + `tabular-nums`。
- **只交付 dark** —— M4：运营控制台类默认仅 dark（token 的 light 定义保留在文件里以忠实真源，但页面不给切换、不展示浅色）。

**一处如实登记的库缺口**：`Pill Status` 的 Status 轴只有 `On-air / Preview / Analyzing / Inactive`，**没有 warning 档**。我没给它加第五个 status 值（那会撞硬规则 #3「不创建未登记的 prop/variant/状态」），而是降到底层 `Pill` 原语——它的 `dotColor` 枚举本来就含 `orange`——套用 PillStatus 同一套 CANONICAL-028 配方只换 hue。这条应登记为 `BRIDGE-CODE-CANONICAL` sync 候选，由 designer 决定要不要在 Figma 补 warning 变体。

**两处本次会话无法验证的**：worktree 里没有 `dist/icons/svg/`（未 build），所以图标是从 `src/icons/raw.ts` 的具名常量逐字取的同一份真源，不是从 dist 取；另外本次只读、Bash 停用，`pnpm audit:no-hardcoded-design-tokens` / `audit:layout-tokens` 这两条本该扫这类自包含页面的闸**没有实跑过**——页面内除图标资产自带的品牌绿（属已登记的 `icon-variable-resolved-to-hex` divergence）外无裸 hex、断点字面量取自 `--bp-*` 值集，但这是我自己比对的，不是闸的结论。