/*
 * Dalton 增强层 —— **叠加**在 W2 之上，不改 W2 原文件（SPEC-04 §1 vendor 入库）。
 *
 * 铁律（CLAUDE.md 关键约束 6）：本文件里**一个色值都不写**，只引用 W2 token。
 * 有一条 CI 闸看着这件事（`apps/web/test/no-hardcoded-color.test.ts`）——
 * 它扫 `src/**` 里除 `styles/w2/**` 之外的全部文件，命中 #hex / rgb() / hsl() 即红。
 * 为什么必须是闸而不是纪律：一个硬编码色在明暗主题切换时才暴露，
 * 而那时它已经被复制到十几个地方了。
 *
 * W2 只提供 `.gs input` 一种输入框（顶栏搜索）。管理页需要通用表单控件，
 * 因此这里补一组，形态与 W2 的按钮/卡片保持同一套圆角、描边与焦点环。
 */

/* ---------- 表单控件 ---------- */
.field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: 14px;
}
.field > label {
  font-size: 12.5px;
  font-weight: 600;
  color: var(--text-2);
}
.field .hint {
  font-size: 12px;
  color: var(--text-3);
}
.field .err {
  font-size: 12px;
  color: var(--bad);
  font-weight: 600;
}

.inp,
select.inp,
textarea.inp {
  width: 100%;
  height: 38px;
  border: 1px solid var(--hairline);
  border-radius: var(--r-md);
  padding: 0 12px;
  font-size: 13.5px;
  font-family: var(--font-sans);
  background: var(--surface);
  color: var(--text);
  outline: none;
  transition: 0.16s var(--ease);
}
textarea.inp {
  height: auto;
  padding: 10px 12px;
  line-height: 1.5;
}
/*
 * 复选项（标签 + 原生 input）。
 *
 * ⚠️ **不要用 W2 的 `.checkbox`**：那是一个 `16×16 + flex:none` 的**装饰方块**
 * （见 w2/core.css），不是标签容器。把它套在 `<label>` 上会把整个标签压成 16px 宽，
 * 于是文字被挤成一列竖排单字——目录页的"只看可直接查询的"曾经就是这个样子
 * （evaluations/phase-3-screens/fix7-n7-viewer-degraded.png）。
 * R1 的 Playwright 断言查的是角标文本与 403 数量，覆盖不到布局，于是它一直没被发现。
 *
 * 用原生 `input[type=checkbox]` + `accent-color` 而不是 W2 那个纯 CSS 方块：
 * 原生控件自带键盘可达性、焦点环与读屏语义，而那个方块要靠调用方另外接一套。
 */
.checkline {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  /* 工具条是 flex-wrap 的：不锁 nowrap 的话窄屏下标签仍会被断成两行。 */
  white-space: nowrap;
  font-size: 13px;
  color: var(--text-2);
  cursor: pointer;
  user-select: none;
}
.checkline input[type='checkbox'] {
  width: 16px;
  height: 16px;
  flex: none;
  margin: 0;
  accent-color: var(--accent);
  cursor: pointer;
}
.checkline input[type='checkbox']:focus-visible {
  outline: none;
  box-shadow: 0 0 0 4px var(--accent-ring);
  border-radius: 4px;
}

.inp::placeholder {
  color: var(--text-3);
}
.inp:focus-visible,
.inp:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 4px var(--accent-ring);
}
/* 语义双编码：错误态不只靠颜色，输入框旁必然有一行 .err 文案。 */
.inp[aria-invalid='true'] {
  border-color: var(--bad);
}
.inp:disabled {
  background: var(--shell-2);
  color: var(--text-3);
  cursor: not-allowed;
}

/* ---------- 三态：loading / 空 / 错误 ---------- */
.state {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  padding: 44px 20px;
  text-align: center;
  color: var(--text-2);
}
.state h4 {
  font-size: 14px;
  color: var(--text-2);
}
/*
 * ⚠️⚠️ **`--text-2`，不是 `--text-3`**（R3-F1 的第二处旁路）。
 *
 * 这一条是**元素选择器**（`.state p`），元素上没有任何类 ——
 * 于是那道靠"类名"触发的静态闸**从来看不到它**，而 `ErrorState` 的**错误正文**
 * 正是这个 `<p>`：R2 实测亮色 **2.29–2.41:1**。
 * 也就是说全站每一条错误说明、每一个空态说明都在 AA 线以下，而闸判绿。
 *
 * ⇒ 这不是"再补一个类名到清单里"能解决的：**触发条件本身**（元素要挂一个
 * 静态可解析的类）就漏。修法见 `apps/web/e2e/a11y-contrast.spec.ts`：
 * **完整性交给浏览器** —— 闸去量真实渲染出来的颜色，而不是去猜类名。
 */
.state p {
  margin: 0;
  font-size: 13px;
  color: var(--text-2);
  max-width: 40em;
}

/* 骨架屏。reduced-motion 下停掉扫光（W2 已有全局 reduce 规则，这里再显式一次）。 */
.skel {
  height: 14px;
  border-radius: 6px;
  background: linear-gradient(90deg, var(--shell-2), var(--shell), var(--shell-2));
  background-size: 200% 100%;
  animation: dalton-shimmer 1.2s var(--ease) infinite;
}
@keyframes dalton-shimmer {
  from {
    background-position: 200% 0;
  }
  to {
    background-position: -200% 0;
  }
}
@media (prefers-reduced-motion: reduce) {
  .skel {
    animation: none;
  }
}

/*
 * 顶栏 Dalton 标（P23-H-25）：入库矢量原件取代 vendor 那个 CSS 渐变方块。
 * vendor 的 `.tbar .bd .logo` 给的是渐变底 + 阴影（那是"方块里写个 D"的形态），
 * 换成 `<img>` 之后底与阴影都由资产自己带，这里把 vendor 那两条关掉。
 * ⚠️ 不改 `styles/w2/core.css`：它是 vendor 入库件（SPEC-04 §1：原文件不改，升级可 diff）。
 */
.tbar .bd img.logo {
  background: none;
  box-shadow: none;
  border-radius: 0;
}

/* ---------- 登录页 · 会话加载/失败的居中壳 ---------- */
/*
 * ⚠️ `.login-shell` 与 `.login-card` **不是登录页在用的类**（Phase 23 轮 B 起）。
 * 登录页本体走下面的 `.login-split` 两栏；这两个类留给 `App.tsx` 的
 * 「会话加载中」与「会话取失败」两支——那两屏没有品牌区可言，居中一张卡才是对的。
 */
.login-shell {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 24px;
  background: var(--bg-app);
}
.login-card {
  width: min(420px, 100%);
  padding: 28px;
}

/* ---------- 页面布局 ---------- */
/*
 * ⭐ D-17：正文最大宽 = `--page-max`，且**这个 token 就是那个数**。
 *
 * 上一版这里写死 `max-width: 1280px`，而 W2 的 `--page-max` 是 1520px ——
 * 也就是说全站有两个"正文最大宽"，改哪个都只改到一半。
 * 基准原型 `report-detail.html` 的 `.page` 是 `max-width:1240px`；
 * ⇒ token 在产品层收到 1240，`.page` 只引用它。覆写写在增强层而不是改
 * `styles/w2/tokens.css`：那是 vendor 原文件（H-02 的排除项）。
 */
:root {
  --page-max: 1240px;
  /*
   * ⭐⭐ E-08 / H-08：**原生控件也得知道现在是暗色**。
   *
   * 全仓此前一处 `color-scheme` 都没有。后果落在那几个**刻意保留的原生控件**上
   * （`.checkline` 的 `input[type=checkbox]` 是既有决策，不推翻；`type=date`
   * 与 `type=range` 同族）：暗色主题下浏览器仍按亮色画它们 —— 白底方块、
   * 浅灰边框、日期选择器整个是白的，长在 `#0D1422` 的底上。
   * `accent-color: var(--accent)` 只管**勾选态的填充**，管不到未勾选态的边框
   * 与弹出的原生日历。
   *
   * `color-scheme` 是这件事唯一的开关：它一并管好未勾选态边框、滚动条、
   * 以及 `type=date` 弹出的原生日历。挂在 `[data-theme]` 上而不是
   * `@media (prefers-color-scheme)`：产品的明暗由用户在顶栏切（P23-A-13 / D-6 裁决），
   * 系统偏好只是初始态的第二顺位（见 theme.ts）。
   */
  color-scheme: light;
}
[data-theme='dark'] {
  color-scheme: dark;
}
/*
 * ⭐ 亮岛也要把 `color-scheme` 翻回来（R2-01）。
 * 它与色值 token 是同一件事的两半：一棵 `data-theme="light"` 的子树若继承了
 * 外层的 `color-scheme: dark`，里面那几个刻意保留的原生控件（checkbox /
 * type=date）会在亮底上被浏览器按暗色画 —— 深底浅框长在白卡片里。
 */
[data-theme='light'] {
  color-scheme: light;
}
.page {
  padding: 20px 22px 56px;
  max-width: var(--page-max);
  margin: 0 auto;
}
.page-h {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 16px;
  flex-wrap: wrap;
}
.page-h h2 {
  font-size: 19px;
}
.page-h .sp {
  flex: 1;
}

/* 表格横向滚动：窄屏下页面本体绝不横向滚。 */
.data-wrap {
  overflow-x: auto;
}

/* ---------- 分页 ---------- */
.pager {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 2px 0;
  font-size: 13px;
  color: var(--text-2);
}
.pager .sp {
  flex: 1;
}

/* ---------- 二次确认弹窗 ---------- */
.confirm {
  width: min(460px, calc(100vw - 32px));
  padding: 22px;
  position: fixed;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  z-index: 51;
}
.confirm h3 {
  font-size: 15.5px;
  margin-bottom: 8px;
}
.confirm p {
  margin: 0 0 18px;
  color: var(--text-2);
  font-size: 13.5px;
  line-height: 1.6;
  /*
   * ⚠️ 确认文案里的换行要**看得见**（HF-6）。
   * `description` 是一个字符串、渲染进单个 <p>，于是不加这一行时 `\n\n` 会被
   * HTML 折成一个空格 —— 一段本来分了三节的后果说明会挤成一坨没人读的墙。
   * `pre-line` 只保留换行、照常折叠多余空格 ⇒ 既有的单行文案渲染**逐像素不变**。
   */
  white-space: pre-line;
}
.confirm .acts {
  display: flex;
  gap: 8px;
  justify-content: flex-end;
}
/* 危险操作用语义色 + 明确文案双编码，不只靠红色。 */
.btn.danger {
  background: var(--bad-bg);
  border-color: var(--bad-bg);
  color: var(--bad);
  font-weight: 700;
}
.btn.danger:hover {
  background: var(--bad);
  border-color: var(--bad);
  color: var(--surface);
}

/* ---------- 审计明细 ---------- */
.detail-json {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--text-2);
  background: var(--shell-2);
  border-radius: var(--r-md);
  padding: 10px 12px;
  margin: 0;
  white-space: pre-wrap;
  word-break: break-word;
  max-height: 220px;
  overflow: auto;
}

/* ---------- 权限位清单 ---------- */
.perm-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

/* ---------- 宽抽屉（元数据目录的字段表）---------- */
/*
 * W2 的 `.drawer` 固定 380px，那是为"改几个字段"的表单设计的。
 * 实体的字段表有 5 列（标签 / 技术名 / 类型 / 约束 / 取值），380px 下右侧两列
 * 会被**裁在视口外**——它不是"挤一点"，而是那两列的按钮点不到（Playwright 实测：
 * `picklist-*` 按钮 30s 都等不到可点击）。真实用户遇到的是同一件事，只是不会有人报障。
 *
 * 因此这一页用宽抽屉，并让内容区自己滚（横向由 `.data-wrap` 兜）。
 * 一个色值都不写——宽度与溢出不属于主题（铁律 6 的闸只管颜色，但纪律是整条）。
 */
.drawer.wide {
  width: min(920px, calc(100vw - 32px));
}
.drawer .card-b {
  overflow: auto;
  max-height: calc(100vh - 64px);
}

/* ---------- 透传降级横幅（Phase 4，R1 N-7 的消费端）---------- */
/*
 * 语义**双编码**：不只靠颜色——一个感叹号角标、一句"读取受限"、一个原因码。
 * 只用 W2 的语义 token（`--warn` / `--warn-bg`），一个色值都不写。
 *
 * 对比度：`--warn` 在浅色主题下是 #9a5b00、暗色下是 #f0b45c，
 * 两者对各自的 `--warn-bg` 与页面底色都 ≥ 4.5:1（e2e 里有一条实测断言）。
 */
.pt-banner {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  margin: 16px auto 0;
  padding: 12px 14px;
  max-width: 1280px;
  border: 1px solid var(--warn);
  border-radius: var(--r-md);
  background: var(--warn-bg);
  color: var(--text);
  font-size: 13px;
  line-height: 1.55;
}
.pt-banner-tag {
  flex: none;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-weight: 800;
  font-size: 13px;
  background: var(--warn);
  color: var(--surface);
}
.pt-banner-body {
  display: flex;
  flex-direction: column;
  gap: 2px;
  flex: 1;
  min-width: 0;
}
.pt-banner-body strong {
  font-size: 13px;
  font-weight: 700;
}
.pt-banner-code {
  flex: none;
  font-family: var(--font-mono);
  font-size: 11.5px;
  color: var(--text-2);
  align-self: center;
  white-space: nowrap;
}

/* ===========================================================================
 * 顶栏窄屏收敛 —— Phase 5 挪来的必修：**320px 视口下顶栏横向溢出 15px**
 *
 * ## 越界的是谁
 *
 * `.tbar` 是一条不换行的 flex：品牌锚 + 导航（最多 9 个链接）+ 弹性空隙 +
 * 语言切换 + 租户徽章 + 用户名 + 退出按钮。flex 项的默认 `min-inline-size: auto`
 * 意味着**它们不会缩到内容宽度以下**，于是在 320px 下总宽度必然超过视口，
 * 而 `.tbar` 自己是 sticky 的 —— 溢出的那一截把整页的横向滚动条也带了出来。
 *
 * Phase 5 的溢出断言收在 `.dashboard` 子树内是对的：它守住了自己该守的、
 * 没替别人的锅背断言。本 Phase 修的是顶栏这一处，断言随之放开到**整页**。
 *
 * ## 三件事一起，缺一条都不够
 *
 * ① **导航自己可横向滚动**（`min-inline-size:0` + `overflow-x:auto`）——
 *    只加 `flex-shrink` 会把链接文字压成竖排；能滚动才是"窄屏下仍然可达"。
 * ② **徽章与用户名可截断**——它们的内容长度由数据决定（租户名可以很长），
 *    不给上限的话，一个长租户名会把上面那条努力全部吃掉。
 * ③ **窄屏下用户名整条让位**——保留租户徽章而不是用户名：
 *    "我在哪个租户"比"我是谁"更要紧（前者决定看到的是谁家的数据）。
 *
 * ## 逻辑属性（SPEC-06 §0：Phase 6 起布局 CSS 用 logical properties）
 *
 * 本段一律 `inline-size` / `padding-inline` / `margin-inline`——RTL 的零成本预留。
 * =========================================================================== */
.tbar {
  /* 顶栏自己也不许溢出：越界的那一截会把整页横向滚动条带出来。 */
  max-inline-size: 100%;
  overflow-x: clip;
}
/*
 * ⚠️ Phase 23 起顶栏里**没有导航链接了**（P23-B-05：一级裸导航 <a> 数量 = 0）。
 * 原本的 `.tbar .app-nav`（平铺 20 条 NavLink 的那一条）已随模块下拉退场；
 * 它的三件事（可横向滚 / 不传手势 / 不换行）搬到了 `.app-subnav` 上——
 * 子导航现在是 `<header>` 的兄弟节点，W2 的 `.subnav{overflow-x:auto}` 直接生效，
 * 这里只补 W2 没给的那两条。
 */
.app-subnav {
  /* 横向滚到尽头时不要把手势传给页面（否则窄屏上会误触发返回）。 */
  overscroll-behavior-inline: contain;
  scrollbar-width: none;
}
.app-subnav::-webkit-scrollbar {
  display: none;
}
/*
 * 模块按钮：W2 的 `.modbtn` 焊了 `min-width: 230px`（那是给"当前模块 + 一行说明"
 * 那种双行按钮留的）。这里只有一个模块名，230px 会在 768px 视口下把工具区挤没。
 * `core.css` 的 `@media (max-width:768px)` 已经把它清零，这里把桌面档也收窄。
 */
.tbar .modbtn {
  min-inline-size: 0;
  flex: none;
  gap: 8px;
}
.tbar .modbtn-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* 子导航里的「更多」折叠入口（P23-B-04：条目数 ≤ 6 的实现侧）。 */
.subnav-more {
  padding-inline: 14px;
  block-size: var(--subnav-h);
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border: 0;
  background: transparent;
  color: var(--text-2);
  font-family: inherit;
  font-size: 13.5px;
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
  border-block-end: 2px solid transparent;
}
.subnav-more:hover {
  color: var(--text);
}
.subnav-more:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--accent-ring);
}
/* 菜单里当前所在的那一项：形状（左侧色条）+ 颜色，不靠纯色单编码。 */
.menu .mi.is-current {
  color: var(--accent-700);
  font-weight: 600;
}
/* `<button>` 当菜单项：清掉浏览器默认外观，形状全走 `.mi`。 */
.menu .mi-btn {
  inline-size: 100%;
  border: 0;
  background: transparent;
  font-family: inherit;
  font-size: 13px;
  text-align: start;
}
.menu .mi:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--accent-ring);
}
.tbar .sp {
  /* 空隙可以被压到 0：它是"把右侧推到底"的手段，不是一段必须存在的宽度。 */
  min-inline-size: 0;
}
.tbar .user-name {
  max-inline-size: 22ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.tbar .btn.sm {
  flex: none;
}

/* 语言切换：顶栏里的下拉要短，且不参与挤压。 */
.locale-switch {
  flex: none;
  display: inline-flex;
  align-items: center;
}
.locale-switch .w2-select {
  inline-size: auto;
  min-inline-size: 92px;
  block-size: 32px;
  padding-inline: 8px;
  font-size: 12.5px;
}

/* 租户徽章（SPEC-04 §1：[客户logo 客户名 · 环境 ●]）。 */
.tenant-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-inline-size: 0;
  max-inline-size: 34ch;
}
.tenant-badge .tenant-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.tenant-logo {
  block-size: 18px;
  inline-size: auto;
  max-inline-size: 72px;
  object-fit: contain;
  flex: none;
}
/*
 * 环境标 —— **语义双编码**（W2 红线）：圆点给颜色，文字给人读。
 * 只用语义 token，四种环境各取一个已有的语义色，不新增色值。
 */
.env-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 11.5px;
  font-weight: 600;
  color: var(--text-2);
  white-space: nowrap;
}
.env-dot {
  inline-size: 8px;
  block-size: 8px;
  border-radius: 50%;
  flex: none;
  background: var(--neutral);
}
.env-chip[data-env='production'] .env-dot {
  background: var(--bad);
}
.env-chip[data-env='uat'] .env-dot {
  background: var(--warn);
}
.env-chip[data-env='sandbox'] .env-dot {
  background: var(--ok);
}
.env-chip[data-env='dev'] .env-dot {
  background: var(--accent);
}

/* 窄屏：用户名让位，顶栏内边距与间距收紧。 */
@media (max-width: 720px) {
  .tbar {
    padding-inline: 12px;
    gap: 8px;
  }
  .tbar .user-name {
    display: none;
  }
  .tenant-badge {
    max-inline-size: 16ch;
  }
}
@media (max-width: 420px) {
  .tbar {
    padding-inline: 8px;
    gap: 6px;
  }
  .tbar .bd {
    font-size: 13px;
  }
  .tbar .bd .logo {
    inline-size: 24px;
    block-size: 24px;
  }
  .locale-switch .w2-select {
    min-inline-size: 68px;
  }
  .tenant-badge .tenant-name {
    max-inline-size: 8ch;
  }
}

/* 只给读屏用的文字（语言切换器的 label）。 */
.sr-only {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* =========================================================================
 * 登录页 · 两栏（基准 docs/ui-prototypes/login.html，Phase 23 轮 B · P23-A）
 *
 * 一个色值都不写：品牌区的网格与光晕、渐变文字、活图卡的系列色，全部走 W2 token
 * （P23-A-02 明写「所有色值来自 token，无 hex 字面量」）。品牌 logo 的颜色在
 * `assets/brand/*.svg` **文件内部**——那是资产本体，不是样式。
 * ========================================================================= */
.login-split {
  display: flex;
  min-block-size: 100vh;
  background: var(--bg-app);
}

/* ---------- 左：品牌区 ---------- */
/*
 * 背景四层（P23-A-02）：两处 radial 光晕（accent / AI 紫）+ 两条 30px 网格线 + 底色。
 * 网格用 `--hairline-2`：它在明暗两套 token 里各自是低透明度的深/浅色，
 * 因此**同一段声明**在两个主题下都成立，不需要 `[data-theme] .login-brand-pane` 覆写。
 */
.login-brand-pane {
  position: relative;
  flex: 1.2;
  min-inline-size: 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  padding: 44px clamp(28px, 3.4vw, 52px);
  background:
    radial-gradient(900px 520px at 12% -8%, var(--accent-soft), transparent 55%),
    radial-gradient(700px 480px at 105% 108%, var(--cat-ai-bg), transparent 55%),
    linear-gradient(var(--hairline-2) 1px, transparent 1px),
    linear-gradient(90deg, var(--hairline-2) 1px, transparent 1px), var(--shell);
  background-size:
    auto,
    auto,
    30px 30px,
    30px 30px,
    auto;
}

/* netech 公司标：图形 N + 文字 ETECH + 中文行（P23-A-03 / A-04）。 */
.netech-lockup {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: flex-start;
  gap: 6px;
}
.netech-mark {
  display: block;
  inline-size: 42px;
  block-size: 38px;
  margin-block-start: 1px;
}
.netech-word {
  display: flex;
  flex-direction: column;
  line-height: 1;
}
.netech-en {
  font-size: 26px;
  font-weight: 800;
  font-style: italic;
  letter-spacing: 0.5px;
  background: linear-gradient(95deg, var(--accent) 0%, var(--accent-700) 100%);
  background-clip: text;
  -webkit-background-clip: text;
  color: transparent;
}
.netech-cn {
  margin-block-start: 5px;
  font-size: 12.5px;
  font-weight: 600;
  /* P23-A-04：≥ 0.4em。它是这个标的识别特征之一，不是随手排的。 */
  letter-spacing: 0.42em;
  color: var(--accent-700);
}

/* 品牌宣言段（P23-A-05..A-08）。 */
.login-hero {
  position: relative;
  z-index: 1;
  max-inline-size: 560px;
  margin-block-start: 14vh;
}
.login-product {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-block-end: 10px;
}
.dalton-mark {
  display: block;
  /* P23-A-05：完整版原子 D，渲染尺寸 ≥ 52px。 */
  inline-size: 62px;
  block-size: 62px;
  flex-shrink: 0;
  filter: drop-shadow(0 7px 22px var(--accent-ring));
}
.login-product-name {
  margin: 0;
  font-size: 36px;
  font-weight: 800;
  letter-spacing: -0.025em;
  line-height: 1.1;
}
.login-product-by {
  margin: 2px 0 0;
  font-size: 12px;
  color: var(--text-3);
}
/* 渐变文字：主标题的高亮词与产品名的 AI 共用一种做法（P23-A-06）。 */
.grad {
  background: linear-gradient(95deg, var(--accent) 10%, var(--cat-ai) 90%);
  background-clip: text;
  -webkit-background-clip: text;
  color: transparent;
}
.login-hero-title {
  margin: 26px 0 18px;
  font-size: clamp(28px, 2.9vw, 46px);
  font-weight: 800;
  letter-spacing: -0.035em;
  line-height: 1.18;
  color: var(--text);
  /*
   * ⚠️ 基准用的是 `white-space: nowrap`（中文八个字放得下）。这里**不照抄**：
   * 英文那句 `Your data starts to think.` 在 46px 下超过 550px，
   * 窄一点的品牌区会被它顶出横向滚动条 —— 而 P23-H-05 是三断点整页零溢出。
   * 换行不改变 A-06 的判据（文案与渐变词），照抄会打掉 H-05。
   */
  text-wrap: balance;
}
.login-hero-sub {
  margin: 0 0 14px;
  font-size: 14.5px;
  font-weight: 600;
  /* P23-A-07：≥ 0.3em。 */
  letter-spacing: 0.35em;
  color: var(--text-2);
}
.login-quote {
  margin: 0;
  font-size: 13px;
  font-style: italic;
  color: var(--text-3);
}

/* ---------- 左：六张活图卡（P23-A-09..A-11） ---------- */
/*
 * 绝对定位 + 轻微旋转的"浮卡阵"，位置逐条对着基准原型的百分比。
 * ⚠️ 它们**只在宽屏出现**：≤1100px 整组隐藏（A-24，品牌宣言段仍在），
 * 中间档（≤1380px）先撤掉三张大的——这不是偷懒，六张卡挤在 700px 里会互相压。
 */
.login-deco-row > * {
  position: absolute;
  z-index: 1;
}
.deco-card {
  padding: 12px 14px;
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--r-lg);
  box-shadow: var(--e2);
}
.deco-t {
  margin: 0;
  font-size: 11.5px;
  color: var(--text-3);
}
.deco-v {
  display: flex;
  align-items: baseline;
  gap: 6px;
  margin: 2px 0 0;
  font-size: 20px;
  font-weight: 750;
  letter-spacing: -0.02em;
}
/*
 * 数字一律 tabular-nums（P23-H-24）。**不用 `.num`**：那个类连字体一起换成 mono，
 * 而这里的数字是品牌区的大字，基准原型用的是正文 sans。
 * 判据是"等宽数位"，不是"等宽字体"——两者在这一格上不是同一件事。
 */
.deco-num {
  font-variant-numeric: tabular-nums;
}
.deco-up,
.deco-ok {
  font-size: 11px;
  font-weight: 600;
  color: var(--ok);
}

/* ① 折线卡 */
.deco-line {
  inset-block-start: 21%;
  inset-inline-end: 6%;
  inline-size: 310px;
  transform: rotate(2deg);
}
.deco-area {
  stroke: none;
}
.stop-c1-25 {
  stop-color: var(--c1);
  stop-opacity: 0.25;
}
.stop-c1-02 {
  stop-color: var(--c1);
  stop-opacity: 0.02;
}
.deco-stroke-c1 {
  stroke: var(--c1);
  fill: none;
}
.deco-stroke-c4 {
  stroke: var(--c4);
  fill: none;
  opacity: 0.85;
}
.deco-dot-c1 {
  fill: var(--c1);
  stroke: var(--surface);
}
.deco-grid {
  stroke: var(--hairline);
}

/* ② 双色柱卡 */
.deco-flow {
  inset-block-end: 9%;
  inset-inline-start: 33%;
  inline-size: 260px;
  transform: rotate(-3deg);
}
.deco-bars2 {
  display: flex;
  align-items: flex-end;
  gap: 6px;
  block-size: 44px;
  margin-block-start: 10px;
}
.deco-bars2 > span {
  display: flex;
  flex: 1;
  align-items: flex-end;
  gap: 2.5px;
  /*
   * ⚠️ `block-size: 100%` **不能省**：里面那两根柱子的高度是百分比，
   * 而百分比要有一个**确定**的参照高度才解析得出来。基准原型这一格没写，
   * 于是它的双色柱实际画出来是 0 高——照抄的话产品上就是一张空卡片
   * （本轮实测：第一版取证截图里"入职 vs 离职"那张卡下半截是空白）。
   */
  block-size: 100%;
}
.deco-bars2 i {
  flex: 1;
  border-radius: 3px 3px 1px 1px;
  transition: block-size 0.65s var(--ease);
}
.deco-bars2 .bar-in {
  background: var(--c2);
  opacity: 0.8;
}
.deco-bars2 .bar-out {
  /* 离职那一根走 `--c6`（玫红）——基准原型用的就是这个色位。 */
  background: var(--c6);
  opacity: 0.75;
}

/* ③ 宽幅趋势卡 */
.deco-hero-chart {
  inset-block-start: 55%;
  inset-inline-start: 6%;
  inline-size: 48%;
  min-inline-size: 420px;
  padding: 15px 18px 10px;
}
.deco-hc-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-block-end: 8px;
}
.deco-hc-legend {
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: 10.5px;
  color: var(--text-2);
}
.deco-hc-legend i {
  inline-size: 9px;
  block-size: 9px;
  border-radius: 3px;
}
.deco-hc-legend i + * {
  margin-inline-end: 4px;
}
.swatch-c1 {
  background: var(--c1);
}
.swatch-c4 {
  background: var(--c4);
}

/* ④ 环形卡 */
.deco-donut {
  inset-block-start: 42%;
  inset-inline-end: 6%;
  min-inline-size: 310px;
  display: flex;
  align-items: center;
  gap: 12px;
  transform: rotate(-4deg);
  box-shadow: var(--e3);
}
.deco-donut svg {
  inline-size: 76px;
  block-size: 76px;
  flex-shrink: 0;
}
.deco-donut-spin {
  transform-origin: 32px 32px;
  animation: dalton-donut-spin 9s linear infinite;
}
@keyframes dalton-donut-spin {
  to {
    transform: rotate(360deg);
  }
}
.donut-seg {
  transition:
    stroke-dasharray 0.7s var(--ease),
    stroke-dashoffset 0.7s var(--ease);
}
.donut-seg-1 {
  stroke: var(--c1);
}
.donut-seg-2 {
  stroke: var(--c2);
}
.donut-seg-3 {
  stroke: var(--cat-ai);
}
.donut-seg-4 {
  stroke: var(--c4);
}
.deco-donut-legend {
  display: flex;
  flex-direction: column;
  gap: 5px;
  font-size: 11.5px;
  color: var(--text-2);
}
.deco-donut-legend i {
  display: inline-block;
  inline-size: 8px;
  block-size: 8px;
  border-radius: 2.5px;
  margin-inline-end: 4px;
  vertical-align: -1px;
}
.donut-swatch-1 {
  background: var(--c1);
}
.donut-swatch-2 {
  background: var(--c2);
}
.donut-swatch-3 {
  background: var(--cat-ai);
}
.donut-swatch-4 {
  background: var(--c4);
}

/* ⑤ 柱状卡 */
.deco-bars {
  inset-block-end: 9%;
  inset-inline-start: 6%;
  inline-size: 250px;
  transform: rotate(-3deg);
  box-shadow: var(--e3);
}
.deco-bars1 {
  display: flex;
  align-items: flex-end;
  gap: 5px;
  block-size: 44px;
  margin-block-start: 10px;
}
.deco-bars1 i {
  flex: 1;
  border-radius: 3px 3px 1px 1px;
  background: var(--c1);
  opacity: 0.85;
  transition: block-size 0.65s var(--ease);
}
.deco-bars1 i:nth-child(4) {
  background: var(--c4);
}

/*
 * ⑥ 洞察卡：**渐变描边**（P23-A-11）——外层 1.5px padding 铺渐变、内层填 `--surface`。
 * 不是 `border`：CSS 的 border 吃不下渐变（`border-image` 会把圆角切方），
 * 而这条描边的圆角是它读起来"像一张卡"的全部理由。
 */
.deco-insight {
  inset-block-end: 13.5%;
  inset-inline-end: 6%;
  inline-size: 310px;
  padding: 1.5px;
  border-radius: var(--r-lg);
  background: linear-gradient(130deg, var(--cat-ai), var(--accent) 70%, transparent);
}
.deco-insight-body {
  display: flex;
  flex-direction: column;
  gap: 7px;
  block-size: 100%;
  padding: 12px 14px;
  border-radius: calc(var(--r-lg) - 1.5px);
  background: var(--surface);
}
.deco-insight-t {
  display: flex;
  align-items: center;
  gap: 5px;
  margin: 0;
  font-size: 11px;
  font-weight: 700;
  color: var(--cat-ai);
}
.deco-insight-t::before {
  content: '';
  inline-size: 6px;
  block-size: 6px;
  border-radius: 50%;
  background: var(--cat-ai);
  animation: dalton-deco-breathe 2.2s var(--ease) infinite;
}
@keyframes dalton-deco-breathe {
  0%,
  100% {
    opacity: 0.35;
    transform: scale(0.8);
  }
  50% {
    opacity: 1;
    transform: scale(1);
  }
}
.deco-insight-p {
  margin: 0;
  font-size: 12px;
  line-height: 1.5;
  color: var(--text-2);
}

/* 卡片浮动。H-20 把「登录页活图卡」列为 infinite 动画的专属白名单。 */
.login-deco-row > * {
  animation: dalton-deco-float 2.8s ease-in-out infinite;
}
.login-deco-row > *:nth-child(2n) {
  animation-duration: 3.6s;
  animation-delay: 0.8s;
}
.login-deco-row > *:nth-child(3n) {
  animation-duration: 4.2s;
  animation-delay: 1.5s;
}
.login-deco-row > *:nth-child(5n) {
  animation-duration: 3.2s;
  animation-delay: 0.4s;
}
@keyframes dalton-deco-float {
  0%,
  100% {
    translate: 0 0;
  }
  50% {
    translate: 0 -18px;
  }
}

/*
 * 卡阵的两道断点（P23-A-24）。判据是**视口**宽度，与基准原型逐条同值：
 *   · ≤1380px 先撤三张大的（宽幅趋势 / 双色柱 / 洞察）——右栏吃掉 460px 之后，
 *     品牌区不到 920px，六张卡会互相压；
 *   · ≤1100px 整组连同那条读屏说明一起撤，**品牌宣言段仍在**（A-24 原文）。
 */
@media (max-width: 1380px) {
  .deco-hero-chart,
  .deco-flow,
  .deco-insight {
    display: none;
  }
}
@media (max-width: 1100px) {
  .login-deco-row,
  .login-deco-note {
    display: none;
  }
}

/*
 * ⭐⭐ `reduce`：**把动画本身关掉**，不是把它跑快（P23-A-20 / H-04）。
 *
 * W2 基座的全局 reduce 规则写的是 `animation-duration: .001ms !important` ——
 * 那让动画瞬间跑完，而 `document.getAnimations()` 里它**仍然在**（`finished`）。
 * A-20 的判据是"长度为 0 或全部 paused"，于是这里显式关 `animation-name`
 * （那条 `!important` 只压 duration，压不到 name）。
 * SMIL 那一层由 `login-deco.tsx` 的 `pauseAnimations()` 停，两层缺一不可。
 */
@media (prefers-reduced-motion: reduce) {
  .login-deco-row > *,
  .deco-donut-spin,
  .deco-insight-t::before {
    animation: none;
  }
  .deco-bars1 i,
  .deco-bars2 i,
  .donut-seg {
    transition: none;
  }
}

/* 版权行（P23-A-18 的"新增版权行"那一半）。 */
.login-copyright {
  position: relative;
  z-index: 1;
  margin: auto 0 0;
  font-size: 12px;
  color: var(--text-3);
}

/* ---------- 右：登录卡 ---------- */
.login-form-pane {
  display: flex;
  flex-direction: column;
  inline-size: clamp(360px, 36vw, 460px);
  min-inline-size: 360px;
  padding: 22px clamp(24px, 3vw, 44px) 26px;
  background: var(--bg-app);
  border-inline-start: 1px solid var(--hairline);
  overflow-y: auto;
}
.login-top-actions {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  gap: 8px;
  margin-block-end: 8vh;
}
.login-form {
  inline-size: 100%;
}
.login-title {
  margin: 0 0 4px;
  font-size: 22px;
  font-weight: 700;
  letter-spacing: -0.01em;
}
.login-sub {
  margin: 0 0 22px;
  font-size: 13px;
  color: var(--text-3);
}
/*
 * ⭐ 主按钮是**琥珀**而不是 accent 蓝（P23-A-16）。
 * 它不复用 `.btn.primary`：那是全站的次高权重动作色，而登录页只有一个动作。
 * 文字色走 `--btn-primary-text`（琥珀上的深棕，token 表注明 AA ≥ 6:1）。
 */
.btn-primary.login-submit {
  inline-size: 100%;
  margin-block-start: 4px;
  padding: 11px;
  border: 0;
  border-radius: var(--r-md);
  background: var(--btn-primary);
  color: var(--btn-primary-text);
  font-family: inherit;
  font-size: 14px;
  font-weight: 700;
  cursor: pointer;
  transition: background 0.16s var(--ease);
}
.btn-primary.login-submit:hover:not(:disabled) {
  background: var(--btn-primary-hover);
}
.btn-primary.login-submit:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}

/* 登录页：安全提示与页脚（品牌行不可关闭，SPEC-04 §1 / P23-H-12）。 */
.login-security {
  margin: 0;
  font-size: 12px;
  line-height: 1.5;
}
.login-foot {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-block-start: auto;
  padding-block-start: 24px;
  font-size: 11.5px;
  text-align: center;
}
/* Powered by：`--text-2` 而不是 `--text-3`，理由在 LoginPage 那一行的注释里。 */
.login-powered {
  color: var(--text-2);
}

/* ---------- 登录页 · 断点（P23-A-23 / A-24 / H-05） ---------- */
@media (max-width: 1000px) {
  .login-brand-pane {
    padding: 32px 30px;
  }
  .login-product-name {
    font-size: 22px;
  }
}

/*
 * ≤700px：两栏堆叠成单列（品牌区在上、登录卡在下），页面可纵向滚动。
 * ⚠️ 品牌宣言段**不撤**（A-24 只让活图卡撤）——撤掉宣言的手机版会变成
 * 一张裸表单，那正是这一轮要改掉的东西。
 */
@media (max-width: 700px) {
  .login-split {
    flex-direction: column;
  }
  .login-brand-pane {
    flex: none;
    padding: 26px 28px 24px;
  }
  .login-hero {
    margin-block-start: 0;
    max-inline-size: none;
  }
  .netech-lockup {
    margin-block-end: 14px;
  }
  .dalton-mark {
    inline-size: 52px;
    block-size: 52px;
  }
  .login-product-name {
    font-size: 26px;
  }
  .login-hero-title {
    margin: 14px 0 12px;
    font-size: clamp(24px, 7vw, 34px);
  }
  .login-hero-sub {
    margin-block-end: 6px;
    font-size: 12px;
    /*
     * ⚠️ 基准在这一档降到 `.25em`，这里**不照抄**：A-07 的断言是
     * 「computed letter-spacing ≥ 0.3em 等效值」，而一条只在宽屏成立的不变量
     * 是最难发现的那一种（谁会在 375px 上量字距）。
     */
    letter-spacing: 0.3em;
  }
  /* 引言在窄屏撤掉（基准同款）：它是宣言的注脚，不是宣言本身。 */
  .login-quote {
    display: none;
  }
  .login-copyright {
    margin-block-start: 18px;
  }
  .login-form-pane {
    inline-size: 100%;
    /* ⚠️ 360 的下限必须一起松开，否则 320px 视口下整页横向溢出 40px。 */
    min-inline-size: 0;
    border-inline-start: none;
    border-block-start: 1px solid var(--hairline);
    padding-block-start: 14px;
  }
  .login-top-actions {
    margin-block-end: 16px;
  }
  .login-foot {
    padding-block-start: 18px;
  }
}

/*
 * ≤380px：只收内边距与产品名。
 * ⚠️ **Dalton 标不再往下收**：A-05 的下限是 52px（`brand.html` 方案 A 的规格），
 * 而一个"只在宽屏满足的规格"等于没有规格 —— 320px 下 52px + 14px 间距 + 产品名
 * 一共 166px，离 284px 的可用宽还远，收它没有任何收益。
 */
@media (max-width: 380px) {
  .login-brand-pane {
    padding: 22px 18px 20px;
  }
  .login-form-pane {
    padding-inline: 18px;
  }
  .login-product-name {
    font-size: 22px;
  }
}

/* ---------- 分享 / 导出（Phase 8）---------- */
/*
 * 一个色值都不写：全部走 W2 语义 token（`--info-*` / `--bad-*` / `--warn-*`），
 * 与降级横幅是同一套。对比度由 `e2e/setup/contrast.ts` 那条实测断言看着。
 */

/*
 * 「报表可见 ≠ 数据可见」那句话。它是本 Phase 的中心语义，因此在视觉上
 * **不是一句 faint 小字**——一条被排版成脚注的规则，读的人会跳过它。
 */
.share-notice {
  margin: 0 0 14px;
  padding: 10px 12px;
  border-radius: var(--r-md);
  background: var(--info-bg);
  color: var(--info);
  border: 1px solid var(--info-bg);
  font-size: 12.5px;
  line-height: 1.6;
}

/*
 * ⚠️ 工具条里的输入框**不能吃满整行**。
 *
 * `.inp` 是 `width: 100%`（管理页的表单需要它），而 `.toolbar` 是
 * `flex-wrap: wrap` —— 两条各自正确的规则凑在一起，结果是新增分享那一排里
 * 「类型 / 搜索 / 被分享人」各占一整行，一个三段的表单被拉成三屏高。
 * 这里只在受管容器里把它改回"按内容伸缩"，不动 `.inp` 本身。
 */
.share-add .toolbar > .inp,
.export-panel-tools > .inp {
  inline-size: auto;
  flex: 1 1 180px;
  min-inline-size: 140px;
}

/* ---------- 页面工具条：独立的那一条（HF-4） ---------- */
/*
 * ⚠️ **`.toolbar` 生来是"表格的帽子"**（w2/core.css 里它就写在「数据表」那一节下）：
 * `border-bottom: 0` + 只有上圆角，靠紧跟其后的 `.data-wrap` 把盒子封口。
 * 全仓 14 处 `.toolbar` 里有 13 处正是这个用法。
 *
 * 证书页那一条底下接的是**卡片**不是表格 —— 直接套 `.toolbar` 会得到一个
 * 缺了底边的半截框。`.lone` 只做"把盒子封上"这一件事。
 *
 * 它顺带是 HF-4 那个排版 bug 的正解：那一条此前写的是 `.tbar`（W2 的**顶栏**，
 * `height: var(--topbar-h)` 焊死 56px + `position: sticky; z-index: 30`），
 * 于是 65.7px 的内容在 56px 的盒子里上下溢出，与上方提示框实测重叠 4.3px。
 * 一道机器闸（`apps/web/test/app-bar-class-scope.test.ts`）从此盯着
 * "`.tbar` 只许出现在 AppShell 里"——写在注释里的规矩，下一个照着抄的人抄不到。
 */
.toolbar.lone {
  border-block-end: 1px solid var(--hairline);
  border-radius: var(--r-lg);
  margin-block-end: var(--sp-3);
}

/*
 * 工具条里的**带标签**字段。`.field` 自带 `margin-bottom: 14px`（它生来是竖排
 * 表单里的一行），塞进一条工具条就变成盒子底部一段无来由的空白 —— 而那段空白
 * 恰好会让"工具条比看起来矮"这类误判再来一次。
 */
.toolbar > .field {
  margin-block-end: 0;
}

/* ---------- SF 连接管理（HF-4） ---------- */
/*
 * 表单按钮行。`.acts` 在 W2 里**只有 `.confirm .acts` 一条定义**——
 * 也就是说表单里那个 `.acts` 一直是个没有样式的 div（按钮贴在一起）。
 * 这里只在本页作用域内补上，不去动全局的 `.acts`：
 * 那会顺手改掉别的页面的观感，而那不是本次 Hotfix 的范围。
 */
.conn-form .acts {
  display: flex;
  gap: var(--sp-2);
  margin-block-start: var(--sp-4);
}

/*
 * 校验结果**逐条列出来**，不是一句"表单有误"。
 * 每一条说的都是"为什么不行"，因此走 `.err`（`--bad`）而不是弱化类。
 */
.conn-problems {
  list-style: none;
  margin: var(--sp-3) 0 0;
  padding: var(--sp-2) var(--sp-3);
  border: 1px solid var(--bad-bg);
  border-radius: var(--r-md);
  background: var(--bad-bg);
}
.conn-problems li + li {
  margin-block-start: var(--sp-2);
}
.conn-problems .err {
  line-height: 1.6;
}

/*
 * 就绪度里那张「还差什么」的清单（HF-4）。
 *
 * ⚠️ 只去掉列表标记与外边距，**不设颜色** —— 每一条的颜色由它自己的
 * `.cell-reason` / `.action-reason`（`--text-2`，AA 达标）决定。
 * 在这里写死一个颜色等于给关键文本开一条绕过 KI-60 那道闸的后门。
 */
.conn-blockers {
  list-style: none;
  margin: var(--sp-2) 0 0;
  padding: 0;
}
.conn-blockers li + li {
  margin-block-start: var(--sp-2);
}
.conn-blockers li {
  line-height: 1.6;
}

.share-add,
.share-transfer {
  margin-block-start: 20px;
  padding-block-start: 14px;
  border-block-start: 1px solid var(--hairline);
}
.share-add h4,
.share-transfer h4 {
  font-size: 13.5px;
  margin-block-end: 10px;
}
/* 移交是不可自撤的动作：入口区整体用危险语义打底，而不只是按钮红一下。 */
.share-transfer p {
  margin: 0 0 10px;
  font-size: 12.5px;
  line-height: 1.6;
}

/*
 * 导出结果条。三种状态各有**不同的处置**，因此三种底色也不同：
 *   · succeeded —— 好；
 *   · running   —— 台账还没收单（流可能还在走），要人再查一次；
 *   · failed    —— 文件已经下下去了但它是残的（契约 ㉔ 的那一类）。
 */
.export-outcome {
  margin-block-start: 12px;
  padding: 10px 12px;
  border-radius: var(--r-md);
  font-size: 12.5px;
  line-height: 1.6;
  background: var(--shell-2);
  color: var(--text-2);
}
.export-outcome.ok {
  background: var(--ok-bg);
  color: var(--ok);
}
.export-outcome.warn {
  background: var(--warn-bg);
  color: var(--warn);
}
.export-outcome.bad {
  background: var(--bad-bg);
  color: var(--bad);
}
.export-outcome code {
  font-family: var(--font-mono);
  font-size: 11.5px;
  word-break: break-all;
}

/* ---------- 禁用态的按钮（Phase 8 实现轮 C）---------- */
/*
 * ⚠️ **W2 里一条 `.btn:disabled` 都没有**，而 `.btn` 自己写死了 `background`
 * 与 `color` —— 浏览器对一个已经被作者上过色的按钮**不会**再加默认的禁用观感。
 * 于是"禁用"这件事在此之前只有读屏器知道：屏幕上它与可点的按钮一模一样。
 *
 * 这一条是被本轮的浏览器证据顶出来的（`sharee-export-disabled.png` 第一版：
 * 四个灰按钮里有一个还是红的「删除」，看上去随时可以点）。
 * 判据 23 要的是"看得出来点不动 + 说得出为什么"，前半句在这里补上。
 *
 * 三重信号，不只靠颜色：**背景变平** + **文字变淡** + **光标 not-allowed**，
 * 并且**hover 不再变化**（一个还会响应 hover 的按钮读起来仍然是可点的）。
 *
 * 对比度：`--text-2` on `--shell-2` = 4.92:1（明）/ 7.48:1（暗），两侧都过 AA。
 * 刻意**不用** `--text-3`：那个 token 的注释原文写着"仅作占位/弱化,不承载关键文本"。
 */
.btn:disabled,
.btn:disabled:hover {
  background: var(--shell-2);
  border-color: var(--hairline);
  color: var(--text-2);
  box-shadow: none;
  cursor: not-allowed;
}

/*
 * ⭐⭐ **「为什么」那一句的唯一承载类**（R1 F-1 之后合并到这里）。
 *
 * ⚠️ 它**不是** `.faint`：那个类是 `--text-3`，而那个 token 自己的注释写着
 * "仅作占位/弱化,**不承载关键文本**"。这一句恰恰是关键文本 ——
 * **一个看不清的原因等于没有原因。**
 *
 * ## 为什么是**一条规则两个名字**，而不是再写一份
 *
 * R1 F-1 的要害不是那 2.41:1，是**这条规矩在本文件里已经写了三次**
 * （`:560` 的 share-notice 一段 / 这里 / `.schedule-warn` 一段），
 * 而投递台账上「这一封为什么没发出去」还是落进了 `.faint`。
 * ⇒ 再加一个第四份定义只会让第五次更容易发生。
 * `.cell-reason` 是**别名**：表格单元格里那一句（跳过原因 / 失败分类）与
 * 按钮旁边那一句是同一件事 —— **"这件事为什么是现在这个样子"**。
 *
 * 判据由 `apps/web/test/no-faint-key-text.test.ts` 看着：
 * `--text-3` 的那几个类**承载关键文本**时当场判红。
 */
.action-reason,
.cell-reason {
  color: var(--text-2);
  font-size: 12.5px;
  line-height: 1.5;
  max-inline-size: 42em;
}

/* ==========================================================================
 * 调度与订阅 / 投递台账 / 收件箱 / 运行监控（Phase 9 实现轮 C）
 *
 * ⚠️ 一律用 W2 token，没有一处字面色值（`no-hardcoded-color.test.ts` 扫这份文件）。
 * ========================================================================== */

/*
 * 成功提示。
 *
 * ⚠️ 这个类**此前只被引用、没有被定义**（`SettingsPage` 的保存提示挂着它）——
 * 也就是说"设置已保存"那句话一直是裸文本。本轮补上定义，两个消费方一起受益。
 * 语义色走 `--ok` / `--ok-bg`（W2 的双编码语义对，AA ≥ 4.5:1 是它自带的性质）。
 */
.ok-note {
  margin: 0 0 12px;
  padding: 10px 12px;
  border-radius: var(--r-md);
  background: var(--ok-bg);
  color: var(--ok);
  border: 1px solid var(--ok-bg);
  font-size: 12.5px;
  line-height: 1.6;
}

.schedule-list,
.schedule-subs,
.schedule-chosen {
  list-style: none;
  margin: 0;
  padding: 0;
}

.schedule-card {
  border: 1px solid var(--hairline);
  border-radius: var(--r-md);
  padding: var(--sp-3);
  margin-block-end: var(--sp-3);
  background: var(--surface);
}

.schedule-card-h {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2);
  margin-block-end: var(--sp-2);
}

/*
 * ⚠️ 危险选项的后果说明。
 *
 * 它**不是** `.faint`（`--text-3` 的注释原文：仅作占位/弱化，不承载关键文本）。
 * 这一段恰恰是判据 23 的全部落点：一段看不清的后果说明等于没有说明。
 * 走 `--warn` / `--warn-bg` 这对语义色，明暗两侧都是双编码的。
 */
.schedule-warn {
  margin: 0 0 var(--sp-2);
  padding: 10px 12px;
  border-radius: var(--r-md);
  background: var(--warn-bg);
  color: var(--warn);
  border: 1px solid var(--warn-bg);
  font-size: 12.5px;
  line-height: 1.6;
  max-inline-size: 52em;
}
.schedule-warn p {
  margin: 0 0 var(--sp-2);
}
.schedule-warn p:last-of-type {
  margin-block-end: var(--sp-1);
}

/*
 * ⭐ 「这份草稿是 AI 生成的，请逐格核对」（Phase 11 实现轮 C）。
 *
 * ⚠️ 与 `.share-notice`（info 蓝）**刻意不同色**：那一族说的是"这件事你该知道"
 * （数据去向、可见范围），而这一条说的是"**你现在要做一件事**"——核对。
 * 排成同一种蓝会让它混进背景里的告示条，而它恰恰是 KI-88 在界面上的落点：
 * 实例默认码值填错**不会报错**，只会安静地写到相邻那一行。
 * ⚠️ 色值一个都不写，走 `--warn` / `--warn-bg` 这对语义 token（明暗双编码，
 * 对比度由 `e2e/setup/contrast.ts` 那条实测断言看着）。
 */
.warn-note {
  margin: 0 0 12px;
  padding: 10px 12px;
  border-radius: var(--r-md);
  background: var(--warn-bg);
  color: var(--warn);
  border: 1px solid var(--warn-bg);
  font-size: 12.5px;
  line-height: 1.6;
  max-inline-size: 52em;
}

.schedule-facts {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: var(--sp-1) var(--sp-3);
  margin: 0 0 var(--sp-2);
  font-size: 12.5px;
}
.schedule-facts dt {
  color: var(--text-2);
}
.schedule-facts dd {
  margin: 0;
}

.schedule-sub-h {
  font-size: 13.5px;
  margin: var(--sp-3) 0 var(--sp-2);
}

.schedule-subs li,
.schedule-chosen li {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2);
  padding-block: var(--sp-1);
  border-block-start: 1px solid var(--hairline-2);
  font-size: 12.5px;
}

.schedule-form,
.schedule-actions {
  margin-block-start: var(--sp-5);
  padding-block-start: var(--sp-3);
  border-block-start: 1px solid var(--hairline);
}
.schedule-form h4 {
  font-size: 13.5px;
  margin-block-end: var(--sp-3);
}

/*
 * 视角模式那一组。`fieldset` 自带的边框与内边距在 W2 里显得很重，
 * 但**不能整个去掉边框**：它是"这几个单选属于同一个问题"的唯一视觉线索，
 * 而那正是这一格最容易被读成两个无关开关的地方。
 */
.schedule-mode {
  border: 1px solid var(--hairline);
  border-radius: var(--r-md);
  padding: var(--sp-3);
  margin-block: var(--sp-3);
}
.schedule-mode legend {
  padding-inline: var(--sp-2);
  font-size: 13px;
  color: var(--text);
}
.schedule-mode .checkline {
  align-items: flex-start;
  gap: var(--sp-2);
  margin-block-end: var(--sp-3);
  font-size: 12.5px;
  line-height: 1.6;
  max-inline-size: 52em;
}
.schedule-mode .checkline input {
  margin-block-start: 3px;
}

/* 监控卡片：每一项一格，"未测量"与 0 必须看得出区别（见 MonitoringPanel）。 */
.metric-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: var(--sp-3);
}
.metric-card {
  border: 1px solid var(--hairline);
  border-radius: var(--r-md);
  padding: var(--sp-3);
  background: var(--surface);
}
.metric-value {
  font-family: var(--font-mono);
  font-size: 20px;
  line-height: 1.3;
}
/* ⚠️ 未测量走 `--text-2` 而不是 `--text-3`：它是关键文本，不是占位。 */
.metric-unmeasured {
  color: var(--text-2);
  font-family: var(--font-sans);
  font-size: 14px;
}
.metric-help {
  color: var(--text-2);
  font-size: 12px;
  line-height: 1.5;
  margin: var(--sp-2) 0 0;
}
.metric-name {
  font-family: var(--font-mono);
  font-size: 11.5px;
  color: var(--text-2);
  /* 指标名很长且没有空格；`anywhere` 优先在已有断点处折，比 `break-all` 好读。 */
  overflow-wrap: anywhere;
}

/* 收件箱 */
.inbox-list {
  list-style: none;
  margin: 0;
  padding: 0;
}
.inbox-item {
  border: 1px solid var(--hairline);
  border-radius: var(--r-md);
  padding: var(--sp-3);
  margin-block-end: var(--sp-2);
  background: var(--surface);
}
/*
 * 未读的那一条：**左侧一道色条 + 一个文字徽章**，两种编码。
 * 只靠颜色区分未读，对色觉障碍用户等于没有区分（AA 的"不只靠颜色"那一条），
 * 而徽章上的字是那一半的另一种编码。
 */
.inbox-item.unread {
  border-inline-start: 3px solid var(--accent);
}
.inbox-item-h,
.inbox-item-f {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2);
}
.inbox-item-f {
  margin-block-start: var(--sp-2);
}
.inbox-item p {
  margin: var(--sp-2) 0 0;
  line-height: 1.6;
}
.inbox-badge {
  margin-inline-start: var(--sp-1);
}

/* 运行监控（挂在投递台账上；见 monitoring-panel.tsx 的说明） */
.monitoring {
  margin-block: var(--sp-3);
}
.monitoring-h {
  display: flex;
  gap: var(--sp-2);
  margin-block-end: var(--sp-2);
}
.monitoring-group {
  font-size: 13px;
  margin: var(--sp-4) 0 var(--sp-2);
  color: var(--text-2);
}

/* 调度编辑器里的参数那一组（形态同 .schedule-mode：fieldset 是"同一个问题"的线索） */
.schedule-params {
  border: 1px solid var(--hairline);
  border-radius: var(--r-md);
  padding: var(--sp-3);
  margin-block: var(--sp-3);
}
.schedule-params legend {
  padding-inline: var(--sp-2);
  font-size: 13px;
  color: var(--text);
}

/* ---------- 查询工作台（Phase 10 实现轮 E）---------- */

/*
 * 构建器表单。**一个色值都不写**（同本文件其余部分，`no-hardcoded-color.test.ts` 看着）。
 */
.q-builder {
  margin-block-end: var(--sp-4);
}

.q-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 0 var(--sp-4);
}

.q-block {
  border: 1px solid var(--hairline);
  border-radius: var(--r-md);
  padding: var(--sp-3);
  margin: 0 0 var(--sp-3);
}
.q-block > legend {
  font-size: 12.5px;
  font-weight: 600;
  color: var(--text-2);
  padding: 0 var(--sp-1);
}

.q-group {
  border: 1px solid var(--hairline-2);
  border-radius: var(--r-sm);
  padding: var(--sp-2);
  margin-block-end: var(--sp-2);
}

.q-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2);
  margin-block-end: var(--sp-2);
}
/* 工具条同款：`.inp` 是 width:100%，放进 flex 行里会各占一整行。 */
.q-row .inp {
  width: auto;
  min-width: 140px;
  flex: 1 1 140px;
}
.q-row .sp {
  flex: 1;
}

/*
 * 实时 URL。
 *
 * ⚠️ 它**不是** `.faint`：这条 URL 是用户复制去 Postman 复现问题的那一段，
 * 也是「构建器与手写等价」那条验收行在界面上的落点 —— 一段看不清的 URL
 * 等于没有这个功能（`--text-3` 自己写着"不承载关键文本"）。
 * 走 `--text` + 等宽字体，长串按字符换行而不是把页面撑出横向滚动条。
 */
.q-url {
  display: block;
  font-family: var(--font-mono);
  font-size: 12.5px;
  line-height: 1.7;
  color: var(--text);
  background: var(--shell-2);
  border: 1px solid var(--hairline);
  border-radius: var(--r-sm);
  padding: var(--sp-2) var(--sp-3);
  overflow-wrap: anywhere;
}

/*
 * ─────────────────────────────────────────────────────────────────────────
 * SAML 签名证书面（Hotfix HF-3）
 * ─────────────────────────────────────────────────────────────────────────
 *
 * ⚠️ 三段证书文本走 `.q-url` 的同一套排版（等宽 + `--text` + 按字符换行），
 * 理由逐字相同：**它们是用户要复制走的那一段**。一段看不清、或者被浏览器
 * 折行折错的 base64，贴进 SF 之后表现为"证书无效"——而那句话与
 * "证书没注册"无法区分。
 *
 * 这里只补"标题行 + 高度上限"：一份 PEM 有 20 多行，三份并排会把台账推到
 * 屏幕之外，而台账正是回答"SF 上那张是不是这张"的地方。
 */
.cert-form {
  margin-block-end: var(--sp-4);
}
.cert-form:last-child {
  margin-block-end: 0;
}
.cert-form-h {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  margin-block-end: var(--sp-1);
}
.cert-form-body {
  max-block-size: 11em;
  overflow: auto;
  /* PEM 自带换行，裸 base64 是一整行 —— 两者都要按原样显示再按字符折。 */
  white-space: pre-wrap;
}

/* ===========================================================================
 * ⭐⭐ 自绘下拉 / 锚定弹层（Phase 23 实现轮 A · P23-H-07 / H-03 / H-09）
 *
 * ## 判据是 computed `appearance`，因此这里必须自己画完整
 *
 * QUALITY-STANDARDS §3 的原生裸控件判定式看的是 computed `appearance` 落在
 * `auto | menulist | menulist-button | textfield | …`。给一个 `<select>` 加
 * `appearance:none` 只能骗过判定式的一半 —— 弹层仍然是**系统画**的，
 * 暗色主题下它是白底黑字，而那正是"长得不像基准"的实际形态。
 * ⇒ 触发器与弹层**两件都自绘**，`.w2-select` 是按钮、`.menu.pop` 是弹层。
 *
 * ## `.pop` 为什么要 `position: fixed`
 *
 * `.tbar` 带着 `overflow-x: clip`（Phase 6 修 320px 整页横向溢出）。
 * `overflow:clip` 裁掉在它内部定位的后代 ⇒ 顶栏里的绝对定位菜单会被拦腰切掉。
 * `fixed` 的包含块是视口，逃得掉；坐标由 `popover.tsx` 量出来并夹在视口内。
 *
 * ## 实色（P23-H-03）
 *
 * 一处 `backdrop-filter` 都不许有 —— SPEC-04 §5「实色禁毛玻璃」。
 * 弹层底色取 `--surface`（`.menu` 已给），阴影取 `--e3`。
 * =========================================================================== */
.w2-select {
  display: inline-flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  inline-size: 100%;
  block-size: 38px;
  padding-inline: 12px;
  border: 1px solid var(--hairline);
  border-radius: var(--r-md);
  background: var(--surface);
  color: var(--text);
  font-family: inherit;
  font-size: 13.5px;
  text-align: start;
  cursor: pointer;
  transition: border-color 0.16s var(--ease);
}
.w2-select.sm {
  block-size: 32px;
  font-size: 12.5px;
  padding-inline: 10px;
}
.w2-select:hover:not(:disabled) {
  border-color: var(--accent);
}
/* 焦点环（P23-H-09）：与 `.btn:focus-visible` 同一形状，不另发明一种。 */
.w2-select:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-ring);
}
.w2-select[aria-expanded='true'] {
  border-color: var(--accent);
}
.w2-select:disabled {
  cursor: not-allowed;
  opacity: 0.6;
}
.w2-select-value {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/*
 * ⚠️ 占位文案用 `--text-2` 而不是 `--text-3`：tokens.css 自己写着
 * 「text-3 仅作占位/弱化，不承载关键文本」，而下拉的占位是"你还没选"这条
 * 关键信息，AA 抽查点位（附表 H-06「筛选控件占位符 / 控件底」）就在这上面。
 */
.w2-select-value.is-placeholder {
  color: var(--text-2);
}
.w2-select-caret {
  flex: none;
  color: var(--text-2);
}

/* 门户弹层：脱离 `.tbar` 的 overflow 裁剪，层序在弹窗（50）之上。 */
.menu.pop {
  position: fixed;
  z-index: 60;
  max-block-size: min(60vh, 420px);
  overflow-y: auto;
  overscroll-behavior: contain;
}
.w2-listbox {
  padding: 6px;
}
.w2-opt {
  justify-content: space-between;
  gap: 10px;
}
.w2-opt-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.w2-opt-hint {
  color: var(--text-2);
  font-size: 12px;
  flex: none;
}
/*
 * 键盘高亮与鼠标 hover 是**同一种**外观：这两者对用户是同一件事
 * （"我现在停在哪一项"），画成两种只会让人以为有两个游标。
 */
.w2-opt.is-active {
  background: var(--accent-soft);
  color: var(--accent-700);
}
.w2-opt.is-disabled {
  color: var(--text-2);
  cursor: not-allowed;
}
.w2-opt.is-disabled:hover {
  background: transparent;
  color: var(--text-2);
}
/* 选中标记是**形状**不是颜色（语义双编码，W2 基座铁律）。 */
.w2-opt-check {
  flex: none;
  inline-size: 6px;
  block-size: 6px;
  border-radius: 50%;
  background: var(--accent);
}
.w2-opt-empty {
  padding: 10px;
  color: var(--text-2);
  font-size: 13px;
}

/* 明暗切换（P23-A-13）：与通知铃同一档的图标按钮，窄屏下不被挤走。 */
.theme-toggle {
  flex: none;
}

/* ===========================================================================
 * ⭐⭐ ⌘K 命令面板（Phase 23 · P23-G-01 / G-12 / G-13 / H-20）
 *
 * ## 实色（G-12）
 *
 * 面板与遮罩**一处 `backdrop-filter` 都没有** —— SPEC-04 §5「实色禁毛玻璃」。
 * 底色取 `.dialog` 给的 `--surface`，阴影取 `--e3`（W2 的最高一档，
 * 它是"浮在整页之上"这件事的视觉表达）。
 *
 * ## 动效在 reduce 下是 `animation: none`，不是"时长压到 0"（G-13）
 *
 * W2 基座那条全局 reduce 规则写的是 `animation-duration: 0.001ms !important`，
 * 而 G-13 的判据是 **`getAnimations()` 为空**——一条时长 0.001ms 的动画
 * **仍然是一条动画**，它照样进那个数组。`animation: none` 才是"没有动画"。
 * 常态时长 120ms，远在 H-20 的 ≤700ms 之内。
 * =========================================================================== */
.cmdk-trigger {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  block-size: 34px;
  inline-size: 260px;
  max-inline-size: 260px;
  flex: none;
  padding-inline: 12px;
  border: 1px solid var(--hairline);
  border-radius: var(--r-md);
  background: var(--surface);
  color: var(--text-2);
  font-family: inherit;
  font-size: 13px;
  cursor: pointer;
  text-align: start;
}
.cmdk-trigger:hover {
  border-color: var(--accent);
  color: var(--text);
}
.cmdk-trigger:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-ring);
}
.cmdk-trigger-label {
  flex: 1;
  min-inline-size: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cmdk-trigger .kbd {
  display: inline-flex;
  gap: 3px;
  flex: none;
}

.cmdk-scrim {
  display: grid;
  /* 面板略偏上：命令面板的心理位置是"从上面掉下来"，正中会挡住正文视线。 */
  align-content: start;
  justify-items: center;
  padding-block-start: 12vh;
  padding-inline: 16px;
}
.cmdk-panel {
  inline-size: min(640px, 100%);
  max-block-size: 70vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  animation: cmdk-in 120ms var(--ease);
}
@keyframes cmdk-in {
  from {
    opacity: 0;
    transform: translateY(-8px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
@media (prefers-reduced-motion: reduce) {
  .cmdk-panel {
    /* `none` 而不是"时长压到 0"：见本节文件头。 */
    animation: none;
  }
}
.cmdk-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 14px;
  border-block-end: 1px solid var(--hairline);
}
/*
 * ⚠️ 弱化色只给**装饰性的放大镜图标**，不给整条 head。
 * 上一版把最弱的那一档文本色写在了 `.cmdk-head` 上 —— `no-faint-key-text`
 * 当场判红，而它判得对：那条规则会顺着继承落到输入框占位符与关闭键上，
 * 而那两处是这个面板上唯一告诉人"能敲什么、怎么退出"的字。
 * token 自己写着「最弱那一档仅作占位/弱化，不承载关键文本」。
 *
 * ⚠️ 顺带一条给后来人：这道闸的 CSS 解析**不剥注释**，因此注释里别写出
 * 「选择器 + 花括号 + 那个变量」的完整形状 —— 它会被当成一条真规则读进去。
 */
.cmdk-head > svg {
  color: var(--text-2);
  flex: none;
}
.cmdk-input {
  flex: 1;
  min-inline-size: 0;
  border: 0;
  background: transparent;
  color: var(--text);
  font-family: inherit;
  font-size: 15px;
  outline: none;
}
.cmdk-input::placeholder {
  color: var(--text-2);
}
.cmdk-body {
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 6px;
}
.cmdk-section + .cmdk-section {
  border-block-start: 1px solid var(--hairline-2);
  margin-block-start: 4px;
  padding-block-start: 4px;
}
.cmdk-opt {
  justify-content: space-between;
  gap: 12px;
}
.cmdk-opt.is-active {
  background: var(--accent-soft);
  color: var(--accent-700);
}
.cmdk-opt-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cmdk-opt-hint {
  flex: none;
  color: var(--text-2);
  font-size: 12px;
}
/*
 * 零结果 / 加载 / 取不到 三种说明共用它。用 `--text-2` 不用 `--text-3`：
 * 这几句是**唯一**告诉用户"接下来能做什么"的文字（G-11），
 * 而 tokens.css 写着 text-3 不承载关键文本。
 */
.cmdk-note {
  padding: 8px 10px 10px;
  color: var(--text-2);
  font-size: 13px;
}

/* 窄屏：触发件收成一个图标位，快捷键提示让位（那台机器多半没有键盘）。 */
@media (max-width: 900px) {
  .cmdk-trigger {
    inline-size: 36px;
    max-inline-size: 36px;
    justify-content: center;
    padding-inline: 0;
  }
  .cmdk-trigger-label,
  .cmdk-trigger .kbd {
    display: none;
  }
}

/* ===========================================================================
 * ⭐⭐ 报表详情 · 精装（Phase 23 实现轮 D · 基准 `docs/ui-prototypes/report-detail.html`）
 *
 * ## 切分线（D-1 总控裁决 2026-08-04）
 *
 * **面包屑 + 页头四段元信息 + 动作区 = 页级**（tab 之上，五个 tab 共享：
 * params/logs/台账 各 tab 同样需要"这是哪份报表、以谁的视角"这行事实）；
 * **参数条 pchip / KPI / 洞察 / 2fr-1fr 栅格 / 明细表 = dashboard tab 内**按原型精装。
 *
 * ## ⚠️ 元信息行走 `--text-2`，不走原型的 `--text-3`
 *
 * 原型 `.sub` 用的是 `--text-3`。而 H-06 的点位表把「页头元信息 / 页底」
 * 列进了 AA ≥ 4.5:1 的五个抽查点，`--text-3` 在页底实测约 2.5:1 ——
 * 与轮 B 的 Powered by 是同一格（那次实测 2.29:1）：
 * **一行读不清的元信息等于没有标注**，而铁律 3 要的正是这行标注。
 * ⇒ 与轮 B 同向处置：降一档到 `--text-2`。这是清单外的刻意不照抄，第三处同族。
 * =========================================================================== */

/*
 * ⭐ 面包屑（D-01）。形状抄 `core.css` 的 `.breadcrumb`，**颜色不抄**：
 * 那一条是 `--text-3`，而 tokens.css 给它的定语是「仅作占位/弱化，不承载关键文本」。
 * 面包屑的首段是**一个链接**（页面上回列表的唯一入口），末段是"你在哪儿" ——
 * 两样都是必须读得清的文字，2.5:1 的实测对比度让它们等于不存在。
 * ⇒ 与轮 B 的 Powered by 同向处置：降一档到 `--text-2`，并因此走自己的类名
 *（`.breadcrumb` 那个名字在 `no-faint-key-text` 的派生集里，蹭它 = 蹭一条豁免）。
 * 这是第四处刻意不照抄基准（同族见 D-5 裁决）。
 */
.report-crumb {
  display: flex;
  align-items: center;
  gap: 7px;
  margin-block-end: 12px;
  font-size: 12.5px;
  color: var(--text-2);
}
.report-crumb a {
  color: var(--text-2);
}
.report-crumb a:hover {
  color: var(--accent-700);
}
.report-crumb .crumb-sep {
  color: var(--text-3);
}
.report-crumb [aria-current='page'] {
  min-inline-size: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ---------- 页头（D-01 / D-02 / D-03） ---------- */
.report-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  margin-block-end: 14px;
  flex-wrap: wrap;
}
.report-head-main {
  flex: 1 1 320px;
  min-inline-size: 0;
}
.report-h1 {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  font-size: 22px;
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.3;
}
/*
 * 元信息四段。分隔用 1px 竖线而不是 `·` 伪内容：读屏会把插进去的标点念出来，
 * 而它不是内容的一部分（同 `.breadcrumb` 里分隔符挂 aria-hidden 的理由）。
 */
.report-meta {
  margin-block-start: 5px;
  display: flex;
  align-items: center;
  gap: 9px;
  flex-wrap: wrap;
  font-size: 12.5px;
  color: var(--text-2);
  font-variant-numeric: tabular-nums;
}
.report-meta > * + * {
  padding-inline-start: 9px;
  border-inline-start: 1px solid var(--hairline);
}
.report-meta b {
  color: var(--text);
  font-weight: 600;
}
/* 视角标注自己是一段（它由 `PerspectiveNotice` 渲染，含截断/软过期两个补充标）。 */
.report-meta .perspective {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.report-meta .cell-reason {
  font-size: 12.5px;
}

/* ---------- 动作区分档（D-03） ---------- */
/*
 * ⚠️ **不能写 `flex-shrink: 0`**（基准原型 `.actions{flex-shrink:0}` 那一句不照抄）。
 * 原型只在宽屏下被看过：320px 下这一组按钮的固有宽度是 190px，
 * 拒绝收缩就直接顶穿视口 —— `reports.spec.ts` 的整页溢出断言当场判红并逐个点名
 *（`button.act.primary → right=510 (client=320)`）。第五处刻意不照抄，同族见 D-5 裁决。
 * `flex: 0 1 auto` + `min-inline-size: 0` 让它先让位、再在自己内部换行。
 */
.report-acts {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  flex: 0 1 auto;
  min-inline-size: 0;
}
/*
 * `.act` 是本页动作区的档位类。**`appearance: none` 不是装饰**：
 * QUALITY-STANDARDS §3 的原生裸控件判定式看 computed `appearance`，
 * 而 `<button>` 的默认值就是 `auto` —— 一个不写它的自绘按钮在判定式下
 * 与浏览器裸控件无法区分（同 D-10 给 `.tab` 补的那一条）。
 */
.act {
  appearance: none;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 7px 14px;
  border-radius: var(--r-md);
  border: 1px solid var(--hairline);
  background: var(--surface);
  font: 500 12.5px var(--font-sans);
  color: var(--text-2);
  cursor: pointer;
  transition:
    color var(--dur-2) var(--ease),
    border-color var(--dur-2) var(--ease),
    background var(--dur-2) var(--ease);
}
.act:hover:not(:disabled) {
  border-color: var(--accent);
  color: var(--accent-700);
}
/* AI 档：底色 `--cat-ai-bg`、文字 `--cat-ai`（D-03 逐字）。 */
.act.ai {
  color: var(--cat-ai);
  border-color: transparent;
  background: var(--cat-ai-bg);
  font-weight: 600;
}
.act.ai:hover:not(:disabled) {
  color: var(--cat-ai);
  border-color: var(--cat-ai);
}
/* 主档：琥珀 `--btn-primary`，**不是** accent 蓝（同 A-16 登录主按钮那一条）。 */
.act.primary {
  background: var(--btn-primary);
  border-color: transparent;
  color: var(--btn-primary-text);
  font-weight: 600;
}
.act.primary:hover:not(:disabled) {
  background: var(--btn-primary-hover);
  color: var(--btn-primary-text);
  border-color: transparent;
}
.act.danger {
  color: var(--bad);
}
.act.danger:hover:not(:disabled) {
  border-color: var(--bad);
  color: var(--bad);
}
/* 禁用三重信号，与 `.btn:disabled` 逐字同一条（背景变平 + 文字变淡 + not-allowed）。 */
.act:disabled,
.act:disabled:hover {
  background: var(--shell-2);
  border-color: var(--hairline);
  color: var(--text-2);
  box-shadow: none;
  cursor: not-allowed;
}

/*
 * ⭐ tab 的 `appearance`（D-10）。W2 的 `.tab` 只给了形状，没有清掉原生外观 ——
 * 而判定式量的正是 computed `appearance`，`<button>` 的默认值 `auto` 落在
 * 「原生裸控件」那一档里。这里补上；形状仍由 `core.css` 的 `.tab` 决定。
 */
.tab {
  appearance: none;
  background: none;
}
/*
 * `.btn` 同一条（W2 只给了形状，没清原生外观）。它已经自带 background / border /
 * padding，因此这一行**不改任何观感** —— 改的是判定式那一侧：
 * 一个 computed `appearance: auto` 的按钮在 QUALITY-STANDARDS §3 的原生裸控件
 * 判定式下与浏览器裸控件无法区分（分页控件 E-06 就撞在这一格）。
 */
.btn {
  appearance: none;
}

/* ---------- 参数条（D-04，dashboard tab 内） ---------- */
.params-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  padding: 10px 14px;
  margin-block-end: 16px;
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--r-lg);
  box-shadow: var(--e1);
}
.params-bar-label {
  font-size: 12px;
  color: var(--text-2);
}
/*
 * 参数 chip。原型的 `.pchip small{opacity:.65}` **不照抄**：
 * H-06 的点位表要求「参数 pchip 文字 / pchip 底」≥ 4.5:1，
 * 而一层 .65 的不透明度会把取值那半句压到 AA 线以下 —— 而取值恰恰是
 * 这个 chip 唯一要说的内容（"周期"是标签，"2026-07"才是答案）。
 * ⇒ 用字重区分主次，不用不透明度。同族第四处刻意不照抄（见 D-5 裁决）。
 */
.pchip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 12px;
  border-radius: var(--r-pill);
  background: var(--accent-soft);
  color: var(--accent-700);
  font-size: 12.5px;
  font-weight: 600;
  max-inline-size: 22em;
}
.pchip .pchip-value {
  font-weight: 400;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.run-as {
  margin-inline-start: auto;
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  color: var(--text-2);
}
.run-as strong {
  color: var(--text);
  font-weight: 600;
}

/* ---------- 明细表 / 图表卡 hover（D-08 / D-09 / H-21） ---------- */
/*
 * ⚠️ **行 hover 的底色从 `--shell` 换成 `--shell-2`**（H-21）。
 *
 * W2 `core.css` 给的是 `--shell`。在**暗色**下它是 `#111A28`，而卡面
 * `--surface` 是 `#151E2E` —— 也就是说 hover 把行**压暗**了。
 * H-21 的原话是「表格行 hover **提亮**在明暗两主题下都可见」，
 * 而基准原型用的正是 `--shell-2`（暗色 `#1B2536`，比卡面亮）。
 * 覆写写在产品层而不是改 `styles/w2/`：那是 vendor 原文件（H-02 的排除项），
 * 改它等于把上游的差异藏进一次本地编辑里。
 */
table.data tbody tr:hover {
  background: var(--shell-2);
}
/* 列表/台账表头与基准同色（原型 `th{background:var(--shell-2)}`；E-02）。 */
table.data thead th {
  background: var(--shell-2);
}
/*
 * 超长报表名不破布局（E-05）：单元格截断，行高与相邻行相同。
 * 截断挂在**链接**而不是 `<td>` 上：`text-overflow` 只对块级/行内块生效，
 * 而 `<td>` 里那个 `<a>` 是行内元素，挂在 td 上对它无效。
 */
table.data td .cell-clip {
  display: block;
  max-inline-size: 42ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
table.data td .report-desc {
  max-inline-size: 52ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ===========================================================================
 * ⭐⭐ 首页指挥中心（Phase 23 实现轮 E · P23-F 全节）
 *
 * 三段的形状来自 SPEC-04 §2.6：KPI 行 → AI 洞察流 → 快捷动作。
 * 视觉语言与 `report-detail.html` 正文同源（卡面 `--surface` + hairline +
 * `--e1`，hover 抬到 `--e2`），因此 F-13 的「同一视觉语言」不是靠像素对齐
 * 而是靠**用的是同一批 token 与同一种卡片构造**。
 * =========================================================================== */
.home-sub {
  margin: -8px 0 22px;
  color: var(--text-2);
  font-size: 13.5px;
}
.home-sec {
  margin-block-end: 28px;
}
.home-sec-h {
  font-size: 14px;
  font-weight: 600;
  color: var(--text-2);
  margin: 0 0 12px;
  letter-spacing: 0.02em;
}

/*
 * ⭐ KPI 行的降列（P23-F-11：4 → 2 → 1）。
 *
 * 四列由文档的 `span: 6`（24 栏制）给；≤960px 的两列由 `dashboard.css` 里
 * D-05 那条通则给（`.db-item[data-primitive='kpi-card']`，报表详情与首页同一条）。
 * ⚠️ 这里只补**最后一档**：那条通则带 `!important` 且特异性高过
 * `.db-item` 的 768px 规则，于是不写这一条的话首页在 320px 下会停在两列，
 * 而 F-11 要的是一列。特异性靠 `.home-kpi-grid` 前缀压过去，作用域也因此
 * 只在首页 —— 报表详情的 KPI 仍然按基准原型停在两列（D-05 原文）。
 */
@media (max-width: 560px) {
  .home-kpi-grid .db-item[data-primitive='kpi-card'] {
    width: 100% !important;
  }
}
/* 指挥中心的 KPI 行不需要 dashboard 自带的底部留白（它下面紧跟着洞察段）。 */
.home-kpi-grid .dashboard {
  padding-block-end: 0;
}

/*
 * ⭐ 洞察卡：**渐变描边**（外层 1.5px padding + 渐变底，内层 `--surface`），
 * 与 `.db-insight-card`（dashboard 内那一种）逐字同构 —— 两处都照
 * `report-detail.html` 的 `.insight` / `.insight-inner`。
 * 不复用 `.db-*` 类名：那一族是渲染层的（迁移件锚点），首页不是渲染层。
 */
.home-insight-list {
  display: grid;
  gap: 12px;
}
.home-insight {
  border-radius: var(--r-lg);
  padding: 1.5px;
  background: linear-gradient(120deg, var(--cat-ai), var(--accent) 60%, transparent);
}
.home-insight-inner {
  border-radius: calc(var(--r-lg) - 1.5px);
  background: var(--surface);
  padding: 14px 16px;
}
.home-insight-t {
  margin: 0 0 6px;
  font-size: 14.5px;
  font-weight: 600;
  color: var(--text);
}
.home-insight-b {
  margin: 0 0 8px;
  font-size: 13.5px;
  color: var(--text);
  line-height: 1.6;
}
.home-insight-src {
  margin: 0 0 8px;
}
.home-insight-drill {
  font-size: 13px;
  font-weight: 600;
  color: var(--accent);
}

/* ---------- 快捷动作 ---------- */
.home-action-row {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
}
@media (max-width: 700px) {
  .home-action-row {
    grid-template-columns: minmax(0, 1fr);
  }
}
.home-action {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 16px 18px;
  border: 1px solid var(--hairline);
  border-radius: var(--r-lg);
  background: var(--surface);
  box-shadow: var(--e1);
  color: var(--text);
  transition:
    box-shadow var(--dur-2) var(--ease),
    border-color var(--dur-2) var(--ease);
}
.home-action:hover {
  border-color: var(--accent);
  box-shadow: var(--e2);
}
.home-action:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-ring);
}
.home-action-t {
  font-size: 14.5px;
  font-weight: 600;
}
.home-action-h {
  font-size: 12.5px;
}

/* ===========================================================================
 * ⭐ 多选列表框（P23-H-07 唯一一处"不换组件"，Phase 23 实现轮 E）
 *
 * 全仓 50 处原生 `<select>` 里 49 处换成了自绘（`components/select.tsx`），
 * 只有 `report-params.tsx` 的 `multi_select` 留着原生 —— 理由写在那个文件里：
 * 自绘那一个是**单选** listbox，多选要的是另一件东西。
 *
 * 于是它走 H-07 判据的**另一半**：computed `appearance` 不是原生值 + W2 外观。
 * ⚠️ 这一条对多选成立、对单选不成立，差别在**候选列表画在哪**：
 * 多选框的候选是元素自身的内容（我们的 CSS 管得到），
 * 而单选下拉的弹层是**系统**画的（`appearance:none` 管不到它）。
 * =========================================================================== */
select.inp.multi {
  appearance: none;
  block-size: auto;
  min-block-size: 96px;
  padding: 6px;
  line-height: 1.6;
}
select.inp.multi option {
  padding: 4px 8px;
  border-radius: var(--r-sm);
  color: var(--text);
}
/* 选中项：底色 + **文字加粗**（语义双编码，不靠颜色单编码）。 */
select.inp.multi option:checked {
  background: var(--accent-soft);
  color: var(--accent-700);
  font-weight: 600;
}

/* ===========================================================================
 * ⭐ P23-H-08 · 其余原生裸控件的 W2 化（Phase 23 实现轮 E）
 *
 * H-08 要的是两件事同时成立：**带 W2 类** + **外观非默认**。
 * `.inp` / `.db-*` 那一半早就在了，缺的是后一半 —— computed `appearance`
 * 还落在 `auto`。轮 D 已经把 `color-scheme` 布好（原生日历/滚动条跟着暗色走），
 * 这里补的是控件**自身**的外观。
 * =========================================================================== */

/*
 * 日期输入（`report-params.tsx` 三处 + `QueryBuilderPage` 的有效日期）。
 *
 * ⚠️ **只清外观，不接管日历**：弹出的那个日历是浏览器画的，而它归
 * `color-scheme` 管（轮 D）。这里把输入框本体收进 W2 的盒子里，
 * 并把那个日期图标按钮的配色交给 `currentColor` —— 亮暗两套自动跟着走。
 */
.inp[type='date'] {
  appearance: none;
}
.inp[type='date']::-webkit-calendar-picker-indicator {
  cursor: pointer;
  opacity: 0.65;
  /* 图标本身是浏览器画的位图；用滤镜让它跟着 `--text` 的明暗走。 */
  filter: invert(var(--w2-icon-invert, 0));
}
.inp[type='date']:hover::-webkit-calendar-picker-indicator {
  opacity: 1;
}
/* 亮岛同上（R2-01）：日期图标的反相开关要跟着最近的主题作用域走。 */
:root,
[data-theme='light'] {
  --w2-icon-invert: 0;
}
[data-theme='dark'] {
  --w2-icon-invert: 1;
}

/*
 * ⭐ 顶栏品牌标是**链接**（Phase 23 实现轮 E 补丁 · 总控裁决 2026-08-04）：
 * 离开指挥中心之后，点 logo 是回去的那条路。
 * 它长得**不该像一条导航链接** —— 不带下划线、颜色跟着正文走；
 * 悬停时只给一点 accent，让"这里能点"看得见但不喧宾夺主。
 */
.tbar a.bd {
  color: var(--text);
  text-decoration: none;
  border-radius: var(--r-sm);
}
.tbar a.bd:hover {
  color: var(--accent);
}
.tbar a.bd:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--accent-ring);
}

/* ===========================================================================
 * 自有数据空间（Phase 12 实现轮 D）
 *
 * ⚠️ 一个色值都不写：全部走 W2 语义 token（`--info` / `--warn` / `--text-2`…）。
 * ⚠️ 没有毛玻璃：`.gov-banner` 与 `.explain` 都是**实色**卡片
 *    （QUALITY-STANDARDS §3 红线：实色禁 backdrop-filter）。
 * ===========================================================================
 */

/*
 * ⭐⭐ 视角横幅（SPEC-03 §4.5 ④）。
 *
 * 它借的是 `.pt-banner`（透传降级横幅）的形状而不是 `.card`：两者是**同一类
 * 东西** —— 一条"你现在看到的东西有个前提"的说明，而不是一块内容。
 * 左侧那条竖边是它与普通卡片的唯一区分，颜色跟着治理态走：
 * 有效 = `--info`（陈述一个事实），归档 = `--warn`（一个要注意的状态）。
 * **不用 `--bad`**：到期不是错误。
 */
.gov-banner {
  display: flex;
  gap: var(--sp-3);
  margin-block: var(--sp-4);
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--hairline);
  border-inline-start: 3px solid var(--info);
  border-radius: var(--r-md);
  background: var(--info-bg);
  color: var(--text);
  font-size: 13px;
  line-height: 1.6;
}
.gov-banner.gov-archived {
  border-inline-start-color: var(--warn);
  background: var(--warn-bg);
}
/* 客户自有数据：它**不在**那套治理里，因此不该长得像一条警示。 */
.gov-banner.gov-plain {
  border-inline-start-color: var(--neutral);
  background: var(--neutral-bg);
}
.gov-body {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  min-width: 0;
}
.gov-body strong {
  font-size: 13.5px;
  font-weight: 700;
}
.gov-inherited,
.gov-source {
  color: var(--text-2);
}
.gov-source {
  font-family: var(--font-mono);
  font-size: 11.5px;
  overflow-wrap: anywhere;
}

/*
 * ⭐⭐ 「为什么读不出来」说明卡。
 *
 * 它**不是**错误态：`.state` 那一套画的是"这里本该有东西、现在没有"，
 * 而这几屏要说的是"这里现在**不该**有东西，原因是……"。
 * 用错容器的后果不是难看，是语义错位 —— 一个 `role="alert"` 的红块
 * 会让人去找运维，而这三种情况里有两种谁也修不了（要等能力落地）。
 */
.explain {
  margin-block: var(--sp-4);
  padding: var(--sp-4) var(--sp-5);
  border: 1px solid var(--hairline);
  border-inline-start: 3px solid var(--warn);
  border-radius: var(--r-md);
  background: var(--warn-bg);
  color: var(--text);
  font-size: 13px;
  line-height: 1.65;
}
.explain-info {
  border-inline-start-color: var(--info);
  background: var(--info-bg);
}
.explain h4 {
  margin: 0 0 var(--sp-2);
  font-size: 14px;
  font-weight: 700;
}
.explain p {
  margin: 0 0 var(--sp-2);
}
.explain p:last-child {
  margin-block-end: 0;
}
.explain ul {
  margin: 0;
  padding-inline-start: var(--sp-5);
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
.explain code {
  font-family: var(--font-mono);
  font-size: 12px;
}

/*
 * 破坏性变更的预览正文。**服务端算出来的原文**（428 的 message），
 * 里面有换行与 `**着重**` —— `white-space: pre-wrap` 是必须的：
 * 折成一段之后那几行"❌ 有 37 行转不过去"会与前一句连在一起。
 */
.change-preview {
  margin-block: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--hairline);
  border-radius: var(--r-md);
  background: var(--shell-2);
  color: var(--text);
  font-size: 13px;
  line-height: 1.7;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* 配额行：两个数 + 一条说明。窄屏堆叠（320px 不横向溢出）。 */
.quota-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-6);
  align-items: baseline;
}
.quota-cell {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.quota-cell b {
  font-size: 20px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.quota-cell span {
  color: var(--text-2);
  font-size: 12px;
}

/*
 * 字段设计器的一行。**不用表格**：每一行有 6 个控件，表格在 320px 下必然
 * 横向溢出（而红线里那一条是"三断点不横向溢出"）。
 */
.field-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
  align-items: flex-end;
  padding: var(--sp-3);
  border: 1px solid var(--hairline);
  border-radius: var(--r-md);
  margin-block-end: var(--sp-3);
}
.field-row .field {
  min-width: 0;
}
.field-row .checkline {
  align-self: center;
}

/* 行表格：值可能很长，给一个上限并允许换行，别把表撑出视口。 */
table.data td.rowcell {
  max-width: 240px;
  overflow-wrap: anywhere;
}

/* ===========================================================================
 * Phase 14 —— 洞察 / 模板中心 / AI 调优工作台
 * 铁律：本段一个色值都不写，只引用 W2 token（no-hardcoded-color 闸盯着）。
 * =========================================================================== */

/* ---------- 洞察：数据源选择器 ---------- */
.source-list {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  margin-block: var(--sp-2);
}
.source-row {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-2);
  border: 1px solid var(--hairline);
  border-radius: var(--r-md);
  background: var(--surface);
}
.source-row .source-name {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 600;
}
.source-row .inp.sm {
  flex: 0 0 200px;
  height: 32px;
}
.source-results {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin-top: var(--sp-2);
}
.btn.source-result {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ---------- 洞察：结果标注条 + 解读 ---------- */
.insight-meta-bar {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  margin-block: var(--sp-3);
  flex-wrap: wrap;
}
.insight-narrative {
  margin-block: var(--sp-3);
}
.insight-narrative-text {
  white-space: pre-wrap;
  line-height: 1.6;
  color: var(--text);
}

/* ---------- 调优看板：指标卡 ---------- */
.metric-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: var(--sp-3);
  margin-block: var(--sp-3);
}
.metric-card {
  border: 1px solid var(--hairline);
  border-radius: var(--r-lg);
  background: var(--surface);
  padding: var(--sp-4);
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
}
.metric-card.hero {
  grid-column: 1 / -1;
  background: var(--accent-soft);
}
.metric-value {
  font-size: 30px;
  font-weight: 700;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
.metric-card.hero .metric-value {
  font-size: 40px;
  color: var(--accent-700);
}
.metric-label {
  font-size: 12.5px;
  font-weight: 600;
  color: var(--text-2);
}
/* ⚠️ `--text-2` 不是 `--text-3`：这条 hint 承载"成功率公式 / 基线值"这类**关键**说明，
 * 而 `--text-3` 焊着"不承载关键文本"（no-faint-key-text 闸 R1-F1）。 */
.metric-hint {
  font-size: 11.5px;
  color: var(--text-2);
}

/* ---------- 调优看板：分布条 ---------- */
.dist-list {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  margin-block: var(--sp-2);
}
.dist-row {
  display: grid;
  grid-template-columns: 160px 1fr 48px;
  align-items: center;
  gap: var(--sp-2);
}
.dist-track {
  height: 10px;
  border-radius: var(--r-sm);
  background: var(--bg-app);
  overflow: hidden;
}
.dist-fill {
  height: 100%;
  background: var(--accent);
  border-radius: var(--r-sm);
}
.dist-num {
  text-align: right;
  font-variant-numeric: tabular-nums;
  color: var(--text-2);
}

/* ---------- 调优：transcript / 固化抽屉正文 ---------- */
.transcript-list {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  max-height: 60vh;
  overflow-y: auto;
}
.transcript-event {
  border: 1px solid var(--hairline);
  border-radius: var(--r-md);
  padding: var(--sp-2) var(--sp-3);
  background: var(--surface);
}
.transcript-role {
  font-size: 11.5px;
  font-weight: 700;
  /* `--text-2` 不 `--text-3`：角色标签（用户/助手/工具）是关键文本，no-faint-key-text 闸盯着。 */
  color: var(--text-2);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.transcript-content {
  white-space: pre-wrap;
  line-height: 1.5;
  color: var(--text);
}
.skill-body-preview {
  border: 1px solid var(--hairline);
  border-radius: var(--r-md);
  background: var(--bg-app);
  padding: var(--sp-3);
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
.skill-body-row {
  display: grid;
  grid-template-columns: 96px 1fr;
  gap: var(--sp-2);
  font-size: 13px;
}
.skill-body-row .k {
  font-weight: 600;
  color: var(--text-2);
}
.skill-body-row .v {
  color: var(--text);
  overflow-wrap: anywhere;
}

/* 队列行内的一排小动作。 */
.row-acts {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-1);
}

/* ===========================================================================
 * 批量导入向导（Phase 15 实现轮 B）—— 全部走 W2 token，无硬编码色值。
 * =========================================================================== */

/* 分步器：一排步骤，标出已过 / 当前 / 未到。 */
.import-steps {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-4);
  align-items: center;
  margin: var(--sp-3) 0;
  padding: 0;
  list-style: none;
}
.import-step {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  /* --text-2（AA 达标）而非 --text-3：步骤名是关键文本，不许放进"不承载关键文本"的弱化色。 */
  color: var(--text-2);
}
.import-step-n {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: 1.6rem;
  block-size: 1.6rem;
  border-radius: var(--r-pill);
  border: 1px solid var(--hairline);
  font-size: 0.8rem;
  font-variant-numeric: tabular-nums;
}
.import-step[data-state='done'] {
  color: var(--text-2);
}
.import-step[data-state='done'] .import-step-n {
  border-color: var(--ok);
  color: var(--ok);
}
.import-step[data-state='current'] {
  color: var(--text);
  font-weight: 600;
}
.import-step[data-state='current'] .import-step-n {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent-700);
}

/* 批次页头：文件名 + 徽章 + 元数据一排。 */
.import-batch-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2) var(--sp-3);
}

/* dry-run / 结果计数：等宽三格。 */
.import-dryrun-grid {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
  margin: var(--sp-3) 0;
}
.import-metric {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--hairline);
  border-radius: var(--r-md);
  min-inline-size: 6rem;
}
.import-metric-n {
  font-size: 1.4rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--text);
}
.import-metric-l {
  font-size: 0.85rem;
  color: var(--text-3);
}

/* 错误行计数 / 逐行违约列表。 */
.import-error-count {
  color: var(--bad);
  font-variant-numeric: tabular-nums;
}
.import-violations {
  margin: 0;
  padding-inline-start: var(--sp-4);
}
.import-violations li {
  color: var(--text-2);
}

/* 宽表格横向滚动（预览 / 修复表可能很宽，不撑破页面）。 */
.table-scroll {
  overflow-x: auto;
}
