/* ============================================================================
 * FITEASE Design System 1.0 —— 候选令牌层 + 组件基线（样板间用）
 *
 * 状态：**已进产品**（2026-09-22 订正）。本文件就是 `web/tokens.css`，`index.html:1` 把它排在
 *       `styles.css` → `brand.css` 之后加载，是**唯一令牌层**。
 *       ⚠️ 这里原本写的是「提案，未进产品／在用户确认样板间之前本文件只活在 `_validation/`」——
 *       那是 UIUX-B1 时期的历史表述，**早已不成立**（UIUX-B2 起它就在产品里，DS1–DS6 又持续追加）。
 *
 * 三条设计原则（来自 UIUX-B1 实测裁定）：
 *   1. **加法式**：`brand.css` 现有的 12 个令牌名（--accent/--bg/--surface/…）是
 *      事实权威，且已有 84 处 var() 引用 —— 本文件**不重定义它们**，只补缺失的类别。
 *      不新建平行命名空间，避免制造第三套来源。
 *   2. **值有出处**：所有颜色取自品牌现行生效值（Chrome 差分实测确认的那一套），
 *      不是我另立配色。见 TASK01_RULING_TABLE.md。
 *   3. **新组件用 `fx-` 前缀**：既有 467（styles）+ 238（brand）个选择器已很拥挤，
 *      `fx-` 既不撞名又可 grep，便于"新页面默认用对"。
 *
 * 已并入用户确认的三条取舍：
 *   · button:disabled  —— 区分「忙」与「禁用」（提交中用 wait，永久禁用用 not-allowed）
 *   · 字体              —— **弃用 Inter**，沿用 brand 的系统字体栈（Inter 无 webfont 来源，
 *                          且从未生效；引入它会多一个外部资产依赖）
 *   · .cards 列宽       —— 由 minmax(160px) 提到 minmax(240px)，避免 1440px 下挤出 8 条窄轨道
 * ========================================================================== */

:root {
  /* ── 字体：沿用 brand.css 现行系统栈（Inter 已弃用）──────────────────── */
  --fx-font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC",
                  "Microsoft YaHei", sans-serif;
  --fx-font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  /* ── 字号 / 行高 / 字重 ────────────────────────────────────────────── */
  --fx-text-display: 34px;  --fx-leading-display: 1.2;
  --fx-text-h1:      24px;  --fx-leading-h1:      1.3;
  --fx-text-h2:      18px;  --fx-leading-h2:      1.4;
  --fx-text-h3:      16px;  --fx-leading-h3:      1.45;
  --fx-text-body:    14px;  --fx-leading-body:    1.6;
  --fx-text-small:   13px;  --fx-leading-small:   1.55;
  --fx-text-caption: 12px;  --fx-leading-caption: 1.5;
  --fx-text-label:   12px;  --fx-leading-label:   1.4;
  --fx-text-numeric: 28px;  --fx-leading-numeric: 1.15;

  --fx-weight-regular: 400;
  --fx-weight-medium:  500;
  --fx-weight-bold:    600;

  /* ── 间距：4px / 8px 栅格（对齐基线里 671 条 px 字面量的高频值）──────── */
  --fx-space-1:  4px;
  --fx-space-2:  8px;
  --fx-space-3:  12px;
  --fx-space-4:  16px;
  --fx-space-5:  20px;
  --fx-space-6:  24px;
  --fx-space-8:  32px;
  --fx-space-10: 40px;
  --fx-space-12: 48px;
  --fx-space-16: 64px;
  /* 语义间距 */
  --fx-gap-inline:   var(--fx-space-2);
  --fx-gap-field:    var(--fx-space-3);
  --fx-gap-section:  var(--fx-space-6);
  --fx-pad-card:     var(--fx-space-5);
  --fx-pad-panel:    var(--fx-space-6);
  --fx-page-gutter:  var(--fx-space-8);

  /* ── 圆角 ──────────────────────────────────────────────────────────── */
  --fx-radius-sm:   4px;
  --fx-radius-md:   6px;
  --fx-radius-lg:   10px;
  --fx-radius-xl:   14px;
  --fx-radius-pill: 999px;

  /* ── 阴影 / 层级 ───────────────────────────────────────────────────── */
  --fx-shadow-xs: 0 1px 2px #1b315208;
  --fx-shadow-sm: 0 2px 5px #1b315204;
  --fx-shadow-md: 0 6px 20px #26395118;
  --fx-shadow-lg: 0 20px 60px #26395130;
  --fx-shadow-pop: 0 8px 36px #26395126;

  --fx-z-base: 1; --fx-z-sticky: 20; --fx-z-drawer: 60;
  --fx-z-modal: 80; --fx-z-toast: 100;

  /* ── 动效（>300ms 必须有理由）────────────────────────────────────── */
  --fx-dur-fast: 120ms;
  --fx-dur-base: 200ms;
  --fx-ease: cubic-bezier(.2, .8, .3, 1);

  /* ── 状态色：值 = brand.css 现行生效值（实测确认）──────────────────── */
  --fx-danger-bg:   #fff0f1;  --fx-danger-line: #edc0c7;  --fx-danger-text: #b5263d;
  --fx-warn-bg:     #fff8e6;  --fx-warn-line:   #e9d3a0;  --fx-warn-text:   #92620a;
  --fx-ok-bg:       #edf8f2;  --fx-ok-line:     #b1dbc4;  --fx-ok-text:     #1d7550;
  --fx-neutral-bg:  #eef3fb;  --fx-neutral-line: #dbe4f2; --fx-neutral-text: #526987;
  --fx-info-bg:     var(--accent-soft);
  --fx-info-line:   #c9dbff;  --fx-info-text:   var(--accent-hover);

  /* ── 表格密度 ──────────────────────────────────────────────────────── */
  --fx-row-h:        44px;
  --fx-cell-pad-y:   10px;
  --fx-cell-pad-x:   14px;

  /* ── 焦点环（styles.css 里 :focus/outline 均为 0，这是补的基线）────── */
  --fx-focus-ring: 0 0 0 3px #2468f22e;
  --fx-card-min: 240px;   /* 取代 minmax(160px) */
}

/* ============================================================================
 * 组件基线（fx- 前缀）—— 样板间演示范围
 * 目标：新页面照着写就默认正确；老页面按批迁移。
 * ========================================================================== */

/* ── 焦点可见：全站基线，不依赖颜色单独表意 ───────────────────────────── */
.fx-focusable:focus-visible,
.fx-btn:focus-visible,
.fx-chip:focus-visible,
a:focus-visible,
button:focus-visible,
select:focus-visible,
input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  box-shadow: var(--fx-focus-ring);
  border-radius: var(--fx-radius-sm);
}

/* ── 页面头 ───────────────────────────────────────────────────────────── */
.fx-page-head { display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--fx-gap-section); margin-bottom: var(--fx-gap-section); }
.fx-page-head__title { font-size: var(--fx-text-h1); line-height: var(--fx-leading-h1);
  font-weight: var(--fx-weight-bold); color: var(--text); margin: 0; }
.fx-page-head__desc { font-size: var(--fx-text-small); color: var(--muted); margin: var(--fx-space-1) 0 0; }
.fx-page-head__actions { display: flex; gap: var(--fx-gap-inline); flex: 0 0 auto; }

/* ── 按钮：一个页面只允许一个 primary ─────────────────────────────────── */
.fx-btn { display: inline-flex; align-items: center; gap: var(--fx-space-2);
  min-height: 34px; padding: 0 var(--fx-space-4); font-size: var(--fx-text-small);
  font-family: inherit; border: 1px solid var(--line); border-radius: var(--fx-radius-md);
  background: var(--surface); color: var(--text); cursor: pointer;
  transition: background var(--fx-dur-fast) var(--fx-ease), border-color var(--fx-dur-fast) var(--fx-ease); }
.fx-btn:hover:not(:disabled) { background: var(--raised); border-color: #c9d5e6; }
.fx-btn--primary { background: var(--accent); border-color: var(--accent); color: #fff;
  font-weight: var(--fx-weight-medium); }
.fx-btn--primary:hover:not(:disabled) { background: var(--accent-hover); border-color: var(--accent-hover); }
.fx-btn--danger { color: var(--fx-danger-text); border-color: var(--fx-danger-line); background: var(--fx-danger-bg); }
/* 禁用 vs 忙：语义不同，cursor 也不同（用户已确认的取舍）*/
.fx-btn:disabled { cursor: not-allowed; opacity: .55; }
.fx-btn[aria-busy="true"] { cursor: wait; opacity: .7; }

/* ── 状态标签：颜色 + 图标 + 文字，三重表达 ───────────────────────────── */
.fx-chip { display: inline-flex; align-items: center; gap: 5px; height: 22px;
  padding: 0 var(--fx-space-2); border: 1px solid transparent; border-radius: var(--fx-radius-sm);
  font-size: var(--fx-text-caption); line-height: 1; font-weight: var(--fx-weight-medium);
  white-space: nowrap; }
.fx-chip__dot { width: 6px; height: 6px; border-radius: var(--fx-radius-pill); background: currentColor; flex: 0 0 auto; }
.fx-chip--pending   { background: var(--fx-warn-bg);    border-color: var(--fx-warn-line);    color: var(--fx-warn-text); }
.fx-chip--approved  { background: var(--fx-ok-bg);      border-color: var(--fx-ok-line);      color: var(--fx-ok-text); }
.fx-chip--rejected  { background: var(--fx-danger-bg);  border-color: var(--fx-danger-line);  color: var(--fx-danger-text); }
.fx-chip--draft     { background: var(--fx-neutral-bg); border-color: var(--fx-neutral-line); color: var(--fx-neutral-text); }
/* UIUX-B7：中性档。**必须存在** —— `app.js:197` 对未识别的状态值会回退到它；
   若回退到裸 `.fx-chip`（`border:1px solid transparent` + 无背景），
   未映射的状态会渲染成几乎看不见的芯片（B7 首次验证时实测到的回归）。 */
.fx-chip--neutral   { background: var(--fx-neutral-bg); border-color: var(--fx-neutral-line); color: var(--fx-neutral-text); }
.fx-chip--cancelled { background: var(--raised);        border-color: var(--line);            color: var(--muted); }
/* UIUX-B7：补齐 `app.js:197` 徽章注入点实际会用到的否定态取值。
   该点硬编码 ['DENY','deny','rejected','revoked','disabled']，而原先只有 `rejected`
   有对应芯片 ⇒ `revoked` / `disabled` 会掉成"看起来中性"。`DENY`/`deny` 由映射归入
   `--rejected`（同为"被否定"）。**纯新增，不改上面任何一条。** */
.fx-chip--revoked   { background: var(--fx-danger-bg);  border-color: var(--fx-danger-line);  color: var(--fx-danger-text); }
.fx-chip--disabled  { background: var(--raised);        border-color: var(--line);            color: var(--muted); }

/* ── UIUX-B7：把既有的 `.badge` / `.pill` 统一到芯片外观 ────────────────────
   为什么放在这里：`.badge` 的几何在 `styles.css:4`、颜色在 `brand.css:7`，
   而本文件是 `index.html` 里**最后加载**的（styles → brand → tokens），
   所以只有在这里才能一次性覆盖两者，且不必去改 4 个 B 层页面文件。

   为什么需要它：`app.js:197` 的注入点只覆盖走共享 `table()` 的列
   （实测 57 个渲染点 / 59 个列），而仓里还有 **24 处手写 `.badge`/`.pill`**
   与 **3 张自建 `<table>`** 不走注入点。若只改注入点，平台上会同时出现
   "新芯片"和"旧徽章"两种外观。这一段让手写站点**不改一行 JS** 就与芯片同形，
   连圆点也用 `::before` 补上（色·点·字三重表达）。

   ⚠️ 这一段**不是 no-op**：它有意改变 `.badge`/`.pill` 的既有外观。
   `UIUX-B5_TOKENS_REPORT.md` 里"tokens.css 对现有渲染是 no-op"的结论
   **自 B7 起不再成立**，已在该报告与本文件注明。

   后续清理：24 处手写站点最终应迁到 `fx-chip`（清单见
   `_validation/uiux-tools/recon-chip-migration.md`），届时本段可收窄为
   `.pill` 兜底或整体删除。 */
.badge, .pill { display: inline-flex; align-items: center; gap: 5px;
  height: 22px; padding: 0 var(--fx-space-2);
  border: 1px solid var(--fx-neutral-line); border-radius: var(--fx-radius-sm);
  background: var(--fx-neutral-bg); color: var(--fx-neutral-text);
  font-size: var(--fx-text-caption); line-height: 1; font-weight: var(--fx-weight-medium);
  white-space: nowrap; }
.badge::before, .pill::before { content: ''; width: 6px; height: 6px;
  border-radius: var(--fx-radius-pill); background: currentColor; flex: 0 0 auto; }
/* 既有的否定态（`brand.css:7` 的 `.badge.deny`）对齐到芯片的 danger 档 */
.badge.deny { background: var(--fx-danger-bg); border-color: var(--fx-danger-line); color: var(--fx-danger-text); }

/* ── 工具条（取代 list-tools 的散装写法）─────────────────────────────── */
.fx-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: var(--fx-gap-inline);
  padding: var(--fx-space-3) var(--fx-space-4); background: var(--raised);
  border: 1px solid var(--line); border-radius: var(--fx-radius-lg); margin-bottom: var(--fx-space-3); }
.fx-toolbar__hint { font-size: var(--fx-text-caption); color: var(--muted); margin-left: auto; }

/* ── 表格 ─────────────────────────────────────────────────────────────── */
.fx-table-wrap { border: 1px solid var(--line); border-radius: var(--fx-radius-lg);
  background: var(--surface); overflow: hidden; }
.fx-table-scroll { overflow-x: auto; }
.fx-table { width: 100%; border-collapse: collapse; font-size: var(--fx-text-small); }
.fx-table thead th { position: sticky; top: 0; z-index: var(--fx-z-sticky);
  background: var(--raised); color: var(--muted); font-size: var(--fx-text-label);
  font-weight: var(--fx-weight-medium); text-align: left; letter-spacing: .02em;
  padding: var(--fx-space-3) var(--fx-cell-pad-x); border-bottom: 1px solid var(--line); }
.fx-table tbody td { padding: var(--fx-cell-pad-y) var(--fx-cell-pad-x);
  border-bottom: 1px solid #eef1f7; color: var(--text); vertical-align: middle; }
.fx-table tbody tr:hover td { background: #f8faff; }
.fx-table tbody tr:last-child td { border-bottom: 0; }
.fx-table__num { font-variant-numeric: tabular-nums; }
/* 窄屏：不隐藏表头，改为整行卡片（替代 content:attr(data-label) 那套幻术）*/
@media (max-width: 1000px) {
  .fx-table thead { display: none; }
  .fx-table tbody tr { display: grid; grid-template-columns: 1fr 1fr; gap: var(--fx-space-1) var(--fx-space-3);
    padding: var(--fx-space-3); border-bottom: 1px solid var(--line); }
  .fx-table tbody td { display: flex; flex-direction: column; gap: 2px; padding: 0; border: 0; }
  .fx-table tbody td::before { content: attr(data-label); font-size: var(--fx-text-caption);
    color: var(--muted); }
}

/* ── 空态：必须回答三个问题 ───────────────────────────────────────────── */
.fx-empty { display: flex; flex-direction: column; align-items: center; text-align: center;
  gap: var(--fx-space-2); padding: var(--fx-space-12) var(--fx-space-6); }
.fx-empty__icon { width: 40px; height: 40px; color: #b9c4d6; }
.fx-empty__title { font-size: var(--fx-text-h3); font-weight: var(--fx-weight-medium); color: var(--text); }
.fx-empty__why { font-size: var(--fx-text-small); color: var(--muted); max-width: 42ch; }
.fx-empty__actions { margin-top: var(--fx-space-2); display: flex; gap: var(--fx-gap-inline); }

/* ── 加载：骨架屏（现状只有 BI 有）───────────────────────────────────── */
.fx-skeleton { border-radius: var(--fx-radius-sm);
  background: linear-gradient(90deg, #eef1f7 25%, #f6f8fc 37%, #eef1f7 63%);
  background-size: 400% 100%; animation: fx-shimmer 1.4s ease-in-out infinite; }
.fx-skeleton--text { height: 12px; }
@keyframes fx-shimmer { 0% { background-position: 100% 0; } 100% { background-position: 0 0; } }
@media (prefers-reduced-motion: reduce) { .fx-skeleton { animation: none; } }
.fx-progress { height: 3px; background: var(--accent-soft); overflow: hidden; position: relative; }
.fx-progress::after { content: ''; position: absolute; inset: 0 auto 0 0; width: 35%;
  background: var(--accent); animation: fx-slide 1.1s var(--fx-ease) infinite; }
@keyframes fx-slide { 0% { left: -35%; } 100% { left: 100%; } }

/* ── 错误：分类型 + 可恢复（现状是整页替换成一个 <p>）────────────────── */
.fx-error { display: flex; gap: var(--fx-space-3); padding: var(--fx-space-5);
  background: var(--fx-danger-bg); border: 1px solid var(--fx-danger-line);
  border-radius: var(--fx-radius-lg); color: var(--fx-danger-text); }
.fx-error__body { display: flex; flex-direction: column; gap: var(--fx-space-1); }
.fx-error__title { font-weight: var(--fx-weight-bold); }
.fx-error__hint { font-size: var(--fx-text-small); color: #8a3a49; }
.fx-error__actions { margin-top: var(--fx-space-2); }
.fx-inline-error { font-size: var(--fx-text-caption); color: var(--fx-danger-text); margin-top: var(--fx-space-1); }

/* ── 审批时间线（企业组件，平台最核心的一条流）──────────────────────── */
.fx-timeline { list-style: none; margin: 0; padding: 0; }
.fx-timeline li { position: relative; padding: 0 0 var(--fx-space-5) var(--fx-space-6); }
.fx-timeline li::before { content: ''; position: absolute; left: 4px; top: 6px; bottom: -2px;
  width: 2px; background: var(--line); }
.fx-timeline li:last-child { padding-bottom: 0; }
.fx-timeline li:last-child::before { display: none; }
.fx-timeline__dot { position: absolute; left: 0; top: 3px; width: 10px; height: 10px;
  border-radius: var(--fx-radius-pill); background: var(--surface); border: 2px solid var(--line); }
.fx-timeline li[data-state="done"] .fx-timeline__dot { background: var(--fx-ok-text); border-color: var(--fx-ok-text); }
.fx-timeline li[data-state="current"] .fx-timeline__dot { background: var(--accent); border-color: var(--accent); }
.fx-timeline li[data-state="rejected"] .fx-timeline__dot { background: var(--fx-danger-text); border-color: var(--fx-danger-text); }
.fx-timeline__who { font-size: var(--fx-text-small); font-weight: var(--fx-weight-medium); }
.fx-timeline__when { font-size: var(--fx-text-caption); color: var(--muted); }

/* ── 卡片：列宽下限由 160 → 240（用户已确认的取舍）──────────────────── */
.fx-cards { display: grid; gap: var(--fx-space-3);
  grid-template-columns: repeat(auto-fit, minmax(var(--fx-card-min), 1fr)); }
.fx-card { background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--fx-radius-lg); padding: var(--fx-pad-card); }
.fx-card__label { font-size: var(--fx-text-label); color: var(--muted); }
.fx-card__value { font-size: var(--fx-text-numeric); font-weight: var(--fx-weight-bold);
  color: var(--text); font-variant-numeric: tabular-nums; margin-top: var(--fx-space-1); }


/* ── UIUX-B7：页级骨架屏（替代 `app.js:272` 的「正在加载…」文本）────────────
   依据 R-D 侦察：改法 A（只换占位节点）= 1 处、**零语义风险**，可立即让
   **每一个页面**在取数期间有真实骨架，且不触碰 `app.js:101` 的
   `!root.isConnected` 折叠判据（改法 B 会让全站「页面操作」工具条静默消失）。 */
.fx-skeleton-page { padding: var(--fx-space-2) 0; display: flex; flex-direction: column; gap: var(--fx-space-5); }
.fx-skeleton-block { display: flex; flex-direction: column; gap: var(--fx-space-3); }
.fx-skeleton--title { height: 20px; width: 220px; }
.fx-skeleton-row { display: grid; grid-template-columns: 90px 1fr 1fr; gap: var(--fx-space-3); }
.fx-skeleton-row > .fx-skeleton--text:first-child { width: 70%; }

/* ── UIUX-B7：共享确认弹窗（新建）───────────────────────────────────────────
   仓库里**不存在**通用是非确认框（R-C 8 项口径穷举全为 0）。本组件补三项能力：
   ① 多段正文（数组 → 多个 <p>，不依赖 `\n` 换行 —— `#notice` 无 white-space，`\n` 会塌缩）；
   ② `**强调**` 解析为 <strong>（全站零 innerHTML、无 markdown 渲染器，
      既有 `window.confirm` 文案里的星号**当前是裸显给用户看的**）；
   ③ 危险操作样式。 */
.fx-confirm { border: 1px solid var(--line); border-radius: var(--fx-radius-xl);
  padding: var(--fx-pad-card); max-width: min(560px, 92vw); box-shadow: var(--fx-shadow-lg);
  color: var(--text); background: var(--surface); }
.fx-confirm::backdrop { background: #17233d66; }
.fx-confirm h2 { margin: 0 0 var(--fx-space-3); font-size: var(--fx-text-h3); font-weight: var(--fx-weight-bold); }
.fx-confirm-body { display: flex; flex-direction: column; gap: var(--fx-space-2);
  font-size: var(--fx-text-small); line-height: var(--fx-leading-small); color: var(--text); }
.fx-confirm-body p { margin: 0; }
.fx-confirm-body strong { font-weight: var(--fx-weight-bold); color: var(--fx-danger-text); }
.fx-confirm-actions { display: flex; justify-content: flex-end; gap: var(--fx-gap-inline);
  margin-top: var(--fx-space-5); }
/* ══ UIUX-W1：共享横向标签栏（`.fx-tabbar` / `.fx-tab`）═══════════════════════════
   来源：用户已确认的 demo `_validation/uiux-shots/UIUX-W1-STARTPAGE/w1-demo.css:246-311`
   （`hall.html` 与 `index.html?v=c` **同一套 DOM 与同一套类名**，见诊断报告 §12.6）。
   本块是 **纯追加**（`COLLABORATION.md` §3 A 层规则）：不改上面任何一条既有规则、任何令牌。

   两处共用：
     · 发起大厅  —— 横向分组栏 + 卡片区（切组）
     · 发起流程页 —— 顶部横向分区栏 + 两列网格（切分区）

   为什么是"一条恒定高的横栏"（数值全部照抄 demo，未改一个）：
     · 只有一行 ⇒ 分组/分区变多时栏高不变、下方卡片区不会上下跳。这正是不选"换行"的理由。
     · 溢出不藏进「更多」菜单 —— 用户的诉求是快速切换，藏起来等于每次多点一次。
     · roving tabindex：16 个标签在 Tab 序列里仍只占 **1 个停靠点**（实测见 §12.7/§13.4）。

   量值（demo 实测，1440×900）：栏高 42px、项高 30px、角标 12px + tabular-nums、
   渐隐宽 26px、滚动按钮 24px、横滚项间距 4px。

   ⚠️ 与 `styles.css` 的层叠关系（本题必须写清）：
     本文件**未分层**，`styles.css` 的相关规则在 `@layer u3-chip-fallback` 里
     ⇒ **未分层规则永远胜过分层规则**（与加载顺序、与特异度都无关）。
     本块选择的都是 `.fx-` 新类名，`styles.css` / `brand.css` 里**一个都没有**
     （grep 实测：`fx-tab` 命中 0 条选择器，只有一条注释提到 `fx-table`），
     所以本块**不参与任何既有级联竞争**，也不会意外压死别人的声明。
     唯一一处**有意**的竞争写在下面 `.fx-tab:focus-visible` 那段注释里。
   ────────────────────────────────────────────────────────────────────────── */

/* 外壳：给两端渐隐与滚动按钮当定位上下文 */
.fx-tabwrap { position: relative; }

/* 栏本体：**恒定一行**（`flex-wrap: nowrap` —— 这条是窄屏验收的关键，
   demo 窄屏未验；它同时保证 360px 下与宽屏下栏高口径一致） */
.fx-tabbar { display: flex; align-items: center; gap: var(--fx-space-1);
  flex-wrap: nowrap;
  padding: 5px; background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--fx-radius-lg); overflow-x: auto; overflow-y: hidden;
  scroll-behavior: smooth; scrollbar-width: thin; }
.fx-tabbar::-webkit-scrollbar { height: 6px; }
.fx-tabbar::-webkit-scrollbar-thumb { background: #d3dcea; border-radius: 3px; }

/* 项：30px 高、`flex: 0 0 auto` 不收缩（不收缩才有可滚的宽度）、`nowrap` 不折字 */
.fx-tab { flex: 0 0 auto; display: inline-flex; align-items: center; gap: 6px;
  height: 30px; padding: 0 12px; border: 1px solid transparent; background: none;
  border-radius: var(--fx-radius-md); cursor: pointer; font: inherit;
  font-size: var(--fx-text-small); color: #41506b; white-space: nowrap; }
/* hover：未选中项用 `--raised`。
   ⚠️ 这里**必须写 `body .fx-tab:hover:not(:disabled)`**，不能只写 `.fx-tab:hover`：
   `brand.css:4` 有一条 `button:hover:not(:disabled){background:#f1f5fc;border-color:#a9bfe5}`
   —— 它的特异度与 `.fx-tab:hover` **相等**（都是 (0,2,1)，`:not()` 本身不计分），
   而我实测到它**真的赢了**：`.fx-tab:hover` 的计算背景是 `rgb(241,245,252)` 而不是
   `--raised` 的 `rgb(247,249,252)`（探针 `y1-probe3.json` 的 `hoverOn`）。
   加 `body` 前缀把它抬到 (0,2,2)，胜出不再依赖加载顺序 —— 这正是仓里
   `styles.css:907-908` 已经写明的做法（"一律用 body 前缀把特异度提上去，不靠运气"）。
   同时**显式抄下 demo 没有、但被 button 基线带来的两条**：`filter:none` 与边框色，
   否则 hover 时会同时出现品牌蓝灰边（`#a9bfe5`）与 `brightness(.96)` 滤镜，
   与 demo 的"只有底色变 `--raised`"不一致。 */
body .fx-tab:hover:not(:disabled) { background: var(--raised); border-color: transparent; filter: none; }

/* 选中态：accent 实底 + 白字（实底是"这条栏只能有一个当前项"的最强表达） */
.fx-tab[aria-selected="true"] { background: var(--accent); border-color: var(--accent);
  color: #fff; font-weight: var(--fx-weight-medium); }

/* 键盘焦点可见：demo 里 `.fx-tab` **没有**自己的 focus 规则，
   实际由 `tokens.css:113` 的全局 `button:focus-visible` 兜住（2px accent + 3px 环）。
   本块把它**显式化**，并把圆角从全局那条的 `--fx-radius-sm`(4px) 提到 `.fx-tab` 自己的
   `--fx-radius-md`(6px)，让焦点环贴合项的真实圆角。
   唯一的有意竞争：本选择器 (0,2,0) 对 `button:focus-visible` (0,1,1)，
   本规则胜出 ⇒ 时**只有圆角**与全局不同，outline/offset/box-shadow 三个取值一字不改。 */
.fx-tab:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px;
  box-shadow: var(--fx-focus-ring); border-radius: var(--fx-radius-md); }

/* 角标：12px（`--fx-text-caption` 的值，**带 12px 字面回退** ——
   这样即使令牌层失效也不会掉成继承的 13px）+ tabular-nums（数字不跳宽） */
.fx-tab__name { flex: 0 0 auto; }
.fx-tab__count { font-size: var(--fx-text-caption, 12px); font-variant-numeric: tabular-nums;
  color: var(--muted); }
.fx-tab[aria-selected="true"] .fx-tab__count { color: #ffffffd9; }

/* 两端渐隐：只在溢出侧出现（`.on` 由页面 JS 按 scrollLeft 切），不吃指针事件 */
.fx-tabbar__fade { position: absolute; top: 0; bottom: 0; width: 26px; opacity: 0;
  pointer-events: none; transition: opacity var(--fx-dur-fast) var(--fx-ease); }
.fx-tabbar__fade.on { opacity: 1; }
.fx-tabbar__fade--l { left: 0; border-radius: var(--fx-radius-lg) 0 0 var(--fx-radius-lg);
  background: linear-gradient(to right, var(--surface), transparent); }
.fx-tabbar__fade--r { right: 0; border-radius: 0 var(--fx-radius-lg) var(--fx-radius-lg) 0;
  background: linear-gradient(to left, var(--surface), transparent); }

/* 左右滚动按钮：**仅在溢出时出现**（demo 用 `hidden` 属性切，页面 JS 负责）。
   与 `.fx-btn` 同时使用（demo 的类名就是 `fx-btn fx-tabscroll ...`），
   这里把几何收紧到 24×24 圆形，覆盖 `.fx-btn` 的 min-height/padding。
   禁用态直接沿用 `.fx-btn:disabled`（`cursor:not-allowed` + `opacity:.55`），不另立一套。 */
.fx-tabscroll { position: absolute; top: 50%; transform: translateY(-50%); z-index: 2;
  width: 24px; height: 24px; min-height: 24px; padding: 0; justify-content: center;
  font-size: 13px; line-height: 1; border-radius: var(--fx-radius-pill);
  background: var(--surface); border: 1px solid var(--line); box-shadow: var(--fx-shadow-xs); }
.fx-tabscroll--l { left: -1px; }
.fx-tabscroll--r { right: -1px; }
.fx-tabscroll[hidden] { display: none; }

/* ── 分区栏/分组栏上的"填写状态"扩展（发起页专用语义，位置与颜色规则与角标一致）──────
   为什么要它：真切换会把未选中的分区藏起来 ⇒ 必须在栏上就能看出哪个分区还有未填的必填项，
   否则又会变成"报错说必填没填、用户在可见区域找不到它"（诊断报告 §13.2 保护①）。 */
.fx-tab__state { font-size: var(--fx-text-caption, 12px); font-variant-numeric: tabular-nums; }
.fx-tab__state--ok { color: var(--fx-ok-text); }
.fx-tab__state--todo { color: var(--fx-warn-text); font-weight: var(--fx-weight-medium); }
.fx-tab__state--opt { color: var(--muted); }
.fx-tab[aria-selected="true"] .fx-tab__state--ok,
.fx-tab[aria-selected="true"] .fx-tab__state--todo,
.fx-tab[aria-selected="true"] .fx-tab__state--opt { color: #ffffffde; }
/* 未选中但**还有未填必填项**的分区：左侧一个琥珀点，扫一眼就能看见 */
.fx-tab__dot { flex: 0 0 auto; width: 6px; height: 6px; border-radius: var(--fx-radius-pill);
  background: var(--fx-warn-text); }
.fx-tab[aria-selected="true"] .fx-tab__dot { background: #fff; }

/* ── 窄屏（360px）：栏**保持一行、横向滚动**，绝不换行 ────────────────────────────
   这里只做两件"避免栏被挤坏"的事，**不改任何几何数值**（栏高/项高/字号/角标一律不变）：
     · 项内边距 12 → 10px：360px 下能多露出约 1 项（可滚宽度小一点，滚动更容易停准）；
     · 渐隐 26 → 20px：360px 视口里 26px 的两端渐隐占比过大。
   规范值（栏高 42 / 项高 30 / 角标 12 / 圆角）**一个都没动**，所以宽屏与窄屏是同一个组件。 */
@media (max-width: 480px) {
  .fx-tab { padding: 0 10px; }
  .fx-tabbar__fade { width: 20px; }
}

/* ══ UIUX-Y3：发起大厅卡片区 / 面板头 / 标签 / 两列网格（`.fx-flow*` / `.fx-panel-head*` /
   `.fx-tag` / `.fx-grid*` / `.fx-col--full` / `.fx-section*`）════════════════════════════
   来源：用户已确认的 demo `_validation/uiux-shots/UIUX-W1-STARTPAGE/w1-demo.css`
   （`.fx-flowgrid`/`.fx-flow*` = :222-238；`.fx-grid*`/`.fx-col--full` = :28-35；
   `.fx-panel-head*` = :287-292；`.fx-tag` = :82-85；`.fx-section*` = :16-25）。
   本块是**纯追加**（`COLLABORATION.md` §3 A 层规则）：上面 429 行、77 个令牌与
   Y1 落地的 `.fx-tabbar` 系列**一行未动**（有逐块机器断言，见报告 §3）。

   为什么需要它：`web/oa.js` 已经在产出这些类名（Y2b 交付的发起大厅 + 发起页分区），
   但产品样式表里**这些类一条规则都没有** ⇒ 页面"有结构没外观"，用户读到的就是
   「太原始了」。本块把 demo 的组件规则搬进设计系统层，DOM 一个字节不用改。

   ⚠️ 与 `brand.css` / `styles.css` 的层叠关系（本题必须写清，Y1 已在此吃过亏）：
     · 本文件**未分层**；`styles.css` 里有 `@layer u3-chip-fallback`（`:789` 与 `:882`）
       ⇒ **未分层规则永远胜过分层规则**（与加载顺序、与特异度都无关）。
       实测复现：`.fx-chip` 的级联里一条 `body td .fx-chip` 都没有（Y1 §6.3/§6.4）。
     · `styles.css` / `brand.css` 里**没有任何 `.fx-flow*` / `.fx-panel-head*` / `.fx-tag` /
       `.fx-grid*` / `.fx-col--full` / `.fx-section*` / `.fx-switchpanel` 选择器**
       （grep 实测：两文件里唯一的 `.fx-*` 是 `.fx-chip` 与 `.fx-table`）
       ⇒ 本块的争用**全部来自元素级基线**，不是来自同名类。

   ⚠️ 为什么每条都写 `body` 前缀（**不是装饰**）：本块的规则命中 `<article>` `<h3>` `<p>`
     `<span>` `<section>` `<label>` `<div>` 这些**元素**，而 `brand.css:3` 有
     `h1,h2,h3,h4{color:var(--text)}` 与 `p,.muted,label{color:var(--muted)}`（(0,0,1)）、
     `styles.css:4` 有 `.panel h3{font-size:14px}`（(0,1,1)）与 `textarea{min-height:82px}`
     （(0,0,1)）、`brand.css:6` 有 `.panel>p{margin:7px 0 12px}`（(0,1,1)）。
     只写 `.fx-flow__name` 这类 (0,1,0) 的选择器时，**`.panel h3` (0,1,1) 会直接赢**
     （`.fx-flow__name` 就是 `<h3>`），卡片标题会掉回 14px。
     `body` 前缀把本块整体抬到 (0,1,1)+ ⇒ 与 Y1 §6.1 的修法同一条路子，
     也正是 `styles.css:907-908` 自己写明的"用 body 前缀提特异度，不靠运气"。
     逐条特异度对照表见报告 §6。

   ⚠️ 刻意**没搬**的（见报告 §7 逐条理由）：`.fx-measure`（demo 量测条）、`.fx-vswitch`
   （形态切换器）、`#hall-panel:focus-visible`（那是 demo 的 id，面板 id 由页面自己定）；
   （表单字段层：`.fx-input` `.fx-select` `.fx-textarea` `.fx-picker` `.fx-drop`
   `.fx-filelist` `.fx-lines` `.fx-iconbtn` `.fx-actionbar`；版式候选层：`.fx-split`
   `.fx-anchor`；大厅搜索层：`.fx-hallbar` `.fx-search` `.fx-group`；我的任务层：
   `.fx-task` 族 `.fx-pending` `.fx-jump`）—— 产品 `oa.js` **当前一个都没产出**，
   现在搬等于往设计系统里放一次性死规则。要它们请另开一轮（清单已在报告 §7 登记）。
   ⚠️ **UIUX-A11 订正（2026-09-24）**：上表四个里 **`.fx-iconbtn` 已实现** —— 见文件末尾
   「UIUX-A11」块；首个真实使用者是 `app.js` 的组织架构展开钮（它原先靠 `btn.style.cssText`
   自带一整块内联样式，A11 起改用这个类）。**其余三个**（`.fx-filelist` / `.fx-lines` /
   `.fx-actionbar`）在 `web/*.js` 里**仍是 0 发射**（A11 只读 grep 实测）⇒ 本段对它们三个
   的描述依然准确，**不要当成"已实现"**。
   ────────────────────────────────────────────────────────────────────────────────── */

/* ── 大厅卡片网格：自动填充的等宽列 ─────────────────────────────────────────────────
   demo `:222` 逐字照搬（`--fx-card-min` = 240px，本文件 `:100` 的既有令牌，非新造）。
   实测 761px 可用宽下产 2 列 / 每列 339px（见报告 §5）。 */
body .fx-flowgrid { display: grid; gap: var(--fx-space-3);
  grid-template-columns: repeat(auto-fill, minmax(var(--fx-card-min), 1fr)); }

/* ── 一张流程卡 ────────────────────────────────────────────────────────────────────
   demo `:224-227`。`flex-direction: column` + 卡脚 `margin-top:auto` ⇒
   同排卡片高度对齐时"发起"按钮永远贴底（这是 demo 卡片不齐整的解法）。 */
body .fx-flow { display: flex; flex-direction: column; gap: var(--fx-space-2);
  padding: var(--fx-space-4); border: 1px solid var(--line);
  border-radius: var(--fx-radius-lg); background: var(--surface); }
body .fx-flow:hover { border-color: #c9d5e6; box-shadow: var(--fx-shadow-xs); }

/* 卡头行容器（`.fx-flow__head`）。demo 里这个 div **没有类名**，靠内联
   `display:flex;align-items:center;gap:10px` 排成一行 —— 那 10px 是 demo 的字面量，
   不是令牌刻度（最近的刻度是 `--fx-space-3`=12px），本块**照抄 10px** 以免几何漂移。 */
body .fx-flow__head { display: flex; align-items: center; gap: 10px; }
body .fx-flow__head > * { min-width: 0; }

/* 首字图标：30×30 圆角方块（demo `:228-231`，逐字）。
   它同时是卡头那一行的高度来源（30px > 标题行高）⇒ 卡头实测 30px。 */
body .fx-flow__icon { flex: 0 0 auto; width: 30px; height: 30px;
  border-radius: var(--fx-radius-md); background: var(--accent-soft);
  color: var(--accent-hover); display: inline-flex; align-items: center;
  justify-content: center; font-size: 15px; font-weight: var(--fx-weight-bold); }

/* 标题（`<h3>`）：必须 `flex:1 1 0` + `min-width:0` 才不会被长名字顶破卡片；
   `width` 显式写 100% 是为了**不依赖 flex 的百分比解析**（Y1 对"靠默认行为赢"的教训）。 */
body .fx-flow__name { flex: 1 1 0; min-width: 0; width: 100%;
  font-size: var(--fx-text-h3); font-weight: var(--fx-weight-medium);
  color: var(--text); margin: 0; }

/* 芯片行（编号 / 版本 / 类型）：demo `:234` */
body .fx-flow__meta { display: flex; flex-wrap: wrap; gap: var(--fx-space-1); }

/* 卡脚：`margin-top:auto` 把它压到底（demo `:235-236`） */
body .fx-flow__foot { margin-top: auto; display: flex; align-items: center;
  justify-content: space-between; gap: var(--fx-space-2); }

/* 卡脚里的「发起」按钮：`.fx-btn` 的既有 `min-height:34px`（本文件 `:132`）比 demo 的
   卡片节奏高 4px，而 `.fx-btn` 的 (0,1,0) 与 `body .fx-flow__foot .fx-btn` 的 (0,2,1)
   是不同选择器、不会互相覆盖 —— 这里**显式收紧到 30px**，让按钮与卡头图标同高。
   这是本块**唯一一处有意偏离 demo 数值**（demo 未给这条按钮规定高度），理由与
   Y1 §7 #4（`.fx-tabscroll` 显式写 `min-height`）相同：把"恰好看着对"变成声明上的确定。 */
body .fx-flow__foot .fx-btn { min-height: 30px; }

/* ── 标签 `.fx-tag`（「⚠ 原分组已失效」）─────────────────────────────────────────────
   ⚠️ 这一条**不能只搬 demo 的 `.fx-tag`**：demo `hall.html:511-513` 的琥珀配色是
   **内联样式**（`tag.style.background = 'var(--fx-warn-bg)'` 等三行），而产品 `oa.js` 的
   同名节点**只有类名、没有内联样式**（CSP 是 `style-src 'self'`，内联 style 属性会被丢弃）
   ⇒ 只搬基础几何的话，这个标签会渲染成"中性灰底"，而它要表达的是**警告**。
   所以本块把 demo 内联的那三个值**落进类规则**（`--fx-warn-bg/--fx-warn-line/--fx-warn-text`
   全是本文件既有的 77 个令牌之一，没有新造颜色）。
   基础几何 = demo `:82-85` 逐字；`--fx-radius-pill` 与 `--fx-neutral-*` 三个中性令牌
   在本条里**用不到**（被琥珀档盖住），故略去以保持一条规则说清一件事。 */
body .fx-tag { display: inline-flex; align-items: center; gap: 5px; height: 22px;
  padding: 0 var(--fx-space-2); border-radius: var(--fx-radius-pill);
  background: var(--fx-warn-bg); border: 1px solid var(--fx-warn-line);
  color: var(--fx-warn-text); font-size: var(--fx-text-caption); }

/* ── 卡片区面板头（「全部流程 / 说明 / 5 个」）─────────────────────────────────────────
   demo `:287-292`。**一处按产品实测收窄的 margin**（不是几何改动，是把 demo 的语境
   换成产品的语境）：demo 的面板头自带 `margin: var(--fx-gap-section) 0 var(--fx-gap-field)`
   —— 在 demo 里它是"卡片区之间的分隔"；在产品里它是面板（`.panel` 已有 18px 内边距、
   且上方紧接 h2 + 说明）的**第一块内容**，再叠 24px 上边距会多出一段空白。
   ⚠️ 收窄的依据是**真机量到的 DOM 真相**：`#oa-hall-panel` 是**卡片网格本身**
   （`oa.js:387-388` 把 id 设在 `fx-flowgrid` 上），不是面板 ⇒ 面板头是它的**兄弟**。
   第一版我按"面板头在 #oa-hall-panel 里面"写了 `.fx-hall-panel .fx-panel-head`，
   CDP `CSS.getMatchedStylesForNode` 实测那条规则**根本没参与级联**（面板头的祖先链是
   `DIV.page-content > DIV#content > SECTION#app`，中间没有那条 id），margin-top 仍是 24px。
   改用 `.panel` 这个**真实存在的祖先**（`app.js:287` 的 `panel()` 造的就是它）后生效。
   下/左右边距与 demo 一致，未动。 */
body .fx-panel-head { display: flex; align-items: baseline; gap: var(--fx-space-2);
  margin: 0 0 var(--fx-gap-field); }
body .fx-panel-head__title { font-size: var(--fx-text-h3); font-weight: var(--fx-weight-bold);
  margin: 0; }
body .fx-panel-head__note { font-size: var(--fx-text-caption); color: var(--muted); margin: 0; }
body .fx-panel-head__count { margin-left: auto; font-size: var(--fx-text-caption);
  color: var(--muted); }

/* ── 两列网格（用户已批准的版式）─────────────────────────────────────────────────────
   demo `:28-35` 逐字：默认两列 / `--1` 单列 / `--3` 三列 / `--full` 跨全宽，
   并且 **≤900px 退成单列**（媒体查询照搬）。
   `> *` 的 `min-width:0` 是**必须补的一条**：网格项是 `<label class="oa-field">`，
   而 `styles.css:4` 给 `label` 铺了 `display:grid` —— 长文本/表格字段会把轨道顶宽，
   两列在窄容器里会横向溢出。`min-width:0` 让轨道按容器分配（实测 360px 无溢出，报告 §5）。 */
body .fx-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--fx-gap-field) var(--fx-space-4); }
body .fx-grid > * { min-width: 0; }
body .fx-grid--1 { grid-template-columns: minmax(0, 1fr); }
body .fx-grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
body .fx-col--full { grid-column: 1 / -1; }
@media (max-width: 900px) {
  body .fx-grid, body .fx-grid--3 { grid-template-columns: minmax(0, 1fr); }
}

/* ── 分区卡（发起页每个分区一张）─────────────────────────────────────────────────────
   demo `:16-25`。`<section>` 与 `<h3>` 都被 `brand.css:3` 的元素级规则命中，
   靠 `body` 前缀稳压（见块首说明）。 */
body .fx-section { background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--fx-radius-lg); padding: var(--fx-pad-panel); }
body .fx-section__head { display: flex; align-items: baseline; gap: var(--fx-space-3);
  margin: 0 0 var(--fx-space-4); padding: 0 0 var(--fx-space-3);
  border-bottom: 1px solid var(--line); }
body .fx-section__title { font-size: var(--fx-text-h3); font-weight: var(--fx-weight-bold);
  color: var(--text); margin: 0; }
body .fx-section__count { margin-left: auto; font-size: var(--fx-text-caption);
  color: var(--muted); }

/* 分区切换面板容器（`oa.js` 的 `fx-switchpanel`，`role=tabpanel` + `tabindex=0`）。
   demo 没有这条规则（demo 的候选 c 用别的方式承载），但它是**产品真实产出的节点**，
   且 `tabindex=0` 的面板必须有可见焦点环 —— 否则"从选中标签按 Tab 离开栏"之后
   读屏与键盘用户看不到焦点在哪（Y1 §5.2 已实测过 focus-visible 需要真键盘模态）。
   圆角对齐 `.fx-tab` 的 6px，与 `tokens.css:119` 的全局 4px 有意不同（同 Y1 §7 #6 的取舍）。 */
body .fx-switchpanel:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px;
  box-shadow: var(--fx-focus-ring); border-radius: var(--fx-radius-md); }

/* ══ 结束（本块到此为止，下面没有内容）══════════════════════════════════════════════ */

/* ══ UIUX-DS2（2026-09-22）错误态字段：`.fx-field--err` / `.fx-field__err` ══════════════════
   为什么补这两个：**产品代码真的会发射它们，而三个样式表里一条规则都没有。**
   实测（`_validation/uiux-tools/ds2-fx-classes.cjs`，只读）：全 `web/*.js` 发射 **55** 个 `fx-` 类名，
   其中**只有这 2 个没有任何规则**（其余 52 个已声明）。发射点：`oa.js:1096` 给字段容器加
   `fx-field--err`、`oa.js:1098` 建 `.fx-field__err` 提示节点、`:1093/:1059` 在填好后撤掉。

   与 demo（`w1-demo.css:314-320`）的**两处必要差异**（照抄会出缺陷）：
     · **不抄 `.fx-field__err{display:none}`**：demo 是"提示节点常驻、默认隐藏"；产品的 `oa.js`
       是**按需创建**（`:1098` 只在有错时建、`:1060-1061` 填好后删）⇒ 若默认 `display:none`，
       **错误提示会看不见**。契约不同，此处只补排版与颜色。
     · **不抄 `.fx-field--err .fx-input/.fx-select/…`**：产品字段用的是原生 `<input>/<select>/<textarea>`，
       **不带** `fx-input` 这类类名 ⇒ 那些选择器在产品里命中不到任何元素。这里直接指向真实元素。

   容器是 `<label class="oa-field">`（`oa.js:625`），而 `.oa-field` 自身**没有任何样式**（Y2 实测），
   所以红只落在"标签文字 + 子控件边框"上（与 demo 的视觉意图一致），**不给容器加边框** ——
   那会在错误出现/消失时造成 2px 布局跳动。

   特异度审计：`body .fx-field--err input` = **(0,2,1)**，压 `brand.css:5` 的
   `input,select,textarea{…border-color:#d4ddea…}`(0,0,1)；按本仓惯例用 `body` 前缀显式取胜，
   不依赖加载顺序（本文件本就在 `brand.css` 之后加载）。
   控件文字色显式钉回 `var(--text)`：否则容器的 `color` 会**继承进控件**，把用户已填的内容也染红。
   用后代选择器而不是 `>`：增强下拉（`searchable-select.js`）会把真实控件包进壳里，
   `>` 会漏掉壳内的 `<select>`；字段容器整体处于错误态，因此作用到其中所有表单控件是正确的。 */
body .fx-field--err { color: var(--fx-danger-text); }
body .fx-field--err input,
body .fx-field--err select,
body .fx-field--err textarea { border-color: var(--fx-danger-line); color: var(--text); }
.fx-field__err { font-size: var(--fx-text-caption); color: var(--fx-danger-text); margin-top: var(--fx-space-1); }

/* ══ 结束（UIUX-DS2 块到此为止）══════════════════════════════════════════════════════════ */

/* ══ UIUX-DS3（2026-09-22）键盘焦点层：补「在 Tab 顺序里、却没有任何 focus 规则」的元素 ═══════════
   怎么量的（`_validation/uiux-tools/ds3-focus-audit.cjs`，只读、可复跑）：在真实夹具的真实页面上
   **遍历每一个可聚焦元素**，把 CSSOM 里所有含 `:focus` 的规则**递归**取出（含 @media 内）、
   **整条选择器**去掉 focus 伪类后逐个 `matches()` ⇒ 没有任何规则匹配的元素就是"聚焦时没有可见指示"。

   实测（发起大厅 33 / 发起页 43 / 待办审批 42 个可聚焦元素）：**只有 1 个缺口** —— `DIV.fx-flowgrid`
   （发起大厅的卡片滚动容器，`oa.js:392` 给它 `tabIndex=0` 以便键盘滚动、却没人给它焦点样式）。
   既有覆盖已经不错：`brand.css:5` 的 `:is(input,select,textarea,button,summary,a):focus-visible`
   + 本文件下面那组 `.fx-focusable/.fx-btn/.fx-chip/a/button/select/input`。**本块只补缺的那一类**。

   为什么写成**通用选择器**而不是只写 `.fx-flowgrid`：`oa.js` 里还有 **3 处** `tabIndex=0` 的生产点
   （`:392` 大厅网格、`:1009` 配置面板、`:2498` 卡片），只修其中一个是"修了会复发"的做法；
   而"凡进入 Tab 顺序的元素必须有可见焦点指示"本身就是规则（WCAG 2.4.7 针对的是可操作组件）。
   `:not([tabindex="-1"])` 排除只供程序化聚焦的容器（例如页内子页签容器 `.rnd-tabs`）——
   它们不在 Tab 顺序里，不该出现焦点环。

   特异度：(0,2,1)（`body` + `[tabindex]` + `:focus-visible`；`:not()` 自身不计分）。
   与本文件既有两条焦点规则（`.fx-tab`、`body .fx-switchpanel`）**同形同值**，保持观感一致；
   无竞争规则（`.fx-flowgrid` 此前一条规则都没有），纯新增。 */
body [tabindex]:not([tabindex="-1"]):focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px; box-shadow: var(--fx-focus-ring);
  border-radius: var(--fx-radius-md); }

/* ══ 结束（UIUX-DS3 块到此为止）══════════════════════════════════════════════════════════ */

/* ══ 侧边栏 / 子菜单配色令牌（UIUX-DS6-B1a）════════════════════════════════════════════

   为什么单开一组：侧栏那套配色此前**一个令牌都没有** —— `#workspace-sidebar`/`#nav`/`.brand`/
   `.sidebar-caption`/`#nav-flyout`/`#nav-backdrop` 范围内 **20 条声明含颜色字面量、共 23 个 hex**，
   而 `var()` 引用只有 7 处。取证：`node _validation/uiux-tools/ds6-sidebar-palette-audit.cjs`（可复跑）。
   没有令牌就换不出深色，只能在页面上再写一套覆盖块 = 第二套配色（章程明令禁止）。

   · **本块的值＝当时生效值的逐字抄录，一个都不改** —— B1a 的验收判据就是"浅色逐像素不变"。
   · **这 23 个色值只出现在侧栏作用域内、侧栏之外 0 引用**（同一脚本 §6 同一次实测）⇒ 抽取不外溢。
   · **B1b** 改其中两个（`--fx-sidebar-caption`、`--fx-sidebar-item-hover-fg`）以满足 WCAG AA；
   · **B2** 在 `body.sb-dark` 上给**同一批名字**换一份赋值 —— 消费点一行不动，这就是深色的全部实现。

   命名：`--fx-sidebar-*` 侧栏本体｜`--fx-flyout-*` 侧栏弹出的子菜单（`#nav-flyout` 是 `<aside>` 的
   兄弟节点，但视觉与语义上都属于侧栏，所以跟着侧栏一起换色）｜`--fx-nav-scrim` 抽屉遮罩。 */
:root {
  /* ── 侧栏本体 ─────────────────────────────────────────────────────── */
  --fx-sidebar-bg: linear-gradient(165deg,#f4f7ff 0%,#f9faff 60%,#f2f1fc 100%);
  --fx-sidebar-border: #e1e7f3;
  --fx-sidebar-brand-sub: #7b88a1;
  /* ⚠️ B1b（用户 2026-09-22 同意"顺手修"）：原值 `#94a0b5` 在侧栏三档渐变上只有
     **2.36:1**（最坏档 `#f2f1fc`；10px 小字，AA 需 4.5）—— 低视力用户实际读不出「工作空间」。
     取值 `#666e7d` ＝ 沿原色相向黑 31% 的**最浅达标值**：对 `#f2f1fc`/`#f4f7ff`/`#f9faff`
     三档分别为 **4.59 / 4.79 / 4.92 : 1**。挑选脚本可复跑：
     `node _validation/uiux-tools/ds6b1b-pick.cjs`（它从本文件与 brand.css 读令牌，不硬编码）。 */
  --fx-sidebar-caption: #666e7d;
  --fx-sidebar-fg: #536079;
  --fx-sidebar-chevron: #96a3ba;
  --fx-sidebar-item-hover-bg: #eaf0ff;
  /* ⚠️ B1b：原值 `var(--accent)`(#2468f2) 在 hover 底 `#eaf0ff` 上只有 **4.24:1**（14px，需 4.5）。
     改用**既有令牌** `--accent-hover`(#1554d1) = **5.72:1** ⇒ 不新造任何色值，
     而且语义本就对上（"强调色的更深一档"正是 hover 该用的）。 */
  --fx-sidebar-item-hover-fg: var(--accent-hover);
  --fx-sidebar-item-active-bg: linear-gradient(100deg,#e2edff,#ebeaff);
  --fx-sidebar-item-active-border: #d5e2ff;
  --fx-sidebar-item-active-fg: #215dd8;
  --fx-sidebar-foot: #8290a7;
  --fx-sidebar-foot-sub: #94a0b3;
  --fx-sidebar-dot: #7198f0;

  /* ── 子菜单（#nav-flyout）──────────────────────────────────────────── */
  --fx-flyout-border: #dce4f2;
  --fx-flyout-shadow: 0 18px 60px #30466b24;
  --fx-flyout-sub: #7b889f;
  --fx-flyout-hover-border: #d4e2ff;
  --fx-flyout-active-sub: #5c7cb4;

  /* ── 移动抽屉（≤760px）────────────────────────────────────────────── */
  --fx-nav-scrim: #182b4c44;
  --fx-sidebar-drawer-shadow: 8px 0 40px #20304a20;

  /* ── 侧栏基色 + 子菜单的**其余消费点**（UIUX-DS6-B2 前置）──────────────
     为什么还要这一组：上面那 21 个是把**字面量**抽出来，而下面这些的原文是
     `var(--surface)` / `var(--accent)` / `var(--accent-soft)` / `var(--line)` / `white`
     —— 它们是"**间接字面量**"：因为在浅色下恰好等价而没有引起注意。
     但深色是"换一份令牌赋值"，这些消费点若不单独命名，深色下就会在深底上冒出
     **白卡片、白分割线、浅蓝 hover**。清单来自
     `node _validation/uiux-tools/ds6-sidebar-palette-audit.cjs --inventory`（43 条颜色类声明的全量清点）。
     ⚠️ 教训：只扫 hex 会以为抽取已完成 —— 关键字色（`background:white`）和间接引用都要算。 */
  --fx-sidebar-color: var(--text);            /* 侧栏基色：`.brand` 的 FITEASE 字等靠继承 */
  --fx-sidebar-brand-fg: #fff;                /* 品牌方块里的 F —— **两套主题都不改**（它是品牌，不是主题） */
  --fx-sidebar-divider: var(--line);          /* `.sidebar-caption` 的上分割线 */
  --fx-flyout-bg: var(--surface);             /* 子菜单面板底（原文是关键字 `white`）*/
  --fx-flyout-heading: var(--accent);         /* 子菜单标题 */
  --fx-flyout-divider: var(--line);           /* 标题下的分割线 */
  --fx-flyout-close: #8190a5;                 /* 「关闭菜单」 */
  --fx-flyout-card-bg: var(--surface);        /* 每个子项的卡片底 */
  --fx-flyout-card-fg: var(--text);           /* 卡片正文（原文靠继承拿到 `--text`）*/
  --fx-flyout-card-hover-bg: var(--accent-soft);
  --fx-flyout-card-hover-fg: var(--accent);
}

/* ── 深色侧栏（UIUX-DS6-B2）：**同一批名字的第二份赋值** ────────────────────────────────
   深色的实现就是下面这一块：`body.sb-dark` 把上面那批 `--fx-sidebar-*` / `--fx-flyout-*`
   换成另一份值，**消费点一行都不用改**（`brand.css` 里那些 `var(--fx-sidebar-…)` 照旧读取）。
   这就是"不写覆盖块、不造第二套配色"的做法 —— 谁想要深色，只需要给这批名字换赋值。

   **为什么挂在 `body` 上、而不是 `#workspace-sidebar` 上**：`#nav-flyout` 是 `<aside>` 的
   **兄弟节点**（见 `index.html:2-3`），挂在 aside 上它根本拿不到这些变量 ⇒
   用户当轮要求的"连子菜单一起深"就做不到。挂在 body 上 + 消费点各自带作用域，一次解决两处。

   **色值纪律：全部由既有令牌派生，一个新手写色值都没有。** 需要"暗一档/亮一档"的地方
   一律用 `color-mix(in srgb, …)` 把 `--line` / `--accent` 混进 `--text`。
   对比度预核（`node _validation/uiux-tools/ds6b1b-pick.cjs`，落地后用真机计算样式复测）：
     · 导航项正文 `--line` on `--text` = **11.59:1** ✓ ｜ 选中项 `--surface` on `--accent` = **4.84:1** ✓
     · hover 底 `mix(--line 10%, --text)` = `#333d4f`（相对底色 1.32:1，**这是可感知度不是门槛**），
       其上放 `--surface` = **10.93:1** ✓
   ⚠️ **hover 有意不追 3:1**：`--accent` 在 `--text` 上本身只有 ~2.7:1，所以"accent 混进 text"
      这条路物理上到不了 3:1；而 hover 是**微妙的提示**，不是"必须看见的边界"，硬追只会做出刺眼色块。
      真正的强表达交给**选中项**（accent 实底 + 白字）。
      （样板间用的是 `mix(--accent 18%, --text)` = `#21355e`，相对底色仅 **1.19:1** —— 基本看不见，不采用。） */
body.sb-dark{
  --fx-sidebar-bg: var(--text);
  --fx-sidebar-border: color-mix(in srgb, var(--line) 40%, var(--text));
  --fx-sidebar-color: var(--line);
  --fx-sidebar-brand-sub: var(--line);
  --fx-sidebar-caption: var(--line);
  --fx-sidebar-divider: color-mix(in srgb, var(--line) 40%, var(--text));
  --fx-sidebar-fg: var(--line);
  --fx-sidebar-chevron: var(--line);
  --fx-sidebar-item-hover-bg: color-mix(in srgb, var(--line) 10%, var(--text));
  --fx-sidebar-item-hover-fg: var(--surface);
  --fx-sidebar-item-active-bg: var(--accent);
  --fx-sidebar-item-active-border: var(--accent);
  --fx-sidebar-item-active-fg: var(--surface);
  --fx-sidebar-foot: var(--line);
  --fx-sidebar-foot-sub: color-mix(in srgb, var(--line) 70%, var(--text));
  /* 子菜单（#nav-flyout）—— 用户当轮明确要求"连子菜单一起" */
  --fx-flyout-bg: var(--text);
  --fx-flyout-border: color-mix(in srgb, var(--line) 40%, var(--text));
  --fx-flyout-heading: var(--line);
  --fx-flyout-divider: color-mix(in srgb, var(--line) 40%, var(--text));
  --fx-flyout-close: color-mix(in srgb, var(--line) 70%, var(--text));
  --fx-flyout-card-bg: var(--text);
  --fx-flyout-card-fg: var(--line);
  --fx-flyout-card-hover-bg: color-mix(in srgb, var(--line) 10%, var(--text));
  --fx-flyout-card-hover-fg: var(--surface);
  --fx-flyout-hover-border: var(--accent);
  --fx-flyout-sub: color-mix(in srgb, var(--line) 60%, var(--text));
  --fx-flyout-active-sub: var(--line);
  /* **有意不改**的两项（别以为是漏了）：
     · `--fx-sidebar-brand-fg`(#fff)：品牌方块是红底白字，属于品牌，与主题无关；
     · `--fx-sidebar-dot`(#7198f0)：在 `--text` 上 5.10:1 已达标，浅色深色同一值即可。 */
}

/* ══ 结束（UIUX-DS6 块：B1a 令牌 + B1b 对比度 + B2 深色）══════════════════════════════════ */

/* ══ UIUX-REPORTS-CSS（2026-09-24，ui-ds / task-2）报表分析·成品库存：`.fx-rpt*` 组件族 ══════
   权威规格 ＝ `fitease-control-center/var/reports/kingdee-fg-inventory-20260924/index.html`
   第 7–93 行 `<style>` ＋ 第 94–111 行 DOM 骨架（**只读参照物：本批对它 0 字节写入**）。
   类名契约 ＝ `web/reports.js` 实际发射、已冻结的 46 个 `fx-rpt*` token（本块**全部覆盖**）。

   ① **为什么落 `tokens.css` 而不是 `styles.css`**：`index.html:1` 的加载序是
      `styles.css → brand.css → tokens.css`，本文件**未分层**、`styles.css` 里有 `@layer`
      ⇒ 未分层规则永远胜过分层规则（与加载顺序、与特异度都无关）。同一个组件写两处只会
      制造第 152 条重复声明（R0 基线已登记 151 条同类债）⇒ `styles.css` 本批零改动。

   ② **配色绑定平台既有令牌，不照抄原型的 hex**（本块唯一成体系的有意偏离）：原型自带
      `--ink/--line/--blue/--or/--gr/--red/…`，原样搬进来就是页面内**第二套配色**
      （设计系统章程与 `UIUX-B2/B5` 报告都明令禁止"另立配色"）。结构、尺寸、圆角、
      状态语义、交互**逐值照抄原型**；颜色一对一到已有令牌：
      `--blue→--accent`｜`--or→--fx-warn-text`｜`--gr/--red→--fx-ok-*/--fx-danger-*`｜
      `--line→--line`｜`--card→--surface`｜`#eef2f8/#eef3fa→--fx-neutral-bg`｜
      `#f7f9fc/#fafbfd→--raised`｜`#e3eaf5→--fx-neutral-line`｜`#f0f4fa→--fx-neutral-bg`。
      代价如实说：原型的 `--blue:#2b6cb0` 与平台 `--accent:#2468f2` 不是同一个值，
      所以选中页签那条 `box-shadow:0 -2px 0 var(--blue) inset` 渲染成 `--accent` 的蓝。

   ③ **`body` 前缀不是装饰**：本块命中 `th/td/input/button/span/p/strong` 这些**元素**，
      而 `brand.css:3-7` 有 `p,.muted,label{color:var(--muted)}`、`td,th{padding:11px 12px}`、
      `button{…}`、`button:hover:not(:disabled){background:#f1f5fc;border-color:#a9bfe5}`
      这类元素级/伪类规则（末者＝**(0,2,1)**，与不带 `body` 的 `.fx-rpt-btn:hover` 同分，
      靠加载顺序赢不稳）⇒ 一律 `body .fx-rpt …` 显式取胜，与 `styles.css:907-908` 自己的
      做法一致（"用 body 前缀提特异度，不靠运气"）。

   ④ **覆盖 B 层 CSSOM 内联值的地方（UIUX-INFO-STRIP 后＝0 处）**：
      · 行号列宽 `col:first-child{width:48px}` —— **`!important` 已撤**（本批撤的最后一处）：
        总控 20:1x 把 `reports.js` 的首列内联宽度由 `56px` 改成 **`48px`**（现行 `reports.js:679`
        `indexCol.style.width='48px'`），`verifier` 在 `task-19` 也独立量过"内联 48 / CSS 48 / 渲染 48
        三处一致" ⇒ 内联值与规格同值、**没有对手了**。撤后复测：行号列宽仍 48px，几何/命中/拖动/打印
        四项不变（见 `_validation/UIUX-INFO-STRIP_REPORT.md`）。fixed 布局下给 `th/td` 写 width 仍然无效
        （列宽由 `<col>` 决定），所以这条规则本身留着 —— 它现在与内联值同值、互为兜底。
      · 列宽把手 `width/height !important` —— **UIUX-A11 已撤**（那个 CSSOM 占位已被总控删除）。
      **至此 `tokens.css` 里除注释外不再有 `!important`**（唯一保留过的"必须压内联"的场景已消化）。


   ⑤ **`table-layout:fixed` 是列宽的前提**：`reports.js` 把宽度写进 `<colgroup>` 的 `<col>`；
      不加 `fixed` 时 `<col>` 的宽度会被单元格内容顶掉。fixed 布局下 `table` 的**已用宽度**
      ＝ max(`width` 计算值, 各列宽之和) ⇒ 列宽总和超过容器时表格横向溢出、由 `.fx-rpt-wrap`
      横向滚动，行号列的 `sticky left:0` 才有意义（这是"不错位"的结构前提）。
   ══════════════════════════════════════════════════════════════════════════════════════════ */

/* ── 容器 ─────────────────────────────────────────────────────────────────────
   原型 ①「渐变横幅」—— **本块刻意不写**：本页在平台壳内，横幅与页头归平台壳，
   加第二遍就是重复的装饰层。 */
body .fx-rpt { display: block; }

/* ── ② 页签栏（圆角 8/8/0/0 ＋ 选中态 inset 蓝线 ＋ 条数）──────────────────────
   原型 `nav{display:flex;gap:6px;padding:10px 22px 0;background:var(--card);
   border-bottom:1px solid var(--line);align-items:flex-end}`；
   `nav button{border:1px solid var(--line);border-bottom:none;background:#f7f9fc;color:var(--muted);
   padding:8px 16px;border-radius:8px 8px 0 0;cursor:pointer;font-size:13px;position:relative;top:1px}`；
   `nav button.on{background:#fff;color:var(--brand);font-weight:600;box-shadow:0 -2px 0 var(--blue) inset}`；
   `nav button .n{color:var(--muted);font-weight:400;font-size:11.5px;margin-left:5px}`。
   ⚠ 页签**不在** `.fx-rpt` 里（`reports.js:1212` 把它挂在页面根上）⇒ 选择器不能带 `.fx-rpt`。
   原型那 22px 左右内边距是它自己整页的留白，平台由 `.page-content` 提供 ⇒ 这里只保留纵向节奏。 */
body .fx-rpt-tabs { display: flex; gap: 6px; align-items: flex-end; flex-wrap: wrap;
  margin: 0 0 14px; padding: 0; border-bottom: 1px solid var(--line); }
body .fx-rpt-tab { position: relative; top: 1px; padding: 8px 16px; cursor: pointer;
  font-family: inherit; font-size: 13px; color: var(--muted); background: var(--raised);
  border: 1px solid var(--line); border-bottom: none; border-radius: 8px 8px 0 0; }
body .fx-rpt-tab:hover:not(:disabled) { background: var(--surface); color: var(--text); }
body .fx-rpt-tab--on { background: var(--surface); color: var(--text); font-weight: 600;
  box-shadow: 0 -2px 0 var(--accent) inset; }
body .fx-rpt-tab-n { margin-left: 5px; font-size: 11.5px; font-weight: 400; color: var(--muted); }

/* ── ③ 工具条（搜索框聚焦蓝环 ＋ `.btn` / `.btn--primary`）────────────────────
   原型 `.toolbar{display:flex;gap:10px;align-items:center;padding:11px 22px;background:var(--card);
   border-bottom:1px solid var(--line);flex-wrap:wrap}`（左右内边距同上，纵向 11px 与分隔线照抄）。 */
body .fx-rpt-toolbar { display: flex; gap: 10px; align-items: center; flex-wrap: wrap;
  padding: 11px 0; background: transparent; border-bottom: 1px solid var(--line); }
/* 原型 `.toolbar input[type=search]{flex:1;min-width:230px;padding:8px 12px;border:1px solid var(--line);
   border-radius:8px;font-size:13px;font-family:inherit;outline:none}` ＋
   `:focus{border-color:var(--blue);box-shadow:0 0 0 3px #2b6cb01f}`。
   ⚠ **有意不抄 `outline:none`**：`brand.css:5` 的 `:is(input,select,textarea,button,summary,a):
   focus-visible` 是全站键盘焦点基线，`outline:none` 会把它一起掐掉（WCAG 2.4.7）。
   改为：轮廓交给平台基线，这里只补原型的蓝环与边框色，并把 offset 收到 1px 以免"双环"。 */
body .fx-rpt .fx-rpt-search { flex: 1 1 230px; min-width: 230px; padding: 8px 12px;
  border: 1px solid var(--line); border-radius: 8px; font-family: inherit; font-size: 13px;
  color: var(--text); background: var(--surface); }
body .fx-rpt .fx-rpt-search:focus { border-color: var(--accent); box-shadow: 0 0 0 3px #2468f21f; }
body .fx-rpt .fx-rpt-search:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* 原型 `.btn{padding:7px 13px;border:1px solid var(--line);background:#fff;border-radius:8px;
   cursor:pointer;font-size:12.6px;color:#2d3748;white-space:nowrap}`＋`:hover{background:#f0f4fa;
   border-color:#c3d3e8}`；`.btn.p{background:var(--blue);color:#fff;border-color:var(--blue)}`＋
   `:hover{background:#245a94}`。禁用态沿用设计系统既有取舍（`.fx-btn:disabled`＝opacity .55）。 */
body .fx-rpt .fx-rpt-btn { padding: 7px 13px; border: 1px solid var(--line);
  background: var(--surface); border-radius: 8px; cursor: pointer; color: var(--text);
  font-family: inherit; font-size: 12.6px; white-space: nowrap; }
body .fx-rpt .fx-rpt-btn:hover:not(:disabled) { background: var(--raised); border-color: #c3d3e8; }
body .fx-rpt .fx-rpt-btn:disabled { cursor: not-allowed; opacity: .55; }
body .fx-rpt .fx-rpt-btn--primary { background: var(--accent); border-color: var(--accent);
  color: #fff; font-weight: 600; }
body .fx-rpt .fx-rpt-btn--primary:hover:not(:disabled) { background: var(--accent-hover);
  border-color: var(--accent-hover); }
/* 工具条里的 `.fx-rpt-input`：页码 `<input type=number>` 与「每页条数」`<select>`
   （原型的分页在页面级、没有这两个控件）⇒ 按 `.btn` 的行高节奏给尺寸，宽度按内容收紧。 */
body .fx-rpt .fx-rpt-toolbar input.fx-rpt-input { width: 4.5em; padding: 6px 8px;
  border: 1px solid var(--line); border-radius: 8px; font-family: inherit; font-size: 12.6px;
  color: var(--text); background: var(--surface); text-align: right; }
body .fx-rpt .fx-rpt-toolbar select.fx-rpt-input { width: auto; max-width: 9em; padding: 6px 8px;
  border: 1px solid var(--line); border-radius: 8px; font-family: inherit; font-size: 12.6px;
  color: var(--text); background: var(--surface); }

/* ── ④ 实时汇总条（tabular-nums ＋ 缺失未计入＝橙色语义）──────────────────────
   原型 `.sum{display:flex;gap:18px;flex-wrap:wrap;padding:9px 22px;background:#eef3fa;
   border-bottom:1px solid var(--line);font-size:12.6px;flex:none;align-items:center}`；
   `.sum b{font-variant-numeric:tabular-nums}`（`reports.js:920` 发的是 `<span class=fx-rpt-sum-v>`
   而不是 `<b>` ⇒ 这里显式补 `font-weight:600` 才与原型同观感）；
   `.sum .k{color:var(--muted)}`；`.sum .warn{color:var(--or)}`＝"缺失 N 行未计入"那一句。 */
body .fx-rpt .fx-rpt-sum { display: flex; gap: 18px; flex-wrap: wrap; align-items: center;
  padding: 9px 12px; background: var(--fx-neutral-bg); border-bottom: 1px solid var(--line);
  font-size: 12.6px; }
body .fx-rpt .fx-rpt-sum-k { color: var(--muted); }
body .fx-rpt .fx-rpt-sum-v { color: var(--text); font-weight: 600;
  font-variant-numeric: tabular-nums; }
body .fx-rpt .fx-rpt-sum-warn { color: var(--fx-warn-text); }

/* ── ⑤ 滚动区 ＋ 表格（`table-layout:fixed`）＋ 两行粘性表头 ────────────────────
   原型 `.wrap{flex:1;overflow:auto;background:var(--card);margin:0}`；
   `table{border-collapse:separate;border-spacing:0;font-size:12.6px;min-width:100%}`。
   ⚠ 三处**不是照抄**，各有实测理由：
    · `table-layout:fixed`：列宽由 `reports.js` 写进 `<colgroup>`，不给 fixed 会被内容顶掉（见块首 ⑤）。
    · `max-height`：原型靠"整页 flex"拿到剩余视口高；这里是面板内嵌块，**必须自己给滚动区高度**，
      否则内容不溢出 ⇒ 两行表头永远没有可粘的滚动（＝粘性失效）。取值见下行注释。
    · `min-height:400px`：这一条**不是审美**，是枚举弹层能不能用的硬前提 —— 弹层从第二行表头向下
      展开，可用高度 ＝ 400 − 73（两行表头）− 15（横向滚动条）≈ 312px；弹层最坏情况见
      `.fx-rpt-ms-pop` 那条（≤300px）⇒ 底部的「清空/确定/取消」永远不会被滚动区自身的
      `overflow:auto` 裁掉。实测该滚动区最终可见高 **285px**（＝300 border-box − 15 滚动条）。
   ⚠ `max-height` 是**设计参数、与粘性规则无关**：判据要的是"内容超出滚动区时表头停在 0/35px"。
      实测（1600×1000 夹具）：本账号授权范围只命中 **5 行**（表体 ≈219px、含表头 292px）⇒ 自然态
      纵向不溢出。因此"不错位"的实测分两半：**横向固定行号列＝自然态实测**（列宽合计 1811px >
      容器 1336px，真滚动 475px 后行号列仍停在 left:0）；**纵向两行表头＝用 CSSOM 把滚动区高度
      临时压到 150px 制造真实溢出后实测**（只动这一个设计参数，样式规则一字未改）；生产
      （50 行/页 ≈1500px）本来就是自然溢出。 */
body .fx-rpt .fx-rpt-wrap { overflow: auto; background: var(--surface); margin: 0;
  min-height: 400px; max-height: min(560px, 66vh); }
body .fx-rpt .fx-rpt-table { width: 100%; min-width: 100%; table-layout: fixed;
  border-collapse: separate; border-spacing: 0; font-size: 12.6px; background: var(--surface); }
/* 原型 `thead th{position:sticky;top:0;z-index:3;background:#eef2f8;text-align:left;padding:8px 10px;
   border-bottom:1px solid var(--line);white-space:nowrap;cursor:pointer;user-select:none;font-weight:600}`
   ＋ `thead tr.f th{top:35px;z-index:2;background:#f7f9fc;cursor:default;padding:5px 6px;font-weight:400}`。
   ⚠ 第二行的 `top:35px` 是原型写死的"第一行高"⇒ 这里**把第一行高也钉成 35px**
   （`height:35px`＋`box-sizing:border-box`），否则两行会错位/露缝 —— 这才是"不错位"的真正前提。
   ⚠ `border-collapse:separate` 同样是前提：`collapse` 下 sticky 单元格的边框与位移在 Chrome 里会抖。 */
body .fx-rpt .fx-rpt-table thead th { position: sticky; top: 0; z-index: 3; box-sizing: border-box;
  height: 35px; padding: 0 10px; text-align: left; white-space: nowrap; cursor: pointer;
  user-select: none; font-weight: 600; font-size: 12.6px; color: var(--text);
  background: var(--fx-neutral-bg); border-bottom: 1px solid var(--line); }
body .fx-rpt .fx-rpt-table thead th:hover { background: var(--fx-neutral-line); }
/* 列名格：只加"长列名截断"（原型靠 `min-width` 顶开，这里列宽由 `<col>` 决定）。
   ⚠ 本规则**刻意不写 background**：写了就会以 (0,3,3) 压掉上面 `:hover`(0,2,4) 的悬停色。
   `padding-right:16px` 是给把手留位（把手绝对定位在单元格右缘）：不预留的话，
   文字会跑到把手下面；而列名比列宽长时，文字会把把手顶出单元格（见下一条）。 */
body .fx-rpt .fx-rpt-table thead th.fx-rpt-th { padding-right: 16px; overflow: hidden;
  text-overflow: ellipsis; }
/* 当前排序列：原型只用箭头表达（`.ar`）；契约里的 `-th--sort` 需要一个"本列是排序键"的落点，
   取设计系统既有的"选中/当前"底色 `--accent-soft`（`.fx-chip--draft/--neutral` 同族）。
   (0,4,3) 压过上一行 `:hover`(0,2,4) 与基础 th(0,2,3)。 */
body .fx-rpt .fx-rpt-table thead th.fx-rpt-th.fx-rpt-th--sort { background: var(--accent-soft);
  color: var(--accent-hover); }
body .fx-rpt .fx-rpt-ar { margin-left: 3px; font-size: 10px; color: var(--accent); }
/* 第二行＝筛选行：`top:35px`（＝上面钉死的第一行高）、层级低于第一行、非指针手形。 */
body .fx-rpt .fx-rpt-table thead tr.fx-rpt-filter-row th { top: 35px; z-index: 2; height: auto;
  padding: 5px 6px; background: var(--raised); cursor: default; font-weight: 400; }
/* 序号列**第二行那一格**（`reports.js:667` 发的是无类名的 `<th>`）也要左粘：
   原型漏了这步 ⇒ 横向滚动时它会滑走，行号列的"固定"在两行之间断开。
   用结构选择器补，不改 DOM、不新增类名。 */
body .fx-rpt .fx-rpt-table thead tr.fx-rpt-filter-row th:first-child { position: sticky; left: 0;
  z-index: 2; background: var(--raised); }

/* ── 列宽把手 `.fx-rpt-resize`（用户 2026-09-24 13:34 的新要求；原型无此控件）────
   UIUX-A11 起**不再需要 `!important`**：B 层那 7 行 CSSOM 占位（`reports.js` 原 `:683-689` 的
   `display/width:6px/height:1.2em/marginLeft/verticalAlign/cursor/touchAction`）已由总控撤除
   （实测 `handle.style.*` 残留 0 命中），本规则是唯一的几何来源；`task-8` 的独立复核也实测过
   "去掉 `!important` 的等价规则 ⇒ 几何/命中完全不变"。撤销后由 `task-11` 真机复测
   几何 10×34 / 命中 / 拖动 / 打印态四项不变。
   ⚠ **必须绝对定位在"本列右缘"，不能留在行内流里** —— 这是实测出来的，不是审美：
     本页列名常比列宽长（`reportsStockFitWidth` 按内容定宽，窄列只有 124–130px），行内流里的把手会
     被文字顶到**单元格右缘之外**（第一版实测：th 右缘 379 / 把手 390–400，溢出 **+21px**）。
     单看这一条还不致命 —— 未加样式前（th 无 `overflow`）官方探针的拖动本来是通的（124px → 164px）；
     **致命的是它同时被我加的 `overflow:hidden`（列名截断）裁掉**：被裁掉的部分既不可见也不参与命中
     测试 ⇒ 那一点落到了**下一个 th**（同级、DOM 更后）⇒ `pointerdown` 不触发、**拖动完全开始不了**
     （第一版实测原文：把手几何 `w:10 h:18`，拖动后 `124px → 124px`、`hash` 为空）。
     改成"单元格右缘的绝对定位覆盖层"后，把手 100% 落在本格内：`elementFromPoint` 在 12 列上
     逐列命中把手自身，命中区 10×35px（＝整行高），拖动 130px → 170px 且重渲染后保持。
   命中区 10×全行高；常态是一条 2px 浅色分隔线，悬停／按下加深。 */
body .fx-rpt .fx-rpt-resize { position: absolute; top: 0; right: 4px; bottom: 0;
  width: 10px; height: auto; margin-left: 0;
  cursor: col-resize; touch-action: none; border-radius: 2px;
  background: linear-gradient(to right, transparent 4px, var(--fx-neutral-line) 4px,
    var(--fx-neutral-line) 6px, transparent 6px); }
body .fx-rpt .fx-rpt-resize:hover { background: linear-gradient(to right, transparent 4px,
  var(--accent) 4px, var(--accent) 6px, transparent 6px); }
body .fx-rpt .fx-rpt-resize:active { background: linear-gradient(to right, transparent 3px,
  var(--accent-hover) 3px, var(--accent-hover) 7px, transparent 7px); }
/* 拖动中态：`reports.js:715/721` 在 `nodes.box`（＝`.fx-rpt` 本体）上加/去 `fx-rpt-resizing`，
   用途是**兜住拖动时的选中文字**并让指针全程保持 col-resize。原型无此控件故无对应规则；
   `*` 只在该类存在期间、且只在 `.fx-rpt` 作用域内生效，随拖动结束自动失效。 */
body .fx-rpt.fx-rpt-resizing { cursor: col-resize; user-select: none; }
body .fx-rpt.fx-rpt-resizing * { cursor: col-resize; user-select: none; }

/* ── ⑥ 三类筛选控件 ───────────────────────────────────────────────────────────
   先给"文本包含"与"数值区间"共用的小控件几何：原型 `thead input,.msbtn{width:100%;padding:4px 7px;
   border:1px solid var(--line);border-radius:6px;font-size:12px;font-family:inherit;background:#fff;
   outline:none}`＋`thead input:focus{border-color:var(--blue)}`
   （`outline:none` 同样有意不抄，理由见 `.fx-rpt-search`）。 */
body .fx-rpt .fx-rpt-filter-row .fx-rpt-input { width: 100%; min-width: 0; padding: 4px 7px;
  border: 1px solid var(--line); border-radius: 6px; font-family: inherit; font-size: 12px;
  color: var(--text); background: var(--surface); }
body .fx-rpt .fx-rpt-filter-row .fx-rpt-input:focus { border-color: var(--accent);
  box-shadow: 0 0 0 2px #2468f21a; }
/* ② 数值区间：原型 `.rng{display:flex;gap:3px;align-items:center}`＋`.rng input{min-width:0;
   text-align:right}`＋`.rng span{color:var(--muted);font-size:11px}` */
body .fx-rpt .fx-rpt-range { display: flex; gap: 3px; align-items: center; }
body .fx-rpt .fx-rpt-range .fx-rpt-input { text-align: right; }
body .fx-rpt .fx-rpt-range-sep { color: var(--muted); font-size: 11px; }
/* ③ 枚举多选：原型 `.ms{position:relative}`；`.msbtn{…text-align:left;white-space:nowrap;
   overflow:hidden;text-overflow:ellipsis}`；`.msbtn.act{background:#e8f1fd;border-color:#9dc2ea;
   color:#1a4d80;font-weight:600}`（`#e8f1fd`→`--accent-soft`，`#9dc2ea` 是它自己的浅蓝边、无同名令牌）。 */
body .fx-rpt .fx-rpt-ms { position: relative; }
body .fx-rpt .fx-rpt-ms-btn { width: 100%; padding: 4px 7px; border: 1px solid var(--line);
  border-radius: 6px; background: var(--surface); color: var(--text); cursor: pointer;
  font-family: inherit; font-size: 12px; text-align: left; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; }
body .fx-rpt .fx-rpt-ms-btn:hover:not(:disabled) { background: var(--raised); border-color: #c3d3e8; }
body .fx-rpt .fx-rpt-ms-btn--act { background: var(--accent-soft); border-color: #9dc2ea;
  color: var(--accent-hover); font-weight: 600; }
body .fx-rpt .fx-rpt-ms-btn.fx-rpt-ms-btn--act:hover:not(:disabled) { background: var(--accent-soft);
  border-color: #9dc2ea; }
/* 弹层本体：原型 `.msp{position:absolute;z-index:20;top:calc(100% + 4px);left:0;width:250px;
   background:#fff;border:1px solid #cbd5e1;border-radius:9px;box-shadow:0 8px 24px #0f172a26;padding:8px}`
   （阴影用同语义的 `--fx-shadow-pop`）。
   ⚠ **不能照抄原型的 `display:none` / `.on`**：`reports.js:569-582` 是"按需建、关了就删"
   （刻意不用 `hidden`/`display` 控制显隐）⇒ 弹层**只在展开时存在**，写 `display:none` 会让它永不出现。
   ⚠ `:has()` 那条**是必要的**：两行表头的每个 `th` 都是 `position:sticky + z-index` ⇒ 各自成层叠
   上下文，弹层的 `z-index:20` **逃不出所在那一格**，横向会被后面同级的筛选格（DOM 更后）盖住；
   `:has()` 把"装着弹层的那一格"整体抬到 6 层（高于第一行 3 与行号列 4），弹层关掉即随节点消失而失效。 */
body .fx-rpt .fx-rpt-ms-pop { position: absolute; z-index: 20; top: calc(100% + 4px); left: 0;
  width: 250px; padding: 8px; background: var(--surface); border: 1px solid #cbd5e1;
  border-radius: 9px; box-shadow: var(--fx-shadow-pop);
  /* ⚠ `display:flex` ＋ `max-height:300px` 是**"底部动作永远可达"的保证**，不是排版偏好：
     弹层挂在第二行表头里、又处在滚动区内部 ⇒ 它会被滚动区自己的 `overflow:auto` 裁切。
     实测：取值只有 2 个时弹层高 181px（没事），但一列 50 个取值时列表撑满 230px ⇒ 弹层 ≈369px，
     底部的「确定/取消」就会被裁掉、筛选根本提交不了。这里把弹层做成**列式 flex、上限 300px**，
     让列表（下一条 `flex:1 1 auto`）先缩、动作行永远留在可见区内。 */
  display: flex; flex-direction: column; max-height: 300px; }
body .fx-rpt .fx-rpt-table thead tr.fx-rpt-filter-row th:has(.fx-rpt-ms-pop) { z-index: 6; }
/* 原型 `.msp input.s{width:100%;margin-bottom:6px}` */
body .fx-rpt .fx-rpt-ms-search { width: 100%; margin-bottom: 6px; padding: 4px 7px;
  border: 1px solid var(--line); border-radius: 6px; font-family: inherit; font-size: 12px;
  color: var(--text); background: var(--surface); flex: 0 0 auto; }
body .fx-rpt .fx-rpt-ms-search:focus { border-color: var(--accent); }
/* 原型 `.msl{max-height:230px;overflow:auto;border:1px solid #eef2f8;border-radius:6px}`；
   `.msl label{display:flex;gap:7px;align-items:center;padding:4px 8px;cursor:pointer;font-size:12.3px}`
   ＋ `label:hover{background:#f2f6fc}`；`.msl label .c{margin-left:auto;color:var(--muted);font-size:11px}`。
   ⚠ 必须显式给 label 上 `color`: `brand.css:3` 有 `p,.muted,label{color:var(--muted)}`，
   否则弹层里的候选值会整体变灰（原型是正文色）。
   `flex:1 1 auto; min-height:90px`：列表是弹层里唯一可压缩的一段（见上一条）。 */
body .fx-rpt .fx-rpt-ms-list { flex: 1 1 auto; min-height: 90px; max-height: 230px; overflow: auto;
  border: 1px solid var(--fx-neutral-bg); border-radius: 6px; }
body .fx-rpt .fx-rpt-ms-list label { display: flex; gap: 7px; align-items: center; padding: 4px 8px;
  cursor: pointer; font-size: 12.3px; color: var(--text); }
body .fx-rpt .fx-rpt-ms-list label:hover { background: var(--raised); }
body .fx-rpt .fx-rpt-ms-list label .fx-rpt-ms-count { margin-left: auto; color: var(--muted);
  font-size: 11px; }
body .fx-rpt .fx-rpt-ms-list p { margin: 6px 8px; font-size: 12px; color: var(--muted); }
/* 原型 `.msa{display:flex;gap:6px;margin-top:6px}`＋`.msa button{flex:1;padding:5px;border:1px solid
   var(--line);background:#fff;border-radius:6px;cursor:pointer;font-size:12px;font-family:inherit}`。
   本页多一个"状态/已选 n"的 `.fx-rpt-ms-count`（原型没有）⇒ 让它独占一行、`flex-wrap` 兜住。 */
body .fx-rpt .fx-rpt-ms-actions { display: flex; gap: 6px; margin-top: 6px; flex-wrap: wrap; }
body .fx-rpt .fx-rpt-ms-actions .fx-rpt-btn { flex: 1 1 auto; padding: 5px; border-radius: 6px;
  font-size: 12px; }
body .fx-rpt .fx-rpt-ms-actions .fx-rpt-ms-count { flex: 1 1 100%; margin-top: 2px; text-align: center;
  color: var(--muted); font-size: 11.5px; line-height: 1.5; }

/* ── ⑦ 表体：列状态与行状态 ───────────────────────────────────────────────────
   原型 `tbody td{padding:6px 10px;border-bottom:1px solid #f1f4f8;white-space:nowrap;
   max-width:420px;overflow:hidden;text-overflow:ellipsis}`＋`tbody tr:hover td{background:#f7fafd}`
   （平台 `brand.css:7` 的 `tbody tr:hover{background:#f8faff}` 是同一语义的既有值，这里保持原型的 #f7fafd）。
   `max-width` 有意归 `none`：fixed 布局下列宽由 `<colgroup>` 决定，再叠 `styles.css:4` 的
   `td{max-width:370px}` 会把长列夹回去；截断交给 `overflow:hidden ＋ text-overflow:ellipsis`。 */
body .fx-rpt .fx-rpt-table tbody td { padding: 6px 10px; max-width: none; overflow: hidden;
  white-space: nowrap; text-overflow: ellipsis; border-bottom: 1px solid #f1f4f8;
  font-size: 12.6px; color: var(--text); }
body .fx-rpt .fx-rpt-table tbody tr:hover td { background: #f7fafd; }
/* 数值列右对齐 ＋ tabular-nums（原型 `td.n{text-align:right;font-variant-numeric:tabular-nums}`）。
   ⚠ 缺失值（`reports.js:898` 的 `—`）在本页**没有自己的类名**（契约里没有 `-miss`）⇒
   原型的 `td.miss{color:#94a3b8}` 无从落地，缺失格与普通数值格同色，只用 `—` 表意。如实登记。 */
body .fx-rpt .fx-rpt-table tbody td.fx-rpt-num { text-align: right;
  font-variant-numeric: tabular-nums; }
/* 名称列（原型 `td.name{max-width:300px}` —— fixed 布局下 max-width 无效，列宽由 `<col>` 决定）：
   这一条是**防御性**的，把名称格钉回正文色，避免继承到任何祖先的 muted。 */
body .fx-rpt .fx-rpt-table tbody td.fx-rpt-name { color: var(--text); font-weight: 400; }
/* 零库存行灰化：原型 `tbody tr.z td{color:#94a3b8}`＋`tbody tr.z td.name{color:#64748b}`。
   ⚠ 两档灰在平台令牌里只有 `--muted` 一个（`--fx-neutral-text:#526987` 更深，不是"更浅"）
   ⇒ 合并为 `--muted`，两档差别不复现（如实登记，不另造第二个灰）。 */
body .fx-rpt .fx-rpt-table tbody tr.fx-rpt-zero td { color: var(--muted); }
/* 固定行号列（48px，sticky left）：原型 `td.ix,th.ix{width:48px;min-width:48px;text-align:right;
   color:#a0aec0;background:#fafbfd;position:sticky;left:0;z-index:2}`＋`thead th.ix{z-index:4}`＋
   `tbody tr:hover td.ix{background:#f0f4fa}`。列宽靠块首 ④ 的 `col:first-child` 覆盖，见那里的理由。 */
body .fx-rpt .fx-rpt-table colgroup col:first-child { width: 48px; }
body .fx-rpt .fx-rpt-table .fx-rpt-ix { position: sticky; left: 0; z-index: 2; width: 48px;
  min-width: 48px; text-align: right; color: var(--muted); background: var(--raised); }
body .fx-rpt .fx-rpt-table thead th.fx-rpt-ix { z-index: 4; }
body .fx-rpt .fx-rpt-table tbody tr:hover td.fx-rpt-ix { background: var(--fx-neutral-bg); }
/* 全空 / 恒定的筛选格（原型 `.dead{display:block;padding:4px 2px;font-size:11.5px;color:#a0aec0;
   text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}`） */
body .fx-rpt .fx-rpt-dead { display: block; padding: 4px 2px; font-size: 11.5px; text-align: center;
  color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* 表头徽标：原型 `.cb{display:inline-block;margin-left:5px;padding:0 5px;border-radius:8px;
   font-size:10px;font-weight:400;vertical-align:1px}`；`.cb.e{background:#e2e8f0;color:#718096}`；
   `.cb.c{background:#fef3c7;color:#92400e}`（"恒定"＝琥珀，对应 warn 档）。
   本页还多一个不带修饰的「金额」徽标（原型没有）⇒ 基础规则给中性灰。 */
body .fx-rpt .fx-rpt-badge { display: inline-block; margin-left: 5px; padding: 0 5px;
  border-radius: 8px; font-size: 10px; font-weight: 400; vertical-align: 1px;
  background: var(--fx-neutral-bg); color: var(--muted); }
body .fx-rpt .fx-rpt-badge--empty { background: var(--fx-neutral-line); color: var(--muted); }
body .fx-rpt .fx-rpt-badge--const { background: var(--fx-warn-bg); color: var(--fx-warn-text); }
/* 状态药丸（绿/红/黄）：原型 `.flag{display:inline-block;padding:0 6px;border-radius:9px;
   font-size:11px;margin-left:6px}`＋`.g{background:#c6f6d5;color:#22543d}`／`.r{background:#fed7d7;
   color:#822727}`／`.y{background:#fefcbf;color:#744210}`。颜色取设计系统既有的三对状态令牌
   （`.fx-chip--approved/--rejected/--pending` 用的是同一组）。
   ⚠ 原型的 `margin-left:6px` **有意不抄**：原型里药丸跟在文字**后面**，而 `reports.js:910`
   让它成为该格**唯一内容**，加左边距会把"右对齐"的位置推偏。 */
body .fx-rpt .fx-rpt-flag { display: inline-block; padding: 0 6px; border-radius: 9px;
  font-size: 11px; white-space: nowrap; }
body .fx-rpt .fx-rpt-flag--g { background: var(--fx-ok-bg); color: var(--fx-ok-text); }
body .fx-rpt .fx-rpt-flag--r { background: var(--fx-danger-bg); color: var(--fx-danger-text); }
body .fx-rpt .fx-rpt-flag--y { background: var(--fx-warn-bg); color: var(--fx-warn-text); }

/* ── 空态 / 加载态 / 失败态（`reports.js:865/971/997` 三处都是 `div.fx-rpt-empty`）──
   原型 `.empty{padding:44px;text-align:center;color:var(--muted)}`（原型只有一行文本，
   本页容器里还有 `<strong>` 与 `<p>` ⇒ 给出可读的两级层次）。 */
body .fx-rpt .fx-rpt-empty { display: flex; flex-direction: column; gap: 6px; align-items: center;
  padding: 44px 24px; text-align: center; color: var(--muted); }
body .fx-rpt .fx-rpt-empty strong { font-size: 14px; font-weight: 600; color: var(--text); }
body .fx-rpt .fx-rpt-empty p { margin: 0; max-width: 56ch; font-size: 12.6px; line-height: 1.7;
  color: var(--muted); }

/* 页脚：原型 `footer{padding:8px 22px;color:var(--muted);font-size:11.6px;background:var(--card);
   border-top:1px solid var(--line)}`。
   ⚠ 它在平台里是 `.panel` 的**直接子 `<p>`** ⇒ 必须压掉 `brand.css:6` 的
   `.panel>p{margin:7px 0 12px;font-size:13px;line-height:1.65}`(0,1,1)，否则页脚会多出 12px 下边距。 */
body .fx-rpt .fx-rpt-foot { margin: 0; padding: 8px 0 0; border-top: 1px solid var(--line);
  background: transparent; color: var(--muted); font-size: 11.6px; line-height: 1.7; }

/* ── ⑧ 打印 ───────────────────────────────────────────────────────────────────
   原型 `@media print{nav,.toolbar,thead tr.f{display:none}body{display:block}.wrap{overflow:visible}}`
   ⇒ 隐藏页签栏／工具条／第二行筛选行，滚动区放开（否则只打印可视的一屏）。
   额外两条（原型没有、平台语境必需）：① 把手在纸上无意义；② `th` 的 sticky 在分页打印时会把
   表头叠在每页内容上 ⇒ 打印态一律回落为静态定位，`thead` 按正常表头组重复。 */
@media print {
  body .fx-rpt-tabs,
  body .fx-rpt-toolbar,
  body .fx-rpt .fx-rpt-table thead tr.fx-rpt-filter-row { display: none; }
  /* 把手在打印态消失。UIUX-A11 起**不再需要 `!important`** —— 它原来只为压 B 层
     `reports.js:683` 那行 `display:inline-block` 的 CSSOM 内联值，占位已由总控撤除
     （A11 复测：打印态 `display=none`，且不再依赖 `!important`）。 */
  body .fx-rpt .fx-rpt-resize { display: none; }
  body .fx-rpt .fx-rpt-wrap { min-height: 0; max-height: none; overflow: visible; }
  body .fx-rpt .fx-rpt-table thead th,
  body .fx-rpt .fx-rpt-table .fx-rpt-ix { position: static; }
}

/* ══ 结束（UIUX-REPORTS-CSS：`.fx-rpt*` 组件族到此为止）════════════════════════════════════ */

/* ══ UIUX-A11（2026-09-24）`.fx-iconbtn`：把"点名却没实现"的图标按钮补上，并让第一个真实使用者用类名 ══
   为什么补规则而不是改注释（task-11 ③ 的二选一）：`tokens.css` 那句"刻意没搬"里点名的四个类，
   只读 grep `web/*.js` 实测 **`.fx-filelist`/`.fx-lines`/`.fx-actionbar` 是 0 发射**（注释准确），
   而 `.fx-iconbtn` **也不是"被页面发射却没人样式化"** —— 它在 `app.js:712` 只出现在一句注释里，
   真正那行是 `btn.style.cssText='background:none;border:0;padding:0 6px 0 0;…'`：
   **页面因为组件缺失，自己带了一整块内联样式**（平台本来的规矩是不写内联样式）。
   ⇒ 补规则 + 让页面改用类名，才是把这块债真的还掉；只改注释等于把"页面自带样式"永久化。

   静止态**逐值照抄**那段内联块（不发明新观感）：
     background:none / border:0 / padding:0 6px 0 0 / margin:0 / cursor:pointer / font:inherit /
     color:var(--muted) / line-height:1  —— UIUX-A11 真机复测：改前后 `16×12`、无底色、无边框、
     padding/margin/cursor/color/line-height 逐项相同（见 `_validation/tmp/a11-clip-after.json`）。
   有意**新增**的两条（内联版没有，因为内联样式会压掉 `brand.css:4` 的 `button:hover`）：
     · `:hover` → 字形颜色由 `--muted` 提到 `--text`（只改字色，不加底框，保持"表格里的一个三角"）；
     · `:disabled` → 设计系统既有取舍（`cursor:not-allowed` + `opacity:.55`）。
   键盘焦点沿用全局 `button:focus-visible`（本文件 `:115`），不另立一套。

   特异度：`body .fx-iconbtn`(0,1,1) 压 `brand.css:4` 的 `button`(0,0,1)、`styles.css:742` 的
   `body td button`(0,0,3)、`brand.css:7` 的 `td button`(0,0,2)；`:hover`/`:disabled` 写成
   `body .fx-iconbtn:hover:not(:disabled)`(0,3,2) 与 `…:disabled`(0,2,1)，压过
   `brand.css:4` 的 `button:hover:not(:disabled)`(0,2,1) ⇒ 不靠加载顺序取胜。 */
body .fx-iconbtn { display: inline-block; padding: 0 6px 0 0; margin: 0;
  border: 0; background: none; cursor: pointer; font: inherit;
  color: var(--muted); line-height: 1; }
body .fx-iconbtn:hover:not(:disabled) { background: none; border-color: transparent;
  filter: none; color: var(--text); }
body .fx-iconbtn:disabled { cursor: not-allowed; opacity: .55; }

/* ══ 结束（UIUX-A11 块到此为止）════════════════════════════════════════════════════════════════ */


/* ══ UIUX-INFO-STRIP（2026-09-24）通用「数据信息条」：`.fx-info-strip*` ═════════════════════════
   用户要求「平台页面跟他那份 HTML 原型一致」，原型顶部那条横幅（标题 + 来源/取数时间/只读说明）
   就是本组件。做成**通用类**而不是报表专用：别的板块（托管报表、BI、知识库列表）以后也要同一条信息带。

   类名契约（5 个类 + 1 个元素级补丁，B 层按此放 DOM，**不需要新类名、不需要内联样式**）：
     · `.fx-info-strip`        容器。语义＝"这条信息的出处与新鲜度"，不是页面主标题。
     · `.fx-info-strip__title` 标题。建议用 `<h2>`/`<div>` 承载（**标题级别由 B 层按页面层级决定**，
                                CSS 不管标签 —— 平台壳已有一级标题，这里再放 `<h1>` 会破层级）。
     · `.fx-info-strip__meta`  小字行（数据来源／取数时间／只读取数未回写…），`flex-wrap` 可折行。
     · `.fx-info-strip__item`  小字行里的**一项**（`white-space:nowrap`：折行时按"项"整体换行，
                                不会把"取数时间：2026-09-24 14:29:02"拆成两半）。
     · `.fx-info-strip__sep`   可选**显式**分隔符（默认不用写：相邻 `__item` 之间由 `::before` 自动补 `·`）。
     · 元素级补丁：`__meta` 里的 `<a>`（例：配套静态报表）用 `--accent-hover`，与平台链接色一致。

   ① **配色：用平台令牌表达同一角色，不照抄原型 hex**（与 `fx-rpt-*` 同一条口径）。
     原型横幅是 `linear-gradient(135deg,#1f3864,#2b4c86)` + 白字 —— 那是**原型自己的深蓝**，
     平台词表里没有深色表面令牌（深色档只有侧栏 `body.sb-dark` 那套、语义是侧栏）⇒ 照搬＝页面内
     第二套配色（章程禁止）。这里取 `--accent-soft` 底 + `--accent` 左边标 + `--text` 标题 +
     `--muted` 小字：在平台的词表里，这就是"一条与页面其余部分分开的信息带"，与 `fx-rpt-*` 的
     汇总条（`--fx-neutral-bg`）**刻意取不同底色**，两条带子挨着也不会糊成一条。
     ⚠ 如实说清代价：**它不等于原型那条深蓝横幅**。要"就是深蓝"需要一条令牌决策（新造
     `--fx-report-banner` 或复用侧栏深色档），给口径我就改。
   ② **字号取平台刻度**：原型 `h1` 19px 是"全页唯一标题"的尺寸；平台壳已有一级标题、面板里还有
      `h2`(15px)，这里用 `--fx-text-h3`(16px) —— 比面板标题大半级、比壳标题小，层级才对。
     小字用 `--fx-text-caption`(12px，原型 12.3px 同档)。
   ③ **打印：横幅保留（与原型一致）。** 总控 2026-09-24 20:5x 裁定：「**打印态按原型来** ——
      纸质报告带上"数据来源 + 取数时间"正是可溯源的要点」（他原先那句"原型的打印态没有横幅"是他记错，
      我实测原型第 92 行 `@media print{nav,.toolbar,thead tr.f{display:none}body{display:block}.wrap{overflow:visible}}`
      只隐藏导航/工具条/筛选行，`header`＝横幅是**保留**的）。
      ⇒ 本组件**不写任何 print 隐藏规则**（我一开始按任务书那句加过 `@media print{…display:none}`，本批已撤）。
      实测（改后）：横幅打印态 `display:flex` 可见；页签/工具条/筛选行/把手仍 `none`；滚动区 `overflow:visible`。
   ④ **窄屏**：`__meta` 是 `flex-wrap:wrap` ⇒ 按项折行；≤760px 收窄内边距（与 `.fx-rpt-toolbar` 的
      窄屏处理同一手法），不隐藏任何一项（信息条藏起来就等于没有）。
   ⑤ 特异度一律 `body .fx-info-strip…`（本文件未分层 ⇒ 压 `styles.css` 的 `@layer`；`body` 前缀压
      `brand.css:3` 的 `p{color:var(--muted)}` 之类元素级规则），与本文件既有做法一致。 */
body .fx-info-strip { display: flex; flex-direction: column; gap: 2px;
  padding: 14px 16px; margin: 0 0 var(--fx-space-3);
  background: var(--accent-soft); border: 1px solid var(--line);
  border-left: 3px solid var(--accent); border-radius: var(--fx-radius-lg); }
body .fx-info-strip__title { font-size: var(--fx-text-h3); font-weight: var(--fx-weight-bold);
  line-height: var(--fx-leading-h3); color: var(--text); }
body .fx-info-strip__meta { display: flex; flex-wrap: wrap; gap: 2px 0;
  font-size: var(--fx-text-caption); line-height: var(--fx-leading-caption); color: var(--muted); }
body .fx-info-strip__item { white-space: nowrap; }
/* 相邻项之间自动补分隔符（B 层不用手写 `·`，也就不会两处漂移）；显式 `__sep` 仍然支持。 */
body .fx-info-strip__item + .fx-info-strip__item::before { content: '·';
  margin: 0 var(--fx-space-2); color: var(--line); }
body .fx-info-strip__sep { margin: 0 var(--fx-space-2); color: var(--line); }
body .fx-info-strip__meta a { color: var(--accent-hover); }
@media (max-width: 760px) { body .fx-info-strip { padding: 12px 12px; } }
/* 打印态**不隐藏**（见块首 ③：与原型一致，纸质报告要带"来源 + 取数时间"）。 */

/* ══ 结束（UIUX-INFO-STRIP 块到此为止）═════════════════════════════════════════════════════════ */

