onboarding 链路走完了（L-core 四份 + AGENTS 硬规则/Onboarding Gate + code-conventions 起手必读段 + domain-tvu + design-process 的两个起手 Gate + 相关 R-rule）。下面先交出 M48 要求的起手清单，再给文件。

## R7 intent + M22

**intent classified as 场景 3.2**（greenfield 单页 dashboard，用 TVU 构建）· Task Entry Mode **US-2**。

一处必须说明的判据冲突：你说的是「为 **已有产品** TVU SaaS Dashboard 新增页面」（本来指向场景 2.1），但同时显式说「**用 TVU 设计系统**」，且交付物是独立单文件、不进那个仓库。宿主 surface 的 canonical 范式帧（M17 要求实测的那张）在本 session 拿不到——`tvu-saas-dashboard` 只有 `.claude/` 在可访问范围内。⇒ 我按「视觉真源 = TVU 库」走，**几何取值全部取自 DS 活源**；落地进那个产品前，M17 要求的「同类控件同宽 / 动作按钮右缘对齐 / 行高」三类仍需拿宿主范式帧复核一次。这是声明过的假设，不是默认。

M22：brief 无 design-direction 类信号词（纯 element 清单），不触发。

## M48 起手 rule-coverage 清单

| 命中 | 怎么落地 |
|---|---|
| **FIGMA_AS_SOT #0** | 所有几何/颜色取自 DS 活源；无一处「code 反向要求改 Figma」 |
| **硬规则 #4** 颜色硬编码=bug | 页面 CSS 零 hex；token 块是 `src/tokens/variables.css` **逐字副本**（非手抄色值，R2.1 要求） |
| **硬规则 #3** 不造未登记 variant | 未新增任何 prop/variant/status 轴 |
| **硬规则 #6** canonical 作 SoT | 镜像 `src/canonical/*` + 其 base，未取 `src/components/*` 的私有形态 |
| **R0** Phase 0 mapping | 见下表 |
| **R3** 无构建链 → 手写 1:1 mirror canonical | Chart/TabList/TabItem/Badge/Button/Tooltip 逐条抄实测值 |
| **R13** affordance trace | 见下 3 行 |
| **R14** layout primitive | 页面 grid、行 flex；唯一 `absolute` = chart tooltip 浮层，已就地注明理由 |
| **R15** library-first | 每个元素先查 canonical + catalog；gap 标 `LIBRARY GAP` |
| **R16** 产品 UI 字符串 | 全部走 `const T = {...}` 单源、全英单语；注释中文 |
| **R9** 静态→交互补全 | hover / focus-visible / disabled / active 四态齐 |
| **R2.1** 独立分享页同样受约束 | 深色为默认、`--sp-*`/`--r-*`/字阶配对、断点取 `--bp-*` 值集 |
| **domain M11** trend 双层信号 | ↓绿改善 / ↑橙恶化 / 无icon中性，阈值 ±1.0%，三层映射表进说明面板 |
| **domain M3** 红=Live | 恶化用 `--orange` **不用 `--red`**（红在本域是 Live） |
| **domain Persona** | CFO（3 秒拿方向）+ SRE（数据新鲜度 T-1 行） |
| **M16** 默认值必评估 | 默认 30D + 聚合粒度随区间切；理由写在下方 |
| **M17**（泛化版） | ⚠️ **未满足**：宿主范式帧不可达，已声明 |
| **M48「机械命中项必附 gate 输出」** | ⚠️ **未满足**：本 session Bash 停用，`audit:consumer-code` / `audit:no-hardcoded-design-tokens` **一条没跑**。我不给这几项打勾 |
| **R23** 受限容器溢出 | 不触发（无固定宽 × 长度不可控内容） |
| **R19** selected 3 层 | 不适用：无自建 selected 行；range 的 active 态照 `TabItem` Figma 真值，不改写成 rail+fill |

## R0 Phase 0 mapping

| Element | Figma / catalog | Code 活源 | 状态 |
|---|---|---|---|
| 时间范围切换 | `Tab List` + `Tab/Item`（Fill=Filled） | `canonical/TabList.vue` → `components/Tab/*.vue` | ✅ 1:1 抄 |
| 涨跌 chip | `Badge`（Type=Rectangle, Tag=Line, Color=Green/Orange/Neutral） | `canonical/Badge.vue` | ✅ 1:1 抄 |
| 折叠按钮 / 导出 | `Button/dark M`（S rimless / M ghost, color=gray 1） | `components/Button/Button.vue` `PALETTE_SPEC`+`SIZE_GEOMETRY` | ✅ 1:1 抄 |
| 趋势图 | `Chart`（type=line） | `canonical/Chart.vue` → `build-chart-option.ts` + `use-chart-tokens.ts` | 🟡 **ECharts 不可用**（单文件禁 CDN）→ 按 R3 手写 SVG 镜像其视觉契约 |
| 图 hover 提示 | `Tooltips` | `components/Tooltip/Tooltip.vue` | 🟡 canonical Chart 不给 tooltip 定样式（ECharts 默认白底）→ 我改用 `Tooltip.vue` dark 变体 token，**显式偏离** |
| ↑↓ / 展开折叠 / 公式 / info 图标 | `icon/Arrow/Up·Down·Double up·Double down` · `icon/Edit/calculate` · `icon/Message/Info 1` | `src/icons/catalog/generated/{arrow,edit,message}.ts` | ✅ 逐字取 path + nodeId |
| 卡片 / 面板外壳 | catalog + `figma-to-code-mapping.json` 扫 card/panel/collapse/accordion/container/surface/tile/stat/kpi → **全 miss** | — | ⚠️ 真 gap → 复用 `Chart.vue` 容器实测三值（`--bg-layer3` / `--r-m` / `--sp-m`），标 `LIBRARY GAP` |

## R13 三行 trace

```
1. Affordance: directional-vertical (intent: 单位成本环比方向指示 + 说明面板展开/折叠)
2. Vocabulary scan: [arrow/up, arrow/down, arrow/double-up, arrow/double-down, arrow/dropdown,
   arrow/sorting, arrow/updown, arrow/switch (src/icons/catalog/generated/arrow.ts 全 16 项)]
3. Chosen: arrow/down + arrow/up = M11 指定的 ↓↑ 双层信号；arrow/double-up (1149:23074) =
   已展开(点击折叠) · arrow/double-down (1149:23106) = 已折叠(点击展开)，语义逐字取自
   figma-component-catalog §已知具体 icon 表。另 edit/calculate (2352:15654) 作「怎么算出来的」
   面板标题图标，message/info-1 (1168:23662) 作数据新鲜度提示（info-2 内嵌 #3892F3 硬编码，不取）。
```

## 顺手查出来的 5 件事（都已实证，不是推断）

1. **`domain-tvu.md` M11 点名的 token `--text-warning` 在 `src/tokens/variables.css` 不存在**（全文件 `warning` 只命中 `--prompt-warning-text`）。我按最近语义绑 `var(--orange)`（M11 描述的「橙红」，且 `--prompt-warning-text` 与其 dark 值同为 `#f68512`）。**没造新 token、没写 hex**——但 M11 的 token 引用那一列需要 owner 订正。
2. **DS 字阶上限是 24px**（`--font-size-large-title-24`），所以英雄数字只能到 24。CFO persona 要求「3 秒拿到方向」，通常英雄数字会到 32/40。要更大就得新增 token → owner 拍。我没越界。
3. **`canonical/TabItem.vue` 有一个键盘可达性缺口**：`tabindex` 非 active 项恒为 `-1`，而 `TabItem`/`TabList` 都没有方向键 roving（`onKeydown` 只处理 Enter/Space）⇒ 非选中 tab 键盘到不了。本文件按 P3(WCAG) 补了 ←→ roving 并标注，DS 侧建议单独立项。
4. **`code-conventions.md` R2.1 点名的「当前实例」`playground/public/onboarding.html` 已经不是那种页面了**——2026-08-13 起它只剩 16 行重定向壳，没有任何 DS 样式。R2.1 那句「当前实例」已 stale。
5. **本 session 一条闸都没跑**（Bash 停用）。落地前至少要跑 `pnpm audit:no-hardcoded-design-tokens`（扫描面含 `playground/public/**/*.html`）+ `pnpm audit:consumer-code`（R14/R19/R9 probe）。**我不替它们打勾。**

## 交付物说明

- 深色是 DS 默认（`:root`）；浅色只靠 `<html data-theme="light">`，页面**不放主题开关**，也不并排展示两套主题。
- token 块是 `src/tokens/variables.css` **逐字副本**，唯一删减 = `@font-face 'Digital Numbers'`（指向 `./fonts/*`，独立文件旁不存在；`--font-family-clock` 变量保留、未使用）。真正的消费路径仍是 `<link>` 到 `tvu-tokens.css` / npm `dist/style.css`——单文件不引外部资源是你的硬约束，我在文件头注明了。
- 三个区间刻意呈现 M11 的**三种态**：7D `+2.9%`（↑橙 恶化）/ 30D `-12.0%`（↓绿 改善）/ 90D `-0.5%`（±1% 内 无 icon 中性）。数字跨区间自洽：7D 就是 30D 序列的最后 7 天，均值算得出来。
- 默认 30D（M16 评估）：月度对账节奏 + 90D 会把这一轮改善稀释掉、7D 又只看得到尾部回升。12M 禁用并给出理由（需 12 个完整账期、现有 9 个），顺带把 R9 的 disabled 态落在一个真实状态上而不是摆样子。
- 未纳入（明确的 scope 边界，不是漏）：TopBar / Breadcrumb 等产品外壳——那是宿主 surface 的范式，M17 说不能拿别处的参考图代替，而它的范式帧我这轮拿不到。

```html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Cost per Token Trend · TVU SaaS Dashboard</title>

<!--
  ============================================================================
  Cost per Token Trend — TVU SaaS Dashboard（单文件高保真 mockup / hi-fi mockup）
  ============================================================================
  R7 intent : 场景 3.2（greenfield 单页 dashboard，视觉真源 = TVU UX Design System）
  Task mode : US-2

  【镜像来源 / mirrored sources — 全部逐条实测，不凭印象】
    tokens        src/tokens/variables.css                       （下方为逐字副本）
    chart 视觉    src/canonical/Chart.vue
                  src/components/Chart/Chart.vue                 （容器 sp-m / r-m / bg-layer3）
                  src/components/Chart/build-chart-option.ts     （legend/axis/series 契约）
                  src/components/Chart/use-chart-tokens.ts       （palette / 轴色 / 图例色解析）
    range 切换    src/canonical/TabList.vue + TabItem.vue
                  src/components/Tab/TabList.vue + TabItem.vue   （Fill=Filled 分段控件）
    涨跌 chip     src/canonical/Badge.vue                        （Rectangle × Line × Green/Orange/Neutral）
    按钮          src/components/Button/Button.vue               （PALETTE_SPEC + SIZE_GEOMETRY）
    图内提示      src/components/Tooltip/Tooltip.vue             （dark 变体 token）
    图标          src/icons/catalog/generated/{arrow,edit,message}.ts

  【UI primitives used（R15 Acceptance 要求的一行）】
    TabList(Filled) · TabItem · Badge(Rectangle/Line) · Button(S rimless / M ghost, gray 1) ·
    Chart(line) 视觉镜像 · Tooltip(dark) · icons: arrow/{up,down,double-up,double-down},
    edit/calculate, message/info-1

  【R13 三行 trace】
    1. Affordance: directional-vertical（意图：环比方向指示 + 说明面板展开/折叠）
    2. Vocabulary scan: arrow.ts 全 16 项（up/down/double-up/double-down/dropdown/sorting/
       updown/switch/next/previous/left-1/left-2/right-1/right-2/to-first-page/to-last-page）
    3. Chosen: arrow/down + arrow/up（M11 指定的 ↓↑ 双层信号）；
       arrow/double-up = 已展开(点击折叠) · arrow/double-down = 已折叠(点击展开)
       —— 语义逐字取自 figma-component-catalog.md §已知具体 icon 表

  【显式声明的偏离 / declared deviations（⛔ 别当成「已合规」读）】
   D1  ECharts 不可用（单文件 + 禁外部 CDN）⇒ 趋势图按 code-conventions R3
       「无构建链则手写 1:1 mirror canonical 的视觉」手绘 SVG，逐项对齐 build-chart-option.ts：
       series 色 = --chart-color-1（palette 索引 1）· 轴线/刻度/网格 = --line-divider-light ·
       轴标签与图例文字 = --text-2 12px + --font-family-base · 图例 bottom + circle 8×8 ·
       线宽 2 + emptyCircle symbol · tooltip trigger=axis。⛔ 这不是「用了 Chart 组件」。
   D2  canonical Chart 不给 tooltip 定样式（落到 ECharts 默认白底）。本页改用 Tooltip.vue 的
       dark 变体 token（--tooltip-bg-dark / --tooltip-border-dark / --tooltip-text-dark /
       --text-style-tips / --r-s / --shadow-l3 / padding 4-8）。这是一处**主动选择**，理由 =
       深色页上白底 tooltip 与 DS 观感断层；需 owner 认可或改回默认。
   D3  M11 点名的 token `--text-warning` 在 variables.css **不存在**（全文件 `warning` 仅命中
       --prompt-warning-text）⇒ 恶化态绑 var(--orange)（M11 描述的「橙红」，与
       --prompt-warning-text 的 dark 值同为 #f68512）。⛔ 未新建 token、未写 hex。
       M3「红=Live」是本域肌肉记忆，所以恶化**不用 --red**。
   D4  DS 无 card/panel/collapse/accordion 组件（catalog + figma-to-code-mapping.json 双向
       扫 card|panel|container|surface|section|accordion|collapse|widget|tile|stat|kpi 全 miss）
       ⇒ 卡片壳复用 canonical Chart 容器的实测三值。见下方 LIBRARY GAP 标记。
   D5  TabItem 无 hover 态（canonical 只声明 transition）⇒ 按 R9「hover = active 的弱版本」
       补 bg-layer4、不改字重/字色。R9 对场景 3 是强制项。
   D6  canonical TabItem/TabList 未实现方向键 roving（tabindex 非 active 恒 -1，keydown 只吃
       Enter/Space）⇒ 非选中 tab 键盘不可达。本页按 P3(WCAG) 补 ←→ roving。DS 侧建议单独立项。
   D7  token 块 = variables.css 逐字副本，唯一删减 = @font-face 'Digital Numbers'（指向
       ./fonts/* 相对路径，独立文件旁不存在）。--font-family-clock 变量保留、未使用。
       ⛔ 真源仍只有一份：正式消费路径是 <link> 到 tvu-tokens.css / npm dist/style.css，
       单文件内联只因为「不引外部 CDN」是本次硬约束。
   D8  M17（新页面照宿主 surface 范式）**未满足** —— tvu-saas-dashboard 的 canonical 范式帧
       本 session 不可达。落地前须复核：同类控件同宽 / 同控件跨状态同宽 / 动作按钮右缘对齐 /
       行高与分隔线间距（贴实测数字，目测不算）。
   D9  本 session Bash 停用 ⇒ audit:no-hardcoded-design-tokens（扫描面含 playground/public/**
       /*.html）与 audit:consumer-code（R14/R19/R9 probe）**一条都没跑**。M48 要求机械命中项
       附 gate 输出，此项如实标未满足，⛔ 不打勾。

  【scope 边界】未含 TopBar / Breadcrumb 等产品外壳：那属宿主 surface 范式（见 D8）。
  ============================================================================
-->

<style>
/* ==========================================================================
 * BEGIN — 逐字副本 of src/tokens/variables.css（唯一删减见文件头 D7）
 * ⛔ 不要在这里手改任何值：真源是仓库那份文件，改设计改 Figma → pnpm generate。
 * ========================================================================== */

/* ── Dark mode (default) ── */
: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-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 / 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);
  --select-line-border: var(--color-grey-8);
  --select-disabled-border: var(--color-grey-8);
  --select-multi-line-border: var(--color-grey-8);
  --select-chip-border: var(--color-grey-9);
  --select-chip-filled-bg: var(--color-grey-13);

  /* ===== Chart categorical palette (12 colors) — CANONICAL-011 D1 ===== */
  --chart-color-1: var(--brand);     /* 134° 绿 (brand) */
  --chart-color-2: var(--red);       /* 6° 红 */
  --chart-color-3: var(--blue);      /* 211° 蓝 */
  --chart-color-4: var(--orange);    /* 31° 橙 */
  --chart-color-5: #2dceae;          /* 169° 青绿 */
  --chart-color-6: #d8c537;          /* 52° 黄 */
  --chart-color-7: #9a4ee5;          /* 265° 紫 */
  --chart-color-8: #e54eb4;          /* 320° 洋红 */
  --chart-color-9: #4e6ee5;          /* 228° 靛蓝 */
  --chart-color-10: #e57a4e;         /* 17° 珊瑚 */
  --chart-color-11: #c47de8;         /* 293° lavender */
  --chart-color-12: #a3d837;         /* 90° chartreuse */
}

/* ── 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 ── */
:root {
  --z-dropdown: 1000;      /* UserMenu 面板 */
  --z-tooltip: 1000;       /* Tooltip */
  --z-modal: 1000;         /* PopupBox */
  --z-select-portal: 1100; /* Select 的 teleport 下拉 */
}

/* ── Layout breakpoints ──
   ⚠️ CSS @media 吃不了 var()，所以"引用"的可校验形式 = 字面量必须 ∈ 本组值集合
   （本页只用 640 / 1100 两档，均在集合内）。 */
: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 ──
   D7：此处删掉了 @font-face 'Digital Numbers'（src url 指向 ./fonts/*，独立文件旁不存在）。
   --font-family-clock 变量保留但本页未使用（TopBar 时钟专用）。 */
: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);
  --notification-bg-light: var(--color-grey-2);

  --tooltip-bg-dark: var(--color-grey-10);     /* #353535 */
  --tooltip-bg-light: var(--color-white);      /* #ffffff */
  --tooltip-border-dark: var(--color-grey-8);  /* #595959 */
  --tooltip-border-light: var(--color-grey-3); /* #f0f0f0 */
  --tooltip-text-dark: var(--color-grey-2);    /* #f8f8f8 */
  --tooltip-text-light: var(--color-grey-13);  /* #141414 */
  --progress-track-bg-dark: var(--line-deep);
  --progress-track-bg-light: var(--color-grey-4);
  --switch-bg-off-dark: var(--color-grey-8);
  --switch-bg-off-light: var(--color-grey-6);
  --switch-bg-disabled-off-dark: var(--color-grey-9);
  --switch-bg-disabled-off-light: var(--color-grey-5);
  --switch-thumb-disabled-dark: var(--color-grey-7);
  --switch-thumb-disabled-light: var(--color-grey-3);

  /* === Domain-specific structural dimension tokens === */
  --pagination-pagesize-selector-width: 120px;
  --pagination-jumper-input-width: 64px;
  --pagination-simple-nav-width: 80px;
  --topbar-section-gap: 80px;
  --topbar-height: 64px;
  --prompt-message-l-max-width: 480px;
  --prompt-message-m-max-width: 320px;
  --notification-width-default: 480px;
  --notification-width-side-pop: 320px;
  --tooltip-max-width: 320px;
  --checkbox-size: 16px;
  --checkbox-radius: 1.2px;
  --radio-circle-size: 16px;
  --radio-circle-border-width: 1.2px;
  --radio-selected-dot-size: 6px;
  --rating-star-size: 24px;
  --slider-track-h-m: var(--sp-xs);
  --slider-track-h-s: var(--sp-xxs);
  --slider-thumb-size-m: 16px;
  --slider-thumb-size-s: 12px;
  --slider-thumb-dot-radius-m: 3.2px;
  --slider-thumb-dot-radius-s: 2.4px;
  --progress-track-h-m: 8px;
  --progress-track-h-s: 4px;
  --progress-track-radius: 20px;
  --badge-circle-size: 24px;
  --switch-track-w: 40px;
  --switch-track-h: 20px;
  --switch-thumb-size: 16px;
  --switch-thumb-inset: 2px;
  --switch-thumb-travel: 20px;
  --form-item-row-height: 32px;
  --form-item-textarea-min-height: 100px;
  --input-number-min-width: 100px;
  --input-number-default-width: 128px;
  --input-number-max-width: 200px;
  --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 段不是 Roboto，走 --font-family-base（系统字体栈）。已登记 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;
  --font-size-pop-title: 16px;
  --font-size-headline-2: 18px;
  --font-size-headline-1: 20px;
  --font-size-large-title-22: 22px;
  --font-size-large-title-24: 24px;
  --line-height-tips: 16px;
  --line-height-body: 21px;
  --line-height-pop-title: 24px;
  --line-height-headline-2: 27px;
  --line-height-headline-1: 30px;
  --line-height-large-title-22: 33px;
  --line-height-large-title-24: 36px;
  --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);
  --prompt-success-bg: #f1fbf3;
  --prompt-success-text: #2fb54e;
  --prompt-info-text: #3892f3;
  --prompt-error-text: #ea4233;
  --prompt-warning-text: #f68512;
  --form-item-root-width-single-line: 638px;
  --form-item-root-width-two-lines: 440px;
  --form-item-label-width-120: 120px;
  --form-item-label-width-200: 200px;
  --form-item-label-width-dynamic: auto;
  --line-divider-deep: var(--line-deep);
  --line-divider-light: var(--line-light);
  --control-default-border: var(--icon-placeholder);
  --control-disabled-bg: var(--line-deep);
  --control-disabled-border: var(--line-border);
  --control-disabled-text: var(--text-disabled);
  --control-disabled-selected-bg: var(--brand-disable);
  --control-disabled-selected-icon: var(--text-tips);
  --radio-default-border: var(--text-tips);
  --radio-selected-dot: var(--bg-layer1);
  --radio-disabled-off-bg: var(--text-disabled);
  --radio-disabled-off-border: var(--bg-layer4);
  --radio-disabled-selected-bg: var(--brand-disable);
}

/* ── Light mode overrides ── */
[data-theme="light"] {
  --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: #299f45;
  --brand-hover: #00ac39;
  --brand-match: #f1fbf3;
  --brand-disable: #8fcc9d;
  --red: #dc2717;
  --red-hover: #eb493a;
  --red-bg: #fff1f0;
  --orange: #e26601;
  --orange-hover: #e97413;
  --orange-bg: #fff3db;
  --orange-tz: #d39f39;
  --blue: #0473d5;
  --blue-hover: #118dfb;
  --blue-bg: #e1eefd;

  --msg-bg-red: #fbddda;
  --msg-bg-brand: #daf2df;
  --msg-bg-blue: #dbebfd;
  --msg-bg-grey: #f6f6f6;

  --text-heading: #000000;
  --text-body: #141414;
  --text-2: #434343;
  --text-tips: #595959;
  --text-placeholder: #9e9e9e;
  --text-disabled: #cccccc;
  --text-grey-en: #434343;
  --text-grey-dis: #9e9e9e;
  --text-grey-hv: #434343;
  --text-primary-btn: #f8f8f8;

  --icon-active: #434343;
  --icon-default: #595959;
  --icon-disabled: #cccccc;
  --icon-placeholder: #9e9e9e;
  --icon-grey-dis: #9e9e9e;

  --bg-layer1: #ffffff;
  --bg-layer2: #f8f8f8;
  --bg-layer3: #f0f0f0;
  --bg-layer4: #dbdbdb;
  --bg-topbar: #ffffff;
  --bg-grey-btn-hv: #cccccc;
  --bg-grey-btn-en: #dbdbdb;
  --bg-grey-btn-dis: #dbdbdb;

  --line-border: #f0f0f0;
  --line-deep: #dbdbdb;
  --line-light: #cccccc;

  --input-line-border: var(--color-grey-5);
  --input-disabled-border: var(--color-grey-5);
  --select-line-border: var(--color-grey-5);
  --select-disabled-border: var(--color-grey-5);
  --select-multi-line-border: var(--color-grey-4);
  --select-chip-border: var(--color-grey-4);
  --select-chip-filled-bg: var(--color-grey-3);

  --chart-color-1: var(--brand);
  --chart-color-2: var(--red);
  --chart-color-3: var(--blue);
  --chart-color-4: var(--orange);
  --chart-color-5: #13a886;
  --chart-color-6: #b3a113;
  --chart-color-7: #7a25c9;
  --chart-color-8: #c52596;
  --chart-color-9: #2545c9;
  --chart-color-10: #c95425;
  --chart-color-11: #a83bd1;
  --chart-color-12: #7ab313;

  --input-filled-bg: var(--bg-layer1);
  --green-bg: var(--brand-match);
  --line-divider-deep: var(--line-deep);
  --line-divider-light: var(--line-light);
  --control-default-border: var(--icon-placeholder);
  --control-disabled-bg: #dbdbdb;
  --control-disabled-border: #f0f0f0;
  --control-disabled-text: #cccccc;
  --control-disabled-selected-bg: var(--brand-disable);
  --control-disabled-selected-icon: var(--text-primary-btn);
  --radio-default-border: var(--icon-placeholder);
  --radio-selected-dot: var(--bg-layer1);
  --radio-disabled-off-bg: #f0f0f0;
  --radio-disabled-off-border: #dbdbdb;
  --radio-disabled-selected-bg: var(--brand-disable);
}

/* ── Element base layer（DS 的 correct-by-construction 兜底，随 variables.css 分发）── */
body {
  font: var(--text-style-body);
  font-family: var(--font-family-base);
}
h1 { font: var(--text-style-headline-1); color: var(--text-body); }
h2 { font: var(--text-style-headline-2); color: var(--text-body); }
h3 { font: var(--text-style-pop-title); color: var(--text-body); }
label { font: var(--text-style-body); color: var(--text-body); }
a { color: var(--brand); }
code,
kbd { font-family: monospace; font-size: var(--font-size-tips); }
button {
  font: var(--text-style-body);
  color: inherit;
  border-radius: var(--r-s);
}
input,
textarea,
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,
textarea::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="menuitem"] { font: var(--text-style-body); color: var(--text-body); }
[role="tab"] { font: var(--text-style-body); color: var(--text-body); }
[role="dialog"] { font: var(--text-style-body); color: var(--text-body); background: var(--bg-layer2); border-radius: var(--r-l); }

/* ==========================================================================
 * END — 逐字副本 of src/tokens/variables.css
 * ========================================================================== */


/* ==========================================================================
 * 页面层 / page layer —— token-only，零 hex（AGENTS 硬规则 #4 · R2 · R2.1）
 * ========================================================================== */

*, *::before, *::after { box-sizing: border-box; }

html { background: var(--bg-layer1); }

body {
  margin: 0;
  /* R2.1：--bg-layer1 = 页面底 · --bg-layer2/3 = 抬升面 */
  background: var(--bg-layer1);
  color: var(--text-body);
  -webkit-font-smoothing: antialiased;
}

/* 视觉隐藏但读屏可读（无颜色、纯几何） */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.page {
  max-width: var(--container-page);
  margin: 0 auto;
  padding: var(--sp-l) var(--sp-m) var(--sp-xxl);
}

/* ── 页头 ── */
.page-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--sp-m);
}
.page-head__titles { min-width: 0; }
.page-head__title {
  margin: 0;
  /* DS 字阶上限 = 24px（--font-size-large-title-24）。更大需 owner 新增 token。 */
  font: var(--text-style-large-title-24);
  color: var(--text-heading);
}
.page-head__sub {
  margin: var(--sp-xxs) 0 0;
  font: var(--text-style-body);
  color: var(--text-tips);
}
.page-head__tools {
  display: flex;
  align-items: center;
  gap: var(--sp-s);
  flex-wrap: wrap;
}

/* ── Button —— 1:1 mirror src/components/Button/Button.vue
   PALETTE_SPEC[style][gray 1] + SIZE_GEOMETRY[size]。
   height / padding / radius 的 px 字面量逐字取自该文件（控件尺寸是 code-authored，
   不在 --sp-*/--r-* 之外另造标度）。 ── */
.btn {
  --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-height: 32px;
  --btn-padding-inline: 16px;
  --btn-radius: 4px;

  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-xxs);              /* SIZE_GEOMETRY.iconGap = 4px */
  min-width: 0;
  height: var(--btn-height);
  padding-inline: var(--btn-padding-inline);
  border: 1px solid var(--btn-border);
  border-radius: var(--btn-radius);
  background: var(--btn-bg);
  color: var(--btn-color);
  font-family: var(--font-family-base);
  font-size: var(--font-size-body);
  font-weight: var(--font-weight-regular);
  line-height: var(--line-height-body);
  cursor: pointer;
  transition: background .15s, color .15s, border-color .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);
}
/* DS focus 惯例（Button/Switch/Checkbox/Radio/MenuList/UserMenu 同款） */
.btn:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.btn:disabled {
  cursor: not-allowed;
  --btn-bg: transparent;
  --btn-color: var(--text-placeholder);   /* rimless/gray 1 · disable */
}
/* style=ghost · color=gray 1 */
.btn--ghost { --btn-border: var(--bg-grey-btn-en); --btn-border-hover: var(--bg-grey-btn-en); }
/* size=S：height 24 · padding 8 · radius 4 */
.btn--s { --btn-height: 24px; --btn-padding-inline: 8px; }
/* icon-only：padding 收成正方，等宽于 height */
.btn--icon { --btn-padding-inline: 0px; width: var(--btn-height); }
.btn__icon { display: inline-flex; width: 16px; height: 16px; }
.btn__icon svg { display: block; }

/* ── TabList / TabItem（Fill=Filled）—— 1:1 mirror src/components/Tab/*.vue ── */
.tab-list { display: flex; align-items: stretch; }
.tab-list--filled {
  background: var(--bg-layer2);
  border: 1px solid var(--line-deep);
  border-radius: var(--r-s);
  padding: 0;
}
.tab-list__items { display: inline-flex; align-items: center; gap: 0; }
.tab-item {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-xs);               /* Figma itemSpacing 8px */
  height: 32px;
  padding: 0 var(--sp-m);
  font: var(--text-style-body);
  color: var(--text-tips);
  cursor: pointer;
  white-space: nowrap;
  user-select: none;
  transition: color .15s, background .15s, border-color .15s;
  outline: none;
}
.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; }
/* D5 · R9：canonical 无 hover ⇒ 补「active 的弱版本」= 只借 active 的 bg，不改字重/字色 */
.tab-item--filled.tab-item--normal:hover:not(.tab-item--disabled) { background: var(--bg-layer4); }
.tab-item--disabled { color: var(--text-disabled); cursor: not-allowed; }
.tab-item:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.tab-list--filled .tab-item:first-child { border-radius: var(--r-s) 0 0 var(--r-s); }
.tab-list--filled .tab-item:last-child { border-radius: 0 var(--r-s) var(--r-s) 0; }
.tab-item__wrap { position: relative; display: inline-flex; }

/* ── Badge（Type=Rectangle · Tag=Line）—— 1:1 mirror src/canonical/Badge.vue
   figmaPalette：Green/Orange line → border+text 同色 · Neutral line → --line-deep / --text-body。
   D3：恶化用 --orange（M11 的 --text-warning 在活源不存在；红在本域 = Live，不能借）。 ── */
.badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-xxs);
  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: transparent;
  color: var(--badge-text);
  font: var(--text-style-tips);
  white-space: nowrap;
}
.badge__icon { display: inline-flex; width: 16px; height: 16px; }
.badge__icon svg { display: block; }
.badge__value { font-variant-numeric: tabular-nums; font-weight: var(--font-weight-semibold); }
.badge--down { --badge-border: var(--brand);     --badge-text: var(--brand); }
.badge--up   { --badge-border: var(--orange);    --badge-text: var(--orange); }
.badge--flat { --badge-border: var(--line-deep); --badge-text: var(--text-body); }

/* ── 卡片外壳 ──
   LIBRARY GAP: DS 无 card / panel / container 组件（catalog + figma-to-code-mapping.json
   双向同义词扫描全 miss）；三值复用 canonical Chart 容器实测（src/components/Chart/Chart.vue
   .tvu-chart = padding --sp-m · radius --r-m · background --bg-layer3）。
   candidate for tvu-design-system intake。 ── */
.card {
  background: var(--bg-layer3);
  border-radius: var(--r-m);
  padding: var(--sp-m);
}

/* ── 概览区 ── */
.overview {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--sp-m);
  margin-top: var(--sp-l);
}
.stat__label {
  margin: 0;
  font: var(--text-style-tips);
  color: var(--text-tips);
}
.stat__value {
  margin: var(--sp-xs) 0 0;
  font: var(--text-style-large-title-24);
  color: var(--text-heading);
  font-variant-numeric: tabular-nums;
}
.stat__unit {
  font: var(--text-style-body);
  color: var(--text-tips);
  margin-left: var(--sp-xxs);
}
.stat__foot {
  margin: var(--sp-xs) 0 0;
  font: var(--text-style-tips);
  color: var(--text-tips);
}
/* 主 KPI：涨跌与数字同排，方向一眼可见 */
.stat--primary .stat__row {
  display: flex;
  align-items: center;
  gap: var(--sp-s);
  flex-wrap: wrap;
  margin-top: var(--sp-xs);
}
.stat--primary .stat__value { margin-top: 0; }
/* M11 双层信号的第二层：数值本身染色（仅 delta，不染水平值） */
.trend--down .stat__delta-text { color: var(--brand); }
.trend--up   .stat__delta-text { color: var(--orange); }
.trend--flat .stat__delta-text { color: var(--text-body); }
.stat__delta-text { font-variant-numeric: tabular-nums; }

/* ── 主体：图 + 说明面板 ── */
.split {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--sp-m);
  margin-top: var(--sp-m);
  align-items: start;
}

/* 图卡：容器三值与 canonical .tvu-chart 一致 */
.chart-card__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-s);
  flex-wrap: wrap;
}
.chart-card__title { margin: 0; font: var(--text-style-pop-title); color: var(--text-body); }
.chart-card__axis-note { font: var(--text-style-tips); color: var(--text-tips); }
/* R14：plot 需要 position:relative 作为 tooltip 浮层的定位参照（非布局用途） */
.chart-plot { position: relative; margin-top: var(--sp-s); }
.chart-plot svg { display: block; width: 100%; }
/* 图例：mirror build-chart-option.ts legend { bottom: 0, icon:'circle', itemWidth/Height: 8,
   textStyle { color: --text-2, fontFamily: --font-family-base, fontSize: 12 } } —— 居中在底部 */
.chart-legend {
  display: flex;
  justify-content: center;
  gap: var(--sp-l);
  flex-wrap: wrap;
  margin-top: var(--sp-xs);
}
.chart-legend__item {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-xs);
  font-family: var(--font-family-base);
  font-size: 12px;
  color: var(--text-2);
}
.chart-legend__dot { width: 8px; height: 8px; border-radius: 50%; }
.chart-foot {
  margin: var(--sp-s) 0 0;
  padding-top: var(--sp-s);
  border-top: 1px solid var(--line-light);
  font: var(--text-style-tips);
  color: var(--text-tips);
}
/* D2 · R14 —— overlay: chart axis tooltip, anchored to the plot area（非布局，故允许 absolute）
   样式 mirror src/components/Tooltip/Tooltip.vue 的 dark 变体 */
.chart-tip {
  position: absolute;
  z-index: var(--z-tooltip);
  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);
  box-shadow: var(--shadow-l3);
  pointer-events: none;
  opacity: 0;
  transition: opacity .12s;
}
.chart-tip[data-visible="true"] { opacity: 1; }
.chart-tip__date { font-weight: var(--font-weight-semibold); }
.chart-tip__row {
  display: flex;
  align-items: center;
  gap: var(--sp-xs);
  margin-top: var(--sp-xxs);
  font-variant-numeric: tabular-nums;
}
.chart-tip__dot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto; }
.chart-tip__num { margin-left: auto; }

/* ── 说明面板（可折叠）── */
.explain { align-self: start; }
.explain__head {
  display: flex;
  align-items: center;
  gap: var(--sp-xs);
  justify-content: space-between;
}
.explain__title {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-xs);
  margin: 0;
  font: var(--text-style-pop-title);
  color: var(--text-body);
}
.explain__title-icon { display: inline-flex; width: 16px; height: 16px; color: var(--icon-default); }
.explain__title-icon svg { display: block; }
.explain__body { margin-top: var(--sp-s); }
.explain[data-collapsed="true"] .explain__body { display: none; }

.def { margin: 0; }
.def__row { padding: var(--sp-s) 0; border-top: 1px solid var(--line-light); }
.def__row:first-child { border-top: 0; padding-top: 0; }
.def__row dt {
  margin: 0;
  font: var(--text-style-tips);
  color: var(--text-tips);
}
.def__row dd {
  margin: var(--sp-xxs) 0 0;
  font: var(--text-style-body);
  color: var(--text-body);
}
.def__formula {
  display: block;
  margin-top: var(--sp-xxs);
  font-variant-numeric: tabular-nums;
  color: var(--text-2);
}
.def__list { margin: var(--sp-xxs) 0 0; padding-left: var(--sp-m); color: var(--text-2); }
.def__list li { margin-top: var(--sp-xxs); }

/* M11 三层信号映射表（icon + 数值色 + token 三列俱全） */
.trend-rule { width: 100%; margin-top: var(--sp-xxs); }
.trend-rule td {
  padding: var(--sp-xxs) 0;
  border-top: 1px solid var(--line-light);
  font: var(--text-style-tips);
  color: var(--text-2);
  vertical-align: middle;
}
.trend-rule tr:first-child td { border-top: 0; }
.trend-rule__icon { width: 24px; }
.trend-rule__icon span { display: inline-flex; width: 16px; height: 16px; }
.trend-rule__icon svg { display: block; }
.trend-rule__cond { white-space: nowrap; font-variant-numeric: tabular-nums; }
.trend-rule__token { text-align: right; color: var(--text-tips); }
.tone--down { color: var(--brand); }
.tone--up { color: var(--orange); }
.tone--flat { color: var(--text-body); }

.freshness {
  display: flex;
  gap: var(--sp-xs);
  align-items: flex-start;
  margin-top: var(--sp-s);
  padding-top: var(--sp-s);
  border-top: 1px solid var(--line-light);
  font: var(--text-style-tips);
  color: var(--text-tips);
}
.freshness__icon { display: inline-flex; width: 16px; height: 16px; flex: 0 0 auto; color: var(--icon-default); }
.freshness__icon svg { display: block; }

/* ── 断点：字面量 ∈ --bp-* 值集合（640 / 1100），audit:layout-tokens 可核 ── */
@media (min-width: 640px) {
  .page { padding-inline: var(--sp-l); }
  .overview { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (min-width: 1100px) {
  .page { padding-inline: var(--sp-xl); }
  .overview { grid-template-columns: 1.4fr 1fr 1fr 1fr; }

  /* 说明面板宽 320px —— DS 无「侧栏说明面板」宽度 token；
     最近先例是 --notification-width-side-pop: 320px（同值、不同组件语义）
     ⇒ 取其数值、不借其名。 */
  .split { grid-template-columns: minmax(0, 1fr) 320px; }
  /* 折叠态 = 竖条 rail：8 + 24(按钮) + 8 = 40px */
  .split[data-panel="collapsed"] { grid-template-columns: minmax(0, 1fr) 40px; }
  .split[data-panel="collapsed"] .explain { padding: var(--sp-xs); }
  .split[data-panel="collapsed"] .explain__title { display: none; }
}
</style>
</head>

<body>
<main class="page">

  <!-- ── 页头 + 时间范围切换入口（需求 3）── -->
  <header class="page-head">
    <div class="page-head__titles">
      <h1 class="page-head__title" id="page-title"></h1>
      <p class="page-head__sub" id="page-sub"></p>
    </div>

    <div class="page-head__tools">
      <!-- TabList · Fill=Filled —— 1:1 mirror canonical TabList/TabItem -->
      <div class="tab-list tab-list--filled"
           data-figma-component="TabList"
           data-figma-type="Filled">
        <div class="tab-list__items" role="tablist" aria-label="Time range" id="range-tabs"></div>
      </div>

      <!-- Button · size=M · style=ghost · color=gray 1 -->
      <button type="button" class="btn btn--ghost" id="export-btn"
              data-figma-component="Button"
              data-figma-size="M"
              data-figma-style="ghost"
              data-figma-color="gray 1"
              data-figma-status="default"></button>
    </div>
  </header>

  <!-- ── 需求 1：概览区（关键数字 + 环比方向）── -->
  <section class="overview" aria-labelledby="overview-h">
    <h2 class="sr-only" id="overview-h">Period overview</h2>

    <div class="card stat stat--primary" id="stat-primary">
      <p class="stat__label" id="stat-primary-label"></p>
      <div class="stat__row">
        <p class="stat__value" id="stat-primary-value"></p>
        <!-- Badge · Type=Rectangle · Tag=Line —— 涨跌方向：icon + 数值双层染色（M11） -->
        <span class="badge" id="delta-badge"
              data-figma-component="Badge"
              data-figma-type="Rectangle"
              data-figma-tag="Line"></span>
      </div>
      <p class="stat__foot" id="stat-primary-foot"></p>
    </div>

    <div class="card stat">
      <p class="stat__label" id="stat-prev-label"></p>
      <p class="stat__value" id="stat-prev-value"></p>
      <p class="stat__foot" id="stat-prev-foot"></p>
    </div>

    <div class="card stat">
      <p class="stat__label" id="stat-tokens-label"></p>
      <p class="stat__value" id="stat-tokens-value"></p>
      <p class="stat__foot" id="stat-tokens-foot"></p>
    </div>

    <div class="card stat">
      <p class="stat__label" id="stat-cost-label"></p>
      <p class="stat__value" id="stat-cost-value"></p>
      <p class="stat__foot" id="stat-cost-foot"></p>
    </div>
  </section>

  <div class="split" id="split" data-panel="expanded">

    <!-- ── 需求 2：趋势图（横轴时间）── -->
    <section class="card chart-card" aria-labelledby="chart-h">
      <div class="chart-card__head">
        <h2 class="chart-card__title" id="chart-h"></h2>
        <span class="chart-card__axis-note" id="chart-axis-note"></span>
      </div>

      <!-- D1：手写 SVG 镜像 canonical Chart(type=line) 的视觉契约 -->
      <div class="chart-plot" id="chart-plot">
        <svg id="chart-svg" role="img" aria-labelledby="chart-h"></svg>
        <div class="chart-tip" id="chart-tip" role="status" aria-live="off"></div>
      </div>

      <div class="chart-legend" id="chart-legend"></div>
      <p class="chart-foot" id="chart-foot"></p>
    </section>

    <!-- ── 需求 4：可折叠说明面板（怎么算出来的）── -->
    <aside class="card explain" id="explain" data-collapsed="false" aria-labelledby="explain-h">
      <div class="explain__head">
        <h2 class="explain__title" id="explain-h">
          <span class="explain__title-icon" aria-hidden="true" data-icon="edit/calculate" data-figma-node-id="2352:15654"></span>
          <span id="explain-title-text"></span>
        </h2>
        <!-- Button · size=S · style=rimless · icon-only；图标方向反映展开/折叠状态 -->
        <button type="button" class="btn btn--s btn--icon" id="explain-toggle"
                aria-expanded="true" aria-controls="explain-body"
                data-figma-component="Button"
                data-figma-size="S"
                data-figma-style="rimless"
                data-figma-color="gray 1">
          <span class="btn__icon" id="explain-toggle-icon" aria-hidden="true"></span>
          <span class="sr-only" id="explain-toggle-label"></span>
        </button>
      </div>

      <div class="explain__body" id="explain-body"></div>
    </aside>

  </div>
</main>

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

  /* ==========================================================================
   * R16 —— 产品 UI 字符串走 const map 单源（本页无 i18n 框架，仍隔离出接口）。
   * 全英单语：禁中英混排。注释走中文（不进 runtime，R16 允许）。
   * ========================================================================== */
  var T = {
    pageTitle: 'Cost per Token Trend',
    pageSub: 'Unit cost of a billable token, and whether it is improving.',
    export: 'Export CSV',

    rangeLabel: { d7: '7D', d30: '30D', d90: '90D', m12: '12M' },
    rangeDisabledReason: '12M needs 12 full billing periods — 9 available',

    statCurrent: 'Cost per token · current period',
    statPrevious: 'Cost per token · previous period',
    statTokens: 'Billable tokens · current period',
    statCost: 'Attributable cost · current period',
    perToken: '/ token',
    vsPrevious: 'vs previous',
    improving: 'improving',
    worsening: 'worsening',
    stable: 'stable',
    deltaAria: 'Change versus previous period',

    chartTitle: 'Cost per token over time',
    axisNote: 'USD per token',
    seriesCurrent: 'Current period',
    seriesPrevious: 'Previous period',
    footRange: 'Range in period',
    footBuckets: 'Buckets',
    footProvisional: 'Last 24 h provisional — cost ledger settles T-1 at 03:00 UTC',
    bucketDay: 'UTC days',
    bucketWeek: 'ISO weeks',

    explainTitle: 'How this is calculated',
    collapsePanel: 'Collapse explanation',
    expandPanel: 'Expand explanation',

    defFormula: 'Formula',
    defFormulaBody: 'Attributable cost ÷ billable tokens, per bucket and for the period as a whole.',
    defNumerator: 'Numerator — attributable cost',
    defNumeratorItems: [
      'Transcode compute',
      'Egress bandwidth',
      'Storage',
      'Third-party licences'
    ],
    defNumeratorNote: 'Excludes one-off migration work and R&D.',
    defDenominator: 'Denominator — billable tokens',
    defDenominatorBody: 'Tokens consumed by billable customer sessions. Internal wallets, test wallets and refunded tokens are excluded.',
    defComparison: 'Comparison window',
    defComparisonBody: 'The previous period is the window of equal length immediately before the current one.',
    defTrendRule: 'Trend rule',
    defTrendImproving: 'Δ ≤ −1.0%',
    defTrendWorsening: 'Δ ≥ +1.0%',
    defTrendStable: 'within ±1.0%',
    defTrendImprovingText: 'improving',
    defTrendWorseningText: 'worsening',
    defTrendStableText: 'stable · no icon',
    defCaveat: 'Known caveat',
    defCaveatBody: 'Reserved-capacity commitments are amortised monthly, so month boundaries can show a step.',
    freshness: 'Cost ledger settles T-1 at 03:00 UTC. The most recent bucket is provisional and may restate.'
  };

  /* ==========================================================================
   * 图标 —— 逐字取自 src/icons/catalog/generated/*.ts（含 figmaNodeId）
   * ⛔ 未自画、未用 Unicode / emoji 充当图标（R13 Step 4 黑名单）
   * ========================================================================== */
  var ICONS = {
    'arrow/up': { node: '1149:23391', svg: '<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>' },
    'arrow/down': { node: '1149:23407', svg: '<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>' },
    'arrow/double-up': { node: '1149:23074', svg: '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M2.9543 12.8872C2.68521 13.0806 2.3103 13.0192 2.11691 12.7501C1.92352 12.481 1.98489 12.1061 2.25397 11.9127L7.47469 8.16064C7.78853 7.93509 8.21134 7.93509 8.52518 8.16064L13.746 11.9127C14.015 12.1061 14.0764 12.481 13.883 12.7501C13.6896 13.0192 13.3147 13.0806 13.0456 12.8872L8.11666 9.3448C8.04692 9.29468 7.95296 9.29468 7.88322 9.3448L2.9543 12.8872ZM2.9521 7.88722C2.68301 8.08061 2.3081 8.01925 2.11471 7.75016C1.92132 7.48108 1.98269 7.10617 2.25177 6.91278L7.47469 3.15911C7.78853 2.93356 8.21134 2.93356 8.52518 3.15911L13.7482 6.91278C14.0172 7.10616 14.0786 7.48107 13.8852 7.75016C13.6918 8.01925 13.3169 8.08061 13.0478 7.88722L8.11666 4.34327C8.04691 4.29315 7.95296 4.29315 7.88322 4.34327L2.9521 7.88722Z" fill="currentColor"/></svg>' },
    'arrow/double-down': { node: '1149:23106', svg: '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M2.94667 3.11282C2.67758 2.91943 2.30267 2.9808 2.10928 3.24988C1.91589 3.51897 1.97726 3.89388 2.24634 4.08727L7.47471 7.84485C7.78855 8.07041 8.21136 8.07041 8.5252 7.84486L13.7536 4.08727C14.0227 3.89388 14.0841 3.51897 13.8907 3.24988C13.6973 2.9808 13.3224 2.91943 13.0533 3.11282L8.11668 6.66069C8.04694 6.71082 7.95298 6.71082 7.88324 6.66069L2.94667 3.11282ZM2.94667 8.11282C2.67758 7.91943 2.30267 7.9808 2.10928 8.24988C1.91589 8.51897 1.97726 8.89388 2.24634 9.08727L7.47471 12.8448C7.78855 13.0704 8.21135 13.0704 8.52519 12.8449L13.7536 9.08727C14.0227 8.89388 14.0841 8.51897 13.8907 8.24988C13.6973 7.9808 13.3224 7.91943 13.0533 8.11282L8.11667 11.6607C8.04693 11.7108 7.95297 11.7108 7.88323 11.6607L2.94667 8.11282Z" fill="currentColor"/></svg>' },
    'edit/calculate': { node: '2352:15654', svg: '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M3.90002 2.20156C3.90002 1.87019 4.16865 1.60156 4.50002 1.60156C4.8314 1.60156 5.10002 1.87019 5.10002 2.20156V4.00194H6.9C7.23137 4.00194 7.5 4.27056 7.5 4.60193C7.5 4.9333 7.23137 5.20193 6.9 5.20193H5.10002V7.00152C5.10002 7.33289 4.8314 7.60152 4.50002 7.60152C4.16865 7.60152 3.90002 7.33289 3.90002 7.00152V5.20193H2.1C1.76863 5.20193 1.5 4.9333 1.5 4.60193C1.5 4.27056 1.76863 4.00194 2.1 4.00194H3.90002V2.20156ZM9.1 4.00154C8.76863 4.00154 8.5 4.27017 8.5 4.60154C8.5 4.93291 8.76863 5.20154 9.1 5.20154H13.9C14.2314 5.20154 14.5 4.93291 14.5 4.60154C14.5 4.27017 14.2314 4.00154 13.9 4.00154H9.1ZM9.1 9.69994C8.76863 9.69994 8.5 9.96857 8.5 10.2999C8.5 10.6313 8.76863 10.8999 9.1 10.8999H13.9C14.2314 10.8999 14.5 10.6313 14.5 10.2999C14.5 9.96857 14.2314 9.69994 13.9 9.69994H9.1ZM9.1 12.1003C8.76863 12.1003 8.5 12.3689 8.5 12.7003C8.5 13.0317 8.76863 13.3003 9.1 13.3003H13.9C14.2314 13.3003 14.5 13.0317 14.5 12.7003C14.5 12.3689 14.2314 12.1003 13.9 12.1003H9.1ZM2.02566 9.12938C2.25998 8.89507 2.63988 8.89507 2.87419 9.12938L4.50068 10.7559L6.12716 9.12938C6.36148 8.89507 6.74138 8.89507 6.97569 9.12938C7.21001 9.36369 7.21001 9.74359 6.97569 9.9779L5.34921 11.6044L6.97541 13.2306C7.20972 13.4649 7.20972 13.8448 6.97541 14.0791C6.7411 14.3134 6.3612 14.3134 6.12688 14.0791L4.50068 12.4529L2.87447 14.0791C2.64016 14.3134 2.26026 14.3134 2.02595 14.0791C1.79163 13.8448 1.79163 13.4649 2.02595 13.2306L3.65215 11.6044L2.02566 9.9779C1.79135 9.74359 1.79135 9.36369 2.02566 9.12938Z" fill="currentColor"/></svg>' },
    'message/info-1': { node: '1168:23662', svg: '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M15 8C15 11.866 11.866 15 8 15C4.13401 15 1 11.866 1 8C1 4.13401 4.13401 1 8 1C11.866 1 15 4.13401 15 8ZM13.8 7.99922C13.8 4.79597 11.2033 2.19922 8.00001 2.19922C4.79676 2.19922 2.20001 4.79597 2.20001 7.99922C2.20001 11.2025 4.79676 13.7992 8.00001 13.7992C11.2033 13.7992 13.8 11.2025 13.8 7.99922ZM8.60002 4.6C8.60002 4.93137 8.3314 5.2 8.00002 5.2C7.66865 5.2 7.40002 4.93137 7.40002 4.6C7.40002 4.26863 7.66865 4 8.00002 4C8.3314 4 8.60002 4.26863 8.60002 4.6ZM7.40002 7C7.40002 6.66863 7.66865 6.4 8.00002 6.4C8.3314 6.4 8.60002 6.66863 8.60002 7V11.4C8.60002 11.7314 8.3314 12 8.00002 12C7.66865 12 7.40002 11.7314 7.40002 11.4V7Z" fill="currentColor"/></svg>' }
  };

  function iconMarkup(name) {
    var i = ICONS[name];
    return i ? i.svg : '';
  }

  /* ==========================================================================
   * 数据 —— 演示用固定值（无随机、无 Date.now，刷新结果稳定）。
   * 跨区间自洽：7D 就是 30D 序列的最后 7 天，均值算得出来。
   * ========================================================================== */

  // 30D 当期（每 UTC 日一点，均值 0.0412）
  var D30_CURRENT = [
    0.0460, 0.0456, 0.0463, 0.0449, 0.0445, 0.0451, 0.0437,
    0.0432, 0.0439, 0.0426, 0.0420, 0.0417, 0.0423, 0.0409,
    0.0404, 0.0397, 0.0401, 0.0390, 0.0385, 0.0379, 0.0374,
    0.0369, 0.0376, 0.0372, 0.0380, 0.0387, 0.0395, 0.0402,
    0.0407, 0.0411
  ];
  // 30D 上一期同区间（均值 0.0468）
  var D30_PREVIOUS = [
    0.0459, 0.0466, 0.0471, 0.0463, 0.0475, 0.0469, 0.0458,
    0.0472, 0.0480, 0.0466, 0.0461, 0.0474, 0.0468, 0.0455,
    0.0463, 0.0477, 0.0470, 0.0482, 0.0464, 0.0458, 0.0471,
    0.0467, 0.0476, 0.0462, 0.0469, 0.0473, 0.0457, 0.0465,
    0.0479, 0.0468
  ];

  var RANGES = [
    {
      key: 'd7',
      label: T.rangeLabel.d7,
      bucket: 'day',
      bucketLabel: T.bucketDay,
      windowText: '7 days',
      endDate: '2026-08-25',
      // 30D 序列的最后 7 天 / 其前 7 天 —— 刻意让短窗呈「恶化」，把 M11 的 ↑ 橙态落在真实状态上
      current: D30_CURRENT.slice(23),
      previous: D30_CURRENT.slice(16, 23),
      kpi: { current: 0.0393, previous: 0.0382, deltaPct: 2.9, tokens: 1.14, cost: 44802, min: 0.0372, max: 0.0411 }
    },
    {
      key: 'd30',
      label: T.rangeLabel.d30,
      bucket: 'day',
      bucketLabel: T.bucketDay,
      windowText: '30 days',
      endDate: '2026-08-25',
      current: D30_CURRENT,
      previous: D30_PREVIOUS,
      kpi: { current: 0.0412, previous: 0.0468, deltaPct: -12.0, tokens: 4.82, cost: 198584, min: 0.0369, max: 0.0463 }
    },
    {
      key: 'd90',
      label: T.rangeLabel.d90,
      bucket: 'week',
      bucketLabel: T.bucketWeek,
      windowText: '90 days',
      endDate: '2026-08-25',
      // 90D 聚合到 ISO 周（13 点），均值 0.0437 / 上一期 0.0439 ⇒ ±1% 内 = stable
      current: [0.0460, 0.0467, 0.0454, 0.0451, 0.0463, 0.0448, 0.0440, 0.0444, 0.0433, 0.0425, 0.0413, 0.0387, 0.0392],
      previous: [0.0442, 0.0435, 0.0448, 0.0431, 0.0446, 0.0429, 0.0452, 0.0437, 0.0441, 0.0433, 0.0447, 0.0438, 0.0428],
      kpi: { current: 0.0437, previous: 0.0439, deltaPct: -0.5, tokens: 13.90, cost: 607430, min: 0.0387, max: 0.0467 }
    },
    {
      key: 'm12',
      label: T.rangeLabel.m12,
      // M12（外部/异步事件不静默 no-op）：不可用要给理由，不是灰掉了事
      disabled: true,
      disabledReason: T.rangeDisabledReason
    }
  ];

  // M16 默认值评估：默认 30D —— 月度对账节奏；90D 会稀释掉本轮改善，7D 只看得到尾部回升。
  var DEFAULT_RANGE = 'd30';
  // M11 阈值（三层信号的第一层触发条件）
  var TREND_THRESHOLD = 1.0;

  var state = { range: DEFAULT_RANGE };

  /* ── 格式化 ── */
  function fmtCost(v) { return '$' + v.toFixed(4); }
  function fmtAxis(v) { return '$' + v.toFixed(4); }
  function fmtMoney(v) { return '$' + v.toLocaleString('en-US'); }
  function fmtTokens(v) { return v.toFixed(2) + ' M'; }
  function fmtPct(v) { return Math.abs(v).toFixed(1) + '%'; }

  var MONTHS = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
  function labelFor(endDateStr, count, bucket, i) {
    var end = new Date(endDateStr + 'T00:00:00Z');
    var stepDays = bucket === 'week' ? 7 : 1;
    var d = new Date(end.getTime());
    d.setUTCDate(d.getUTCDate() - (count - 1 - i) * stepDays);
    return MONTHS[d.getUTCMonth()] + ' ' + d.getUTCDate();
  }
  function buildLabels(r) {
    var out = [], i;
    for (i = 0; i < r.current.length; i++) out.push(labelFor(r.endDate, r.current.length, r.bucket, i));
    return out;
  }

  /* ── M11 三层信号判定 ── */
  function trendOf(deltaPct) {
    if (deltaPct <= -TREND_THRESHOLD) return { key: 'down', icon: 'arrow/down', word: T.improving };
    if (deltaPct >= TREND_THRESHOLD) return { key: 'up', icon: 'arrow/up', word: T.worsening };
    return { key: 'flat', icon: null, word: T.stable };
  }

  function rangeByKey(key) {
    for (var i = 0; i < RANGES.length; i++) if (RANGES[i].key === key) return RANGES[i];
    return null;
  }

  /* ==========================================================================
   * 时间范围切换（TabList Filled）
   * D6：canonical 只处理 Enter/Space 且非 active 项 tabindex=-1 ⇒ 补 ←→ roving（P3/WCAG）
   * ========================================================================== */
  var tabsEl = document.getElementById('range-tabs');

  function renderTabs() {
    tabsEl.innerHTML = '';
    RANGES.forEach(function (r) {
      var isActive = r.key === state.range;
      var wrap = document.createElement('span');
      wrap.className = 'tab-item__wrap';

      var el = document.createElement('div');
      el.className = 'tab-item tab-item--filled ' +
        (isActive ? 'tab-item--active tab-item--active-white' : 'tab-item--normal') +
        (r.disabled ? ' tab-item--disabled' : '');
      el.setAttribute('role', 'tab');
      el.setAttribute('aria-selected', String(isActive));
      el.setAttribute('data-figma-component', 'TabItem');
      el.setAttribute('data-figma-property1', isActive ? 'Active' : 'Normal');
      el.setAttribute('data-figma-property2', 'White');
      el.setAttribute('data-figma-type', 'Filled');
      el.dataset.key = r.key;
      el.textContent = r.label;

      if (r.disabled) {
        el.setAttribute('aria-disabled', 'true');
        el.setAttribute('tabindex', '-1');
        el.setAttribute('title', r.disabledReason);
      } else {
        el.setAttribute('tabindex', isActive ? '0' : '-1');
        el.addEventListener('click', function () { select(r.key); });
        el.addEventListener('keydown', onTabKeydown);
      }

      wrap.appendChild(el);
      tabsEl.appendChild(wrap);
    });
  }

  function onTabKeydown(e) {
    var key = e.key;
    if (key === 'Enter' || key === ' ') {           // canonical TabItem.activate()
      e.preventDefault();
      select(e.currentTarget.dataset.key);
      return;
    }
    if (key !== 'ArrowRight' && key !== 'ArrowLeft') return;
    e.preventDefault();
    var enabled = RANGES.filter(function (r) { return !r.disabled; });
    var idx = enabled.findIndex(function (r) { return r.key === state.range; });
    var next = enabled[(idx + (key === 'ArrowRight' ? 1 : enabled.length - 1)) % enabled.length];
    select(next.key);
    var el = tabsEl.querySelector('[data-key="' + next.key + '"]');
    if (el) el.focus();
  }

  function select(key) {
    var r = rangeByKey(key);
    if (!r || r.disabled || key === state.range) return;
    state.range = key;
    renderAll();
  }

  /* ==========================================================================
   * 概览区
   * ========================================================================== */
  function renderOverview() {
    var r = rangeByKey(state.range);
    var k = r.kpi;
    var t = trendOf(k.deltaPct);

    document.getElementById('stat-primary-label').textContent = T.statCurrent;
    document.getElementById('stat-primary-value').innerHTML =
      fmtCost(k.current) + '<span class="stat__unit">' + T.perToken + '</span>';

    var card = document.getElementById('stat-primary');
    card.className = 'card stat stat--primary trend--' + t.key;

    // Badge · Rectangle/Line —— M11 双层信号：icon + 数值同色
    var badge = document.getElementById('delta-badge');
    badge.className = 'badge badge--' + t.key;
    badge.setAttribute('data-figma-color', t.key === 'down' ? 'Green' : (t.key === 'up' ? 'Orange' : 'Neutral'));
    badge.setAttribute('aria-label', T.deltaAria + ': ' + fmtPct(k.deltaPct) + ' ' + t.word);
    badge.innerHTML =
      (t.icon ? '<span class="badge__icon" aria-hidden="true">' + iconMarkup(t.icon) + '</span>' : '') +
      '<span class="badge__value stat__delta-text">' + (k.deltaPct > 0 ? '+' : k.deltaPct < 0 ? '−' : '') + fmtPct(k.deltaPct) + '</span>' +
      '<span>' + t.word + '</span>';

    document.getElementById('stat-primary-foot').textContent =
      T.vsPrevious + ' ' + r.windowText + ' · ' + fmtCost(k.previous) + ' ' + T.perToken;

    document.getElementById('stat-prev-label').textContent = T.statPrevious;
    document.getElementById('stat-prev-value').textContent = fmtCost(k.previous);
    document.getElementById('stat-prev-foot').textContent = 'The ' + r.windowText + ' before this window';

    document.getElementById('stat-tokens-label').textContent = T.statTokens;
    document.getElementById('stat-tokens-value').textContent = fmtTokens(k.tokens);
    document.getElementById('stat-tokens-foot').textContent = 'Billable sessions only';

    document.getElementById('stat-cost-label').textContent = T.statCost;
    document.getElementById('stat-cost-value').textContent = fmtMoney(k.cost);
    document.getElementById('stat-cost-foot').textContent = 'Transcode · egress · storage · licences';
  }

  /* ==========================================================================
   * 趋势图 —— D1：mirror canonical Chart(type=line) 的视觉契约
   *   series 1 色 = --chart-color-1（palette 索引 1）
   *   series 2 色 = --chart-color-3（dataset.color 显式覆写；⛔ 不用索引 2 = --chart-color-2 红，
   *                 因 domain-tvu M3「红=Live」在本域是肌肉记忆）
   *   轴线/刻度/网格 = --line-divider-light · 轴标签+图例 = --text-2 12px --font-family-base
   *   legend bottom · circle 8×8 · 线宽 2 · emptyCircle symbol · tooltip trigger=axis
   * ========================================================================== */
  var SVG_NS = 'http://www.w3.org/2000/svg';
  var PLOT_H = 320;
  var PAD = { top: 12, right: 12, bottom: 26, left: 60 };  // containLabel 的手工等价

  function svgEl(name, attrs) {
    var el = document.createElementNS(SVG_NS, name);
    for (var k in attrs) if (Object.prototype.hasOwnProperty.call(attrs, k)) el.setAttribute(k, attrs[k]);
    return el;
  }

  // ECharts value-axis 的 nice 刻度等价物（splitNumber 默认 5）
  function niceScale(min, max, count) {
    var span = max - min || 1;
    var rough = span / count;
    var mag = Math.pow(10, Math.floor(Math.log(rough) / Math.LN10));
    var norm = rough / mag;
    var step = (norm <= 1 ? 1 : norm <= 2 ? 2 : norm <= 5 ? 5 : 10) * mag;
    var lo = Math.floor(min / step) * step;
    var hi = Math.ceil(max / step) * step;
    var ticks = [];
    for (var v = lo; v <= hi + step / 2; v += step) ticks.push(Number(v.toFixed(6)));
    return { lo: lo, hi: hi, ticks: ticks };
  }

  var chartCtx = null;

  function drawChart() {
    var r = rangeByKey(state.range);
    var plot = document.getElementById('chart-plot');
    var svg = document.getElementById('chart-svg');
    var labels = buildLabels(r);
    var w = Math.max(plot.clientWidth || 640, 280);

    svg.setAttribute('width', w);
    svg.setAttribute('height', PLOT_H);
    svg.setAttribute('viewBox', '0 0 ' + w + ' ' + PLOT_H);
    while (svg.firstChild) svg.removeChild(svg.firstChild);

    var all = r.current.concat(r.previous);
    var scale = niceScale(Math.min.apply(null, all), Math.max.apply(null, all), 5);

    var x0 = PAD.left, x1 = w - PAD.right, y0 = PAD.top, y1 = PLOT_H - PAD.bottom;
    var n = r.current.length;
    var xAt = function (i) { return n === 1 ? (x0 + x1) / 2 : x0 + (x1 - x0) * i / (n - 1); };
    var yAt = function (v) { return y1 - (y1 - y0) * (v - scale.lo) / (scale.hi - scale.lo); };

    var axisColor = 'var(--line-divider-light)';
    var labelColor = 'var(--text-2)';

    // splitLine（value 轴）+ y 轴刻度文字
    scale.ticks.forEach(function (t) {
      var y = yAt(t);
      svg.appendChild(svgEl('line', {
        x1: x0, y1: y, x2: x1, y2: y, stroke: axisColor, 'stroke-width': 1, 'shape-rendering': 'crispEdges'
      }));
      var tx = svgEl('text', {
        x: x0 - 8, y: y + 4, fill: labelColor, 'text-anchor': 'end',
        'font-family': 'var(--font-family-base)', 'font-size': 12
      });
      tx.style.fontVariantNumeric = 'tabular-nums';
      tx.textContent = fmtAxis(t);
      svg.appendChild(tx);
    });

    // axisLine + axisTick（两轴都设了，故两条线都画）
    svg.appendChild(svgEl('line', { x1: x0, y1: y0, x2: x0, y2: y1, stroke: axisColor, 'stroke-width': 1, 'shape-rendering': 'crispEdges' }));
    svg.appendChild(svgEl('line', { x1: x0, y1: y1, x2: x1, y2: y1, stroke: axisColor, 'stroke-width': 1, 'shape-rendering': 'crispEdges' }));

    // category 轴：标签按可读间隔抽稀（ECharts axisLabel interval:'auto' 的等价行为）
    var every = n <= 8 ? 1 : n <= 16 ? 2 : 5;
    labels.forEach(function (lab, i) {
      var x = xAt(i);
      svg.appendChild(svgEl('line', { x1: x, y1: y1, x2: x, y2: y1 + 5, stroke: axisColor, 'stroke-width': 1, 'shape-rendering': 'crispEdges' }));
      if (i % every !== 0 && i !== n - 1) return;
      var tx = svgEl('text', {
        x: x, y: y1 + 18, fill: labelColor, 'text-anchor': 'middle',
        'font-family': 'var(--font-family-base)', 'font-size': 12
      });
      tx.textContent = lab;
      svg.appendChild(tx);
    });

    // series（画序：上一期在下，当期在上）
    var series = [
      { name: T.seriesPrevious, data: r.previous, color: 'var(--chart-color-3)' },
      { name: T.seriesCurrent, data: r.current, color: 'var(--chart-color-1)' }
    ];
    series.forEach(function (s) {
      var pts = s.data.map(function (v, i) { return xAt(i) + ',' + yAt(v); }).join(' ');
      svg.appendChild(svgEl('polyline', {
        points: pts, fill: 'none', stroke: s.color, 'stroke-width': 2,
        'stroke-linejoin': 'round', 'stroke-linecap': 'round'
      }));
      // emptyCircle symbol（ECharts line 默认 symbol）
      s.data.forEach(function (v, i) {
        svg.appendChild(svgEl('circle', {
          cx: xAt(i), cy: yAt(v), r: 2.5,
          fill: 'var(--bg-layer3)', stroke: s.color, 'stroke-width': 1.5
        }));
      });
    });

    // axisPointer（tooltip trigger:'axis' 的十字准线）
    var pointer = svgEl('line', {
      x1: 0, y1: y0, x2: 0, y2: y1, stroke: 'var(--line-border)', 'stroke-width': 1, opacity: 0
    });
    svg.appendChild(pointer);

    var focus = series.map(function (s) {
      return svgEl('circle', { cx: 0, cy: 0, r: 4, fill: s.color, opacity: 0 });
    });
    focus.forEach(function (f) { svg.appendChild(f); });

    chartCtx = { r: r, labels: labels, xAt: xAt, yAt: yAt, x0: x0, x1: x1, n: n, series: series, pointer: pointer, focus: focus };

    svg.setAttribute('aria-label',
      T.chartTitle + ' — ' + r.label + ', ' + T.axisNote + ', ' + labels[0] + ' to ' + labels[n - 1]);

    // 图例（bottom · circle 8×8 · --text-2 12px）
    var legend = document.getElementById('chart-legend');
    legend.innerHTML = series.slice().reverse().map(function (s) {
      return '<span class="chart-legend__item">' +
        '<span class="chart-legend__dot" style="background:' + s.color + '"></span>' + s.name +
        '</span>';
    }).join('');

    document.getElementById('chart-h').textContent = T.chartTitle;
    document.getElementById('chart-axis-note').textContent = T.axisNote;
    document.getElementById('chart-foot').textContent =
      T.footRange + ': ' + fmtCost(r.kpi.min) + ' – ' + fmtCost(r.kpi.max) +
      ' · ' + T.footBuckets + ': ' + r.bucketLabel +
      ' · ' + T.footProvisional;
  }

  /* ── 图内 axis tooltip（hover / 键盘 focus 均可触发）── */
  var tipEl = document.getElementById('chart-tip');

  function showTip(clientX) {
    if (!chartCtx) return;
    var plot = document.getElementById('chart-plot');
    var rect = plot.getBoundingClientRect();
    var x = clientX - rect.left;
    var c = chartCtx;
    var i = 0, best = Infinity;
    for (var j = 0; j < c.n; j++) {
      var d = Math.abs(c.xAt(j) - x);
      if (d < best) { best = d; i = j; }
    }
    var px = c.xAt(i);
    c.pointer.setAttribute('x1', px);
    c.pointer.setAttribute('x2', px);
    c.pointer.setAttribute('opacity', 1);
    c.series.forEach(function (s, k) {
      c.focus[k].setAttribute('cx', px);
      c.focus[k].setAttribute('cy', c.yAt(s.data[i]));
      c.focus[k].setAttribute('opacity', 1);
    });

    var cur = c.series[1].data[i], prv = c.series[0].data[i];
    var diff = ((cur - prv) / prv) * 100;
    var t = trendOf(diff);
    tipEl.innerHTML =
      '<div class="chart-tip__date">' + c.labels[i] + '</div>' +
      c.series.slice().reverse().map(function (s) {
        return '<div class="chart-tip__row">' +
          '<span class="chart-tip__dot" style="background:' + s.color + '"></span>' +
          '<span>' + s.name + '</span>' +
          '<span class="chart-tip__num">' + fmtCost(s.data[i]) + '</span></div>';
      }).join('') +
      '<div class="chart-tip__row"><span>Δ</span><span class="chart-tip__num tone--' + t.key + '">' +
        (diff > 0 ? '+' : diff < 0 ? '−' : '') + fmtPct(diff) +
      '</span></div>';

    tipEl.setAttribute('data-visible', 'true');
    var tw = tipEl.offsetWidth, ph = tipEl.offsetHeight;
    var left = Math.min(Math.max(px + 12, 0), Math.max(rect.width - tw, 0));
    tipEl.style.left = left + 'px';
    tipEl.style.top = Math.max(PAD.top, Math.min(PLOT_H - ph - PAD.bottom, PLOT_H / 2 - ph / 2)) + 'px';
  }

  function hideTip() {
    tipEl.setAttribute('data-visible', 'false');
    if (!chartCtx) return;
    chartCtx.pointer.setAttribute('opacity', 0);
    chartCtx.focus.forEach(function (f) { f.setAttribute('opacity', 0); });
  }

  var plotEl = document.getElementById('chart-plot');
  plotEl.addEventListener('mousemove', function (e) { showTip(e.clientX); });
  plotEl.addEventListener('mouseleave', hideTip);

  /* ==========================================================================
   * 说明面板（可折叠）—— 图标方向反映展开/折叠状态
   *   arrow/double-up   = 已展开（点击折叠）
   *   arrow/double-down = 已折叠（点击展开）
   * ========================================================================== */
  var panelExpanded = true;
  var splitEl = document.getElementById('split');
  var explainEl = document.getElementById('explain');
  var toggleBtn = document.getElementById('explain-toggle');

  function renderPanelChrome() {
    document.getElementById('explain-title-text').textContent = T.explainTitle;
    document.getElementById('explain-toggle-icon').innerHTML =
      iconMarkup(panelExpanded ? 'arrow/double-up' : 'arrow/double-down');
    document.getElementById('explain-toggle-label').textContent =
      panelExpanded ? T.collapsePanel : T.expandPanel;
    toggleBtn.setAttribute('aria-expanded', String(panelExpanded));
    toggleBtn.setAttribute('title', panelExpanded ? T.collapsePanel : T.expandPanel);
    explainEl.setAttribute('data-collapsed', String(!panelExpanded));
    splitEl.setAttribute('data-panel', panelExpanded ? 'expanded' : 'collapsed');
  }

  toggleBtn.addEventListener('click', function () {
    panelExpanded = !panelExpanded;
    renderPanelChrome();
    drawChart();               // 列宽变了 → 重绘（宽度是 JS 计算的，不是 viewBox 缩放）
  });

  function renderPanelBody() {
    var r = rangeByKey(state.range);
    var rows = [
      {
        dt: T.defFormula,
        dd: T.defFormulaBody +
            '<code class="def__formula">cost per token = attributable cost ÷ billable tokens</code>'
      },
      {
        dt: T.defNumerator,
        dd: '<ul class="def__list">' +
            T.defNumeratorItems.map(function (s) { return '<li>' + s + '</li>'; }).join('') +
            '</ul>' + T.defNumeratorNote
      },
      { dt: T.defDenominator, dd: T.defDenominatorBody },
      {
        dt: T.defComparison,
        dd: T.defComparisonBody + ' ' +
            'Current: ' + r.windowText + ' ending ' + buildLabels(r).slice(-1)[0] + '. ' +
            'Buckets: ' + r.bucketLabel + '.'
      },
      { dt: T.defTrendRule, dd: trendRuleTable() },
      { dt: T.defCaveat, dd: T.defCaveatBody }
    ];

    document.getElementById('explain-body').innerHTML =
      '<dl class="def">' +
      rows.map(function (row) {
        return '<div class="def__row"><dt>' + row.dt + '</dt><dd>' + row.dd + '</dd></div>';
      }).join('') +
      '</dl>' +
      '<p class="freshness">' +
        '<span class="freshness__icon" aria-hidden="true">' + iconMarkup('message/info-1') + '</span>' +
        '<span>' + T.freshness + '</span>' +
      '</p>';
  }

  // M11 Acceptance：trend rule 必须同时给出 icon 阈值 + 数值颜色 + token 引用
  function trendRuleTable() {
    var rowsData = [
      { icon: 'arrow/down', tone: 'down', cond: T.defTrendImproving, text: T.defTrendImprovingText, token: '--brand' },
      { icon: 'arrow/up', tone: 'up', cond: T.defTrendWorsening, text: T.defTrendWorseningText, token: '--orange' },
      { icon: null, tone: 'flat', cond: T.defTrendStable, text: T.defTrendStableText, token: '--text-body' }
    ];
    return '<table class="trend-rule">' + rowsData.map(function (row) {
      return '<tr>' +
        '<td class="trend-rule__icon tone--' + row.tone + '">' +
          (row.icon ? '<span aria-hidden="true">' + iconMarkup(row.icon) + '</span>' : '') +
        '</td>' +
        '<td class="trend-rule__cond tone--' + row.tone + '">' + row.cond + '</td>' +
        '<td class="tone--' + row.tone + '">' + row.text + '</td>' +
        '<td class="trend-rule__token"><code>' + row.token + '</code></td>' +
      '</tr>';
    }).join('') + '</table>';
  }

  /* ── 渲染入口 ── */
  function renderAll() {
    document.getElementById('page-title').textContent = T.pageTitle;
    document.getElementById('page-sub').textContent = T.pageSub;
    document.getElementById('export-btn').textContent = T.export;
    renderTabs();
    renderOverview();
    renderPanelChrome();
    renderPanelBody();
    drawChart();
    hideTip();
  }

  renderAll();

  // 宽度变化重绘（保持轴标签 12px 真实字号，不做 viewBox 拉伸）
  if (typeof ResizeObserver !== 'undefined') {
    var ro = new ResizeObserver(function () { drawChart(); });
    ro.observe(document.getElementById('chart-plot'));
  } else {
    window.addEventListener('resize', drawChart);
  }
})();
</script>
</body>
</html>
```

一句总结：**能从活源实测的我都实测了；不能实测的（宿主范式帧、机械闸）我标了未满足，没替它们打勾。** 落地前需要你拍的三件事：M11 那个不存在的 `--text-warning`、英雄数字要不要超过 24px 字阶、以及图内 tooltip 用 DS Tooltip 还是照 canonical 留 ECharts 默认（D2）。