我已走完 onboarding 链路（L-core 四份 + AGENTS 全文 + code-conventions 起手必读段 + domain-tvu 全文 + design-process M22/M48 + R7/R9/R13/R15/R23），并从活源取到 token、canonical 组件几何与图标 SVG。下面是交付。

---

## 起手声明（R7 / Task Entry Mode）

**Intent classified as 场景 3.2**（Greenfield 单页 · 用 TVU 构建）+ **US-2**。视觉真源 = TVU library，R1/R2/R3/R15 全套强约束。Pre-Phase 0 不触发（你已给出完整 element 清单）。

**M22 design-intent 对账**（2 条，< 3 合并进 mapping）：
| 你的原话 | 落到哪个决策 |
|---|---|
| 「一屏内掌握所有直播源的当前状况」 | 信息层级 = 状态筛选条（含计数）+ 单表全展开，不做折叠卡；每行状态附**原因副标题**，不用点开就知道为什么告警 |
| 「对单个源执行日常操作」 | 行内双按钮常驻（不藏进 ⋯ 菜单），符合 M7「actions 权重最高」 |

## R0 · Phase 0 Element→Component Mapping

| Element | 库真源 | 取数路径 | 状态 |
|---|---|---|---|
| 页面顶栏 | Figma `Top bar` | `src/canonical/TopBar.vue` → `components/TopBar/TopBar.vue` | ✅ 1:1 mirror |
| 顶栏主导航 | Menu sub-node `4771:7173` | `components/MenuList/MenuList.vue` `--horizontal` | ✅ **无下划线**（owner 2026-08-14 裁定，active 仅换文字色） |
| 用户菜单 | 产品文件 `DtZcMkhNy6qh6jbQQnhreQ` `6423:85` | `components/UserMenu/UserMenu.vue` | ✅ file-local 真源，360px 面板 / 2 条分隔线 |
| 面包屑 | Figma `Breadcrumb` | `components/Breadcrumb/BreadcrumbItem.vue` | ✅ 分隔符 = `navigation/arrow-right` |
| 状态筛选 | Figma `Tab List` `4452:7149` | `components/Tab/Tab.vue` + `TabItem.vue`（filled） | ✅ gap 0 / bg layer2 |
| 搜索框 | Figma `input box/line` | `components/Input/Input.vue`（size m） | ✅ 不加 prefix icon（库无此 slot，不发明 API） |
| 列表 | Figma `Table` `4265:16132` | `canonical/Table.vue` → `components/Table/Table.vue` | ✅ cell padding 8/8/8/0 · 含 INFRA-F129 ① 的 `td` 中和 |
| 状态徽章 · 正常/离线 | Figma `Pill Status` `5672:140617` | `canonical/PillStatus.vue` | ✅ On-air / Inactive 直接命中 |
| 状态徽章 · **告警** | — | Pill base 的 `bgCss/dotCss/borderCss/textCss` token override | 🟡 **LIBRARY GAP**（见下） |
| 行内操作 | Figma `Button` 8-set | `canonical/ButtonBridge.vue` → `components/Button/Button.vue` | ✅ ghost/rimless gray S，几何逐字取自 `SIZE_GEOMETRY` |
| 二次确认 | Figma `Popup Box` `1939:43682` | `components/PopupBox/PopupBox.vue` | ✅ focus trap / ESC / body lock / 焦点还原全 mirror |
| 结果反馈 | Figma `Notification` `1408:17154` | `components/Notification/Notification.vue`（`slide`×`success`） | ✅ M12 反馈载体走 Notification set |
| URL 截断提示 | Figma `Tooltip` | `components/Tooltip/Tooltip.vue` | ✅ R23 三件套 |

**唯一的 gap：`Pill Status` 的 `Status` 轴只有 `On-air / Preview / Analyzing / Inactive`，没有 warning 值。** 我没有自画：用的是 base `Pill` 已公开的 token-driven override（`CANONICAL-028` 明确允许，且 `Pill.vue` 的 `dotColor` enum 本就含 `orange`），配方逐字沿用 PillStatus 的 `Active=True` 公式，只把 hue 换成 `--orange`。代码内已标 `<!-- LIBRARY GAP -->`，建议登记 `backlog.md` BRIDGE-CODE-CANONICAL。
> 附带订正一处文档漂移：`domain-tvu.md` M11 引用的 `--text-warning` **在 `src/tokens/variables.css` 里不存在**，真源只有 `--orange`。我用 `--orange`，没有为了对齐文档去造 token。

## R13 · Affordance trace

```
1. Affordance: status-warning (intent: 行内标记「源仍在推但指标异常」)
2. Vocabulary scan: [status/warning (src/icons/generated/status.ts), status/error,
   status/error-filled-triangle, indicator/live (= 红底 "Live" 字样徽标, 非行内图标),
   indicator/offline (= 灰色 "Offline" 字样徽标), media/record-stop, navigation/more]
3. Chosen: status/warning — currentColor 可染 --orange, 16×16 与 body 行高匹配;
   indicator/live 与 indicator/offline 经查是 broadcast 角标（16×8 / 22×10 字样图形），
   语义是画面叠加角标不是行内状态位 ⇒ 不复用，状态改由 Pill Status 承载。
```

## 两条按规则做出的、和「直觉设计」相反的决策

1. **「正常」= 红色，不是绿色。** M3 逐字：广播行业 ON AIR 惯例，红 = Live 是客户的肌肉记忆，禁止套 SaaS 默认（绿=活跃/红=危险）。三态 = On air（红）/ Warning（橙）/ Offline（灰）。
2. **行内「Stop stream」按钮不用红。** M3 同一条限定 destructive 红「出现在 modal 内部，与 Live badge（table 行）位置不重叠」——行内红按钮会和同一行的 On-air 红 pill 打架。所以行内是 ghost gray，红色只出现在确认弹窗的 Confirm 上。

其余规则落点：M4 只交付 dark（不内联 light overrides、不做 toggle）· M7 视觉权重 actions > identifiers > status · M9 全部 literal 动词 · **M10 离线拆两子型**（Idle 带 `Last ran …` / NeverStarted 带 `Never started` + CTA）· M11 RTT 趋势 icon + 数值同时染色 · R9 补齐 hover/focus-visible/disabled（红/橙禁用态的 `opacity: 0.4` 是 Figma 真值，照写）· R16 文案走 `T` const map · R23 URL 列真截断才挂 tooltip。

**如实标注的边界**：① `dist/icons/svg/` 在本 worktree 不存在（worktree 无 `dist`，且不可软链），我改用同源的 `src/icons/raw.ts` / `generated/*.ts` 取 SVG ② 本会话只读，`pnpm audit:no-hardcoded-design-tokens` / `pattern:lookup` 无法实跑，我逐条人工核过：页面内**零裸色值**，唯二 hex 是逐字复制的库资产（`LogoTVU` 的 `#56AF31`、`message/success-2` 的 `#2FB54E`）③ `--font-family-clock` 的 Digital Numbers 字体文件未内联，按 token 自带 fallback 链降级为 monospace（生产环境由 npm 包自带该 woff）。

```html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Live Sources — TVU MicroApps Console</title>
<style>
/* ============================================================================
   1. DESIGN TOKENS — 逐字取自 tvu-design-system/src/tokens/variables.css
   只内联 dark 侧（:root）。依据 domain-tvu.md M4：monitoring / operator console
   类页面默认仅交付 dark mode，不生成 light 对比。故刻意不带 [data-theme="light"]。
   ⛔ 不要在本文件手改这些值 —— 真源是 Figma Variable，改设计改 Figma 后跑 pnpm generate。
   ========================================================================= */
:root {
  /* ===== Neutrals (grey scale, light → dark) ===== */
  --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-primary-btn: #f8f8f8;

  /* ===== Icon ===== */
  --icon-active: #f8f8f8;
  --icon-default: #9e9e9e;
  --icon-disabled: #595959;
  --icon-placeholder: #7b7b7b;

  /* ===== Surface / background ===== */
  --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 / border ===== */
  --line-border: #595959;
  --line-deep: #353535;
  --line-light: #434343;

  /* ===== Input / select border aliases ===== */
  --input-line-border: var(--color-grey-8);
  --input-disabled-border: var(--color-grey-8);
}

/* ── Spacing ── */
:root {
  --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 ── */
:root {
  --r-xs: 2px;
  --r-s: 4px;
  --r-m: 8px;
  --r-l: 12px;
  --r-xl: 16px;
  --r-xxl: 20px;
}

/* ── Z-Index / Elevation ──
   刻意不新造 --z-toast：真源注释逐字写着「--z-toast 一类当前无消费者、无实测值，
   不凭空造」。本页 toast 复用 --z-dropdown（它与 modal 不同时在场）。 */
:root {
  --z-dropdown: 1000;
  --z-tooltip: 1000;
  --z-modal: 1000;
  --z-select-portal: 1100;
}

/* ── Layout breakpoints ──
   CSS @media 吃不了 var()，所以「引用」的可校验形式 = 字面量 ∈ 本组值集合
   （audit:layout-tokens 机械核）。本文件下方的 @media 只用 1100 / 640。 */
:root {
  --bp-sm: 640px;
  --bp-md: 960px;
  --bp-lg: 1100px;
  --bp-xl: 1280px;
}

/* ── Layout containers ── */
:root {
  --container-narrow: 720px;
  --container-prose: 840px;
  --container-page: 1600px;
  --container-wide: 1800px;
}

/* ── Typography ──
   --font-family-clock 的 'Digital Numbers' (SIL OFL 1.1) woff 未内联到单文件，
   按 token 自带的 fallback 链降级为 'Courier New' / monospace。
   生产环境经 npm 包自带 src/tokens/fonts/DigitalNumbers-Regular.woff，形态一致。 */
:root {
  --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);
  --font-family-clock: 'Digital Numbers', 'Courier New', monospace;
}

/* ── Domain-specific component-variant tokens (theme-stable) ── */
:root {
  --notification-bg-dark: var(--color-grey-10);
  --tooltip-bg-dark: var(--color-grey-10);
  --tooltip-border-dark: var(--color-grey-8);
  --tooltip-text-dark: var(--color-grey-2);
  --notification-width-default: 480px;
  --notification-width-side-pop: 320px;
  --tooltip-max-width: 320px;
  --topbar-section-gap: 80px;
  --topbar-height: 64px;
  --control-height-xxs: 32px;
}

/* ── Effect Style tokens — figma 真源 (3-layered drop shadows) ── */
:root {
  --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, 0.6);
  --mask-overlay-blur: 8px;
}

/* ── Text Style tokens — figma 真源 7 Text Styles ──
   family 段走 --font-family-base（系统字体栈），非 Roboto —— 已登记的 code-first
   divergence: font-family-system-stack-not-roboto-2026-08-13。 */
:root {
  --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;
  --line-height-tips: 16px;
  --line-height-body: 21px;
  --font-weight-regular: 400;
  --font-weight-semibold: 600;
}

/* ── Derived aliases for component compatibility ── */
:root {
  --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 LAYER — 逐字取自 variables.css 末尾的 correct-by-construction 兜底
   ========================================================================= */
*, *::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);
  min-height: 100vh;
}

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 {
  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);
}

.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;
}

.tvu-icon { display: inline-flex; flex: 0 0 auto; }
.tvu-icon svg { display: block; }

/* ============================================================================
   3. COMPONENT LAYER — 1:1 mirror of tvu-design-system canonical / base components
   无构建链的单文件 demo 按 code-conventions R3 手写 mirror，不重新设计 API/视觉。
   ========================================================================= */

/* ---- TopBar — mirror components/TopBar/TopBar.vue ---- */
.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;
}
.topbar-brand { display: flex; align-items: center; gap: var(--sp-m); flex-shrink: 0; }
.topbar-title { font: var(--text-style-pop-title); color: var(--text-heading); white-space: nowrap; }
.topbar-menu { display: flex; align-items: center; gap: var(--sp-xxl); flex: 1; }
.topbar-right { display: flex; align-items: center; gap: var(--sp-m); flex-shrink: 0; margin-left: auto; }

/* TopBar timezone clock face — the only sanctioned consumer of --font-family-clock,
   paired with --orange-tz (amber), per the token's own doc block. */
.topbar-clock { display: inline-flex; align-items: baseline; gap: var(--sp-xs); }
.topbar-clock__zone { font: var(--text-style-tips); color: var(--text-tips); }
.topbar-clock__time {
  font-family: var(--font-family-clock);
  font-size: var(--font-size-body);
  color: var(--orange-tz);
  letter-spacing: 0.06em;
}

/* ---- MenuList — mirror components/MenuList/MenuList.vue ----
   horizontal 的真源是 published `Top bar` 内的 Menu sub-component (4771:7173)。
   ⛔ 设计里没有下划线：active `4771:7142` 与 normal `4771:7140` 只差文字色。
   owner 2026-08-14 裁定「以 Figma 为准」，此前 code-first 加的 2px 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;
  background: none; border: 0;
}
.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--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;
  background: none; border: 0; width: 100%; text-align: left;
}
.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); }
.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; }

/* ---- UserMenu — mirror components/UserMenu/UserMenu.vue ----
   真源不是 DS library，是产品文件 Micro-Apps-20250923 (DtZcMkhNy6qh6jbQQnhreQ) node 6423:85。
   面板固定 360 · 恰好 2 条分隔线（1px light 在 header 下 / 2px deep 在 Language 上）。 */
.user-menu { position: relative; display: inline-flex; }
.user-menu__avatar {
  width: 32px; height: 32px; border-radius: 50%; border: none; padding: 0;
  background: var(--brand);
  /* on-fill content 永远用 --text-primary-btn，不用会随主题翻转的 --text-heading */
  color: var(--text-primary-btn);
  cursor: pointer; display: inline-flex; align-items: center; justify-content: center;
  overflow: hidden; flex-shrink: 0;
}
.user-menu__avatar:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.user-menu__initials { font: var(--text-style-tips); font-weight: var(--font-weight-semibold); line-height: 1; }
.user-menu__panel {
  position: absolute; top: calc(100% + var(--sp-xs)); right: 0;
  width: 360px;
  background: var(--bg-layer3);
  border-radius: var(--r-l);
  padding: var(--sp-xxs) var(--sp-xxs) var(--sp-xs);
  box-shadow: var(--shadow-l3);
  z-index: var(--z-dropdown);
  display: flex; flex-direction: column; gap: var(--sp-xxs);
}
.user-menu__header { display: flex; flex-direction: row; align-items: center; gap: var(--sp-xs); padding: var(--sp-s); }
.user-menu__panel-avatar {
  width: 34px; height: 34px; border-radius: 50%;
  background: var(--brand); color: var(--text-primary-btn);
  display: inline-flex; align-items: center; justify-content: center; overflow: hidden; flex-shrink: 0;
}
.user-menu__name-col { display: flex; flex-direction: column; gap: var(--sp-xxs); padding: var(--sp-xxs); flex: 1; min-width: 0; }
.user-menu__name-row { display: flex; align-items: center; gap: var(--sp-xs); }
.user-menu__name { font: var(--text-style-body); font-weight: var(--font-weight-semibold); color: var(--text-heading); }
.user-menu__role {
  font: var(--text-style-tips); color: var(--brand);
  background: color-mix(in srgb, var(--brand) 18%, transparent);
  border-radius: var(--r-xs); padding: 0 var(--sp-xxs);
}
.user-menu__email { font: var(--text-style-tips); color: var(--text-tips); }
.user-menu__divider { height: 1px; background: var(--line-divider-light); }
.user-menu__slice-line { height: 2px; background: var(--line-divider-deep); }
.user-menu__body { display: flex; flex-direction: column; gap: var(--sp-xxs); padding: var(--sp-xxs); }
.user-menu__body .menu-list--vertical .menu-list__item { min-height: 32px; padding-top: 0; padding-bottom: 0; }
.user-menu__body .menu-list--vertical .menu-list__item:not(.is-active):not(.is-disabled) { color: var(--text-body); }
.user-menu__body .menu-list--vertical .menu-list__item:not(.is-active):not(.is-disabled):hover { color: var(--text-heading); }
.user-menu__lang {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--sp-xs); padding: var(--sp-xs) var(--sp-s); font: var(--text-style-body);
}
.user-menu__lang-label { color: var(--text-heading); }
.user-menu__lang-options { display: inline-flex; align-items: center; }
.user-menu__lang-option { border: none; background: none; padding: 0; cursor: pointer; font: var(--text-style-body); color: var(--text-tips); }
.user-menu__lang-option.is-selected { color: var(--brand); }
.user-menu__lang-sep { color: var(--text-tips); margin: 0 var(--sp-xxs); }
.user-menu__signout {
  border: none; background: none; text-align: left; cursor: pointer;
  font: var(--text-style-body); color: var(--text-heading);
  padding: var(--sp-xs) var(--sp-s); border-radius: var(--r-s);
}
.user-menu__signout:hover { background: var(--bg-layer3); color: var(--text-heading); }
.user-menu__lang-option:focus-visible,
.user-menu__signout:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }

/* ---- Breadcrumb — mirror components/Breadcrumb/{Breadcrumb,BreadcrumbItem}.vue ---- */
.breadcrumb { display: flex; align-items: center; flex-wrap: wrap; margin: 0; padding: 0; list-style: none; }
.breadcrumb-item { display: inline-flex; align-items: center; gap: var(--sp-xxs); padding: var(--sp-xxs) var(--sp-xs); list-style: none; }
.breadcrumb-item:first-child .breadcrumb-item__separator { display: none; }
.breadcrumb-item__separator {
  display: inline-flex; align-items: center; width: 16px; height: 16px;
  color: var(--icon-default); flex-shrink: 0;
}
.breadcrumb-item__label {
  display: inline-flex; align-items: center; font: var(--text-style-body);
  text-decoration: none; cursor: pointer; color: var(--text-2);
  background: transparent; border: 0; transition: color 0.15s ease;
}
.breadcrumb-item--Default .breadcrumb-item__label:hover { color: var(--text-body); }
.breadcrumb-item--current .breadcrumb-item__label { color: var(--brand); cursor: default; pointer-events: none; }
.breadcrumb-item--current .breadcrumb-item__separator { color: var(--text-2); }

/* ---- Button — mirror 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);
  border-radius: var(--btn-radius-square);
  background: var(--btn-bg);
  color: var(--btn-color);
  opacity: var(--btn-opacity);
  font-family: var(--font-family-base);
  font-size: var(--btn-font-size);
  font-weight: 400;
  line-height: var(--btn-line-height);
  cursor: pointer;
  transition: background 0.15s, color 0.15s, border-color 0.15s, box-shadow 0.15s, opacity 0.15s;
  white-space: nowrap; outline: none; user-select: none;
}
.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_GEOMETRY.S / .M — square radius, hug width */
.btn--s { --btn-height: 24px; --btn-font-size: 14px; --btn-line-height: 21px; --btn-radius-square: 4px; --btn-padding-inline: 8px; }
.btn--m { --btn-height: 32px; --btn-font-size: 14px; --btn-line-height: 21px; --btn-radius-square: 4px; --btn-padding-inline: 16px; }
.btn--icon { --btn-gap: 4px; }

/* PALETTE_SPEC.ghost['gray 1'] */
.btn--ghost-gray { --btn-bg: transparent; --btn-color: var(--text-2); --btn-border: var(--bg-grey-btn-en); --btn-border-width: 1.2px;
  --btn-bg-hover: var(--bg-grey-btn-en); --btn-color-hover: var(--text-2); --btn-border-hover: var(--bg-grey-btn-en); }
.btn--ghost-gray:disabled { --btn-bg: transparent; --btn-color: var(--text-placeholder); --btn-border: var(--bg-grey-btn-en); }

/* PALETTE_SPEC.rimless['gray 1'] */
.btn--rimless-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--rimless-gray:disabled { --btn-color: var(--text-placeholder); }

/* PALETTE_SPEC.rimless.green */
.btn--rimless-green { --btn-bg: transparent; --btn-color: var(--brand); --btn-border: transparent;
  --btn-bg-hover: var(--green-bg); --btn-color-hover: var(--brand); --btn-border-hover: transparent; }

/* PALETTE_SPEC.filling.red — destructive confirm。M3：红只在 modal 内部出现，
   与 table 行的 On-air 红 badge 不重叠。 */
.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; }
/* Figma 真值：red / orange 的 disable + loading 容器 opacity = 0.4（80 处提取命中），
   这是「遵守 Figma」而不是用 opacity 偷懒。 */
.btn--fill-red:disabled { --btn-opacity: 0.4; }

/* ---- Pill — mirror components/Pill/Pill.vue（几何 1:1 于 Figma `Pill Status`）---- */
.pill {
  display: inline-flex; align-items: center;
  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 tokens — Active=True bg 是 hue 的 18%（color-mix over token，非 --msg-bg-*）。
   On-air / Inactive 直接来自 canonical/PillStatus.vue 的 HUE / BG_BASE 表。 */
.pill--onair   { --pill-bg: color-mix(in srgb, var(--red) 18%, transparent);       --pill-border: var(--red);           --pill-dot: var(--red);           --pill-text: var(--red); }
.pill--offline { --pill-bg: color-mix(in srgb, var(--line-deep) 18%, transparent); --pill-border: var(--color-grey-7);  --pill-dot: var(--color-grey-7);  --pill-text: var(--color-grey-7); }

<!-- LIBRARY GAP: Figma `Pill Status` (5672:140617) 的 Status 轴只有
     On-air / Preview / Analyzing / Inactive，没有 warning 值。
     此处不自画：走 base Pill 已公开的 token-driven override（CANONICAL-028，
     且 Pill.vue 的 DotColor enum 本就含 'orange'），配方逐字沿用 PillStatus 的
     Active=True 公式，只把 hue 换成 --orange。
     candidate for tvu-design-system intake → backlog.md BRIDGE-CODE-CANONICAL。
     注：domain-tvu.md M11 引用的 --text-warning 在 variables.css 中不存在，
     真源只有 --orange，故用 --orange 而非造 token。 -->
.pill--warning { --pill-bg: color-mix(in srgb, var(--orange) 18%, transparent);    --pill-border: var(--orange);       --pill-dot: var(--orange);       --pill-text: var(--orange); }

/* ---- Tab (filled) — mirror components/Tab/{Tab,TabItem}.vue ---- */
.tab { display: flex; align-items: stretch; flex-wrap: nowrap; }
.tab--filled {
  background: var(--bg-layer2);
  border: 1px solid var(--line-deep);
  border-radius: var(--r-s);
  padding: 0;
  gap: 0; /* Figma Filled variant 4452:7149 无 itemSpacing */
}
.tab-item {
  display: inline-flex; align-items: center; gap: var(--sp-xs);
  height: 32px; padding: 0 var(--sp-m);
  font: var(--text-style-body); color: var(--text-tips);
  cursor: pointer; white-space: nowrap; user-select: none;
  background: none; border: 0;
  transition: color 0.15s, background 0.15s, border-color 0.15s;
}
.tab-item--filled { border-radius: var(--r-s); }
.tab-item--filled.tab-item--normal { background: var(--bg-layer3); color: var(--text-2); }
.tab-item--filled.tab-item--active-white { background: var(--bg-layer4); color: var(--text-body); font-weight: 700; }
.tab-item:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }
.tab-item__count { color: var(--text-tips); font-variant-numeric: tabular-nums; font-weight: 400; }
.tab-item--active-white .tab-item__count { color: var(--text-2); }

/* ---- Input (line, size m) — mirror components/Input/Input.vue ----
   刻意不加 prefix-icon：库的 Input 没有该 slot，加它等于发明 API（R15/R3）。 */
.tvu-input-wrap { position: relative; display: block; }
.tvu-input {
  display: block; width: 100%;
  padding: var(--sp-xs) var(--sp-s);
  background: transparent;
  border: 1px solid var(--input-line-border);
  border-radius: var(--r-s);
  color: var(--text-body);
  font-size: 14px; font-family: var(--font-family-base);
  outline: none; transition: border-color 0.15s;
}
.tvu-input--m { height: 32px; }
.tvu-input::placeholder { color: var(--text-placeholder); }
.tvu-input:hover:not(:disabled) { border-color: var(--brand); }
.tvu-input:focus:not(:disabled) { border-color: var(--brand); }

/* ---- Table — mirror canonical/Table.vue → components/Table/Table.vue ---- */
.tbl-wrap { position: relative; width: 100%; overflow-x: auto; }
.tbl {
  width: 100%;
  table-layout: fixed;
  border-collapse: collapse;
  font: var(--text-style-body);
  color: var(--text-body);
  background: var(--bg-layer2);
  min-width: 1040px;
}
/* INFRA-F129 ①：中和 variables.css 的全局元素级 `td { border-top: 1px solid var(--line-border) }`。
   在 border-collapse 冲突里它会赢过组件自己的行分隔线，导致 Vue 链与 CE 链画出不同颜色。
   Figma 侧 tbody 单元格 live 复验确认无 stroke（strokes=[]），所以正解是中和而不是补一条。 */
.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 0.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-xs) var(--sp-xs) var(--sp-xs) 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); }
.tbl-cell--right .tbl-cell__content { justify-content: flex-end; }
.tbl-empty { text-align: center; padding: var(--sp-m) var(--sp-xs); color: var(--text-tips); }

/* ---- Tooltip — mirror components/Tooltip/Tooltip.vue (theme=dark, placement=top-start) ---- */
.tooltip-wrap { position: relative; display: inline-flex; isolation: isolate; max-width: 100%; min-width: 0; }
.tooltip-trigger { display: inline-flex; min-width: 0; max-width: 100%; }
.tooltip-box {
  position: absolute;
  z-index: var(--z-tooltip);
  white-space: normal;
  width: max-content;
  max-width: var(--tooltip-max-width);
  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);
  bottom: calc(100% + 6px);
  left: 0;
  display: none;
  word-break: break-all;
}
/* v-show 的 CSS 等价：hover 或 focus-within 才显示（a11y：tooltip 必须键盘可达）。
   R23：只有真正发生截断（.is-truncated 由 JS 用 scrollWidth > clientWidth 判定）才挂。 */
.tooltip-wrap.is-truncated:hover .tooltip-box,
.tooltip-wrap.is-truncated:focus-within .tooltip-box { display: block; }
.tooltip-arrow {
  position: absolute; width: 8.5px; height: 8.5px;
  background: var(--tooltip-bg-dark);
  transform: rotate(45deg);
  bottom: -5px; left: var(--sp-s);
  border-right: 1px solid var(--tooltip-border-dark);
  border-bottom: 1px solid var(--tooltip-border-dark);
}

/* ---- PopupBox — mirror components/PopupBox/PopupBox.vue ---- */
.popup-overlay {
  position: fixed; 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;
  /* PopupBox 的 width 是 prop（默认 680）；此处传 480 —— 一个 prop 值，不是魔法 CSS 数字 */
  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;
}
/* owner icon rule (2026-06-09): close × rest=Icon/Default, hover=Icon/Active */
.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); }

/* ---- Notification (form=slide · type=success) — mirror components/Notification/Notification.vue ----
   M12：web / SaaS 侧的反馈载体走 Notification set（设备侧才是 Message set）。
   slide = 被动 toast：无按钮、宽 --notification-width-side-pop、gap --sp-s。 */
.notif {
  display: grid; gap: var(--sp-s);
  width: var(--notification-width-side-pop);
  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__main { display: flex; align-items: flex-start; gap: var(--sp-m); }
.notif__icon { flex: 0 0 auto; width: 24px; height: 24px; }
.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__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); }

/* toast 落位：不新造 --z-toast（真源禁止凭空造无消费者的层级），复用 --z-dropdown */
.toast-region {
  position: fixed;
  top: calc(var(--topbar-height) + var(--sp-l));
  right: var(--sp-l);
  z-index: var(--z-dropdown);
  display: grid; gap: var(--sp-s);
}
.toast-region[hidden] { display: none; }

/* ============================================================================
   4. PAGE LAYER — 页面级布局（TVU 规范未覆盖的部分按 P3 通用 UX 兜底，全部走 token）
   ========================================================================= */
.page { max-width: var(--container-page); margin: 0 auto; padding: var(--sp-l) var(--sp-l) var(--sp-xxl); }
.page-head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--sp-l); margin: var(--sp-xs) 0 var(--sp-l); flex-wrap: wrap; }
.page-title { font: var(--text-style-large-title-24); color: var(--text-heading); margin: 0 0 var(--sp-xs); }
.page-summary { font: var(--text-style-tips); color: var(--text-tips); display: flex; align-items: center; gap: var(--sp-xs); flex-wrap: wrap; }
.page-summary__sep { color: var(--line-border); }
.page-summary__strong { color: var(--text-2); font-variant-numeric: tabular-nums; }

.toolbar { display: flex; align-items: center; gap: var(--sp-m); margin-bottom: var(--sp-m); flex-wrap: wrap; }
.toolbar__search { flex: 0 1 320px; min-width: 220px; margin-left: auto; }

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

/* 行内两行结构：主标识 + meta 副标题（M10 的两个 Inactive 子型靠副标题措辞区分） */
.cell-stack { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.cell-name { font: var(--text-style-body); color: var(--text-body); font-weight: var(--font-weight-semibold); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cell-meta { font: var(--text-style-tips); color: var(--text-tips); display: flex; align-items: center; gap: var(--sp-xxs); min-width: 0; }
/* R23 单行 tail-ellipsis：宽度由 table-layout: fixed 的父级约束 */
.cell-ellipsis { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.cell-url { font: var(--text-style-body); color: var(--text-2); }
.cell-dest { font: var(--text-style-body); color: var(--text-body); }
.cell-none { color: var(--text-disabled); }

/* 状态原因行：颜色不是唯一信号 —— dot + 文案 + 图标三重编码 */
.status-cell { display: flex; flex-direction: column; gap: var(--sp-xxs); align-items: flex-start; }
.status-reason { font: var(--text-style-tips); display: flex; align-items: center; gap: var(--sp-xxs); }
.status-reason--warning { color: var(--orange); }
.status-reason--muted { color: var(--text-tips); }

/* M11 trend：icon 阈值 + 数值染色的双层信号（漏掉数值色 = 不完整 spec） */
.metric { display: flex; flex-direction: column; gap: 2px; font-variant-numeric: tabular-nums; }
.metric__primary { font: var(--text-style-body); color: var(--text-body); }
.metric__secondary { font: var(--text-style-tips); color: var(--text-tips); display: inline-flex; align-items: center; gap: var(--sp-xxs); }
.metric--diverging { color: var(--orange); }
.metric--converging { color: var(--brand); }

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

.popup-facts {
  display: grid; grid-template-columns: auto 1fr; gap: var(--sp-xs) var(--sp-m);
  margin-top: var(--sp-m); padding: var(--sp-s) var(--sp-m);
  background: var(--bg-layer3); border-radius: var(--r-s);
}
.popup-facts__k { font: var(--text-style-tips); color: var(--text-tips); }
.popup-facts__v { font: var(--text-style-tips); color: var(--text-body); word-break: break-all; }
.popup-warn { display: flex; align-items: flex-start; gap: var(--sp-xs); margin-top: var(--sp-m); color: var(--orange); font: var(--text-style-tips); }

.page-foot { margin-top: var(--sp-m); font: var(--text-style-tips); color: var(--text-tips); display: flex; justify-content: space-between; gap: var(--sp-m); flex-wrap: wrap; }

/* 断点字面量必须 ∈ --bp-* 值集合（1100 = --bp-lg，640 = --bp-sm），否则 audit:layout-tokens 判 off-scale */
@media (max-width: 1100px) {
  .topbar { gap: var(--sp-l); }
  .topbar-clock { display: none; }
  .col-health { display: none; }
}
@media (max-width: 640px) {
  .page { padding: var(--sp-m) var(--sp-s) var(--sp-xl); }
  .topbar-menu { display: none; }
  .toolbar__search { margin-left: 0; flex-basis: 100%; }
}
</style>
</head>
<body>

<!-- ── TopBar (Tag=After Login) ───────────────────────────────────────────── -->
<header class="topbar">
  <div class="topbar-brand">
    <!-- LogoTVU — 逐字取自 src/icons/raw.ts（品牌资产固定色，非页面自定义 hex） -->
    <span class="tvu-icon" aria-hidden="true"><svg width="24" height="24" 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.6575 12.0533C12.7649 12.0533 12.8543 12.0186 12.926 11.9492C12.9973 11.8798 13.0333 11.7951 13.033 11.6953C13.033 11.6246 13.0119 11.5608 12.9701 11.5041C12.9273 11.448 12.8468 11.39 12.7279 11.3296C12.6304 11.2805 12.5665 11.2378 12.5359 11.2011C12.5057 11.1651 12.4904 11.1274 12.4904 11.088C12.4904 11.0433 12.5087 11.0042 12.5457 10.9705C12.5828 10.9368 12.627 10.9201 12.6789 10.9201C12.7605 10.9201 12.8441 10.9605 12.9293 11.0419L13.032 10.9378C12.9161 10.8281 12.8013 10.7733 12.6871 10.7733C12.5889 10.7733 12.5077 10.804 12.4435 10.8651C12.3793 10.9265 12.347 11.0032 12.347 11.0957C12.347 11.1667 12.367 11.2298 12.4075 11.2849C12.4476 11.3399 12.5237 11.396 12.6358 11.4527C12.7401 11.5051 12.8074 11.5481 12.8369 11.5825C12.8665 11.6175 12.8814 11.6576 12.8814 11.7023C12.8814 11.7567 12.859 11.8041 12.8142 11.8444C12.7693 11.8848 12.7146 11.9048 12.6507 11.9048C12.5593 11.9048 12.473 11.8595 12.3915 11.7694L12.2913 11.8812C12.3337 11.9349 12.3874 11.9769 12.4527 12.0076C12.5175 12.038 12.5858 12.0533 12.6575 12.0533ZM11.9733 12.0533L11.3829 11.4987V12.0533H11.2319V10.3467H11.3829V11.3188L11.9264 10.8219H12.148L11.5037 11.4093L12.1856 12.0533H11.9733ZM10.6556 10.9878C10.7049 10.916 10.7569 10.8623 10.8117 10.8267C10.8665 10.7911 10.9234 10.7733 10.9826 10.7733C11.0274 10.7733 11.075 10.7874 11.126 10.8157L11.0416 10.9512C11.0078 10.9365 10.9792 10.9293 10.9561 10.9293C10.902 10.9293 10.85 10.9512 10.8 10.9953C10.7501 11.0391 10.7118 11.1075 10.6856 11.1999C10.6656 11.271 10.6556 11.4147 10.6556 11.6312V12.0533H10.4902V10.8055H10.6556V10.9878ZM9.64269 10.7733C9.83034 10.7733 9.98595 10.8411 10.1095 10.9769C10.2217 11.1007 10.2778 11.2472 10.2781 11.4167C10.2781 11.5868 10.2187 11.7353 10.1001 11.8625C9.9816 11.9896 9.82934 12.0533 9.64302 12.0533C9.45603 12.0533 9.3031 11.9896 9.18456 11.8625C9.06569 11.7353 9.00659 11.5868 9.00659 11.4167C9.00659 11.2482 9.06269 11.102 9.17488 10.9782C9.29809 10.8417 9.45403 10.7733 9.64269 10.7733ZM9.88152 11.8421C9.95431 11.8007 10.0121 11.742 10.0548 11.6659C10.0976 11.5899 10.1189 11.5081 10.1186 11.4197C10.1186 11.2832 10.0718 11.1667 9.97768 11.0706C9.88352 10.9742 9.77166 10.9262 9.6421 10.9262C9.51155 10.9262 9.39969 10.9745 9.30586 11.0706C9.21203 11.1667 9.16528 11.2832 9.16528 11.4197C9.16528 11.5078 9.18665 11.5899 9.22939 11.6659C9.27213 11.742 9.3299 11.8007 9.40269 11.8421C9.47548 11.8835 9.55529 11.9042 9.6421 11.9042C9.72892 11.9042 9.80872 11.8835 9.88152 11.8421ZM8.33623 12.0533H8.36592L8.90069 10.7733H8.73604L8.35108 11.7025L7.95565 10.7733H7.92698L7.53391 11.7025L7.15637 10.7733H6.99341L7.5194 12.0533H7.5491L7.9425 11.1368L8.33623 12.0533ZM6.88747 10.9474H6.63214V12.0533H6.4714V10.9474H6.25171V10.8089H6.4714V10.3467H6.63214V10.8089H6.88747V10.9474ZM5.83626 11.7904C5.79475 11.8261 5.74412 11.8548 5.68505 11.8761C5.62564 11.8978 5.56454 11.9085 5.5021 11.9085C5.37214 11.9085 5.26278 11.8635 5.17434 11.773C5.08591 11.6826 5.04034 11.5668 5.0373 11.4253H6.14579C6.1441 11.2592 6.09887 11.1204 6.00976 11.0092C5.8842 10.8517 5.71644 10.7733 5.50648 10.7733C5.30261 10.7733 5.13958 10.8501 5.01806 11.0039C4.9222 11.1247 4.87427 11.2622 4.87427 11.4167C4.87427 11.5808 4.93097 11.728 5.04439 11.8581C5.1578 11.9883 5.31476 12.0533 5.51594 12.0533C5.60674 12.0533 5.68775 12.0396 5.75964 12.0126C5.83154 11.9853 5.89601 11.9456 5.95339 11.8935C6.01077 11.8414 6.06141 11.7727 6.10529 11.6876L5.97162 11.6182C5.92301 11.697 5.87778 11.7547 5.83626 11.7904ZM5.19687 11.0383C5.28395 10.9615 5.38758 10.9232 5.50875 10.9232C5.582 10.9232 5.65187 10.9385 5.71837 10.9689C5.78486 10.9996 5.8382 11.0396 5.8787 11.0897C5.91921 11.1397 5.94992 11.2065 5.97119 11.2899H5.05273C5.08548 11.1781 5.13341 11.094 5.19687 11.0383ZM4.45639 10.8445C4.38617 10.7969 4.30564 10.7733 4.21478 10.7733C4.1256 10.7733 4.04273 10.7945 3.96686 10.8369C3.89098 10.8793 3.82176 10.9433 3.75919 11.0291V10.8055H3.60278V12.053H3.75919V11.5954C3.75919 11.4309 3.76652 11.3181 3.78149 11.2562C3.80545 11.1615 3.85471 11.0818 3.92958 11.0178C4.00446 10.9535 4.08899 10.9214 4.1835 10.9214C4.26603 10.9214 4.33326 10.9423 4.38451 10.984C4.43576 11.0257 4.4707 11.0876 4.48867 11.1704C4.50032 11.2186 4.50597 11.3143 4.50597 11.4583V12.0533H4.66239V11.4114C4.66239 11.2418 4.64575 11.1167 4.61213 11.0366C4.57852 10.9556 4.52661 10.8917 4.45639 10.8445ZM12.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">MicroApps Console</span>
  </div>

  <!-- MenuList orientation=horizontal — active 仅换文字色，设计里没有下划线 -->
  <nav class="topbar-menu" aria-label="Primary">
    <ul class="menu-list menu-list--horizontal" role="menubar">
      <li><button class="menu-list__item is-active" role="menuitem" aria-current="true" type="button">Sources</button></li>
      <li><button class="menu-list__item" role="menuitem" type="button">Receivers</button></li>
      <li><button class="menu-list__item" role="menuitem" type="button">Recordings</button></li>
      <li><button class="menu-list__item" role="menuitem" type="button">Analytics</button></li>
    </ul>
  </nav>

  <div class="topbar-right">
    <div class="topbar-clock">
      <span class="topbar-clock__zone">UTC</span>
      <span class="topbar-clock__time" id="clock">12:04:37</span>
    </div>

    <!-- UserMenu — 真源 Micro-Apps-20250923 node 6423:85 -->
    <div class="user-menu" id="userMenu">
      <button type="button" class="user-menu__avatar" id="userMenuTrigger" aria-expanded="false" aria-haspopup="menu" aria-label="Nancy Zeng">
        <span class="user-menu__initials">NZ</span>
      </button>
      <div class="user-menu__panel" id="userMenuPanel" role="menu" hidden>
        <div class="user-menu__header">
          <span class="user-menu__panel-avatar" aria-hidden="true"><span class="user-menu__initials">NZ</span></span>
          <div class="user-menu__name-col">
            <div class="user-menu__name-row">
              <span class="user-menu__name">Nancy Zeng</span>
              <span class="user-menu__role">Operator</span>
            </div>
            <span class="user-menu__email">nancyzeng@tvunetworks.com</span>
          </div>
        </div>
        <div class="user-menu__divider"></div>
        <div class="user-menu__body">
          <ul class="menu-list menu-list--vertical" role="none">
            <li role="none"><button class="menu-list__item" role="menuitem" type="button"><span class="menu-list__label">Account settings</span></button></li>
            <li role="none"><button class="menu-list__item" role="menuitem" type="button"><span class="menu-list__label">Organization</span></button></li>
            <li role="none"><button class="menu-list__item" role="menuitem" type="button"><span class="menu-list__label">API tokens</span></button></li>
          </ul>
          <div class="user-menu__slice-line"></div>
          <div class="user-menu__lang">
            <span class="user-menu__lang-label">Language</span>
            <span class="user-menu__lang-options">
              <button type="button" class="user-menu__lang-option is-selected">EN<span class="user-menu__lang-sep">/</span></button>
              <button type="button" class="user-menu__lang-option">中文</button>
            </span>
          </div>
          <button type="button" class="user-menu__signout">Sign out</button>
        </div>
      </div>
    </div>
  </div>
</header>

<main class="page">
  <!-- Breadcrumb — 分隔符 = navigation/arrow-right (icon/Arrow/right, --icon-default) -->
  <nav aria-label="breadcrumb">
    <ol class="breadcrumb">
      <li class="breadcrumb-item breadcrumb-item--Default"><a class="breadcrumb-item__label" href="#">Console</a></li>
      <li class="breadcrumb-item breadcrumb-item--Default">
        <span class="breadcrumb-item__separator tvu-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.81581 3.26255C8.58149 3.02823 8.58149 2.64833 8.81581 2.41402C9.05012 2.1797 9.43002 2.1797 9.66434 2.41402L14.8099 7.55958C14.8156 7.56484 14.8212 7.57023 14.8267 7.57575C15.061 7.81007 15.061 8.18997 14.8267 8.42428L9.66482 13.5862C9.43051 13.8205 9.05061 13.8205 8.81629 13.5862C8.58198 13.3518 8.58198 12.9719 8.81629 12.7376L12.9541 8.59984H1.6C1.26863 8.59984 1 8.33121 1 7.99984C1 7.66847 1.26863 7.39984 1.6 7.39984H12.9531L8.81581 3.26255Z" fill="currentColor"/></svg></span>
        <a class="breadcrumb-item__label" href="#">Live</a>
      </li>
      <li class="breadcrumb-item breadcrumb-item--current">
        <span class="breadcrumb-item__separator tvu-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.81581 3.26255C8.58149 3.02823 8.58149 2.64833 8.81581 2.41402C9.05012 2.1797 9.43002 2.1797 9.66434 2.41402L14.8099 7.55958C14.8156 7.56484 14.8212 7.57023 14.8267 7.57575C15.061 7.81007 15.061 8.18997 14.8267 8.42428L9.66482 13.5862C9.43051 13.8205 9.05061 13.8205 8.81629 13.5862C8.58198 13.3518 8.58198 12.9719 8.81629 12.7376L12.9541 8.59984H1.6C1.26863 8.59984 1 8.33121 1 7.99984C1 7.66847 1.26863 7.39984 1.6 7.39984H12.9531L8.81581 3.26255Z" fill="currentColor"/></svg></span>
        <span class="breadcrumb-item__label" aria-current="page">Sources</span>
      </li>
    </ol>
  </nav>

  <div class="page-head">
    <div>
      <h1 class="page-title">Live Sources</h1>
      <p class="page-summary" id="pageSummary"></p>
    </div>
  </div>

  <!-- 工具条：Tab (filled) 状态筛选 + Input (line, m) 搜索 -->
  <div class="toolbar">
    <div class="tab tab--filled" role="tablist" aria-label="Filter by status" id="statusTabs"></div>
    <div class="toolbar__search">
      <div class="tvu-input-wrap">
        <label class="sr-only" for="sourceSearch">Search sources</label>
        <input id="sourceSearch" class="tvu-input tvu-input--m" type="search" placeholder="Search name, origin or destination" autocomplete="off" />
      </div>
    </div>
  </div>

  <div class="card">
    <div class="tbl-wrap">
      <table class="tbl">
        <colgroup>
          <col style="width: 22%" />
          <col style="width: 25%" />
          <col style="width: 16%" />
          <col style="width: 17%" />
          <col class="col-health" style="width: 10%" />
          <col style="width: 210px" />
        </colgroup>
        <thead class="tbl-head">
          <tr>
            <th scope="col" class="tbl-cell">Source</th>
            <th scope="col" class="tbl-cell">Origin</th>
            <th scope="col" class="tbl-cell">Destination</th>
            <th scope="col" class="tbl-cell">Status</th>
            <th scope="col" class="tbl-cell col-health">Health</th>
            <th scope="col" class="tbl-cell tbl-cell--right"><span class="tbl-cell__content" style="justify-content:flex-end">Actions</span></th>
          </tr>
        </thead>
        <tbody class="tbl-body" id="sourceRows"></tbody>
      </table>
    </div>
  </div>

  <p class="page-foot">
    <span id="rowCount"></span>
    <span>Auto-refresh every 5 s · Times shown in UTC</span>
  </p>
</main>

<!-- ── PopupBox — 停止推流二次确认 ──────────────────────────────────────────
     closeOnBackdrop=false（DS 默认）· closeOnEscape=true · closable=true
     footer 走 slot：ghost gray Cancel + filling red Stop stream。
     M3：destructive 红只在 modal 内部出现。 -->
<div class="popup-overlay" id="stopOverlay" hidden>
  <div class="popup-box" id="stopDialog" role="dialog" aria-modal="true" aria-labelledby="stopDialogTitle" tabindex="-1">
    <header class="popup-box__header">
      <h2 id="stopDialogTitle" class="popup-box__title">Stop stream?</h2>
      <button type="button" class="popup-box__close" aria-label="Close" data-close>
        <span class="tvu-icon" aria-hidden="true"><svg width="12" height="12" 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">
      <p style="margin:0" id="stopDialogLede"></p>
      <div class="popup-facts">
        <span class="popup-facts__k">Source</span><span class="popup-facts__v" id="factSource"></span>
        <span class="popup-facts__k">Origin</span><span class="popup-facts__v" id="factOrigin"></span>
        <span class="popup-facts__k">Destination</span><span class="popup-facts__v" id="factDest"></span>
        <span class="popup-facts__k">On air for</span><span class="popup-facts__v" id="factUptime"></span>
      </div>
      <p class="popup-warn">
        <span class="tvu-icon" aria-hidden="true" style="margin-top:1px"><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>
        <span>The receiver goes to standby immediately. Restarting requires the field operator to re-arm the encoder.</span>
      </p>
    </div>

    <footer class="popup-box__footer">
      <button type="button" class="btn btn--m btn--ghost-gray" data-close>Cancel</button>
      <button type="button" class="btn btn--m btn--fill-red" id="stopConfirm">Stop stream</button>
    </footer>
  </div>
</div>

<!-- ── Notification form=slide · type=success（被动 toast，无按钮）───────── -->
<div class="toast-region" id="toastRegion" hidden>
  <div class="notif" role="status" aria-live="polite">
    <div class="notif__main">
      <span class="notif__icon tvu-icon" aria-hidden="true"><svg width="24" height="24" 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="#2FB54E"/></svg></span>
      <div class="notif__body">
        <div class="notif__header">
          <p class="notif__title" id="toastTitle">Stream stopped</p>
          <button type="button" class="notif__close" aria-label="Close" id="toastClose">
            <span class="tvu-icon" aria-hidden="true"><svg width="12" height="12" 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="toastDesc"></p>
      </div>
    </div>
  </div>
</div>

<script>
'use strict';

/* ============================================================================
   R16 — Annotation vs Product UI String Boundary
   产品 UI 字符串走 const map 单源（即使当前项目还没接 i18n），禁止 inline 中英混排。
   注释可以是中文，它不进 runtime、不进翻译流。
   ========================================================================= */
const T = {
  stopStream: 'Stop stream',
  viewDetails: 'View details',
  setUp: 'Set source and destination',
  cancel: 'Cancel',
  statusOnAir: 'On air',
  statusWarning: 'Warning',
  statusOffline: 'Offline',
  neverStarted: 'Never started',
  notSet: 'Not set',
  filterAll: 'All',
  emptyFiltered: 'No sources match this filter.',
  tableEmpty: 'No data',
  stopLede: (name) => `This will interrupt the live feed from ${name}.`,
  toastTitle: 'Stream stopped',
  toastDesc: (name, dest) => `${name} is offline. ${dest} released to standby.`,
  summary: 'sources',
  onAirCount: 'on air',
  warningCount: 'need attention',
  offlineCount: 'offline',
  showing: (n, total) => `Showing ${n} of ${total} sources`,
};

/* ── 图标：全部取自 TVU library registry，无自画 SVG ──────────────────────
   status/warning  → src/icons/generated/status.ts (IconStatusWarning, currentColor)
   navigation/more → src/icons/generated/navigation.ts (IconMore, currentColor)
   media/record-stop → src/icons/generated/media.ts (IconMediaRecordStop) — 见下方选型说明
   arrow up/down   → src/icons/raw.ts (IconArrowUp / IconArrowDown, currentColor)  */
const ICON = {
  warning: '<svg width="12" height="12" 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>',
  arrowUp: '<svg width="12" height="12" 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="12" height="12" 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>',
};

/* ============================================================================
   数据 — mockup 用虚构运营数据
   M10：Offline 拆两个子型（Inactive-Idle 有 history / Inactive-NeverStarted 从未运行），
        禁止合并为单一 Inactive variant —— 两者的用户心智完全不同
        （「停了可重启」 vs 「从未配置，先配再用」）。
   M11：trend 必须同时给 icon 阈值 + 数值颜色映射。本页阈值：
        RTT Δ ≥ +15 ms → ↑ --orange（diverging）· Δ ≤ -15 ms → ↓ --brand（converging）
        · ±15 ms 内 → 无 icon、中性色。
   ========================================================================= */
const SOURCES = [
  {
    id: 'src-nyc-01', name: 'NYC-Studio-A-Cam1', device: 'TVU One · TV1-88204',
    origin: 'srt://ingest-use1.tvunetworks.com:9010?streamid=nyc-studio-a-cam1&latency=200',
    dest: 'R-NYC-Control-01', destKind: 'TVU RPS',
    status: 'onair', uptime: '02:14:07',
    bitrate: '12.4 Mbps', rtt: '38 ms', trend: 'stable',
  },
  {
    id: 'src-ldn-02', name: 'LDN-OB-Truck-2', device: 'TVU One · TV1-77315',
    origin: 'srt://ingest-euw2.tvunetworks.com:9010?streamid=ldn-ob-truck-2&latency=300',
    dest: 'R-LDN-Gallery-03', destKind: 'TVU RPS',
    status: 'onair', uptime: '05:02:11',
    bitrate: '9.8 Mbps', rtt: '64 ms', trend: 'stable',
  },
  {
    id: 'src-tok-03', name: 'TOK-Roof-Uplink', device: 'TVU One · TV1-51022',
    origin: 'rtmp://ingest-apne1.tvunetworks.com/live/tok-roof-uplink',
    dest: 'R-TOK-Master-01', destKind: 'TVU Grid',
    status: 'onair', uptime: '00:46:33',
    bitrate: '14.1 Mbps', rtt: '121 ms', trend: 'converging', trendDelta: '-22',
  },
  {
    id: 'src-sfo-04', name: 'SFO-Field-Pack-7', device: 'TVU One · TV1-63980',
    origin: 'srt://ingest-usw1.tvunetworks.com:9010?streamid=sfo-field-pack-7&latency=400',
    dest: 'R-SFO-Newsroom-02', destKind: 'TVU RPS',
    status: 'warning', uptime: '01:08:52',
    reason: 'Packet loss 4.2% for 3 min',
    bitrate: '6.2 Mbps', rtt: '210 ms', trend: 'diverging', trendDelta: '+87',
  },
  {
    id: 'src-ber-05', name: 'BER-Arena-Feed', device: 'TVU One · TV1-40117',
    origin: 'srt://ingest-euc1.tvunetworks.com:9010?streamid=ber-arena-feed&latency=250',
    dest: 'R-BER-Studio-01', destKind: 'TVU Grid',
    status: 'warning', uptime: '03:27:40',
    reason: 'Bitrate below 4 Mbps floor',
    bitrate: '3.6 Mbps', rtt: '96 ms', trend: 'diverging', trendDelta: '+18',
  },
  {
    id: 'src-syd-06', name: 'SYD-Harbor-Cam', device: 'TVU One · TV1-29644',
    origin: 'srt://ingest-apse2.tvunetworks.com:9010?streamid=syd-harbor-cam&latency=200',
    dest: 'R-SYD-Control-02', destKind: 'TVU RPS',
    /* M10 子型 A — Inactive-Idle：曾运行、有 history ⇒ 副标题给 Last ran 区间 */
    status: 'offline', offlineKind: 'idle', lastRan: 'Last ran 06:12 → 08:47 · 2 h 35 min',
  },
  {
    id: 'src-lax-07', name: 'LAX-Backup-Encoder', device: 'TVU One · TV1-90551',
    origin: 'srt://ingest-usw2.tvunetworks.com:9010?streamid=lax-backup-encoder&latency=200',
    dest: 'R-LAX-Standby-01', destKind: 'TVU RPS',
    status: 'offline', offlineKind: 'idle', lastRan: 'Last ran 21:30 → 23:58 · 2 h 28 min',
  },
  {
    id: 'src-mia-08', name: 'MIA-NewSetup-01', device: 'TVU One · TV1-11238',
    /* M10 子型 B — Inactive-NeverStarted：无 route、无 delta，只给 CTA prompt，
       且 CTA 必须是 literal verb（M9）。 */
    origin: '', dest: '', destKind: '',
    status: 'offline', offlineKind: 'never',
  },
];

const STATUS_META = {
  onair:   { label: T.statusOnAir,   pill: 'pill--onair' },
  warning: { label: T.statusWarning, pill: 'pill--warning' },
  offline: { label: T.statusOffline, pill: 'pill--offline' },
};

let activeFilter = 'all';
let query = '';

const $ = (sel) => document.querySelector(sel);
const esc = (s) => String(s).replace(/[&<>"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]));

function counts() {
  return {
    all: SOURCES.length,
    onair: SOURCES.filter((s) => s.status === 'onair').length,
    warning: SOURCES.filter((s) => s.status === 'warning').length,
    offline: SOURCES.filter((s) => s.status === 'offline').length,
  };
}

function visibleSources() {
  const q = query.trim().toLowerCase();
  return SOURCES.filter((s) => {
    if (activeFilter !== 'all' && s.status !== activeFilter) return false;
    if (!q) return true;
    return [s.name, s.origin, s.dest, s.device].join(' ').toLowerCase().includes(q);
  });
}

/* ---- Tab (filled) 状态筛选 ------------------------------------------------ */
function renderTabs() {
  const c = counts();
  const tabs = [
    { key: 'all', label: T.filterAll, n: c.all },
    { key: 'onair', label: T.statusOnAir, n: c.onair },
    { key: 'warning', label: T.statusWarning, n: c.warning },
    { key: 'offline', label: T.statusOffline, n: c.offline },
  ];
  $('#statusTabs').innerHTML = tabs.map((t) => {
    const active = t.key === activeFilter;
    return `<button type="button" role="tab" aria-selected="${active}" tabindex="${active ? 0 : -1}"
      data-filter="${t.key}"
      class="tab-item tab-item--filled ${active ? 'tab-item--active-white' : 'tab-item--normal'}">
      <span>${esc(t.label)}</span><span class="tab-item__count">${t.n}</span></button>`;
  }).join('');
}

/* ---- 状态列 --------------------------------------------------------------
   三态区分不只靠颜色：pill dot + pill 文案 + 原因副标题（告警另配 status/warning 图标）
   ⇒ 色觉障碍用户同样可分辨。M7：pill 用 18% 软底、小 padding，视觉上最安静。 */
function statusCell(s) {
  const meta = STATUS_META[s.status];
  let reason = '';
  if (s.status === 'warning') {
    reason = `<span class="status-reason status-reason--warning"><span class="tvu-icon" aria-hidden="true">${ICON.warning}</span>${esc(s.reason)}</span>`;
  } else if (s.status === 'onair') {
    reason = `<span class="status-reason status-reason--muted">Uptime ${esc(s.uptime)}</span>`;
  } else if (s.offlineKind === 'idle') {
    reason = `<span class="status-reason status-reason--muted">${esc(s.lastRan)}</span>`;
  } else {
    reason = `<span class="status-reason status-reason--muted">${esc(T.neverStarted)}</span>`;
  }
  return `<div class="status-cell">
      <span class="pill ${meta.pill}"><span class="pill__dot" aria-hidden="true"></span><span class="pill__label">${esc(meta.label)}</span></span>
      ${reason}
    </div>`;
}

/* ---- Health 列 — M11 双层信号（icon + 数值同时染色）---------------------- */
function healthCell(s) {
  if (s.status === 'offline') return '<span class="cell-none">—</span>';
  let rtt;
  if (s.trend === 'diverging') {
    rtt = `<span class="metric__secondary metric--diverging"><span class="tvu-icon" aria-hidden="true">${ICON.arrowUp}</span>RTT ${esc(s.rtt)} (${esc(s.trendDelta)})</span>`;
  } else if (s.trend === 'converging') {
    rtt = `<span class="metric__secondary metric--converging"><span class="tvu-icon" aria-hidden="true">${ICON.arrowDown}</span>RTT ${esc(s.rtt)} (${esc(s.trendDelta)})</span>`;
  } else {
    rtt = `<span class="metric__secondary">RTT ${esc(s.rtt)}</span>`;
  }
  return `<span class="metric"><span class="metric__primary">${esc(s.bitrate)}</span>${rtt}</span>`;
}

/* ---- R23 — 受限单元格文字溢出：截断 CSS + tooltip 容器（是否显示由 JS 判定）-- */
function truncatedCell(text, extraClass) {
  if (!text) return `<span class="cell-none">${esc(T.notSet)}</span>`;
  return `<span class="tooltip-wrap" data-truncatable>
      <span class="tooltip-trigger"><span class="cell-ellipsis ${extraClass}">${esc(text)}</span></span>
      <span class="tooltip-box" role="tooltip">${esc(text)}<span class="tooltip-arrow"></span></span>
    </span>`;
}

function renderRows() {
  const rows = visibleSources();
  const tbody = $('#sourceRows');

  if (!rows.length) {
    tbody.innerHTML = `<tr class="tbl-empty-row"><td colspan="6" class="tbl-empty">${esc(T.emptyFiltered)}</td></tr>`;
  } else {
    tbody.innerHTML = rows.map((s) => {
      const isOffline = s.status === 'offline';
      const isNever = isOffline && s.offlineKind === 'never';

      /* 行内主操作：Stop stream。
         ⛔ 刻意不用 red —— M3 把 destructive 红限定在 modal 内部，理由是它不能与
         table 行里的 On-air 红 badge 位置重叠。行内用 ghost gray（正规库 size S，
         非自制 XS chip，满足 M7「actions 视觉权重最高」）。 */
      const stopBtn = `<button type="button" class="btn btn--s btn--ghost-gray"
          data-action="stop" data-id="${s.id}" ${isOffline ? 'disabled' : ''}
          ${isOffline ? 'title="Source is not streaming"' : ''}>${esc(T.stopStream)}</button>`;
      const secondaryBtn = `<button type="button" class="btn btn--s btn--rimless-gray" data-action="details" data-id="${s.id}">${esc(T.viewDetails)}</button>`;

      /* M10 acceptance：NeverStarted 的 bootstrap CTA 必为 literal verb */
      const nameMeta = isNever
        ? `<button type="button" class="btn btn--s btn--rimless-green" style="padding-inline:0" data-action="setup" data-id="${s.id}">${esc(T.setUp)}</button>`
        : `<span class="cell-meta cell-ellipsis">${esc(s.device)}</span>`;

      return `<tr class="tbl-row" data-row="${s.id}">
        <td class="tbl-cell"><span class="cell-stack"><span class="cell-name">${esc(s.name)}</span>${nameMeta}</span></td>
        <td class="tbl-cell">${truncatedCell(s.origin, 'cell-url')}</td>
        <td class="tbl-cell">${s.dest ? `<span class="cell-stack"><span class="cell-dest cell-ellipsis">${esc(s.dest)}</span><span class="cell-meta cell-ellipsis">${esc(s.destKind)}</span></span>` : `<span class="cell-none">${esc(T.notSet)}</span>`}</td>
        <td class="tbl-cell">${statusCell(s)}</td>
        <td class="tbl-cell col-health">${healthCell(s)}</td>
        <td class="tbl-cell tbl-cell--right"><span class="row-actions">${stopBtn}${secondaryBtn}</span></td>
      </tr>`;
    }).join('');
  }

  $('#rowCount').textContent = T.showing(rows.length, SOURCES.length);
  markTruncated();
}

/* R23 — truncation detection：只有真正截断才挂 tooltip + title。
   反模式是「所有 cell 一律包 Tooltip」：短文本也弹 = 视觉噪音 + 屏幕阅读器读两遍。
   a11y 双保险：aria-describedby（富 tooltip 通道）+ title（no-JS / 触屏 fallback）。 */
function markTruncated() {
  document.querySelectorAll('[data-truncatable]').forEach((wrap, i) => {
    const inner = wrap.querySelector('.cell-ellipsis');
    const box = wrap.querySelector('.tooltip-box');
    if (!inner || !box) return;
    const truncated = inner.scrollWidth > inner.clientWidth;
    wrap.classList.toggle('is-truncated', truncated);
    if (truncated) {
      const id = `tt-${i}`;
      box.id = id;
      inner.setAttribute('aria-describedby', id);
      inner.setAttribute('title', inner.textContent);
      inner.setAttribute('tabindex', '0');
    } else {
      inner.removeAttribute('aria-describedby');
      inner.removeAttribute('title');
      inner.removeAttribute('tabindex');
    }
  });
}
/* 动态内容 / 容器 resize 后重判 */
if (typeof ResizeObserver !== 'undefined') {
  const ro = new ResizeObserver(() => markTruncated());
  ro.observe(document.body);
}

function renderSummary() {
  const c = counts();
  $('#pageSummary').innerHTML =
    `<span class="page-summary__strong">${c.all}</span> ${esc(T.summary)}` +
    `<span class="page-summary__sep">·</span><span class="page-summary__strong">${c.onair}</span> ${esc(T.onAirCount)}` +
    `<span class="page-summary__sep">·</span><span class="page-summary__strong">${c.warning}</span> ${esc(T.warningCount)}` +
    `<span class="page-summary__sep">·</span><span class="page-summary__strong">${c.offline}</span> ${esc(T.offlineCount)}`;
}

function renderAll() { renderTabs(); renderSummary(); renderRows(); }

/* ============================================================================
   PopupBox 行为 — 1:1 mirror components/PopupBox/PopupBox.vue
   focus trap · ESC 关闭 · body 滚动锁 · 关闭后焦点还原 · backdrop 不关闭（DS 默认）
   ========================================================================= */
const overlay = $('#stopOverlay');
const dialog = $('#stopDialog');
let pendingId = null;
let lastFocused = null;
let originalBodyOverflow = '';

const FOCUSABLE = 'button:not([disabled]),[href],input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])';
const focusableEls = () => Array.from(dialog.querySelectorAll(FOCUSABLE)).filter((el) => !el.hasAttribute('disabled') && el.tabIndex !== -1);

function openStopDialog(id) {
  const s = SOURCES.find((x) => x.id === id);
  if (!s || s.status === 'offline') return;
  pendingId = id;

  $('#stopDialogLede').textContent = T.stopLede(s.name);
  $('#factSource').textContent = `${s.name} · ${s.device}`;
  $('#factOrigin').textContent = s.origin;
  $('#factDest').textContent = `${s.dest} (${s.destKind})`;
  $('#factUptime').textContent = s.uptime;

  lastFocused = document.activeElement instanceof HTMLElement ? document.activeElement : null;
  originalBodyOverflow = document.body.style.overflow;
  document.body.style.overflow = 'hidden';
  overlay.hidden = false;
  window.addEventListener('keydown', onDialogKeydown);
  requestAnimationFrame(() => (focusableEls()[0] || dialog).focus());
}

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

function onDialogKeydown(e) {
  if (e.key === 'Escape') { e.preventDefault(); closeStopDialog(); return; }
  if (e.key !== 'Tab') return;
  const els = focusableEls();
  if (!els.length) { e.preventDefault(); dialog.focus(); return; }
  const first = els[0], last = els[els.length - 1];
  if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); }
  else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); }
}

/* M12：操作结果不得静默 —— 走 Notification set（web/SaaS 侧的反馈谱系），
   form=slide 是被动 toast，不带按钮。 */
let toastTimer = null;
function showToast(title, desc) {
  $('#toastTitle').textContent = title;
  $('#toastDesc').textContent = desc;
  $('#toastRegion').hidden = false;
  clearTimeout(toastTimer);
  toastTimer = setTimeout(() => { $('#toastRegion').hidden = true; }, 6000);
}

function confirmStop() {
  const s = SOURCES.find((x) => x.id === pendingId);
  if (!s) return;
  const dest = s.dest;
  /* 停流后落到 M10 的 Inactive-Idle 子型（有 history），不是 NeverStarted */
  s.status = 'offline';
  s.offlineKind = 'idle';
  s.lastRan = `Last ran → just now · ${s.uptime}`;
  closeStopDialog();
  renderAll();
  showToast(T.toastTitle, T.toastDesc(s.name, dest));
}

/* ---- 事件接线 ------------------------------------------------------------ */
document.addEventListener('click', (e) => {
  const tab = e.target.closest('[data-filter]');
  if (tab) { activeFilter = tab.dataset.filter; renderAll(); return; }

  const act = e.target.closest('[data-action]');
  if (act) {
    if (act.dataset.action === 'stop') openStopDialog(act.dataset.id);
    return;
  }

  if (e.target.closest('[data-close]')) { closeStopDialog(); return; }
});

$('#stopConfirm').addEventListener('click', confirmStop);
$('#toastClose').addEventListener('click', () => { $('#toastRegion').hidden = true; clearTimeout(toastTimer); });

$('#sourceSearch').addEventListener('input', (e) => { query = e.target.value; renderRows(); });

/* ---- UserMenu 开合（pointerdown 捕获阶段关闭，同 UserMenu.vue）---------- */
const umTrigger = $('#userMenuTrigger');
const umPanel = $('#userMenuPanel');
umTrigger.addEventListener('click', () => {
  const open = umPanel.hidden;
  umPanel.hidden = !open;
  umTrigger.setAttribute('aria-expanded', String(open));
});
document.addEventListener('pointerdown', (e) => {
  if (umPanel.hidden) return;
  if (e.composedPath().includes($('#userMenu'))) return;
  umPanel.hidden = true;
  umTrigger.setAttribute('aria-expanded', 'false');
}, true);

/* ---- TopBar 时钟（--font-family-clock 的唯一 sanctioned 消费者）---------- */
function tickClock() {
  const d = new Date();
  const p = (n) => String(n).padStart(2, '0');
  $('#clock').textContent = `${p(d.getUTCHours())}:${p(d.getUTCMinutes())}:${p(d.getUTCSeconds())}`;
}
tickClock();
setInterval(tickClock, 1000);

renderAll();
</script>
</body>
</html>
```

---

## M48 · rule-coverage 结账（交付前逐条打勾）

| 来源 | 命中项 | 落地 | 状态 |
|---|---|---|---|
| locked spec | 单文件 / 可直接打开 / 不引 CDN | token 与图标全部内联，零外部请求 | ✅ |
| domain-tvu | M3 状态色 · M4 dark only · M5 无创建入口 · M7 权重 · M9 literal 动词 · **M10 两子型** · M11 双层信号 · M12 反馈谱系 | 见上文 | ✅ |
| domain-tvu | M13 中断恢复态 | **未覆盖**（本页 scope 无中断/掉线场景，rationale 如实登记） | ⚪ |
| code-conventions | R0 mapping · R2 token-only · R3 mirror canonical · R9 交互态 · R13 trace · R15 library-first · R16 字符串边界 · R23 溢出三件套 | 见上文 | ✅ |
| state-completeness | On air / Warning / Offline-Idle / Offline-NeverStarted / 筛选空态 / 行 hover / 按钮 disabled / modal 打开·取消·确认 / toast | 全部可在页面上走到 | ✅ |
| 机械 gate | `audit:no-hardcoded-design-tokens` 等 | **未跑**（本会话只读，Bash 停用）；人工核过零裸色值，唯二 hex 是逐字复制的库资产 | ⚠️ |

**可以点开看的路径**：点任一 On air / Warning 行的 `Stop stream` → 确认弹窗（ESC / 点 × / Cancel 都可退出）→ `Stop stream` 确认 → 该行落到 Offline-Idle 且 Stop 按钮转禁用 + 右上角 toast；把窗口拉窄到 URL 被截断时 hover 那一列会出 tooltip（没截断时不出）。

**建议的后续动作**（不在本次交付范围，交你拍板）：把 `Pill Status` 的 warning 值提给 designer 补进 Figma 库，并在 `backlog.md` 立一条 BRIDGE-CODE-CANONICAL；顺带订正 `domain-tvu.md` M11 里那个不存在的 `--text-warning` 引用。