/* ==== /css/design-tokens.css ==== */
/* ==========================================================================
   达播工作台 Design Tokens — 浅色主题
   三层结构：Primitive(A1) → Semantic(A2) → Component(B/C)
   主色 #0064E0（Meta 钴蓝，替换原 TDesign 腾讯蓝 #0052D9）
   规则：全站禁用裸 hex（仅 #fff/#000 例外），一律引用本文件 CSS 变量。

   2026-09-06 底座重构（第十七轮）：
   1) 色彩统一为 TDesign 官方单一色板 —— 清除 Apple iOS 系统色、Tailwind 图表色
   2) 字号基准 13px → 14px（对齐 TDesign fontBodyMedium），全站正文不再发虚
   3) 字重 510/590 → 500/600（PingFang/Noto SC 真实字重，消除合成加粗）
   4) 阴影重做三档可感知层级，卡片不再只靠 1px 边框
   ========================================================================== */

:root {
  /* ------------------------------------------------------------------
     一、Primitive（A1-identity / A1-structure）—— 原始值，不直接用于组件
     ------------------------------------------------------------------ */

  /* 主色 · Meta 钴蓝（500=主色 #0064E0；600=hover 深、700=active 更深） */
  --color-primary-50:  #E5EEFE;   /* 激活底 / 选中 tint / 行 hover / KPI 蓝卡
                                     ⚠️ 2026-09-25 UI 美化试妆（方向 A · 品牌化清新）：由 #EDF4FE 加深。
                                        **加深幅度受对比度硬约束**：本令牌作底时会与 --color-accent-text
                                        （= primary-500 #0064E0）同框（base.css 的 .nav-item.is-active、
                                        layout.css 的页标签、pages-finance 的 fin-chain__lab 等），
                                        故必须满足「主色字 on 本底 ≥ 4.5:1」。
                                        沿色相扫描后取最深合规值：本值 4.61:1（原 #EDF4FE 为 4.87:1，
                                        更深的 #DEEBFE 会掉到 4.47:1 → 不合规，已否决）。
                                        可见度：Δ通道 25（原 17）、L 94.7%（原 96.3%）—— 从"几乎看不出是蓝的"
                                        变为明确的品牌浅蓝，一处改动作用于 49 处引用。 */
  --color-primary-100: #DBE8FE;
  --color-primary-200: #B9D3FD;
  --color-primary-300: #94BBFB;
  --color-primary-400: #5E9BF8;
  --color-primary-500: #0064E0;   /* 基准主色（Meta 钴蓝） */
  --color-primary-600: #0457CB;   /* hover（比主色深） */
  --color-primary-700: #0040A3;   /* active（更深） */
  --color-primary-800: #002B73;
  --color-primary-900: #001B4D;

  /* 品牌青（2026-09-25 UI 美化 · 方向 A 新增）——
     与 --color-chart-2 同值但**语义独立**：它是品牌色族的第二色，专供「蓝→青」渐变与点缀，
     不要用它替代图表分类色（图表色有自己的一套成对维护规则）。 */
  --color-brand-cyan: #029CD4;

  /* 品牌渐变（方向 A「品牌化清新」的核心资产，2026-09-25 新增）
     三档：主渐变（蓝→青）/ hover 深一档 / active 更深一档 / 柔底渐变（浅蓝→浅青，可铺大面积而不压内容）。
     ⚠️ 历史红线：只允许**同族**渐变（蓝→青，色相差约 18°，属相邻色），禁止跨色相（尤其 Indigo→Pink）。
     ⚠️ 渐变只用于「块级装饰」（横幅 / 主 CTA / 强调卡），**不得**用于正文文字（可读性）。
     ⚠️ 像素门禁 `tests/ui-token-standards.test.js` 对裸 hex 的例外只覆盖本文件 —— 故渐变的
        三个色标全部写在这里，业务 CSS 一律 `var(--gradient-*)` 引用。 */
  --gradient-brand:        linear-gradient(120deg, var(--color-primary-500) 0%, var(--color-brand-cyan) 100%);
  --gradient-brand-hover:  linear-gradient(120deg, var(--color-primary-600) 0%, #0189BE 100%);
  --gradient-brand-active: linear-gradient(120deg, var(--color-primary-700) 0%, #0177A6 100%);
  --gradient-brand-soft:   linear-gradient(120deg, var(--color-primary-50) 0%, #E4F4FB 100%);

  /* 中性色 · TDesign Gray1-14（14 阶，CIELab 亮度均分） */
  --color-gray-0:   #FFFFFF;
  --color-gray-50:  #F3F3F3;   /* Gray1  页面底 / hover 底 */
  --color-gray-100: #EEEEEE;   /* Gray2 */
  --color-gray-200: #E8E8E8;   /* Gray3  弱分隔线 */
  --color-gray-300: #DDDDDD;   /* Gray4  默认边框 */
  --color-gray-400: #C6C6C6;   /* Gray5  输入框描边 */
  --color-gray-500: #A6A6A6;   /* Gray6  禁用文字 */
  --color-gray-600: #8B8B8B;   /* Gray7 */
  --color-gray-700: #777777;   /* Gray8 */
  --color-gray-800: #5E5E5E;   /* Gray9 */
  --color-gray-900: #4B4B4B;   /* Gray10 */

  /* 字体栈（全站唯一）
   ⭐⭐⭐ 2026-10-08 00:4x **补macOS 中文兜底**（真机撞到才补，不是预防性加料）
     ────────────────────────────────────────────────────────────────────────────────
     起因：headless/自动化 Chrome 截图时，**中文全部画不出来**（图标正常、文字空）。
     逐层排查结论（都排除了）：
       · DOM 侧正常 —— `.nav-group-text` 的 w=135/h=19、深墨色、无溢出，**文字在**；
       · 系统字体侧：`--font-family-body` = "Noto Sans SC", "PingFang SC", "Inter",
         -apple-system, sans-serif，而**这台机器既没有 Noto Sans SC，也没有 PingFang SC**
         （`/System/Library/Fonts/PingFang.ttc` 不存在）⇒ 落到通用 `sans-serif`
         ⇒ Chrome 自动化环境解析不出中文 ⇒ 文字空白。
     ⇒ 补`"Hiragino Sans GB"`（macOS 自带，/System/Library/Fonts/Hiragino Sans GB.ttc 实测在）
        与 `"Heiti SC"` / `"STHeiti"` 两档兜底，**排在 sans-serif 之前**。
     ⚠️ **这不是"为了截图好看"**：任何没装 PingFang 的 macOS（老系统 / 精简环境 /
        CI 容器）都会走到同样的坑 ⇒ 属于真实的健壮性缺口。
     ⚠️⛔ **顺序有讲究**：PingFang SC 仍是第一选择（乐哥在真机上看的就是它），
        新增的只做**兜底**，一个都不许提到它前面 —— 换字形会影响他实际看到的观感。 */
  --font-family-body: "Noto Sans SC", "PingFang SC", "Hiragino Sans GB",
                      "Heiti SC", "STHeiti", "Inter", -apple-system, sans-serif;
  --font-family-mono: "JetBrains Mono", ui-monospace, "SF Mono", monospace;

  /* 字号阶梯（对齐 TDesign：12 / 14 / 16 / 20 / 24 / 30 / 36）
     正文基准 14px —— 中文在 12px 以下会发虚，禁止再降到 13px。 */
  --font-size-xs:   12px;   /* 辅助 / 徽章 / 时间戳 / 表头小字 */
  --font-size-sm:   14px;   /* 表格正文 / 次级正文（原 13px，2026-09-06 提升） */
  --font-size-base: 14px;   /* 正文 / 表单基准（TDesign fontBodyMedium） */
  --font-size-lg:   16px;   /* 卡片标题 / 强调（TDesign fontBodyLarge） */
  --font-size-xl:   20px;   /* 模块标题 */
  --font-size-2xl:  24px;   /* 页面标题 */
  --font-size-3xl:  30px;   /* KPI 数字 */
  --font-size-4xl:  36px;   /* 看板大数字（极少用） */

  /* ⭐⭐⭐ 2026-10-08 15:3x 侧栏一级平铺项高 42 → **34**（与二级项同值）
     ────────────────────────────────────────────────────────────────────────────────
     乐哥：「首页的这个选中框，和二级菜单的**大小不一样**，这个不对，改改，要一致」。
     ▸ 现状（真机）：`.nav-item`（首页）= 42、`.nav-item--sub`（我的报销/达人列表…）= 34
       ⇒ 两个选中/悬停胶囊**差 8px**，同一张侧栏里并排看得见（他截图里 首页 白胶囊
       明显比 我的报销 那块高）。
     ▸ 取 34 而不是把二级抬到 42：**二级的 34 是今天（15:0x）刚按飞书定的新标准**
       （飞书行高 34 + 4 缝），把二级改回 42 会反转当天最新的口径。
     ▸ 层级不靠高度表达：一级仍是 **15px/500**、二级 **14/400/`--color-muted`**
       ⇒ 体量、字重、颜色三维仍在。
     ⚠️⛔ **旧关系锁已废**：本条原写「必须等于 `--control-height-row`（= 对齐右侧悬浮 tab 栏 42）」
       （乐哥 10-07 22:55 的要求）。**本次有意的方向反转** —— 侧栏菜单统一 34 之后，
       「首页与 tab 栏等高」这条不再成立（tab 栏仍是 42，它属于页头控件那一档）。
       门禁 `ui-1010-three-fixes` 已同步改为「**必须等于二级项高度**」。
     ⚠️ 为什么不复用 `--control-height-row`：那个管的是**页头筛选行**的搜索框/月份框/按钮，
        语义不同；混用会让侧栏菜单高度被页头控件的调整带走。 */
  --nav-item-h:           34px;   /* 侧栏一级平铺项（与二级项同高，见上） */

  /* ⭐⭐⭐ 2026-10-08 14:5x 侧栏**品牌区总高**（含菜单起始留白）—— 它就是那条边界线
     ────────────────────────────────────────────────────────────────────────────────
     乐哥 14:30（截图 + 三句）：
       ① 「左边菜单栏文字的高度，按照我截图这个为标准做，就是红框线下面」
       ② 「logo 和文字看要不要整体下移」
       ③ 「如果菜单栏展开了很多，滑动的时候，也不要突破这个界限，如果突破的隐藏了」
     ▸ 真机像素级比对（同口径量「乐哥截图」与「本地渲染」，都按 CSS px）：
         元素              乐哥截图    本地（旧，= 线上同版）
         logo 顶部         y=16       y=12
         品牌标题「星盟达人后台」 y=34       y=30
         首个菜单项「首页」文字  y=105      y=118
       ⇒ 乐哥那条红线画在 **y=92**，而 92 正是**页头卡（`.page-header`）顶边**
         （真机实测 `.page-header` top = 92）—— 菜单就从这条线下面开始。
     ▸ **本令牌 = 92**：品牌区一口气长到 92，菜单紧贴其下沿开始。
       于是「品牌区下沿 = 菜单起始线 = 页头卡顶边 = 滚动裁切边」**是同一根线**。
     ⛔⛔ **为什么必须把留白并进品牌区、而不是继续当 `.sidebar__nav` 的 padding-top**
       （这是乐哥第③条的**结构性**修法，不是调参）：
         留白长在滚动容器**内部**时，滚动内容可以进入这段留白 ⇒ 菜单项上滑时会
         出现在「logo 下方那段空档」里，只在容器外缘（品牌区下沿 y=60）才被裁住
         ⇒ 视觉上**越过了乐哥画的那条线**。并把留白并进品牌区后，滚动容器上缘 = 92、
         裁切边 = 92 ⇒ 物理上滑不出去（不需要遮罩、不需要 JS）。
     ▸ 旧值 `--sidebar-nav-pad-top: 43`（= 103 − 60，对齐 tab 胶囊顶边 103.2）**已废**：
       乐哥这次截图定的标准是 **92（页头卡顶边）**，比 103 高 11px。
     ⚠️ 改本值要连带核对：`--tabs-bar-height`(60)、页头卡 padding 10 + border 1（真机 92）、
        以及 `.sidebar__nav` 的左右 12px —— 那 12 撑的是「竖向基线 62」，一个字都不能动。 */
  --sidebar-brand-h:      92px;

  /* 字重三级体系
     注意：必须是 400 / 500 / 600 —— PingFang SC 与 Noto Sans SC 只有这三档，
     用 510/590 会触发浏览器合成加粗，Mac/Windows 粗细不一致、字形边缘发毛。 */
  --font-weight-regular:  400;   /* Read */
  --font-weight-medium:   500;   /* Emphasize */
  --font-weight-semibold: 600;   /* Announce */

  /* 行高 */
  --font-line-height-body: 1.5;
  --font-line-height-tight: 1.2;

  /* 字距 */
  --font-letter-spacing-body:    0;
  --font-letter-spacing-display: -0.02em;   /* ≥30px 标题 */
  --font-letter-spacing-caps:     0.06em;   /* ALL CAPS */

  /* 间距（4px 网格，禁止 5/7/11/13 等离网格野值） */
  --space-1:  4px;
  --space-2:  8px;
  --space-3:  12px;
  --space-4:  16px;
  --space-5:  20px;
  --space-6:  24px;
  --space-8:  32px;
  --space-10: 40px;
  --space-12: 48px;

  /* ══════════════════════════════════════════════════════════════════════
     控件高度 · **两档制**（2026-09-26 乐哥拍板「两档制」+「令牌化全局兜底」）
     ══════════════════════════════════════════════════════════════════════
     ⛔ 改前的乱象（真机实测 9 页）：输入框 3 档（28/34/44）、选择框 4 档
        （28/30/34/44）、按钮 9 档（28/30/32/36/44/46/48/57/123）——
        同一排里 28 / 30 / 44 三种高度并排（业绩总表工具栏最明显）。
     ⛔ 根因：2026-09-25 建了这三个令牌，但**几乎没用**
        （--control-height 使用 0 处、-sm 5 处、-touch 1 处，而硬编码有 65 处），
        且"紧凑档"只写在 4 个具体选择器里（`.toolbar--list` 等）——
        **不是全局规则** ⇒ 任何新页面/新容器漏掉就回落 44px。

     ✅ 现在全站**只有两档**（内容区）：
        · 标准档 44px —— 表单页 / 弹窗 / 页面头部主按钮（输入类操作，从容）
        · 紧凑档 34px —— 列表工具栏 / 表格行内 / 分页区（信息密度高，34 刚好）
     ⚠️ 顶栏与侧栏**自成一套**、不在此两档内（顶栏 48 胶囊、侧栏 46 菜单行）——
        它们由各自的作用域规则（`.page-tabs-bar .icon-btn` 等）自保，本令牌管不到。

     ⚠️ 用法纪律（避免又退回散写）：
        · 控件高度**一律**写 `var(--control-height)` / `var(--control-height-sm)`
        · 新增页面若把控件放进 `.toolbar`，**自动**是 34px（全局兜底已覆盖）
        · 真要第三种高度时，先说明理由再加令牌，别就地写 px（那是乱象的起点）
     ══════════════════════════════════════════════════════════════════════ */
  --control-height:       44px;   /* 标准档：表单 / 弹窗 / 页面头部主按钮 */
  --control-height-sm:    34px;   /* 紧凑档：列表工具栏 / 表格行内 / 分页区 */
  --control-height-touch: 40px;   /* 历史遗留（手机端最小触摸目标）—— 见下方说明 */

  /* ⭐⭐⭐ 2026-10-07 19:50 新增第三档「页头筛选行档」= **42px**
     ────────────────────────────────────────────────────────────────────────────────
     ▸ 为什么需要（乐哥 19:42 实报）：「搜索框，月份筛选框，还有右边新增排期的
       高度和左边tab栏的不一样，你看下要视觉上一致」。
     ▸ 但**实测结论与他（和我的）截图里的印象不同**（1920 视口，含边框）：
         tab 托盘 **42** ｜ tab 选中项 34 ｜ 搜索框 34 ｜ 月份框 34 ｜ 新增排期 34
       ⇒ **三个控件本来就完全一致**（都是 34），真正"高 8px"的是**托盘本身**
       （`padding: 3px` + 2px 边框 = 34+6+2 = 42）。
       ⇒ 两条路可选：① 托盘降到 34（治本，但托盘那 3px 内距正是它托住胶囊的地方）
                      ② **其他控件升到 42**（乐哥 19:45 拍板走这条）
     ⚠️⛔⛔ **为什么不是直接把 `--control-height-sm` 改成 42**：
        本令牌被 **48 处引用、跨 8 个文件**（table.css 17 / pages.css 10 /
        overlays.css 6 / layout.css 8 / pages-influencer.css 2 / components.css 2 /
        cost-chart.css 1）—— 表格行内控件、分页、工具栏全都在用 34。
        一改就是**「改外观顺手连累几何」**（本日铁律第一条，已犯三次）。
        ⇒ 另起一档，**只给页头筛选行用**（作用域由 layout.css 里那三条
          `:has(.table-tabs--in-header)` / `.page-header--filter-bar >` 限定）。
     ⚠️ 为什么 42 而不是 44：44 是标准档，会让这一行比卡片内的紧凑控件高一档；
        42 正好等于托盘的视觉高 ⇒ 整行中心线与外框全部对齐，且只比全局紧凑档高 8px
        （页头本就比表格区宽松，这个量级不影响全站密度）。 */
  --control-height-row:  42px;   /* 页头筛选行档：与 .table-tabs--in-header 的 42 同源 */

  /* 圆角阶梯（TDesign：3 / 6 / 9 / 12 / 9999，严格递增）
     ⭐ 2026-09-25 Gashora 像素级试妆：卡片 12 → **16px**、弹窗 12 → **20px**。
     参考图（Gashora）卡片圆角实测 ≈16-20px——大圆角是"柔和、有呼吸感"的直接来源。
     ⚠️ 与表格内嵌改造配套：表头背景已去、表格不再贴卡片边 ⇒ 大圆角不会顶破表头。 */
  --radius-none: 0;
  --radius-sm:   4px;    /* 徽章 / 小标签 */
  --radius-seg:  6px;    /* 分段控件 / 筛选 / Tab 项 */
  --radius-md:   10px;   /* 按钮 / 输入框 / 菜单行（2026-09-25：8 → 10，与卡片 16 协调） */
  --radius-lg:   16px;   /* 卡片（2026-09-25：12 → 16，Gashora 风） */
  --radius-xl:   20px;   /* 弹窗 / 抽屉（2026-09-25：12 → 20） */
  --radius-pill: 9999px; /* 头像 / 状态胶囊 */
  --radius-full: var(--radius-pill);  /* 兼容别名，勿再新增 50% / 999px 写法 */

  /* ⭐ 2026-09-26「内容分组 Tab」的托盘 —— 2026-09-28 全站升级为**白底胶囊 + 外框**
     （乐哥：「所有页面 tab 胶囊这个 ui 不统一，有的有外框，有的没有，需要补进去」）：
     原「灰托盘 + 选中白块」在**白底卡片/页头内**套灰底，与卡片语言割裂；
     统一为「白底 + 1px 边框 + 圆角 16 + 选中渐变蓝胶囊」，与详情页 Tab 行 /
     页头胶囊 / .seg 同一套语言（全站内容分组切换只此一套）。
     用于：业绩总表「直播业绩/私域业绩」、达人详情、达人表单、新建排期/排期详情、
     签约公司详情/表单、成本下钻弹窗、新增用户弹窗（共 11 处）。
     ⚠️ 令牌名保持 `--tab-tray-*`（改名会波及多处引用，收益低）；语义已更新为"胶囊容器"。
     ⚠️ 圆角 12 → 16（跟 --radius-lg 走）：白底卡形态下与卡片圆角同档才不突兀
        （原 12 是"灰托盘"形态的取值 —— 那时它是独立的小控件，不与卡片并列）。 */
  /* ⭐⭐⭐ 2026-10-07 13:40 改回 **Apple 灰托盘**（乐哥：「这个悬浮的tab不对，
     首先tab的底色，**之前不是说用apple那种吗**」）
     ────────────────────────────────────────────────────────────────────────────────
     ▸ 为什么必须换：内容页现在是**纯白** `--color-surface`(#FFF)，而托盘也是纯白
       ⇒ 同色相接 ⇒ **托盘整个"化掉"**，看不出这是个选项组（实测截图：只剩胶囊和几个字）。
     ▸ Apple 的做法：托盘是**浅灰一层**，与白底内容卡拉开一档；选中项才是实心品牌蓝。
       ⇒ 层次变成「白内容卡 → 浅灰托盘 → 实心蓝胶囊」三级，选项组才读得出来。
     ⬇️ 取值 #F1F3F5：与 `--color-surface-2`(F1F5F9) 同族但更中性一档，
        在白底上可见（Δ ≈ 10 阶，与 Apple 托盘的观感同档），又不会脏。
     ⚠️ 别写回 var(--color-surface)：那正是 10-07 上午"托盘看不见"的原因。
     ⚠️ 托盘变灰后，**未选中项**仍应是白底（不然灰底上再压一层灰更闷）——
        hover 用 `--color-surface`（白），active 用品牌蓝实心。 */
  --tab-tray-bg:             #F1F3F5;
  /* ⭐⭐⭐ 2026-10-08 15:3x **令牌已删除**（原 `--tab-tray-hover-bg` = rgba(255,255,255,0.55)）
     ──────────────────────────────────────────────────────────────────────────────
     乐哥：「二级页面这个**白色的卡片**还没去除干净」——真机取证（他的截图 2582×1034，
     沿胶囊中线逐点取色）：蓝块右侧紧邻约 50 CSS px 是 **(248,250,250)**，
     正是 0.55 白压在灰托盘(241,243,245) 上的结果 ⇒ 那块"白卡"就是**鼠标悬停块**。
     在灰托盘里，半透明白读起来仍像"第二个选中/一张白卡" ⇒ 干脆**不给底色**：
     hover 只变文字色（与一级页胶囊 `.pill-filter__item:hover` 一直以来的做法统一）。
     ⚠️ 历史：10-07 是不透明白（会切断滑动的蓝块）→ 10-08 14:1x 降为 55% 半透明
       （乐哥"粘滞感"那轮）→ **15:3x 直接去掉底色**。三次都是同一个东西在收窄。
     ⛔ 别再引入"hover 底色"：真要加，必须先想清楚它与滑动的 ink（首个子元素、绘制在最下）
        的层叠关系（`.is-inking` 那套兜底仍保留在 pages-influencer.css，就是给这一天的）。 */
  --tab-tray-active-bg:      var(--color-surface);        /* 保留：ink 指示块用（现已全站隐藏） */
  --tab-tray-radius:         var(--radius-lg);            /* 12 → 16（与卡片圆角同档） */
  --tab-tray-item-radius:    8px;

  /* ⭐⭐⭐ 2026-09-28「克制玻璃」统一语言（乐哥：「顶栏透明度、左侧菜单栏、浮岛、
     tab玻璃效果，看能不能统一同一套效果」）——
     之前四处各自写值（顶栏 .72 单色 / 侧栏纯白不透明 / 浮岛 .70→.44 / Tab .66→.40），
     数值漂移、观感不统一。现抽出四个令牌，**四处引用同一套**：
       · --glass-hi / --glass-lo：透白渐变（上实下透 —— 光源从上来，玻璃上缘更亮）
       · --glass-blur：模糊 + 饱和度（saturate 让穿过的色彩"活"起来，是玻璃质感的关键）
       · --glass-lit / --glass-lift：上缘 1px 高光 + 外投影（浮起的"光边"）
     ⚠️ 透明度取 0.70→0.46：比"白 82%"透（能看出底下内容在动），
        又比全液态玻璃（0.5→0.24）克制 —— 文字区仍清晰可读（管理后台的硬约束）。
     ⚠️ 玻璃只用于"浮在内容之上的层"（顶栏/侧栏岛/浮岛/吸顶 Tab）——
        静态的卡片、表格**不许**用（它们不是浮层，加了会糊）。 */
  --glass-hi:    rgba(255, 255, 255, 0.70);
  --glass-lo:    rgba(255, 255, 255, 0.46);
  --glass-blur:  blur(20px) saturate(180%);
  --glass-lit:   inset 0 1px 0 rgba(255, 255, 255, 0.95);
  --glass-lift:  0 8px 26px rgba(16, 24, 40, 0.14);
  --glass-bg:    linear-gradient(180deg, var(--glass-hi) 0%, var(--glass-lo) 100%);

  /* ⭐⭐⭐ 2026-09-28「液态玻璃」（Liquid Glass，乐哥拍板选 **L3 浓液态** 档）
     ─────────────────────────────────────────────────────────────────────────────
     用途：**全站 tab 条吸顶那一刻**的质感 —— 比上面的"克制玻璃"明显更强。
     乐哥原话：「这个透白毛玻璃不行，做能液态玻璃那种」+ 三档里选了 L3。

     ▸ 与克制玻璃的差别（为什么另起一套而不是改 --glass-*）：
       · 克制玻璃是**多个浮层共用**的日常语言（顶栏/侧栏/浮岛），0.70→0.46 很轻；
       · 液态玻璃只在**"贴顶"这一个时刻**出现（乐哥：「刚进来应该没那么重的阴影吧，
         是不是和其他内容叠加才有」）⇒ 它是"临时状态"的质感，两者语义不同，不能混用。
       · 改 --glass-* 会连带改顶栏/侧栏/浮岛（那是乐哥已验收的形态）。

     ▸ 四层构造（液态玻璃的关键在**边缘**，不在模糊本身）：
       ① 斜向渐变底（135deg）—— 模拟光在玻璃里折射的走向（180deg 直下会显"塑料"）
       ② blur + saturate —— 让穿过的内容"活"起来
       ③ **四边内高光**（上/下/左/右各一条 inset）—— 这是"厚玻璃"的立边
          （只做上缘 = 普通高光条；四边都做才有"一块玻璃"的实体感）
       ④ 内发光带（上下各一道大半径 inset 白）—— 玻璃的"厚度透光"
       ⑤ 双层外投影（近密 + 远疏）—— 浮起的量感
     ⚠️ 只在**吸顶态**挂载（`is-stuck`），静态页面零开销、零视觉影响。

     ⭐⭐ 2026-09-28 晚 调「真的看得出透」（乐哥：「这个悬浮 tab 透明度几乎不可见，
        看不出是透明的，得能看得出来」）——
     改前三项叠加把玻璃做成了"纯白"：alpha 平均 ~0.4 + blur(40) + **brightness(1.10)**，
     而 tab 底下滚过的多是**白色卡片**（白+白+提亮）⇒ 与不吸顶的白底**几乎无差别**。
     改后（三处同改，缺一不可）：
       · alpha 0.52/0.26/0.42 → **0.40/0.15/0.30**（白色覆盖少一档，底下内容能透出来）
       · blur 40 → **22**（40 会把穿过的一切抹成均匀一片，看不出"有东西在下面"）
       · **去掉 brightness(1.10)**（它是"看着像纯白"的主因；提亮只该由内高光负责）
       · saturate 220% → 150%（白底上高饱和无收益，降一档更稳）
     ⚠️ 可读性底线：玻璃上还要压 tab 文字，alpha 不能无限降 ——
        再往下调（如 ≤0.25/0.08）会开始与背景内容"打架"。
     ⚠️ 改的是令牌 ⇒ `.tabs` / `.sf-tabs__track` / `.page-header--has-tabs` 三处吸顶态**同时**生效。

     ⭐⭐⭐ 2026-10-07 18:00 乐哥：「感觉好像有点奇怪，透明度和顶栏一样吗」
     ────────────────────────────────────────────────────────────────────────────────
     ▸ **实测**（真机 computed，两层紧挨着，肉眼一眼看出割裂）：
       顶栏 `.page-tabs-bar`            白 0.70 → **0.46**（180deg 直下，blur20）
       吸顶卡 `.page-header--has-tabs.is-stuck` 白 0.40 → **0.15** → 0.30（135deg，blur22）
       ⇒ 中段 **0.46 vs 0.15**，吸顶卡比顶栏**透近 3 倍**。
       ⇒ 截图里能直接看到 tab 卡后面**绿/粉/蓝色块透出来**，而顶栏是实心白 ——
          上下两层紧挨着，却是两套完全不同的浮层语言 ⇒ 这就是"奇怪"的来源。
     ⚠️ 09-28 那次调"看得见透"的前提是「tab 底下滚过的是**白卡片**」；
        现在内容区是**彩色状态标签/按钮**（已入驻/否/local:jerry…），
        0.15 的中段会把它们整片透出来 ⇒ 那个前提已经不成立了。

     ✅ 改法：**只对齐顶栏的透明度档**（0.70 → 0.46），三档等比放大：
        0.40/0.15/0.30 → **0.70/0.46/0.58**
        · 0.70/0.46 = 顶栏 `--glass-hi` / `--glass-lo` 的**原值**（逐值对齐，不是约等）
        · 0.58 = 首尾的中点，让 135deg 斜向仍保留"上缘更实、下缘更透"的玻璃感
     ⛔ **blur 22 与 135deg 都不许动**：
        · blur 22 是 09-28 定的（40 会把穿过的内容抹成一片，看不出"下面有东西"）
        · 135deg 斜向是"液态玻璃"与"普通毛玻璃"的分界（180deg 直下会显塑料）
        这次只解决**透明度档位不一致**，不碰这两条已验收的设定。 */
  --liquid-bg: linear-gradient(135deg,
                 rgba(255, 255, 255, 0.70) 0%,
                 rgba(255, 255, 255, 0.46) 48%,
                 rgba(255, 255, 255, 0.58) 100%);
  --liquid-blur: blur(22px) saturate(150%);
  --liquid-edge: rgba(255, 255, 255, 0.85);
  /* ⭐⭐⭐ 2026-10-07 17:14 乐哥：「tab悬浮，不要搞那么重阴影吧，你看下我给你的这个
     参考图，这个就是标杆」（Apple iPhone Duo 页面顶部那条吸顶卡）
     ────────────────────────────────────────────────────────────────────────────────
     ▸ **标杆的实测值**（用户提供参考图，逐像素量出来的，不是目测）：
       卡片外侧**投影 = 0**。下缘外侧 y=129/130 是 2px 边框线（#E6E6E9），
       再往下 y=131..174 **全 255 纯白** ⇒ 一点扩散都没有。
       左缘外侧同理：x=238/239 是 2px 边框线，x=240..247 全纯白。
       ⇒ 标杆 = **白实体底 + 1px 淡线 + 零投影**，浮起感来自**边框**而非阴影。
     ▸ **原值的毛病**（就是乐哥说的"重"）：
       `0 16px 44px rgba(16,24,40,0.22)` —— 透明度 **0.22** 是规范上限 0.07 的 **3 倍**，
       44px 扩散落在 64px 高的卡上 ⇒ 上下左右糊出一圈灰雾，内容穿过时整行发灰。
       叠加 6 条 inset 白色高光（合计白量 0.65/0.50/0.85×2/0.70）更是又厚又脏。

     ✅ 改法（三处同时生效：`.page-header--has-tabs` / `.tabs` / `.sf-tabs__track`
        都读本令牌）：
       · 外投影：**删掉 44px/0.22 那条**，换一条极淡的托地影
       · inset 高光：**只留上下两条**（玻璃感所需的"上缘受光/下缘反光"），
         删掉左右两条与两条大范围白色内发光
       · 透明度全部 ≤ 0.07，与 §5.3「低透明度 + 大扩散」公式对齐
     ⚠️ 为什么保留一点投影而不是完全 0：卡片下方是**滚动内容**（表格行会穿过去），
        零投影时内容贴着玻璃走、完全没有"托住"的感觉。基准图的 0 能成立是因为
        下方本来就是纯白空背景 —— 我们的下方是**动的内容**，不能照抄 0。
     ⛔ 别再写回 0.1 以上的透明度：那就是乐哥说"重"的直接原因。

     ⭐⭐ 2026-10-07 18:00 乐哥：「然后要不加一点点阴影吧」
     ────────────────────────────────────────────────────────────────────────────────
     ▸ 上一版是 `0 2px 6px -2px rgba(...,0.07)`，实机下缘偏暗 19 —— 偏**轻**，
       内容穿过时卡片没有"托住"的感觉。
     ✅ 加到 `0 3px 10px -3px rgba(...,0.10)`：
       · 透明度 0.07 → **0.10**。⚠️ 超出 §5.3 建议区间（0.03~0.07）**是有意的**：
         该区间针对的是"静态卡片浮起"，而这是**吸顶浮层压在滚动内容之上**，
         需要更强的接触阴影才能读出"浮在内容上"。属场景差异，不是回退。
       · 模糊 6 → **10px**、spread −2 → **−3**（更大更散，不是"贴边一圈"）
       · 仍远小于 17:14 那版的 44px/0.22（那版才是乐哥说"重"的）
       · inset 两条（0.85 / 0.55 白）**保持不变** —— 那是玻璃边缘受光，不属于"阴影"。 */
  --liquid-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.85),
    inset 0 -1px 0 rgba(255, 255, 255, 0.55),
    0 3px 10px -3px rgba(16, 24, 40, 0.10);
  /* 内层小框（装胶囊的那圈）在液态玻璃上的形态 —— 乐哥：「内层小框做」：
     白卡变玻璃后，小框如果还是**不透明白底**，就像"玻璃上贴了张白纸"（实测踩到）。
     这里给它**更透的白 + 更亮的边**，与外层玻璃形成两层玻璃的层次。 */
  /* ⭐⭐⭐ 2026-10-07 15:50 30% 半透明白 → **32% 半透明蓝灰**（乐哥：「tab栏浮动的那个透明，
     你改没了，加回来」）
     ────────────────────────────────────────────────────────────────────────────────
     ▸ 病根：常态托盘是 **Apple 灰 `--tab-tray-bg: #F1F3F5`（实体）**，
        而吸顶时被换成 `rgba(255,255,255,0.30)` = **30% 半透明白**
        ⇒ 托盘底色在吸顶时**整个化掉**（与 13:45「托盘白底化掉」同一类病，
          只是这次发生在**吸顶态**）。
        真机实测：吸顶后托盘 computed background 变 rgba(255,255,255,.3)，
          底色/边框层次读不出来，看起来像"tab 栏的底没了"。
     ▸ 新值 `rgba(227, 238, 251, 0.32)`：**色相跟着 `--tab-tray-bg` 的 #E3EEFB 走**
        （同一支蓝 ⇒ 常态与吸顶是"同一个蓝"在不同透明度下），
        透明度 0.32 让底下的表格内容**仍能透出**（玻璃的前提）。
     ⚠️ 别用纯白半透明：那会让托盘在吸顶时偏白，与常态的蓝灰脱节 ⇒ 两态不像同一套。
     ⛔ 别改成不透明（alpha=1）：那就失去"浮动透明"的效果了。 */
  --liquid-tray-bg: rgba(227, 238, 251, 0.32);
  --liquid-tray-edge: rgba(255, 255, 255, 0.62);

  /* 顶部区（第一行）的高度。
     ▸ 2026-09-19 结构重排后，**全页顶部只有一行**（面包屑那行已撤，标签栏升为第一行），
       所以本值与 --header-height 相等，二者都指"顶部那一行"。
       ⚠️ 为什么仍保留两个令牌：`--header-height` 还被 analytics.js 的 headerHeight()
          读来当**吸顶高度**（它只关心"顶部占多高"，不关心归谁）；
          而本令牌描述"标签栏 / 品牌区这一行多高"。两者当前同值、语义不同，别合并。
     ▸ 高度按下述三次调整，**每次都要连着标签体量一起改**（只改栏高会让标签显小）：
        ① 2026-09-19 晚 56 → 60（乐哥：「顶栏要不再高一点点？现在感觉有点挤」）。
          ⭐ 实测："挤"的主因**不是栏高而是间距** —— 工具按钮间只有 2px、标签间 4px。
             那次是**三处一起调**才见效：栏高 56→60、标签 36→40、间距 2/4→6/6。
        ② 2026-09-20 60 → 52（乐哥：「最上面的标签，是不是太大，太占位置了」）。
          ⭐ 本轮实测出的客观依据（这才是"太大"的硬证据，不是感觉）：
             · 标签 40px **比全站所有按钮都大** —— `.btn`=36、`.btn--sm`=30；
             · 标签栏内图标钮 40px **比全站 `.icon-btn` 基准 36 大**（100 处在复用它），
               标签栏是**全站唯一**把它放大到 40 的地方；
             · 栏高 60px 是全站最高的横条。
           ⇒ 收回高度、**间距 6px 一个字不动**（上一条已证间距才是治"挤"的药）。
             栏 52 / 标签 34 / 图标钮 36：标签上下各留 9px，仍比图标钮大一圈、不显小，
             量级与 Chrome 浏览器页签相当。内容区起点 84 → 76，**每屏净赚 8px**。
        ③ 2026-09-26 96 → 84（乐哥：「顶部的菜单高度，稍微矮一点点吧」）。
          ⭐ 收到 84 的依据是**实测内部最高元素**（不是拍数字）：
             顶栏内最高的可交互元素 = `.page-tabs-bar__tools`（含「我的」胶囊）**58px**、
             logo 46px ⇒ 96px 时上下留白各 19px，明显偏松（顶栏像"一条厚带子"）。
             收到 84 ⇒ 上下留白各 13px，仍比"贴边"宽裕，但顶栏不再压住首屏。
          ⚠️ 为什么只减 12 而不是减更多：**58px 内容 + 2×13px 是这套内容的舒适下限**——
             再往下（如 76）留白只剩 9px，搜索胶囊/头像几乎顶到上下边缘，会重新变成
             2026-09-19 那次"挤"的观感（乐哥当时反馈过）。
        ④ 2026-09-26 84 → **72**（顶栏 A6 定稿，乐哥逐轮拍板：原型 .tmp-check/topbar-proto/）。
          ⭐ 这次不是孤立收高度，是**整套重构**，内容物全部重排（详见 layout.css 顶栏段）：
             · 多标签导航恢复（9-25 隐藏的那套 DOM/逻辑原样启用，只删 display:none）；
             · 搜索 300px 胶囊 → 40×40 图标盒（乐哥：「搜索改成图标吧，不要输入框了」）；
             · 「我的」胶囊 → 40px 纯圆头像（乐哥：「改成一个圆圈，里面只有头像」）；
             · 铃铛/搜索/头像统一 40×40「有框无底」（1px 描边、透明底）；
             · 栏体改半透明毛玻璃（乐哥：「顶栏做一点透明，正文内容滚上去时更好看」）。
          ⭐ 高度的依据同样是**实测**：原型先在 64px 上验证（乐哥：太矮不协调），
             加到 72 ⇒ 控件 40px 上下各留 16px（与 84/58 的 13px 同档，不挤不空）。
          ⚠️ 历史调过的"挤/空"两个方向仍由 tests/page-tabs.test.js 的 H1/H2 锁着，
             本值再调时不需要改断言（它们锁关系不锁数值）。
     ⚠️⚠️ 改本值**必须同时**改 `--header-height`（下方 137 行）—— 两处不同值会让
        分析页 `/analytics` 的目录条 sticky 错位（它读 --header-height 当吸顶高度：
        `top: var(--header-height)`，而它要贴在标签栏下沿）。
        ⇒ 两值同源，改一个必须改另一个。公式：`--header-height = --frame-gap + --tabs-bar-height`
        （由 tests/restruct-topbar.test.js 的 C1 静态锁住，漏改会立刻红）。
     ⚠️ 侧栏 top / 内容区 margin-top / 品牌区高度 / 标签栏高度**四处都是 `calc()` 或
        `var()` 引用本令牌**，改一处自动全跟 —— **不要**去动那四处（它们本来就该跟着变）。 */
  --tabs-bar-height: 60px;   /* ⭐⭐ 2026-10-06：72 → **60**（**去掉多标签**，顶栏只留
                                「品牌 ／ 页面定位（面包屑）／ 右侧工具」三段，与参考图一致）。
                                依据：顶部不再有标签行 ⇒ 60 = 控件 40 + 上下各 10。
                                ⚠️ 手机端本来就是"无标签"形态（≤768px 块里隐藏标签格），
                                   本次只是**把那套形态提到桌面**。
                                历史：56→60（09-19）→52（09-20）→76→88→96（09-25）→84→72（09-26）→60。
                                作顶栏高度的唯一来源。 */

  /* 阴影三档（可感知层级：卡片 md、悬浮/下拉 lg、描边 ring）
     ⭐ 2026-09-25 Gashora 像素级试妆：全面**调柔调散**——参考图卡片阴影极淡但分明浮起，
     旧值 opacity 偏高、偏移偏小 ⇒ 阴影"贴"在卡片下缘显脏。改为低透明度 + 大扩散。 */
  --shadow-flat:   none;
  /* ⭐⭐⭐ 2026-10-08 与 `--shadow-card` **同批修正 Y 偏移归零**（乐哥：「下面比其他几个边重，
     这个得做一样」——见下方 shadow-card 处的完整说明）。
     `--shadow-sm` 实测用在：tab 托盘的滑动白块（pages-influencer）/ 圆形翻页按钮（pages-finance）/
     快捷入口与取景框的 hover 态（pages）—— 全是"小卡片/小控件"，同样有下重问题，一并归零。 */
  --shadow-sm:     0 0 2px rgba(16, 24, 40, 0.03), 0 0 3px rgba(16, 24, 40, 0.04);
  --shadow-md:     0 2px 4px rgba(16, 24, 40, 0.03), 0 8px 20px rgba(16, 24, 40, 0.05);
  --shadow-lg:     0 4px 8px rgba(16, 24, 40, 0.04), 0 16px 40px rgba(16, 24, 40, 0.07);
  --shadow-ring:   0 0 0 1px var(--color-border);
  --shadow-raised: var(--shadow-lg);   /* 旧名兼容别名 */
  /* 卡片浮起（苹果式）：0.5px 发丝线 + 大而散的柔影。用在纯白底弹层内的模块卡——
     白底上没有明暗差可用，只能靠这一层影把卡片"托"起来（浅灰底不适用，会显脏）。 */
  --shadow-card-float: 0 0 0 0.5px rgba(16, 24, 40, 0.12), 0 1px 3px rgba(16, 24, 40, 0.04), 0 10px 22px rgba(16, 24, 40, 0.08);

  /* ⭐⭐⭐ 卡片浮起（2026-10-07 乐哥选「A 档」：把 20px 模糊收掉）
     ────────────────────────────────────────────────────────────────────────
     参照系与实测依据（不是审美猜测）：
     · 乐哥给的 Apple 参照图里，**白卡边缘过渡实测只有 1 格**（灰度 246 → 255 一步到位）
       ⇒ 卡片的"干净"来自边界清晰、不扩散。
     · 改前配方 `0 1px 2px .03, 0 8px 20px .05` 实测（dpr2 逐物理像素扫卡片右缘）：
         255 → 237（边框）→ 246 → 248 → 250 → 251 …爬 **13~25 个 CSS px** 才回到底盘色
       ⇒ 边框外一大片灰雾，这就是乐哥说的「没咱们的这么重」。
     · 改后配方（下方值）实测扩散收敛到 **约 3 格（1.5~2 CSS px）**，与 Apple 同档。
     ⚠️ 为什么以前是 20px：当时底盘接近白（色差仅 9 阶）、色差托不住层次，
        被迫用宽扩散造"浮起"。现在保留一条 1px 贴合影（勾边界）+ 4px 淡扩散（托起），
        边界依然是"一根线"。
     ⛔ 别写回 20px：那会让每张卡外面都罩一片灰雾，卡片多时页面发闷。
     ⚠️ 与 `--shadow-card-float`（弹层内的模块卡，带 0.5px 发丝环）仍是两档：
        那个用在纯白底上需要更强自我勾边；这个用在外层卡片上，越轻越好。

     ⭐⭐⭐ 2026-10-08 乐哥：「悬浮阴影是不是下面比其他几个边重啊，这个得做一样，
           都和上面那个边一样」（达人详情·佣金配置里的白卡，实测截图圈出）
     ────────────────────────────────────────────────────────────────────────
     🐞 根因：两道影的 **Y 偏移都是 +1px** ⇒ 阴影整体下移 ⇒ 下边缘可见、
        上边缘几乎被盖住 ⇒ 视觉上"下边比上边重"。
        （`0 1px 2px` 离上边缘只剩 2−1=1px 的可见带，离下边缘有 2+1=3px。）
     ✅ 改法：**Y 偏移归零**（`0 1px` → `0 0`）—— 上下左右各 2px / 4px 完全对称。
        ⚠️ 为什么不是"把下边也变没"（直接删掉贴合影）：卡片的边界感来自
           "极淡贴合影 + 1px 实边框"的组合，删掉影会让白卡在浅底上"贴不住"。
           对称后四边各有一圈同样淡的影，恰好是乐哥说的"四个边一样"。
        ⚠️ 只改 Y 偏移、**不动模糊与透明度**（0.03/0.05）—— 那是 10-07 按 Apple
           参照图逐像素标定的（扩散收敛到 1.5~2 CSS px），改了就把那次成果推翻。 */
  --shadow-card:   0 0 2px rgba(16, 24, 40, 0.03), 0 0 4px rgba(16, 24, 40, 0.05);

  /* ⭐⭐ 2026-10-08「选中态浮起」—— 乐哥动效第②条：「选中要有悬浮突出效果」。
     语义：**选中的胶囊 = 浮在托盘上的一小块**（与侧栏选中项的"白卡浮起"同一套语言：
     都是"选中的那个东西离底更近一层"）。
     ▸ 为什么投影带**品牌蓝**而不是中性黑：胶囊底是品牌渐变，中性黑影落在蓝上会发灰
       发脏（与 `.btn--primary:hover` 投影同一条经验：同色系 rgba(0,100,224,…)）。
     ▸ 量级（2px/6px、.28）是**四档实拍对比**选的（无 / 1-3-.20 / 2-6-.28 / 2-8-.35）：
       1-3-.20 在 1x 下只差 2 个灰阶、肉眼读不出"浮"；2-8-.35 开始发重；
       **2-6-.28 是"看得出浮起、又不脏"的档**（截图见 deliverables/选中浮起-改前改后）。
     ⚠️ 只给**选中态**，未选中项不加影 —— 一排全浮 = 全没浮（对比才是"突出"的来源）。 */
  --shadow-pill-active: 0 2px 6px rgba(0, 100, 224, 0.28);

  /* ══════════════════════════════════════════════════════════════════════════
     动效（2026-09-26 全站升级 · 乐哥：「要求轻盈灵动，看着自然舒服，
     不要那种傻快傻快的效果」）
     ──────────────────────────────────────────────────────────────────────────
     ⭐ 「傻快」的根因不是时长不够，是**曲线**：原来只有一条
        `cubic-bezier(0.2, 0, 0, 1)`，进出共用 —— 它头尾都偏"硬"，
        动起来像"被推着走完"，所以显得机械。

     ⭐⭐ 现在按**用途**分三条曲线（这是"轻盈"的主要来源）：

       ┌────────────────┬──────────────────────────────┬──────────────────────┐
       │ 用途            │ 曲线                          │ 为什么                │
       ├────────────────┼──────────────────────────────┼──────────────────────┤
       │ 进场 / hover    │ cubic-bezier(.22, 1, .36, 1) │ 开头快、尾巴长：      │
       │ --motion-ease   │ = easeOutQuint                │ 立刻有反应，落地是    │
       │                 │                               │ "滑进去"而不是"刹住"  │
       │ 位移 / 形变      │ cubic-bezier(.65, 0, .35, 1) │ 两端都缓：位置在变    │
       │ --motion-ease-  │ = easeInOutQuad               │ 的东西（指示块、宽度）│
       │ in-out          │                               │ 起停不吃力            │
       │ 退场            │ cubic-bezier(.4, 0, 1, 1)     │ 干脆收走 —— 离场拖泥  │
       │ --motion-ease-  │ = easeInQuad                  │ 带水会让人觉得"卡"     │
       │ exit            │                               │                      │
       └────────────────┴──────────────────────────────┴──────────────────────┘

     ⚠️ 时长只从 150/200/300 微调到 **160/240/340**（+7%~13%）——
        不是靠"变慢"来显得柔和（那会变成迟钝），而是靠曲线 + 位移量 +
        错峰（见 --motion-stagger）。慢一点点的同时"尾巴"更长，观感才轻。
     ⚠️ `--motion-ease-standard` 是**兼容别名**（全站 133 处在用，几乎全是**颜色类**过渡：
        background-color / color / border-color / box-shadow）。⛔ 不要删它：
        删了那些地方会静默回退成浏览器默认的 `ease`（无报错）。
        ⛔⛔ **它不能指向带过冲的曲线**（2026-09-26 修，乐哥实报）——见下方专节。 */
  --motion-fast: 110ms;   /* hover / active / 选中 / 按压反馈（立刻跟手） */
  --motion-base: 155ms;   /* 小浮层：下拉 / 行内菜单 / 日期面板 */
  /* ══════════════════════════════════════════════════════════════════════════
     ⭐⭐ 2026-10-17 侧栏一级菜单「折叠」专用档（手风琴）
     ──────────────────────────────────────────────────────────────────────────
     乐哥：「手风琴的动画傻快傻快的，你优化下，要 iOS 那种优美的感觉」。

     ▸ 为什么原来的观感是"傻快"：折叠用的是 `--motion-base`(155ms) +
       `--motion-ease-in-out`。155ms 是**小浮层**（下拉/日期面板）的档，
       而 iOS 的列表展开大致在 300-400ms；更关键的是那条曲线**带 3.85% 过冲**
       （为"灵动"设计的），用在**高度形变**上会读作"顶一下再落回来" —— 急。

     ▸ iOS 的做法（SwiftUI / UIKit 默认的 "smooth"）：
       · 时长 0.35s 左右（比浮层长一倍，能看清"拉出来"的过程）
       · 曲线 `cubic-bezier(0.32, 0.72, 0, 1)`：起手快、收尾极缓，**不过冲**
       · 收比展利落一点（iOS 收起普遍比展开快 20% 左右）
       ⇒ 三个值都照这个来。⛔ 别复用到别处：这是"高度形变"专用的档，
         弹层/下拉用它会显得拖沓（它们要"立刻跟手"）。

     ⚠️ 与 `--motion-base` 的关系：**不动**那个变量（全站 39 处引用）。
        这里只新增，避免"调一个折叠把全站弹层都拖慢"。 */
  --nav-fold-dur: 360ms;                              /* 展开 */
  --nav-fold-dur-close: 290ms;                        /* 收起（略快，收要利落） */
  --nav-fold-ease: cubic-bezier(0.32, 0.72, 0, 1);    /* iOS "smooth"：起手快、收尾缓、无过冲 */
  /* ⭐⭐⭐ 2026-09-29：**大浮层档被激活**（220 → 300ms）。
     ─────────────────────────────────────────────────────────────────────────────
     🐞 根因（逐帧实测 + 全站 grep 发现）：本令牌注释写着"跨屏 / 大面板"，
        但 **全站 0 处引用** —— 弹窗/抽屉当初误用了 `--motion-base`(155ms)。
        而 C 档曲线（pop，y1=1.68）在 **t=0.25 就走了 91%**：
        定格实测弹窗 **60ms 就已完全到位**，整段 155ms 里**只有 45ms 可见运动、
        110ms 是空转**（元素已静止，动画在等时间走完）。
        ⇒ 观感是"啪地一下冒出来"，不是"从容地展开"——乐哥：「要自然」。
     ⭐ 现在按 Apple 官方浮层的节奏分层：**大浮层 300ms + 无过冲曲线**
        （`--motion-ease-sheet`）—— 到 50% 用 49ms、到 99% 用 216ms，
        **眼睛能看到完整过程**，且落位平顺不回弹。
     ⚠️ 为什么不用 base：155ms 配无过冲曲线，可见运动只有约 111ms，
        仓促感仍在；300ms 是 Apple sheet/modal 的实际时长区间（300~350ms）。
     ⚠️ 分层纪律（照做，别一改全改）：
        · 大浮层（弹窗 / 抽屉 / 搜索弹层）→ `--motion-slow` + `--motion-ease-sheet`
        · 小浮层（下拉 / 行内菜单 / 日期面板）→ `--motion-base` + `--motion-ease-sheet`
        · 小物件蹦一格（开关滑块）→ **保留 `--motion-ease-pop` 的过冲**，那是它该有的手感 */
  --motion-slow: 300ms;   /* 大浮层：弹窗 / 抽屉 / 搜索弹层（原 220ms，从未被引用） */

  /* ⭐⭐⭐ 2026-10-08 00:5x **页面进场专用时长**（Apple 官网式渐显）
   * ────────────────────────────────────────────────────────────────────────────────
   * 乐哥：「页面切换…我们应该做 apple 官网那种渐变显示」。
   * ▸ **为什么不能复用 `--motion-base`(155ms)**：那是**小浮层**的档
   *   （下拉 / 行内菜单 / 日期面板，见上面 C 档三档的分工）。
   *   页面进场是**大���面**、且是用户主动等待的那一下 ⇒ 用小浮层的档必然读成"闪"。
   * ▸ **420ms 的取法**（不是拍的）：Apple 官网产品页的段落进场约 400~600ms；
   *   取**下沿 420** —— 再慢会让"点菜单 → 看到内容"的等待感变得明显，
   *   而这个页面的内容量（表格几十行）本身就偏重，慢一点反而更协调。
   * ⭐ 2026-10-08 乐哥：「Apple 渐显速度太慢了，稍微快 10%」⇒ **420 → 378ms**（420×0.9）。
   *   ⚠️ 只动时长，**曲线/位移不动** —— 快慢是时长维度，"弹不弹"是曲线维度，别一起动。
   * ⚠️⛔ **为什么必须单独一档，而不能就地改 `--motion-base`**：
   *   `--motion-base` 全站 133 处引用（几乎全是弹层/下拉）。
   *   改它 ⇒ 所有下拉、菜单、日期面板一起变成 420ms ⇒ 全站变"黏"。
   *   ⚠️ 这就是「**动效语言分层**」：同一套语言里，"小浮层快"与"页面慢"是并存的两档。
   * ⚠️ 配套：曲线与位移在 base.css 的 `@keyframes page-fade-in` 里同时改了
   *   （pop→standard 无过冲、位移 12→8、scale 0.985→0.995），
   *   **时长与曲线必须成对改** —— 只改时长不换曲线，"弹一下"的感觉会留着。 */
  --motion-page-enter: 378ms;

  /* ⭐⭐⭐ 2026-10-08 00:6x **切换标签专用时长**（内容左右滑动）
   * ────────────────────────────────────────────────────────────────────────────────
   * ▸ **为什么不能复用 --motion-page-enter(378)**：
   *   两者是**频率完全不同的两个动作**——
   *     · 页面进场：一天几十次（点侧栏菜单），可以慢、要有"浮现"的仪式感；
   *     · 标签切换：一天几百次（在几个页之间来回跳），420ms 会觉得**黏**。
   *   ⇒ 250ms：够读出"往哪个方向换页"，又不拖沓。
   * ⚠️ 这条与上面那条是**同一课的两个面**：
   *   「同一套动效语言」≠「所有动作同一档」。
   *   **无方向性的动作幅度小、时长可长；有方向性的动作幅度大、时长要短。**
   *   （今晚已在页面进场上吃过教训：位移 12px + pop 曲线 = 读作"弹"而不是"浮现"。） */
  --motion-tab-slide:   250ms;

  /* ⭐⭐⭐ 2026-09-26 第三版：**C 档「活泼」定稿**（乐哥：「动画用 c」）─────────────
     定稿过程（两轮调参 → 三档在线试用 → 老板拍板）：
       v1 150/200/300 + 单曲线        → 乐哥：「不要那种傻快傻快的效果」
       v2 160/240/340 + 三条曲线      → 乐哥：「**还是不够灵动**」
          ⛔ 那次误诊了：把"不灵动"归因于时长，于是**拉长**到 240ms。
             真因是**位移曲线慢起步**（`cubic-bezier(.65,0,.35,1)` 的 y1=0）——
             逐帧实测前 2 帧只走了 1px。**加长时长只会让"糊"更明显。**
       v3（本版）= B 档「灵动」 + C 档「活泼」，在真实系统里用 `?motion=` 现场对比
          ⇒ 乐哥选了 **C**（更短时长 + 更大的落位过冲）。
     ⚠️⚠️ **两条曲线的分工是本版的核心**，别把它们合并成一个：
        · 位移（`--motion-ease-in-out`，y1=1.34 / 过冲 **3.85%**）
          用于"滑过去"的元素（tab 指示块、抽屉）——
          真机实测：86px 行程上过冲 **3.3px**，读作"落位时轻咬一下"，不读作"没对准"。
        · 弹入（`--motion-ease-pop`，y1=1.68 / 过冲 **13.61%**）
          用于"从无到有"的元素（卡片入场、列表行、开关滑块、弹窗缩放）——
          它们没有"对准"语义，过冲大一点才生动。
          真机实测：弹窗缩放峰值 1.0078（880px 宽上 ≈ 7px），温和。
        ⇒ 实测依据：两组过冲都是"肉眼刚好读出生命力"的量级；
          再往上（位移 y1≥1.45 / 弹入 y1≥1.85）就会被 tests/motion-system.test.js 抓红。
     ⚠️ 过冲**只在落位段**，起手段仍是"立刻走"（t=0.25 时已走 91%）——
        这是"灵动"的来源，别为了"稳"把起步改慢。 */
  --motion-ease:          cubic-bezier(0.2, 1.34, 0.4, 1);   /* 进场 / hover（快起 + 过冲）——**只给位移用** */
  --motion-ease-in-out:   cubic-bezier(0.2, 1.34, 0.4, 1);   /* 位移 / 形变（同源，保证语言统一） */
  --motion-ease-exit:     cubic-bezier(0.5, 0, 0.9, 0.6);    /* 退场（**慢起**快收，不抢戏） */
  /* ⭐⭐ 2026-10-08 14:x **滑行曲线**（慢起慢收、无过冲）—— 专给"指示器平移"用（当前仅
     `.tabs__ink`，即 tab 选中框）。
     ⚠️ **为什么不复用 --motion-ease-in-out**：那条是"**快起** + 落位过冲"（乐哥要的"灵动"，
        注释里还明确写"别为了稳把起步改慢"）。但乐哥对 tab 选中框的要求是
        「**丝滑**地滑到下一个选中框」—— 实测：快起曲线下 **45ms 就走完 76% 行程**，
        读作"唰一下到位、几乎看不见过程"，与"丝滑"正好相反。
     ▸ 语义分工（两条曲线各管一类动作，不是"两套语言"）：
       · **位置变化**（页面进场 / 内容面板 / 卡片落位）= 灵动（快起 + 微过冲）
       · **指示器平移**（选中框从 A 滑到 B）= 丝滑（两端缓、中段匀速）
     ▸ 取值 `cubic-bezier(0.45, 0, 0.25, 1)`：起步与落位各占约 1/4 行程，中间匀速滑行
        （Material 的 standard easing 档），全程可读、无过冲。 */
  --motion-ease-glide:    cubic-bezier(0.45, 0, 0.25, 1);

  /* ⭐⭐⭐ 2026-09-26 **颜色类专用曲线（无过冲）** —— 乐哥实报「鼠标移到菜单上，
     选中框会快速闪好几次」的修复。
     ──────────────────────────────────────────────────────────────────────────────
     🐞 事故链：v3 定稿时把本别名指向了 `--motion-ease`（带过冲 y1=1.34），
        本意是让历史那 133 处 hover/选中态"自动升级、更灵动"。
        ⛔ 但**过冲曲线用在颜色上 = 闪**：颜色分量会冲过目标色再回落。
        逐帧实测（hover 侧栏菜单项，.tmp-check 对照实验）：
           · 过冲版：bg 冲到 **rgb(249,254,255)**（比目标 241,245,249 还亮）→ 再回落；
             文字色冲到 **rgb(27,29,31)**（比目标更黑）→ 再回 —— 人眼读作"闪一下"；
             鼠标滑过几个菜单项就连闪几次。
           · 无过冲版：全程**单调趋近**目标色，不冲过头。
        ⇒ 颜色的"过冲"没有任何视觉意义（不像位移有"落位"语义），只会闪。
     ⭐ 本值 `cubic-bezier(0.2, 0, 0.2, 1)`（easeOutQuad 类）：无过冲 + 快起缓收，
        与 --motion-ease 共享 x1=0.2 的起手节奏（语言统一）。
     ✅ 分工（记住这三条，别再合并）：
        · 颜色 / 状态 / 布局尺寸（本值）—— **绝不过冲**
        · 位移 / 形变（--motion-ease-in-out，过冲 3.85%）—— 滑过去的元素
        · 弹入（--motion-ease-pop，过冲 13.6%）—— 从无到有的元素
     ⚠️ 由 tests/motion-system.test.js 静态锁住"本值必须无过冲"（反向注入有牙），
        想用过冲请显式引用 --motion-ease / --motion-ease-pop。 */
  --motion-ease-standard: cubic-bezier(0.2, 0, 0.2, 1);

  --motion-ease-pop:      cubic-bezier(0.34, 1.68, 0.5, 1);  /* 弹入（落位回弹，约 13.6%） */

  /* ⭐⭐⭐ 2026-09-29 **浮层曲线（Apple 式，无过冲）** —— 乐哥：「动效参考 Apple 官方的，要自然」
     ─────────────────────────────────────────────────────────────────────────────
     🎯 为什么另起一条而不是改 `--motion-ease`：那条（pop，y1=1.68 / 过冲 13.6%）被
        **开关滑块**等"小物件蹦一格"的场景使用 —— 过冲对那类是正确的（东西真的"蹦"进格子）。
        浮层不是"蹦"，是"展开"：带 14% 回弹会读成"弹一下才落定"，与 Apple 的从容不是一回事。
     ⭐ Apple 的"自然"来自两点（**都不是回弹**）：
        ① **全程可感知**：t=0.25 走 78%、t=0.5 走 95% —— 运动均匀铺开在整段时间里，
           而不是"前 1/4 时间冲完、剩下空转"；
        ② **平滑收尾**：末段减速收敛到 1，没有越过目标再弹回。
        ⇒ 曲线取 `cubic-bezier(0.32, 0.72, 0, 1)`（弹簧临界阻尼的常用近似，
          与 Apple 自家浮层观感一致）。
     实测（定格 · 弹窗 300ms）：0ms scale .94 → 60ms .979 → 120ms .995 → 300ms 1.000，
        全程单调趋近、**无过冲**（过冲峰值 0.0%）。
     ⚠️ 由 tests/motion-system.test.js 静态锁住"本曲线必须无过冲"，想用过冲请显式引用
        `--motion-ease` / `--motion-ease-pop`。 */
  --motion-ease-sheet: cubic-bezier(0.32, 0.72, 0, 1);

  /* 进场位移量。12px（C 档）：配合"快起"曲线，进场有明确的"从下长出来"。
     ⚠️ 真机实测（页面区块 translateY 逐帧）：12 → 6.28 → 2.06 → **-1.48** → 0
        —— 落位时越过终点 1.5px 再收回，肉眼读作"落定"，不晃。
     ⚠️ 别再加大：超过 16px 在表格式密集页面会读成"整页在跳"。 */
  --motion-rise:  12px;
  /* 按压缩放。0.96 = 缩小 4%。
     ⚠️ 原型里的 C 档写的是 0.955 —— 定稿时**收到 0.96**：0.955 离"按钮在躲你的手指"
        那条线（0.95）只差 0.005，在"C 档已经很快"的前提下再加深度按压，
        会从"有咬合感"滑向"廉价"（按压深度与时长无关，不该跟着档位一起激进）。
     ⚠️ 到 0.95 就是廉价感的来源，别试探那条线。 */
  --motion-press: 0.96;
  /* 列表 / 卡片逐块进场的错峰间隔。30~40ms 是人眼能读出"依次"的最小差。
     ⚠️ 原型里的 C 档写的是 26ms —— 定稿时**回到 30ms**：低于 30ms 就退化成
        "一起出现"，错峰的意义消失（错峰是"灵动"里最省力的一笔，不该被砍）。
     ⚠️ 总时长必须封顶（见 base.css 里用 `:nth-child(-n+6)` 限帽），
        否则 20 行的表会跑到 600ms，用户等数据时反而觉得慢。 */
  --motion-stagger: 30ms;

  /* 图标（Lucide 内联 SVG，三档尺寸，stroke-width 2） */
  --icon-size-sm: 16px;
  --icon-size-md: 20px;
  --icon-size-lg: 24px;
  --icon-stroke-width: 2;

  /* 布局 */
  --container-max:         1440px;
  /* ⭐⭐ 2026-09-28 16:55 乐哥**再次拍板**：回到 **1120**。
     ────────────────────────────────────────────────────────────────────────
     原话：「新建排期里的二级页面，我们需要是**之前的宽度**，不是现在的。」
           +「上面白色底的标题行，宽度得做得跟二级页面的宽度一样。**你做错了**」

     ⛔ 我做错在**方向**：
        15:03 把本令牌 1120→1280、窄档 1144→1360 后，**内层还有 4 处硬编码 1120**
        在生效 ⇒ 实际内容列仍是 **1120**，而**白色标题行 `.detail-head` 是撑满
        `.page--narrow` 内容区的 1280** ⇒ 那段时间是「标题行 1280 / 内容卡 1120」，
        左右各差 80px、明显不齐。
        16:06 我修的时候，选择**把内容卡改宽到 1280 去凑标题行** —— 把不齐抹平了，
        但代价是**内容整体变宽 160px**。乐哥要的是**反方向**：
        **内容保持 1120，把标题行收到 1120 跟它对齐。**

     ⚠️⚠️ 与 `layout.css` 的 `.page--narrow` 是**两个来源取小**的关系 ⇒ **必须同值**。
        本处 1280→**1120**，窄档 1360→**1200**（1200 − 左右 padding 各 40 = 内容 1120）。
        两源同值后：`.detail-head` / `.sf-hero-line` / `.sf-tabs__inner` /
        `.sf-body__content` / 内容卡 **全部 1120、左缘对齐成一条竖线**（一处改齐全部）。
     ⚠️ 为什么改窄档而不是给标题行单独加 max-width：单独加会让「1360 的页容器里
        飘着 1120 的标题行」，左右还要各自居中一次、极易错位；改档位是**唯一宽度来源**，
        符合本项目的"两档制"（不自造第三档）。 */
  /* ⭐⭐ 2026-10-06 全站缩放系数（乐哥提「页面 dpi 有点大，想整体调小」）
     ──────────────────────────────────────────────────────────────────────
     ▸ 根因（真机实测，不是猜）：他的 Mac + 4K 显示器，**系统缩放设成 1920×1080 等效**
       ⇒ dpr = 2。同一块 4K 屏上，元素物理尺寸是 Win 4K@100%（dpr 1）的**整整 2 倍**：
       正文物理 28px vs 14px / 表格行高 134px vs 67px / 侧栏 480px vs 240px。
       他「浏览器要缩到 80~90% 才舒服」= 在手动把这 2 倍拉回来。
     ⇒ 修法 = 在「被系统放大过」的设备上把整页缩回去（应用规则见 base.css）。
     ⛔ **不能用"改字号令牌"代替**：`var(--font-size-*)` 840 处，但 `height: Npx` 硬编码
        316 处 vs `--control-height*` 仅 49 处、全站 px 共 4278 处 ⇒ 只改字会变成
        "字小了、按钮/行高没小"，必然错位。整页缩放**只能靠 zoom**。
     ⛔ **不能无条件全局缩**：Win 4K@100%（dpr 1）正文物理已是 14px，再 ×0.85 ⇒
        11.9px **过小**（把本来正常的人弄坏）。⇒ 必须 dpr 感知。
     ▸ 应用条件：`@media (min-resolution: 1.5dppx) and (min-width: 1600px)`（见 base.css）。
       命中 = Mac 4K@1920等效 / Mac 5K / MacBook Retina(≥1600) / Win 4K@150%+；
       不命中 = **Win 4K@100%** / Win 1080p / MacBook 13"(1440) / 手机。 */
  --app-scale:             0.85;

  /* ⭐⭐ 2026-10-06：1120 → **1330**（= .page--narrow 的 1410 − 左右 padding 各 40）
     ⛔ 为什么必须跟着放大：`zoom` 会**连宽度上限一起缩** —— `.page--narrow` 的 1200
        是逻辑值，缩完只剩视觉 **1020** ⇒ 表单页比原来**窄 15%**。
        补救 = 宽度上限 **÷ 0.85 等比补偿**（1200 ÷ 0.85 ≈ 1410），
        使**视觉宽度与缩放前一致** ⇒ 达成"**字变小、页面占位不变**"。
     ⚠️ 与 `.page--narrow` 仍是"两源取小、必须同值"（门禁 ④c 锁住）。
     （原注释：乐哥 2026-09-28 16:55 把 1280 收回 1120，理由是标题行要与内容卡对齐。） */
  /* ⭐⭐ 2026-10-09 乐哥拍板：**1330 → 1040**（视觉 1120 → **884**）。
     ────────────────────────────────────────────────────────────────────────
     他给了一张「新增达人 · 基础信息」的截图：「页面有点宽，主要是每个表格的输入框很宽，
     然后两个间隔很大」。实测根因：内容列 1120、两列各 **630**，而控件限宽 **380**
     ⇒ 每格右侧空 250、**两控件净距 269.8** —— 而 380 这个限宽正是他 2026-09-29 亲手定的
     （原话「输入框没必要那么宽，右边就不用挨这么近」，当时净距 **51~71px**）。
     ⇒ **不是限宽错了，是内容列变宽之后没人跟着调**。
     我出三档真实渲染候选（A 现状 1120 / B 910 / **C 1040**）→ 他选 **C**，并要**整板块一起收**
     （不区分"表单页/详情页"，避免同一个板块里两种宽度）。
     ▸ 生效范围 = **整个"壳家族"**（都读本令牌）：`.sf-shell` / `.detail-shell`（达人、公司）、
       排期壳（`pages-schedule.css` 的 hero / tabs / 内容列 / 底部按钮）、报销单。
     ⛔ **不要顺手改 `.page--narrow`（仍是 1410）**：它是 `app.js` 按"二级页档位"给
       **所有**非表格二级页挂的，一刀切会把全站二级页都收窄。
       壳页的档位由 `layout.css` 里一条 `:has()` 规则单独收 —— 那条必须留着，
       否则**页头标题行**（撑满档位、自己没有 max-width）会比壳宽一大截。
     ⚠️ 门禁 `page-width-tiers ④c` 已按新关系改写（本条与 `.page--narrow` **刻意不再同值**）。 */
  --form-max-width:        1040px;
  /* ⭐⭐ 2026-10-06（乐哥：「侧栏和当前风格不协调」）**240 → 视觉 240**
     ────────────────────────────────────────────────────────────────────────
     ▸ 根因（量化，不是"看着窄"）：240 是**逻辑值**，而高密度宽屏会命中 base.css 的
       `@media (min-resolution:1.5dppx) and (min-width:1600px){ body{ zoom:var(--app-scale) } }`
       ⇒ 240 × 0.85 = **视觉仅 204px**；而内容区因"视口逻辑宽被放大"反而更宽
       ⇒ **两侧比例失衡**，这就是"不协调"的来源。
     ▸ 为什么要补偿：zoom 会把宽度**一起缩**。要"视觉回到缩放前的 240"，逻辑宽必须
       ÷ --app-scale。本项目 `--form-max-width`(1330≈1120÷0.85)、`.page`(1900≈1615÷0.85)
       已是同一套补偿逻辑。
     ⛔⛔ **补偿只在 zoom 生效的媒体查询里开**（见下方 `--sidebar-width-scaled`）：
       无条件补偿会让 **1440 普通屏 / Win 1080p / 手机**（这些设备不缩放、也不该变宽）
       侧栏凭空变成 282px —— 这是把"修高密度屏"变成"弄坏所有屏"。
     ⛔ 侧栏**内部竖向基线 62**（容器 padding 12 ＋ 项 padding 22 ＋ 图标 16 ＋ gap 12）、
       一级 44 / 二级 40 / 分组 30 一律不动 —— 改任一项要整串重算（见 DESIGN-SPEC §8.8）。 */
  --sidebar-width:         224px;         /* 未缩放屏（1440 / 1080p / 手机）：原设计稿 240 −16
     ⭐ 2026-10-08 22:0x（乐哥：「左边菜单栏我让你适当缩小一点点」）——
        240 → **224**（视觉窄 16px，约 6.7%，"一点点"档）。只动这一个令牌：
        `.sidebar` 的 `width` 与 `.app-main` 的 `margin-left` 都读它 ⇒ 五板块一次生效。
        ⛔ 侧栏内部竖向基线（62 / 一级 44 / 二级 40 / 分组 30）一律不动 —— 这是**横向**收窄。
        ▸ 想再窄/回退：改这一处即可（232 = 只收 8px，208 = 收 32px，240 = 原样）。 */
  /* 被系统放大过的设备：zoom 0.85 会把侧栏一起缩到 204 ⇒ 除回来补偿（240 ÷ 0.85 ≈ 282）。
     ⛔ 这个令牌只在 zoom 生效的同一媒体查询里被引用（见文件末尾 + base.css 同条件）。 */
  --sidebar-width-scaled:  calc(224px / var(--app-scale));
  --sidebar-width-icon:    64px;
  --sidebar-offset:        14px;
  /* ⭐⭐⭐ 2026-10-07「被侧栏悬浮包裹」（乐哥选W3 档）
     ────────────────────────────────────────────────────────────────────────
     ▸ 诉求（乐哥原话）：「侧边栏上下可以做一点**入侵的圆角**，这样就有一点包裹住的感觉了，
       同理内容页最右边也是」⇒ 要的是"侧栏与内容区各自成岛、中间有缝"的包裹感。
     ▸ 为什么需要**新令牌**：要让缝真的出现，必须**同时**改三处，它们必须同值——
       · `.sidebar` 的 `top/left/bottom`（内缩）
       · `.sidebar` 的 `width` = `var(--sidebar-width) − inset`
         ⚠️⚠️ **为什么必须改 width**（实测连栽两回，这是根因）：
           ① 只给侧栏 `left:inset` ⇒ 右缘 240→240+inset，而内容区
              `margin-left: calc(var(--sidebar-width) + inset)` 里令牌仍是 240
              ⇒ 内容左缘也 240+inset ⇒ **缝永远是 0**（两栏同步平移）。
           ② 改用 `right: calc(var(--sidebar-width) + inset)` 也**不生效** ——
              本元素写着 `width: var(--sidebar-width)`，而 fixed 元素同时有
              `left + right + width` 时**CSS 规定 width 胜出**（left/right 被
              over-constrained 忽略）⇒ 写的 right 直接被丢掉。
           ✅ 只有显式改 `width` 才真的出现缝。
       · `.app-main` / `.page-tabs-bar` 的 `margin-left` / `left`
     三处各写一遍数值的话，改一处就会"看起来没生效"。⇒ 抽成令牌。
     ▸ 为什么是 **8px**（W3 档，乐哥 2026-10-07 10:47 选）：
       · W2 是 14px（= frame-gap）—— 真机看下来侧栏会变成一张**独立浮岛**，
         和内容区之间的关系反而更散；
       · W3 用 8px 缝 + 10px 圆角，包裹感同样成立，但内容区只少 22px
         （达人列表那个最宽的表格在 1900 上限下富余 100+px，不受影响）。
     ⚠️ 这个值与 `--frame-gap`(14px) **故意不同**：frame-gap 是"内容白卡离视口边的缝"，
        它管的是右/下两边；inset 管的是"侧栏四周离视口 + 侧栏与内容区之间"，
        两者是不同的缝，不要混用。 */
  --shell-inset:          8px;
  /* ⭐⭐⭐ 2026-10-07「zoom 下 100vh 不含缩放」—— 边框上下不对称的真凶
     ────────────────────────────────────────────────────────────────────────────────
     乐哥报障（14:10）：「下面怎么留白这么多，和上面顶栏的间距完全不一样」。
     实测（1873×942 dpr2 ⇒ 命中 zoom 0.85）：
       · `.app-main` 的 `height: calc(100vh − 2×inset)` 里 **100vh 是未缩放的逻辑视口高 942**；
       · 而 body 有 `zoom: 0.85` ⇒ **框整体被缩 0.85**（942×0.85 ≈ 800）；
       · 于是框比视口矮了 942 − 800 = **142px** ⇒ 下缝多出 148、上缝只有 7。
       · 真机逐值：上缝 7 / 下缝 148 / 框高 787（应为 942−14 = 928）。
     ⚠️ 这是 `zoom` 的固有行为：**`vh` 单位不跟随 zoom，只有元素盒缩放** ⇒
        任何"框 = 100vh − 常数"的高度在 zoom 下都会偏小，偏小量 = 常数 × (1 − scale) + vh×(1−scale)。
     ✅ 修法：把 100vh **除以缩放**，让缩放后的视觉高正好等于视口高。
     ⛔ 不能写成 `height: calc(100vh / var(--app-scale) - 2*inset)` 到处重复 ——
        这里抽成令牌，**未缩放屏保持 100vh 不变**（普通 1440/1080p/手机不缩放）。
     ⚠️ 配套：`.app-shell { height: 100vh }` 同样要改（它约束着框的上边界），
        见 base.css 里同条件媒体查询中的覆盖。 */
  --shell-vh:            100vh;
  /* 壳层圆角：侧栏右下 + 内容区四角（同一档，保持"包裹"的圆角语言统一） */
  --shell-inset-radius:   10px;
  /* ⭐⭐⭐ 2026-10-07 A2 档：侧栏底色加到乐哥参考图那一档。
     ▸ 取值依据（**从参考图逐像素取的**，不是印象）：
       参考图（企业微信）侧栏灰度 **235** / 内容卡 **251** ⇒ 色阶差 **16**；
       我们改造前是 248 / 255 ⇒ 色阶差仅 **7** ⇒ 侧栏偏淡、两边读成"同一块"。
       取 **#EBEDF0**（灰度 237，色阶差 ≈16）落在两者之间偏 Apple 那一档，
       又比参考图的 235 稍亮一档 —— 我们的内容卡是纯白 #FFF，比参考图的 251 更亮，
       底色若照抄 235 会显得"脏"。
     ⚠️ 必须走令牌：全站禁裸 hex（`no-raw-hex` 门禁会红），改这里一处即可。 */
  /* ⭐⭐⭐ 2026-10-07 15:20 **#EBEDF0 → #E3EEFB**（乐哥：「整个底栏的颜色…感觉当前这个不搭
     （好像太淡了）」，随后明确方向：「**蓝色，清新点的那种**」）
     ────────────────────────────────────────────────────────────────────────────────
     ▸ 改前的两个问题（实测依据，不是凭感觉）：
        ① 旧值 #EBEDF0(235,237,240) 是**中性灰**（三通道几乎相等、饱和度仅 4%）
           ⇒ 与白色内容屏只差 **20 阶**明度 ⇒ 侧栏读成「另一块灰板」而不是「底色」；
        ② 无色相倾向 ⇒ 与项目主色（钴蓝 #0064E0）零呼应 ⇒ 这就是乐哥说的「不搭」。
     ▸ 新值 #E3EEFB(227,238,251)：**低饱和蓝**、色相 213°、蓝-红差 +22、饱和度 16%
        ⇒ 有明确的清新蓝倾向，又不像 #DCE9F9 那样蓝得发沉。
     ⚠️⛔ **必须与 `--color-content-bg` 同值**（本文件下方那条写的是**字面值**）
        —— 它就是「底色包住内容屏」那四缝露出的颜色；不同色 ⇒ 侧栏与缝隙读成
        两块东西，10-07 中午刚立起来的「显示屏浮在底色上」结构直接失效。
     ⛔ 别把 content-bg 改成 `var(--color-sidebar-bg)`：门禁 `frame-flush ④` 要按
        **字面值**比对「底色 == 侧栏色」，写成引用会让那条判据解析不到值 ⇒ 静默失效。

     ⭐⭐⭐ **2026-10-07 21:35 #E3EEFB → #E8EEF8**（乐哥 21:27：「侧边栏那个颜色，
        感觉还是深了，要再淡一点点」；21:35 拍板「就按你说的那个上」）
        ────────────────────────────────────────────────────────────────
        ▸ **方向 = 降饱和度，不是提明度**（这是本批最关键的判断）：
           他说「深」，有两种可能的解法，混在一起就会走错：
             · **降饱和度**（明度不变、只减蓝味）→ 蓝味变轻、观感"轻"，但
               **白内容屏浮起的高度完全不变** ⇒ 不会破坏 10-07 立的包裹结构；
             · **提明度**（往白靠）→ 更亮，但白屏与底色的差值被压缩 ⇒ 屏幕"糊"，
               正是 15:20 他说「太淡了」的同一个病。
           ⇒ 选降饱和度：明度差 28 → 23 阶，仍在门禁区间内（22~36）。
        ▸ **被否掉的候选（留档，防后人重犯）**：
           `#E9EEF6`（降饱和的更轻一档）—— 蓝-红差只剩 **13**，低于门禁下限 15
           ⇒ 退回 15:20 那次「不搭」的病根（中性灰、无色相倾向、看不出是蓝）。
           ⇒ **降饱和有下限，不能越过门禁的蓝-红差 15**。
           `#EAF2FC`（当初 5 档里最淡的第 1 档）—— 明度差仅 **14**，同样低于下限 22
           ⇒ 白内容屏会糊在底色上，**不建议**，要上必须同时改门禁判据方向。
        ▸ 新值 **#E8EEF8**(232,238,248)：蓝-红差 **16**（≥15 ✅）、与白屏明度差 **23**
           （22~36 ✅）、色相仍是蓝调 ⇒ 比 #E3EEFB 轻，但**仍然是"底色"不是"白板"**。
        ⚠️ 同批 `--color-content-bg` 也必须改（见下方那条注释）——
           **两处必须同值**，否则侧栏与四缝读成两块东西。 */
  --color-sidebar-bg:     #E8EEF8;
  /* ⭐ 2026-10-10 新增：**侧栏二级菜单引导线**的颜色（乐哥：「菜单栏是不是可以参考这个增加引导线」）
     ────────────────────────────────────────────────────────────────────────────
     ▸ 为什么**不复用** `--color-gray-300`(#DDDDDD) / `--color-gray-400`(#C6C6C6)：
       那两档是**中性灰**（R=G=B）。侧栏底 `--color-sidebar-bg` #E8EEF8 是**蓝调**
       （蓝 248 / 红 232，蓝-红差 16），中性灰压上去会读成"发暖的脏线"、与底色不同色系。
     ▸ 取值 **#A9B7CC**(169,183,204)：与底色**同色系**（蓝>红>绿），
       在 #E8EEF8 上对比度 ≈ **1.75:1** —— 与飞书那条线（#B3B3B3 压白底，≈2.1:1）同级观感：
       看得见是"一根线"，但不抢选中卡的焦点。
     ▸ ⛔ 别用主色蓝：视觉重量会盖过选中卡的白底，且与业务域色图标打架。
     ▸ 用法见 base.css 的 `.nav-subitems::before`（线在二级卡**外侧**，卡左缘与之相切）。 */
  --nav-guide-line:       #A9B7CC;
  /* ⭐ 2026-09-25 Gashora 骨架落地新增：**浮岛缝隙**。
     骨架由三块浮起白卡组成（顶栏卡 / 侧栏岛 / 内容区白大卡），它们之间、以及它们与视口边缘
     之间的缝隙**必须全等** —— 否则会读成"没对齐"。
     ⚠️ 这是 2026-09-19「悬浮版」踩过的坑（当时左/中/右 2px、上/下 8px，差 4 倍）。
        本令牌就是用来消灭那种不对等的：所有缝隙一律 var(--frame-gap)，不许各写各的数值。 */
  --frame-gap:             14px;
  --page-padding:          var(--space-10);  /* 40px 内容区**左右 + 底部**留白（"呼吸感"来源）
     ⚠️ 2026-09-29 由 32 改 40：2026-09-25「呼吸感」那一刀把 `.page` 的 gutter
        从 32 加到 40，但**只改了 `.page` 一处实现** —— 财务/门户/系统配置/hr 四个
        独立外壳板块各有自己的容器或覆盖规则，**没跟上**（实测四者留白各不相同）。
        ⇒ 令牌对齐实际值，所有板块统一引用它，以后调一处全站生效。

     🔁🔁 2026-10-08 当天**改了又改回来**（完整记录，别再重复这个来回）
     ────────────────────────────────────────────────────────────────────────────
     ▸ 20:5x 我把它 40 → 16，依据是乐哥那句「最右边边框还是有点宽，弄的和上面一样；
       左边这个也宽，再窄点」+ 澄清「我指的就是外部的那个窗口，就是淡蓝色的那个」。
       ⛔ **我理解错了**：他说的是**蓝框外侧那圈淡蓝色带**（`.app-shell` 与视口之间的
          那圈）的宽度，**不是**内容卡到蓝框的内侧留白。于是我把内容页的左右留白
          压到 16 —— 而真正该修的两个成因（框**内**的滚动条槽 / 框**外**的根节点槽）
          当时一个都没认出来。
     ▸ 22:33 乐哥：「我们里面内容页，模块距离两边的距离现在太挤了，得改成之前那样
       …… 之前我说改边框，你是不是改了内容页的」⇒ **正是**。当场改回 **40**。
       那两个真成因后来各自归位（见下方两条 ⭐ 注释）：
         · 框**内**：右侧改用 `--page-padding-right`（= 本令牌 − `--frame-gutter`）
         · 框**外**：撤掉根节点 `html` 上的 `scrollbar-gutter: stable`（它白占 15px）
     ▸ 所以现在四边是：**上 16**（`.page` 用 `--space-4`，09-28「内容区高一点点」定的）
       / **左右 40 / 下 40**（本令牌）—— 与 10-08 改动之前**逐像素一致**。
     ⛔⛔ 以后听到"太挤 / 那个边框太宽"，先分清他指的是哪一层，别混：
          · **框外那圈蓝带** → `--shell-inset`(8) + 根节点不许有 `scrollbar-gutter`
          · **框内卡片留白** → 本令牌（左右/下）与 `--space-4`（上）
       混了就会像 10-08 这样"修一个、弄坏另一个"。
     ⚠️ 顶栏（`.page-tabs-bar`）与财务/门户/hr/项目各容器都引本令牌 ⇒ 改这一处全站生效。 */

  /* ⭐⭐⭐ 2026-10-08 21:3x **抽屉的定位框** = 蓝框 `.app-main` 的**视口坐标**（四个值一套）
     ▸ 为什么是"视口坐标"而不是让抽屉绝对定位在蓝框内（上一版做法）：
       蓝框 `.app-main` 本身是**滚动容器**（`overflow-y: auto`，乐哥 10-07 要的"框不随滑动消失"）
       ⇒ 挂在它里面的 `absolute` 子元素**一定跟着内容滚**。
       真机复现：在抽屉内滚轮 +500 ⇒ `.app-main.scrollTop 0 → 404`、`.drawer` top 8 → -396、
       标题栏整个跑出框外被裁（乐哥：「这个怎么滑动还能滑上去的？」）。
     ▸ `position: fixed` 的元素相对**视口**定位，任何滚动容器都带不走它；
       而 `getBoundingClientRect()` 返回的**就是**视口坐标 ⇒ 直接写进 left/top/width/height
       就与蓝框逐像素重合（也不用再补"滚动条那 15px"：rect 宽度是 border box，本来就含它）。
     ▸ 运行时由 app.js 的 `syncDrawerFrame()` **实测写入**（切页 + resize + ResizeObserver 盯蓝框）——
       **不写死 px**：这四个值随窗口尺寸 / 侧栏折叠 / 缩放而变。
     ▸ 默认值：`left/top = 0`、`width/height = 0` ⇒ 未同步前是一个 **0×0 的不可见空壳**
       （本层 `pointer-events: none`、且没有子节点时不显示）⇒ 不会闪出一个错位的抽屉。
     ⚠️ 必须在令牌表里**声明**：未声明的令牌会被全站门禁判红（"用到的 var 必须有定义"）；
       也不许写成 `var(--x, 兜底)`（兜底会把"令牌名拼错"静默掩盖）。 */
  --drawer-frame-left:     0px;
  --drawer-frame-top:      0px;
  --drawer-frame-width:    0px;
  --drawer-frame-height:   0px;

  /* ⭐⭐⭐ 2026-10-08 21:3x **内容区右侧被"滚动条保留槽"占掉的宽度**（运行时由 JS 实测写入）
     ▸ 病根（乐哥：「上面说蓝边那个不等宽的，现在右边还是不等宽」——上一轮我只改了留白令牌
       40→16，但右边**还多出这一条**）：
         蓝框 `.app-main` 是滚动容器且带 `scrollbar-gutter: stable`（10-08 为"切 tab 不左右抖"加的）
         ⇒ **恒定预留**一条滚动条槽。实测：`offsetWidth 1649 − clientWidth 1634 = 15`
         ⇒ 卡片到蓝框：**左 16 / 右 31**（`.page` 左右 padding 明明都是 16）——
            右边那多出来的 15 就是这条槽。
     ▸ 修法：各"承载留白的容器"（`.page` / `.fin-main` / `.ptl-canvas` / 顶栏）
       **右侧内边距减掉这条槽** ⇒ 视觉留白 = `padding + gutter = 16px`，与左边、与上面一致。
     ▸ 为什么要 JS 量而不是写死 15px：滚动条在 macOS 默认是 overlay（不占位 ⇒ 本值 0），
       Windows / "始终显示滚动条" 是 classic（通常 15~17 且用户可改）；
       而且**同一台机器上滚动条有无也会变**（长页有、短页无）⇒ 写死必然在某处错位。
     ▸ 默认 0px ⇒ 不占位的环境行为与"不减"完全相同（零回归）。 */
  --frame-gutter:          0px;
  /* 承载留白的容器**右侧**该用多少 = 名义留白 − 滚动条槽（见上）。
     ⚠️ 只减右侧：槽只在右边。左侧、底部仍用名义值 `--page-padding`。 */
  --page-padding-right:    calc(var(--page-padding) - var(--frame-gutter));
  /* ⭐ 2026-09-25 Gashora 骨架落地：本令牌语义**变了**（不再是"与 --tabs-bar-height 同值"）：
     以前 = "顶部那一行的高度"（与 --tabs-bar-height 同值，52）；
     现在 = "**吸顶高度**" —— 即"贴到顶栏下沿"该用多少。给两处用：
            · analytics.js 的 headerHeight()（分析页目录条算吸附位置）
            · 分析页目录条 `top: var(--header-height)`
     ⚠️ 值 = **上缝 + 顶栏高** = `--frame-gap + --tabs-bar-height`。
        ⛔ 不是"上缝 + 顶栏高 + 下缝"（那是内容区顶边，124→现在 112）——
           目录条要贴在**顶栏下沿**，不是内容区顶边。
     ⚠️ 两令牌的关系（= frame-gap + tabs-bar-height）由 tests/restruct-topbar.test.js 的 C1
        静态锁住，改 --tabs-bar-height 却漏改这里会**立刻变红**。
     ▸ 2026-09-26：96 → 84 ⇒ 本值 110 → 98（14 + 84）。
     ▸ 2026-09-26：84 → 72（A6 定稿）⇒ 本值 98 → **86**（14 + 72）。 */
  --header-height:         60px;   /* ⭐⭐ 2026-10-06：86 → **60**（= --tabs-bar-height，**不再加 frame-gap**）。
                                      语义仍是"吸顶高度"（analytics 目录条 / 分析页锚点用它）：
                                      · 09-25 起顶栏是**浮起的卡**（上方有一条 frame-gap 的缝）
                                        ⇒ 吸顶高度 = 缝 + 顶栏高（最后是 86 = 14 + 72）。
                                      · 10-06 顶栏改回**贴顶**（top:0，与内容区拼成一张卡）
                                        ⇒ 上方那条缝不存在了 ⇒ 吸顶高度 = 顶栏高。
                                      ⛔ 别再按 `frame-gap + tabs-bar-height` 算：会多出 14px，
                                         分析页目录条会停在半空（实测会露出 14px 滚动内容）。
                                      ⚠️ 与 --tabs-bar-height 仍必须**同步改**（门禁 C1/C5 锁着关系）。 */
  --section-y:             var(--space-8);
  --bp-md:  768px;
  --bp-lg:  1024px;
  --bp-xl:  1280px;

  /* 层级 z-index */
  --z-base:     0;
  --z-dropdown: 1000;
  --z-sticky:   1100;
  --z-modal:    1200;
  --z-toast:    1300;

  /* ------------------------------------------------------------------
     二、Semantic（A2）—— 语义别名，组件层引用的唯一入口
     ------------------------------------------------------------------ */

  /* 前景 · TDesign 文字灰阶（font-gray-1/2/3/4 = 黑 90%/60%/40%/26%）
     对比度（白底）：primary 17.5:1 / secondary 5.7:1 / tertiary 2.85:1
     tertiary 仅供占位符与禁用态，不得用于正文。 */
  --color-fg:        #1C1E21;   /* 主文字 */
  --color-fg-2:      #3F4753;   /* 正文 */
  /* ⚠️ 2026-09-25 UI 美化试妆：次级文字由「紫调灰」转「冷调蓝灰」并加深一档 ——
     muted #5D6C7B → #546579（白底对比度 6.5:1），meta #8595A4 → #7C8DA3（3.6:1）。
     理由：全站 323 + 102 处次级文字是"灰感"的最大来源；转冷调后与主色同族，
     白底可读性不降反升。 */
  --color-muted:     #546579;   /* 次级 / 副标题 */
  --color-meta:      #7C8DA3;   /* 辅助 / 时间戳 */
  /* ⚠️ 历史变量补齐（2026-09-13）：`--color-fg-3` 早年被 4 处引用但**从未定义**
     （table.css 的 .kw-scope__sep、pages-influencer.css 一处、sample-distributions.js 圆点两处）。
     其中 2 处没写 fallback → `color` 声明**静默失效**（继承父级色，长期无人发现）；
     另 2 处写了 `var(--color-fg-3,#C7C7CC)` → 一直显示 fallback 值。
     语义上它就是「第三级前景 = 辅助灰」，与 --color-meta 同级，故就地定义（而不是改 4 处调用点）——
     这样 4 处同时恢复正确，也不再有"引用了不存在的变量"这一隐患。 */
  --color-fg-3:      #7C8DA3;   /* 三级前景（兼容历史命名，等价 --color-meta；试妆同步转冷） */
  --color-placeholder: #B9C1CC; /* 占位符 */
  --color-disabled:    #C4CBD4; /* 禁用 */

  /* 表面（TDesign Gray1 / White：卡片与页面底有明确分层）
     ⚠️⚠️ 2026-09-25 UI 美化试妆 **v2 修正**（乐哥反馈：「背景这个我存疑，因为这样看着也不简洁清新」）：
       · v1 把页面底改成 #F0F5FB（饱和度 58% 的明确蓝）——**方向错了**。
         实测全屏约 40% 面积铺这块蓝 ⇒ 大面积冷色 = 沉闷；且它与白卡只差 3.7% 明度 ⇒
         卡片像"贴纸浮在蓝布上"⇒ 块状感强 ⇒ 反而更不简洁。这是"用大面积带色去凑彩色占比指标"的误判。
       · v2 定调：**背景要近白（几乎看不出颜色），彩色只出现在小面积元素上**。
         这正是 Notion / Linear / Vercel 的做法（底色饱和度 <15%，靠"极浅底 + 弱边框 + 留白"出透气质感）。
       · ⭐ 关键认识：像素指标里的「纯灰」不等于"脏"——**明亮的近白**会被算作纯灰，但它恰恰是"简洁"的来源。
         真正要打的是"暗、闷、无层次"，不是"有没有色相"。故 v2 不再追求降低纯灰占比。
     三色同源（同色相、等差明度），保证不出现"两种白"的脏感。 */
  --color-bg:        #F6F6F6;   /* 页面底（⭐ 2026-09-25 Gashora 骨架落地：#F8FAFC → #F8F8F8 → **#F6F6F6**。
                                   参考图实测底色 #F8F8F8——**中性灰白、不带蓝调**；
                                   旧值 #F8FAFC 带一丝冷蓝，与"克制、干净"的目标气质不符。
                                   明度几乎不变（97.3%），仅去掉色相 ⇒ 卡片仍靠阴影而非底色反差区分。
                                   再调 #F6F6F6：参考图实测的**界面灰底**值——比 #F8F8F8 略深一档，
                                   才能让「侧栏浮岛 / 顶栏卡 / 内容区白大卡」浮起的层次读得出来
                                   （三块白卡之间的缝隙就是露这条灰底）。 */
  /* ⭐⭐ 2026-10-06 **壳层两底色**（乐哥：「底色不能和左边菜单栏一样颜色，应该是其他颜色，
     你看下之前我们底色可以不，然后整个内容页也得有点悬浮效果」）
     ────────────────────────────────────────────────────────────────────────────────
     左 ← 这三层从外到内、每层都要能一眼分开：
       · **侧栏底** `--color-app-bg`  #F4F7FC（浅蓝白，带一点蓝）
       · **内容区底盘** `--color-content-bg` #F0F2F5（中性灰）← 白卡就浮在它上面
       · **内容白卡 / 模块卡** `--color-surface` #FFFFFF
     ⚠️ 为什么不用他提的 #F6F6F6：它与侧栏底的明度只差 **1 阶**（96.9% vs 96.5%），
        肉眼读起来仍是"同一块"，达不到"两色分开"的目的。
        #F0F2F5 是 B 端后台的经典底盘值，与侧栏底的差别在**色相**（蓝 vs 中性），
        比单纯压暗更好认。
     ⛔ 侧栏与内容底必须**保持不同**（这条有门禁锁：`frame-flush ④`）。 */
  --color-app-bg:    #F4F7FC;
  /* ⭐⭐ 2026-10-06 最终（乐哥：「或者底色**用之前那个接近白色的**也行，
     但是**得有悬浮效果**，其他模块，也加个悬浮，会不会好点」）
     ⇒ 内容底盘回到 **接近白 #F6F6F6**（就是"之前那个"），
       "浮起"改由 **阴影** 承担（见 `--shadow-card`），而不是靠压暗底色。
     ⚠️ 与侧栏底 #F4F7FC 的色距只有 6.4 —— **这条不再作为判据**：
        判据从"两底色分得开"改成"**卡片靠阴影浮起**"（见 --shadow-card 的说明与门禁）。 */
  --color-content-bg: #E8EEF8;   /* ⭐⭐⭐ 2026-10-07「显示屏浮在底色上」
     ────────────────────────────────────────────────────────────────────────────────
     乐哥原话：「这个内容也就是一个有圆角的显示屏，然后显示屏是在底色（**侧边栏的颜色**）
     上面的，然后侧边栏是底色，他整个包着内容页」。
     ⇒ **底色 = 侧栏色** —— 这才是"侧边栏就是底色、底色把内容页整个包住"的字面实现。
     ⚠️ 这推翻了 10-06 的「近白底盘 #F6F6F6 + 卡片靠阴影浮起」：
        那版把"底色"读成"内容卡下面的衬底"，而这一版乐哥指的是**侧栏那块的颜色**
        （他上一轮亲手定的 #EBEDF0）。
     ⚠️⚠️ **这里写字面值、不写 `var(--color-sidebar-bg)`**（两条理由）：
       ① 两者必须**永远相等**，但**各自可独立调** —— 写引用就绑死了，
          以后想单独微调底色（不动侧栏）就没法调；
       ② 门禁要按字面量比对"底色 == 侧栏色"，若一边是引用，正则解析不到值 ⇒ 断言失效。
     ⭐⭐ **2026-10-07 15:20 随侧栏一起改为 #E3EEFB**（乐哥要「清新蓝」，
        原 #EBEDF0 中性灰「太淡、也不搭」）。⚠️ 两处必须同值 —— 见
        `--color-sidebar-bg` 处那段注释（不同色 ⇒ 四缝露色与侧栏对不上，包裹感消失）。
        ⚠️ 本轮这两条同时被门禁反向注入验过（只改一处 ⇒ `frame-flush ②/④` 立刻变红）。
     ⭐⭐⭐ **2026-10-07 21:35 随侧栏一起 #E3EEFB → #E8EEF8**（乐哥 21:27「感觉还是深了，
        要再淡一点点」；21:35 拍板按推荐值上）—— 降饱和方向，明度差 28 → 23 阶（仍 ≥22）。
        ⚠️⛔ **两处必须同值**（本批改了一处就要改另一处）：
           本令牌**只被 `base.css:71` 的 `body` 引用一处**，它就是「四缝 + 视口外圈」露出的色。
           若与侧栏不同值 ⇒ 侧栏与缝隙读成**两块东西**，
           10-07 中午刚立起来的「显示屏浮在底色上」包裹结构直接失效。
           ⚠️ 所以**不能只改侧栏不改本条** —— 反向注入里就有这一路（「底盘≠侧栏」）。
     ⛔ 别再写"接近白"：#F6F6F6(246) 与 #EBEDF0(237) 只差 9 阶，
        而白卡是 255 ⇒ 屏幕与底色几乎同亮，"显示屏"完全读不出来（这是 10-07 上午的病根）。 */
  --color-surface:   #FFFFFF;   /* 卡片（纯白，始终不变） */
  --color-surface-2: #F1F5F9;   /* hover / 次级底 / 表头底（v1 #EEF3FA → v2 调浅）
                                   ⚠️ 表头底仍比页面底**深一档**（Δ通道 8 vs 8，但明度低 1.3%），
                                      使表头在近白页面上仍是清晰的"标题行"——乐哥已认可表头效果，保持该层次关系。
                                   这是全站引用最多的表面令牌（183 处，含 --table-header-bg / --input-disabled-bg）。 */

  /* 边框（同色相冷调，与主色同族）
     ⚠️ 试妆：由中性灰 #E5E8EC/#EDEFF1 微调为蓝灰 —— 347 处引用，
        整体边界从"冷灰"变"蓝灰"，与底色/主色形成同一色彩体系。 */
  --color-border:      #E1E8F2;
  --color-border-soft: #EDF0F4;  /* 卡片描边（⭐ 2026-09-25 Gashora 试妆：#E8EEF7 → #EDF0F4，
                                    更淡且去蓝调——参考图卡片几乎看不到描边，靠柔和阴影区分） */

  /* 强调（每屏可见使用 ≤ 2 处：主 CTA + 激活态） */
  --color-accent:        var(--color-primary-500);
  --color-accent-hover:  var(--color-primary-600);
  --color-accent-active: var(--color-primary-700);
  --color-accent-on:     #FFFFFF;
  --color-accent-text:   var(--color-primary-500);  /* 主色文字/图标（链接、激活态） */
  --color-accent-border: var(--color-primary-200);  /* 主色描边（2026-09-25 补：权限页 perm-chip 等此前引用不存在的令牌，一直走兜底值） */
  --color-accent-bg:     var(--color-primary-50);

  /* 语义色（Meta 功能色：成功 / 告警 / 危险，浅底成对） */
  --color-success:    #31A24C;
  --color-success-bg: #EAF6EF;
  --color-warning:    #F2A918;
  --color-warning-bg: #FEF5E7;
  --color-danger:     #E41E3F;
  --color-danger-bg:  #FDE8EB;
  --color-danger-soft: #FDE8EB;
  --color-price:      var(--color-danger);   /* 价格：与危险色同源，避免多一套红 */
  --color-info:       #0064E0;
  --color-info-bg:    #EDF4FE;
  --color-info-soft:  #EDF4FE;

  /* 语义文字色（无底色场景专用）：--color-success/--color-warning 是给「浅底+同色字」配对的，
     单独当正文文字放在白底上小字可读性不足，故各深一档；仍属同色系，不引入新色相 */
  --color-success-text: #1B7F3B;   /* 白底上的成功文字（对比度 ≈5.0:1） */
  --color-warning-text: #B25C0C;   /* 白底上的警示文字（对比度 ≈4.8:1） */

  /* 语义「描边 / 整行底」变体（2026-09-14 补，把散落在 pages.css 的硬编码色收进令牌）
     用途：① 描边比 --color-warning 浅，边框上更柔和；② 整行警示底比 --color-warning-bg 更浅，
           避免与行内内容抢视觉。--color-danger-strong / -border 同理，用于深底文字与危险描边。 */
  --color-warning-border:    #F2C879;   /* 警示描边（如"待结算"卡片外框） */
  --color-warning-bg-soft:   #FFFBF0;   /* 警示整行底（比 --color-warning-bg 更浅） */
  --color-warning-bg-hover:  #FFF6E3;   /* 上者 hover */
  --color-warning-accent:    #E8A33D;   /* 强调描边（行首色条） */
  --color-danger-strong:     #D93025;   /* 白底危险文字（比 --color-danger 更深，对比更足） */
  --color-danger-border:     #F5B5AE;   /* 危险描边 */

  /* 统一「提示 / 警示条」规格（2026-09-14 乐哥定稿）
     ------------------------------------------------------------------
     乐哥原话：「所有的警示提醒，应该做成（截图）这种，**不需要右边清楚的叉**，
               不要做成现在提成总览里那种**白底**的」
     起因：提成总览 / 达人结算 / 华大对账 的说明条原为「近白底 + 左竖线」（--color-surface-2，
           几乎与卡片同色）→ 不够醒目、且与看板页的「透明底 + 左竖线」形态各不相同。
     定稿形态：**浅色底 + 圆角 + 左侧图标**（TDesign Alert 形态），**不带关闭叉**。
     语义色**分两档保留**（不合并）：
       · 说明类（info）→ 浅蓝底 + 蓝图标
       · 警示类（warn）→ 浅橙底 + 橙图标
     ⚠️ 不把警示类也改成蓝色：那会让"警告"失去辨识度（乐哥要的是**形态统一**，
        不是把所有提示都降级成同一种语义）。
     用法：各页面自有类名（.an-banner / .st-rule__inner / .st-pending-bar …）
          统一引用下面这组变量，保证以后新增提示条自动同规格。 */
  --notice-radius:        var(--radius-md);
  --notice-pad-y:         var(--space-2);
  --notice-pad-x:         var(--space-3);
  --notice-gap:           var(--space-2);
  --notice-bg:            var(--color-info-bg);
  --notice-border:        var(--color-primary-100);
  --notice-ico:           var(--color-info);
  --notice-fg:            var(--color-fg-2);
  --notice-warn-bg:       var(--color-warning-bg);
  --notice-warn-border:   var(--color-warning-border);
  --notice-warn-ico:      var(--color-warning);

  /* KPI 徽章色调（柔和色块：图标底 + 同色系深字。2026-09-14 立 —— 原先这套 5 色
     在 dashboard.js 与 influencer-detail.js **各写一份**，改一处另一处不跟随；
     收进令牌后成为唯一来源，JS 侧用 UI.cssVar() 读取。 */
  --color-tone-blue-bg:   var(--color-primary-50);
  --color-tone-blue-fg:   var(--color-primary-500);
  --color-tone-teal-bg:   #DCF3E9;
  --color-tone-teal-fg:   #0F6E56;
  --color-tone-orange-bg: #FBEDD9;
  --color-tone-orange-fg: #854F0B;
  --color-tone-green-bg:  #E9F3DB;
  --color-tone-green-fg:  #3B6D11;
  --color-tone-purple-bg: #DDD9FB;
  --color-tone-purple-fg: #534AB7;

  /* ⭐⭐ 每笔明细卡的循环色板（2026-10-08 乐哥，**他明确授权自定义**：
     「每一笔报销的框一个颜色，每一笔一个颜色…框的颜色和表头的一致，
      注意不要用很艳的颜色，不同笔数的颜色你自己做一个自定义就行，固定下来，可以循环重复」）
     ──────────────────────────────────────────────────────────────────────────
     用途：`pages.css` 的 `.eb-dt--c1..c5 .dt__hd`（提交报销页每笔明细的头部底色）。
     ▸ **为什么另立一组、不复用 `--color-tone-*-bg`**：那一组是 **KPI 卡的底**，
       比这里需要的浓一档；而且实测拿它按 32% 混进表头灰后，
       **橙色那支极差只剩 5**（等于没颜色，违背"这样就很直观了"）、
       **紫色那支把明度拉低 0.065**（那一笔会比别的笔明显重，违背"和表头的一致"）。
     ▸ **定值口径**（逐支验算过，见下）：
       · 感知明度（HSL 的 L）与 `--color-surface-2`（表头灰 #F1F5F9，L=0.9608）**差 ≤ 0.024**
         ⇒ "和表头一致"（看着是同一档的浅色，不会某笔特别重）。
         ⚠️ 度量用 **HSL 的 L，不是 WCAG 相对亮度** —— 相对亮度里绿色权重 0.7152 最高，
            任何偏紫/偏蓝的浅色都会被它判成"暗一大截"（实测紫差 -0.065），
            但肉眼看着明明一样亮。这是**判据选错度量**，不是颜色不好。
       · RGB 极差 **12~18**（基色 8）⇒ 一眼能分出是哪一笔，又不至于艳。
     ▸ 色相顺序按"相邻差异最大"排：**蓝 → 青 → 橙 → 绿 → 紫**（冷暖交替，
       否则相邻两支看着像同一个色）。5 支一组循环（第 6 笔回第 1 支）。 */
  --color-entry-tone-1: #EAF2FC;   /* 蓝   L=0.9529 Δ−0.0078 极差 18 */
  --color-entry-tone-2: #E9F5F1;   /* 青   L=0.9373 Δ−0.0235 极差 12 */
  --color-entry-tone-3: #FAF2E9;   /* 橙   L=0.9471 Δ−0.0137 极差 17 */
  --color-entry-tone-4: #EEF5E9;   /* 绿   L=0.9373 Δ−0.0235 极差 12 */
  --color-entry-tone-5: #EFEDFC;   /* 紫   L=0.9588 Δ−0.0020 极差 15 */

  /* KPI 图标块（2026-09-25 Gashora 像素级试妆：参考图的彩色实心圆角方块 + 白图标）。
     色值取自参考图实测（紫/橙/粉/蓝），绿补一个同明度的标准绿。
     ⚠️ 与 --color-tone-*-fg 是两套：tone-fg 是"浅底上的深字"（字色），本组是"实心方块"（底色）。
     只用于看板 KPI 卡（pages.css 的 .kpi-card__icon--*），别挪作正文/表头色。

     ⭐ 2026-10-09 乐哥：「tab 的卡片颜色样式感觉不好看…你整体看下」——
        实测病根**不是色相不对**（色相是当初照参考图定的，保留），而是**四色的明度/饱和度不成档**：
        #6090E0（灰调、偏暗）· #34A853（高饱和）· #F2781C（很艳）· #9050D0（偏暗且艳）
        ⇒ 并排时像"四个来源不同的色"。现在**只做两件事**：色相一律不动，
        ① 明度对齐（都压到同一档 L≈0.55~0.60）② 饱和度收敛到同一区间。
        外加 blue 直接锚定品牌主色 —— 它本来就是"GMV"这个头号指标的色，
        用 --color-primary-500 才与全站主色同源（改前那个 #6090E0 与主色是两个蓝）。 */
  --kpi-icon-blue:   #0064E0;   /* = --color-primary-500（品牌钴蓝） */
  --kpi-icon-green:  #2FA45A;
  --kpi-icon-orange: #E8952B;
  --kpi-icon-purple: #6E5BD8;
  --kpi-icon-pink:   #F03070;

  /* 图表/实心块用色（2026-10-09）—— **同一语义在"图表"与"KPI 图标"里必须是同一个色**。
     病根：首页柱状图与「达人业绩占比」条的 GSV 走 `--color-tone-green-fg`（#3B6D11 暗橄榄），
     而 KPI 卡的 GSV 图标走 --kpi-icon-green（鲜绿）⇒ 同一屏上出现**两个"GSV 绿"**，
     读的人会把它们当成不同指标。GMV 同理（图里是品牌蓝、图标是灰蓝 #6090E0）。
     ⚠️ 为什么不直接把 `--color-tone-green-fg` 改掉：它语义是"**浅底上的深字**"（全站多处当文字色），
        换成鲜绿会让那些地方的对比度掉下来 —— 那是另一件事，不能顺手一起改。
     ⇒ 单独立本组，与 KPI 图标同源（引用同一批令牌，若图标色再调这里自动跟随）。 */
  --color-chart-gmv: var(--kpi-icon-blue);
  --color-chart-gsv: var(--kpi-icon-green);

  /* ── 侧栏一级菜单图标 · 业务域色（2026-09-25 乐哥选定「业务域分色」方案）──────────
     用途：给 11 个业务分组的**图标**上色，让人**靠颜色找菜单** ——
     人找菜单是按业务想（"我要看钱 → 往绿色那一片找"），不是按第几个找。
     ⚠️ 为什么是「按业务域」而不是「每个分组一个颜色」：
        6 个业务域配 6 色 ⇒ **颜色本身在说"这是哪一类活"**；11 色全彩是纯装饰、花哨无规律。
     ⚠️ 协调性依据：色值**优先复用既有资产**（--kpi-icon-*，已经在看板里用了 5 个），
        只有「商品青」「工具灰」是新登记的，且这两个也按"中饱和中明度"对齐既有色系的彩度
        （不引入酸亮色）。
     ⚠️ **只给图标上色、文字仍用正文灰**（.nav-group-text 不动）⇒ 彩色面积小、不抢内容。
     ⚠️ 上色规则在 base.css（按 [data-domain] 选择器），domain 值由 app.js 的
        NAV_DOMAIN 映射集中产出（**不按中文组名选择器** —— 组名改过一次，
        按中文名写 CSS 会在改名时静默失效）。 */
  --nav-domain-analysis: var(--kpi-icon-blue);    /* 经营数据类：经营分析 / 业绩管理 / 月度汇报 */
  --nav-domain-ops:      var(--kpi-icon-orange);  /* 运营执行类：达人信息 / 直播排期 */
  --nav-domain-goods:    #0FA3A3;                 /* 商品类：货品与佣金（新登记，青） */
  --nav-domain-finance:  var(--kpi-icon-green);   /* 财务钱款类：结算与对账 / 成本管理 / 单据与票据 / 付款与台账 */
  --nav-domain-risk:     var(--kpi-icon-pink);    /* 风险异常类：售后分析 */
  --nav-domain-tool:     #8A94A6;                 /* 工具类：系统设置 / 门户（新登记，中性灰蓝 —— 刻意最不抢眼） */
  --nav-domain-personal: var(--kpi-icon-purple);  /* 个人类：我的 */

  /* ⛔⛔ 2026-10-08 23:40 **`--nav-domain-*-a`（强调色）已删除，别再恢复**
     ──────────────────────────────────────────────────────────────────────────
     当天走了个来回，结论记在这里，免得下次又走一遍：
       · 23:26 乐哥给了三张飞书截图 +「飞书彩色图标是属于双色拼接的」
         ⇒ 我加了 7 个 `-a` 强调色，做成"两个颜色并排"（第一版还是飞书采样的青绿/黄，很艳）。
       · 23:37 乐哥：「但是颜色不要太艳啊」⇒ 改成"主色同色系去饱和 50% + 提亮"的浅档。
       · 23:40 乐哥看完直接拍板：
           「要不用这版的颜色吧？这版是**统一色调**，只是不同地方透明度不一样，这个最高级好看」
         ⇒ **他要的不是双色，是"一个色调 + 靠透明度分层"**。
     ✅ 现在的做法：图标第二层 = 主色 + `fill-opacity: .38`（在 icons-solid.js 渲染时写死），
        **不需要任何强调色令牌** ⇒ 所以把这 7 个 `-a` 删掉，避免留一堆没人用的死令牌
        （死令牌最容易让人以为"还有双色模式"，下次又改回去）。
     ⇒ 想调层次深浅：改 icons-solid.js 里的 `MONO_OP`（一处，全站生效）。 */

  /* 标签 / 头像缺色兜底 + 品牌色（2026-09-14 收进令牌，原先散在 ui.js 里硬编码） */
  --color-tag-fallback:    #A6ADBC;   /* 标签未指定颜色时的兜底灰 */
  /* ⭐ 2026-10-19 标签三色的默认值（正常由 JS 内联覆写：`UI.tagFg/tagBg/tagLine`）。
     ⚠️ 必须在这里**定义**（哪怕只是指向别的令牌）：`css-var-defined` 门禁会扫到
        未定义的 `var(--x)` 并判红 —— 未定义的声明会被浏览器**整条丢弃**（静默）。
     ⚠️ 用 `--tag-line` 而不是在 `var()` 里写兜底：`ui-token-standards` 禁止兜底写法
        （令牌拼错时会被兜底静默掩盖）。 */
  --tag-line:              var(--color-border);
  --color-brand-starmedia: #2F6BFF;   /* 星盟品牌蓝（仓库标签专用，非 TDesign 色板） */
  --color-tag-location:    #B45309;   /* 排期日历「地点」标签（暖琥珀，刻意区别于达人/平台色） */

  /* 页面水印 / 顶部横幅（watermark.js 专用；原先硬编码在 JS 里） */
  --color-watermark:        #7C8BA1;   /* 平铺水印文字色（低透明度叠加） */
  --color-watermark-strong: #64748B;   /* 水印文字色（高透明度档） */

  /* ── Gashora 骨架令牌（2026-09-25 落地，乐哥指定参考图）──────────────────
     用途：把"顶栏浮起卡"这一套骨架用到的颜色收进令牌
     （项目规范：CSS 里不许写裸 hex，一律引令牌 —— 见 tests/ui-token-standards.test.js）。
     命名按"用在哪"而非"是什么色"：同一个 #F4F4F6 若以后要调，只需改这一处。
     ⚠️ 2026-09-27：`--color-surface-canvas`（内容区白大卡底 #FCFCFC）**已删除** ——
        乐哥给了 PMS 参考图后拍板去掉那层白卡（详见 layout.css 的 .page-stack 注释）。
        它是死令牌的直接原因：删掉后**不要再引用**，否则 var() 取不到值会静默失效。 */
  --color-frame-border:        #EBECEF;  /* 模块卡的描边色（.card / .module-card）——
                                            在页面灰底上替代阴影划界（卡片 vs 底差 9 阶 + 细边框） */
  --color-topbar-field-bg:     #F4F4F6;  /* 顶栏搜索胶囊底色 */
  --color-topbar-chip-border:  #E8E8EC;  /* 顶栏头像胶囊的边框 */
  --color-topbar-chip-hover:   #F7F7F9;  /* 顶栏头像胶囊 hover 底 */
  --color-banner-danger:    #D43A38;   /* 页面顶部红色提示条底色 */

  /* 模块标题栏状态底（全局通用，2026-09-10 立）
     用途：带「开启/关闭」状态开关的模块，标题栏整条底色随状态切换。
     黄 = 开启（单独维护 / 偏离默认，需留意）；绿 = 关闭（跟随体系 / 默认正常态）。
     用法：标题栏容器加 .is-on / .is-off → background 取 --color-head-on-bg / --color-head-off-bg，
           标题文字取对应 --color-head-on-fg / --color-head-off-fg。 */
  --color-head-on-bg:  var(--color-warning-bg);    /* 开启态标题底：浅黄 */
  --color-head-on-fg:  var(--color-warning-text);  /* 开启态标题字：深橙 */
  --color-head-off-bg: var(--color-success-bg);    /* 关闭态标题底：浅绿 */
  --color-head-off-fg: var(--color-success-text);  /* 关闭态标题字：深绿 */

  /* 图表分类色（TDesign 官方扩展色板，定性色板顺序：品牌蓝 → 天蓝 → 绿 → 黄 → 橙 → 红 → 紫 → 粉）
     严禁再使用 Tailwind 默认色板。 */
  --color-chart-1: #0064E0;   /* 品牌蓝 */
  --color-chart-2: #029CD4;   /* Cyan5 */
  --color-chart-3: #2BA471;   /* Green5 */
  --color-chart-4: #F5BA18;   /* Yellow4 */
  --color-chart-5: #E37318;   /* Orange5 */
  --color-chart-6: #D54941;   /* Red6 */
  --color-chart-7: #8E56DD;   /* Purple6 */
  --color-chart-8: #E851B3;   /* Pink5 */

  /* 事项类型色（TDesign 官方色板，全站统一，勿改）
     2026-09-06：由 Apple iOS 系统色迁移至 TDesign 同族色 */
  --color-item-arrival:     #8B8B8B;   /* 到达     Gray7 */
  --color-item-trace:       #2BA471;   /* 溯源拍摄 Green5 */
  --color-item-source-live: #008858;   /* 溯源直播 Green6（深绿，区分溯源拍摄） */
  --color-item-course:      #0064E0;   /* 小课堂   品牌蓝 */
  --color-item-interview:   #8E56DD;   /* 博士访谈 Purple6 */
  --color-item-live:        #D54941;   /* 带货直播 Red6 */
  --color-item-departure:   #A6A6A6;   /* 离开     Gray6 */
  --color-item-custom:      #8B8B8B;   /* 自定义事项 Gray7（统一、不显眼，与到达同灰） */

  /* 事项类型浅底（TDesign 对应色阶 1-2，随主色成对维护） */
  --color-item-arrival-soft:     #EEEEEE;   /* Gray2 */
  --color-item-trace-soft:       #E3F9E9;   /* Green1 */
  --color-item-source-live-soft: #C6F3D7;   /* Green2 */
  --color-item-course-soft:      #EDF4FE;   /* 主色浅底 */
  --color-item-interview-soft:   #FBF0FF;   /* Purple1 */
  --color-item-live-soft:        #FFF0ED;   /* Red1 */
  --color-item-departure-soft:   #F3F3F3;   /* Gray1 */
  --color-item-custom-soft:      #EEEEEE;   /* Gray2 */

  /* ------------------------------------------------------------------
     三、Component（B-slot / C-extension）—— 组件专用 token
     ------------------------------------------------------------------ */

  /* 聚焦环 */
  --focus-ring: 0 0 0 3px rgba(0, 100, 224, 0.22);

  /* 按钮 */
  --button-primary-bg:     var(--color-accent);
  --button-primary-fg:     var(--color-accent-on);
  --button-primary-hover:  var(--color-accent-hover);
  --button-primary-active: var(--color-accent-active);
  /* 主按钮渐变（2026-09-25 UI 美化第二刀「组件层」启用）——
     全站 185 处 `.btn--primary` 同时生效，是「品牌化清新」最高杠杆的一处改动。
     ⚠️ 三态各一档：hover 深一档、active 更深一档 —— 若某态不写，会回落成纯色造成"闪变"。
     ⚠️ 语义按钮不走这里：`.btn--danger`（危险）必须保持纯红，红色渐变会被读成"警示升级"。 */
  --button-primary-image:        var(--gradient-brand);
  --button-primary-image-hover:  var(--gradient-brand-hover);
  --button-primary-image-active: var(--gradient-brand-active);
  --button-secondary-bg:     transparent;
  --button-secondary-fg:     var(--color-accent-text);
  --button-secondary-border: var(--color-gray-400);
  --button-ghost-bg:         transparent;
  --button-ghost-fg:         var(--color-fg);
  --button-danger-bg:        var(--color-danger);
  --button-danger-fg:        #FFFFFF;
  /* ⭐ 2026-09-26「软蓝按钮」：浅蓝底 + 蓝字，用于**表格行内的业务动作**
     （入库 / 维护 / 确认 / 查看 等，见 table.css 的操作列统一段）。
     为什么需要它：这些动作原先直接用 `.btn--primary`（主按钮同款蓝实心块），
     一屏 20 行 = 20 个蓝块，比页面右上角真正的 CTA（新增 / 导出）还抢眼 ——
     而它只是"这一行的次级动作"。主按钮权重要留给页面级动作。
     ⚠️ 三态：hover 深一档（primary-200），与 `.btn--primary` 的"hover 深一档"同规律。 */
  --button-soft-bg:          var(--color-primary-100);
  --button-soft-bg-hover:    var(--color-primary-200);
  --button-soft-fg:          var(--color-accent-text);

  /* 输入框 */
  --input-bg:            var(--color-surface);
  /* ⭐ 2026-09-25 UI 美化第二刀：由硬编码 `#E6E8EB`（中性灰）改为引用 `--color-border`
     （蓝灰族 #E1E8F2）。原因：全站边框已统一转冷调蓝灰，输入框描边若仍是中性灰，
     会在一排控件里"偏黄/偏脏"（乐哥要调的「那些框」之一）。改引用后与其它框同族，
     且以后调边框色一处生效。 */
  --input-border:        var(--color-border);
  --input-focus-border:  var(--color-accent);
  --input-error-border:  var(--color-danger);
  --input-disabled-bg:   var(--color-surface-2);
  --input-disabled-text: var(--color-disabled);

  /* ⭐ 字段标签左内缩（2026-10-09 乐哥：「文字向右移 6px，其他不动」）
     用途：表单字段的标签文字（「达人名称」「品牌名」这种）往右内缩一点点，
     不再死死贴着容器左缘 —— 乐哥说"标签现在有点偏左…视觉上很奇怪"。
     ⚠️⚠️ 必须用 padding-left，**不许用 margin-left**：
       · padding-left 只推文字，控件左缘 / 网格列宽 / 右缘全都不动 = 他要的"其他不动"；
       · margin-left 会把整个字段块（含控件）一起推走，还会挤压列宽 ⇒ 控件跟着变窄。
     ⚠️ 全站三套字段标签都引用本令牌（components.css 的 `.field-label`、
       pages-influencer.css 的 `.pg-field__label`、pages-schedule.css 的 `.sf-field-col .sf-label`）
       —— 改值**只改这里一个数**，三处一起变（门禁 tests/form-unify.test.js ⑦ 锁住）。
     ⚠️ 「标签在左」的行内小标签（`.comm-field-label` 那种同行 label）**不适用**，
       它们本来就在控件左侧、没有"贴容器左边"的问题。 */
  --field-label-indent: 6px;

  /* 开关：激活态轨道色。模块需换色时**只覆盖这个变量**，不要去重写 .switch 的选择器
     （重写选择器会与组件默认规则同特异性、靠加载顺序生效，极易静默失效） */
  --switch-active-bg: var(--color-accent);

  /* 表格
     ⭐ 2026-09-25 Gashora 像素级试妆：表头由「冷调浅蓝底 + 品牌深蓝字」改为「白底 + 灰字」——
        参考图（Gashora）的表头**没有底色**，靠底部一条细分隔线与数据区分，字是中性灰。
        这推翻了同日上午「方向 A 品牌化」定的品牌蓝表头 —— 是乐哥看参考图后的新方向。
        ⚠️ 影响面：全站所有表格（table.css 的 .table thead th）与日历周历行
           （pages-schedule.css .calendar__weekdays）共用本条，改一处全站表头同时换装。
        ⚠️ 表头白底后，flush 卡片（.module-card--flush 有 overflow:hidden）会把大圆角裁切干净，
           不存在"表头背景顶破圆角"的问题。 */
  --table-header-bg:  var(--color-surface);
  --table-header-fg:  var(--color-fg-2);
  --table-header-line: #EFEFEF;   /* 表头下分隔线（Gashora 落地新增：中性浅灰，见 table.css）*/
  --table-row-hover:  var(--color-primary-50);
  --table-row-alt:    var(--color-bg);   /* 隔行底色（斑马纹）：比主指标行 --color-surface-2 稍深，看得出差异但不喧宾 */
  /* ⭐ 2026-09-25 Gashora 骨架落地：行分隔线由 --color-border-soft(#EDF0F4) 改为**独立中性灰**。
     参考图实测的表格行线是 #F2F2F2（比卡片描边更淡）——行线是"最轻"的一级线，
     不能与卡片描边同色同重，否则表格内部看着和卡片边界一样重。 */
  --table-divider:    #F2F2F2;

  /* 徽章（状态「深字 + 浅底」成对）
     ⭐ 2026-09-25 UI 美化第三刀：字色改为**徽章专用的加深值**（下方注释说明），
        不再直接引用 `--color-warning` / `--color-danger` ——
        那两个令牌被 9 个 CSS 文件 100+ 处引用（图表、成本、财务…），
        动它们会波及无关模块；而 `--badge-*-fg` **只被 `.badge--*` 使用**（已核对），
        改这里 = 只影响徽章文字，是可控的最小面。

     ⚠️ 为什么必须加深（实测对比度，非审美偏好）：
        徽章底色是一层**近白的浅色**，而 `--color-warning`(#F2A918) 与 `--color-success`(#31A24C)
        自身亮度偏高 —— 亮字压在浅底上，对比度实测只有 **1.86:1**（橙）/ 3.28:1（绿），
        **远低于 WCAG 正文 4.5:1**，12px 小字几乎糊成一片（这是既有问题，非本次引入）。
        ⇒ 取「同色相、深一档」的值，**语义不变**（未授权仍是橙色系、通过仍是绿色系），
          只是让人看得清。实测：橙 5.23:1 / 绿 5.09:1 / 红 5.62:1 —— 全部达标。
     ⚠️ 这三条只用于**文字色**；底色（`--badge-*-bg`）保持原样不动。 */
  --badge-success-fg: #0B7A55;   /* 原 var(--color-success) 3.28:1 → 5.09:1 */
  --badge-success-bg: var(--color-success-bg);
  --badge-warning-fg: #9A5B00;   /* 原 var(--color-warning) 1.86:1 → 5.23:1（提升最显著） */
  --badge-warning-bg: var(--color-warning-bg);
  --badge-danger-fg:  #C4142F;   /* 原 var(--color-danger) 4.30:1 → 5.62:1 */
  --badge-danger-bg:  var(--color-danger-bg);
  --badge-info-fg:    var(--color-info);
  --badge-info-bg:    var(--color-info-bg);

  /* KPI 大数字（2026-09-25 UI 美化第二刀）——
     诊断结论里「彩色点占比 0.5%~2.1%」「品牌蓝存在感 1%~2%」的最大单一来源就是它：
     30px 的大数字此前一律 `--color-fg`（近黑），是屏幕上最大的"无彩色块"。
     改为品牌蓝后，每个含 KPI 的页面都自带一处主色强调（17 个页面受益）。
     ⚠️ 取 primary-600 而非 500：#0457CB 在彩色柔底卡上对比度 5.57:1，
        比 #0064E0 的 4.61:1 更稳（柔底卡有 5 种色调，必须按最差配对取值）。
     ⚠️ **利润 / 亏损类 KPI 不受本令牌影响** —— pages.css 里 `.num-pos` / `.num-neg`
        有更高特异性，仍显示红/绿（语义色优先级高于品牌色，这是算钱相关，不能改）。 */
  --kpi-value-fg: var(--color-primary-600);
}

/* ⭐⭐ 2026-10-06 高密度屏的**宽度补偿**（与 base.css 的 zoom 严格同条件）
   ──────────────────────────────────────────────────────────────────────────
   ▸ 为什么必须同条件：`zoom: 0.85` 只在 `(min-resolution:1.5dppx) and (min-width:1600px)`
     命中。若在这里无条件补偿，**1440 笔记本 / Win 1080p / 手机**（本就不缩放）侧栏会
     凭空从 240 变 282 ⇒ 把"修高密度屏"变成"弄坏所有屏"。
   ▸ 只改 `--sidebar-width` 一个令牌：`width`(base.css .sidebar) 与
     `margin-left`(layout.css .app-main) 都读它 ⇒ 侧栏变宽、内容区自动让位，
     ⛔ 侧栏内部竖向基线 62 / 一级 44 / 二级 40 / 分组 30 一律不动。
   ⚠️ 与 `--form-max-width`(1330) / `.page`(1900) 的补偿是两回事：那两个是
     **上限**、本来就该等比放大；这一条是**固定宽度**、不缩放屏必须维持 240。 */
@media (min-resolution: 1.5dppx) and (min-width: 1600px) {
  :root {
    --sidebar-width: var(--sidebar-width-scaled);
  }
}

/* 手机端：整体字号下调约 1px，让一屏显示更多内容（团队视力均无问题） */
@media (max-width: 768px) {
  :root {
    --font-size-xs:   11px;
    --font-size-sm:   13px;
    --font-size-base: 13px;
    --font-size-lg:   15px;
    --font-size-xl:   18px;
    --font-size-2xl:  21px;
    --font-size-3xl:  26px;
    --font-size-4xl:  32px;
  }
}

/* 无障碍：偏好减少动效时关闭位移/缩放，仅保留透明度变化 */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}


/* ==== /css/base.css ==== */
/* ==========================================================================
   达播工作台 — base.css
   全局 reset / 排版 / 工具类 / 布局骨架（app-shell + 侧栏）
   规则：所有颜色/间距/圆角引用 design-tokens.css 变量，禁止裸 hex（#fff/#000 例外）
   ========================================================================== */

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

* {
  margin: 0;
  padding: 0;
}

html,
body {
  height: 100%;
}

/* 常驻预留纵向滚动条的位置（乐哥 2026-09-18「同页面切 tab 也会向右移动然后闪一下」）
   --------------------------------------------------------------------------
   根因（实测）：本应用页面**变高变矮时滚动条会出现/消失**，而滚动条占 15px 宽
     ⇒ 内容区可用宽在 1585 ↔ 1600 之间反复跳变
     ⇒ 所有 `width:100%` 的容器（卡片、工具栏、右对齐的页头按钮组）**右边缘横向移动 15px**。
   实测（同口径、同一次会话，只在"长页/短页"之间切换）：
     不处理      ：页头按钮组右缘 1553 → 1568（**+15px**）、卡片右缘 1553 → 1568（+15px）
     加上本规则后：页头按钮组右缘 1553 → 1553（**0px**）、卡片右缘 1553 → 1553（0px）
   而"切 tab"恰好会改变页面高度（加载态的行数与真实数据不同）→ 所以表现为"切 tab 时向右移动"。

   ⚠️ 判据说明（这一条我一开始判错过，记下来避免重犯）：
      第一次我量的是 `documentElement.clientWidth` 是否被预留（得到"没预留"→ 误判无效）。
      但**用户看到的是元素位置**，正确判据是量"右对齐元素的 right 边界在长短页之间是否变化"。
      换成后者一量，结论就反了：本规则确实消除了可见位移。
   ⚠️ 试过但**不行**的写法：`html{overflow-y:scroll}` —— 它把跳变从视口转移到了 body
      （body 因 `overflow-x:hidden` 计算成 overflow-y:auto，也成了滚动容器）→ 仍有 11px 位移。 */
html {
  /* 🔴🔴 2026-10-08 22:1x **撤销根节点上的 `scrollbar-gutter: stable`**（乐哥：
     「保留224，但是右侧的蓝色边要处理和上方的一样」）
     ────────────────────────────────────────────────────────────────────────────────
     ▸ 现象（量他的截图 + 本地复现，两处对得上）：蓝框**右侧的淡蓝色带比上/下宽 12~15px**
       · 他的截图：右 19 视觉px / 上 0 / 下 7（他的机器 zoom 0.85 ⇒ 8×0.85=7 ✔︎）
       · 本地 dpr1：右 23 / 上 8 / 下 8（差 **+15**）
       · 本地 dpr2：右 44 / 上 14 / 下 14（差 **+15**，视觉 ≈13）
     ▸ 根因：`scrollbar-gutter: stable` 在**根节点**上恒定预留一条 15px 槽
       （实测 `body.clientWidth 1905` 而 `window.innerWidth 1920`）
       ⇒ 整个 `.app-shell` 只有 1905 可用宽 ⇒ 蓝框右缘被推到 1897（应 1912）
       ⇒ 右侧蓝带 = 15(槽) + 8(inset) = **23**，而上下都是 8。
     ▸ 为什么现在可以撤销：这条是 **2026-09-18** 为"同页面切 tab 时整页滚动条出现/消失 ⇒
       内容右移 15px"加的。但 **2026-10-07「边框锁死」之后视口本身不再滚动**
       （`html, body { overflow: hidden }`，滚动已下沉到 `.app-main`），
       ⇒ 根节点**永远不可能出现滚动条** ⇒ 这条预留只剩"白站 15px"的副作用。
       ⚠️ 真正承担"切 tab 不左右抖"的是 `.app-main` 上的同一条（layout.css，门禁 ② 锁着），
          它**原样保留**，别一起删。
     ▸ 删掉后：右缘回到 8 = 上 = 下（本地 dpr1 实测 8 / 8 / 8），且长短页切换**仍无位移**
       （`scripts/frontend-debug/verify-root-gutter-1008.js` 真机量：长短页 frame 宽度与右对齐元素右缘逐项相同）。 */
  /* ⭐⭐⭐ 2026-10-07「边框锁死 · 框内滚动」：**视口本身不再滚动**。
     乐哥原话：「顶栏和底栏滑动的时候会消失……上下边框也是锁死的，不会随着滑动而变」。
     ⇒ 滚动**下沉到 `.app-main`**（它是那台"显示屏"的内部）。
     ⚠️⚠️ `html, body { overflow: hidden }` 少一条就会出现**双层滚动条**
        （外层 window 一条、内层 .app-main 一条），用户会以为页面有两根滚动条。
     ⚠️ `overflow-x: hidden` 不能少：`.app-main` 内部若出现横向溢出（宽表格），
        外层会跟着横向滚 ⇒ 右边框跟着跑，边框就不"死"了。
     ⚠️ 为何用 `hidden` 而不是 `clip`：**这里就是要它别滚**（`clip` 虽也不滚动，
        但会让程序里 `window.scrollTo` 静默无效、且不阻止橡皮筋回弹），
        与 `.app-main` 那处"必须 auto"的结论相反 —— 两者目的不同，别记混。 */
  overflow: hidden;
}

body {
  font-family: var(--font-family-body);
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-regular);
  line-height: var(--font-line-height-body);
  letter-spacing: var(--font-letter-spacing-body);
  color: var(--color-fg);
  /* ⭐⭐⭐ 2026-10-07「**侧栏就是底色**」（乐哥：「然后侧边栏是底色，他整个包着内容页」）
     ────────────────────────────────────────────────────────────────────────────────
     壳层现在是**两层**（不是三层）：
       · **底色** `--color-content-bg` = `#EBEDF0` = **侧栏色**（同一个值）
       · **显示屏** `--color-surface` `#FFFFFF` —— 内容页，四边内缩 + 四角圆角
     ⇒ 侧栏与四缝、视口外圈**连成一整片底色**，把白色内容页整个包在中间。
     ⚠️⚠️ 别把它改回与侧栏**不同**的色（这是 10-06～10-07 上午的病根）：
        两色不同时侧栏读成"另一块板"、不是底色 ⇒「底色包着内容页」根本不成立。
        门禁 `frame-flush ④` 现在锁的是"底盘 == 侧栏色"，不是"接近白"。
     ⚠️ 也别改回 #F6F6F6：它与白卡(255) 只差 9 阶 ⇒ 白屏与底色几乎同亮，浮不起来。 */
  background: var(--color-content-bg);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  /* ⭐⭐⭐ 2026-10-07「边框锁死」：body 也**不滚动**（滚动已下沉到 `.app-main`）。
     ⚠️⚠️ `overflow-x: hidden` 本身已让 body 在 y 方向计算成 `auto`
        （见上方 09-18 那条注释里记的踩坑）⇒ 只写 `overflow-x` **挡不住** window 滚动，
        必须显式 `overflow: hidden`。
     ⚠️ 写成 `overflow: hidden` 会不会误伤 `.app-shell` 之外的 fixed 弹层？
        不会：`position: fixed` 相对**视口**定位，只要祖先没有 transform/filter/contain，
        body 的 overflow 管不到它们（弹层实测正常，见 verify 脚本的弹层检查）。 */
  overflow: hidden;
}

/* ⭐⭐ 2026-10-06 高密度屏「整站等比缩放」（乐哥：「页面 dpi 有点大，想整体调小」）
   ──────────────────────────────────────────────────────────────────────────
   ▸ 只在**被系统放大过**的设备上缩（dpr ≥ 1.5 且视口 ≥ 1600）。
     条件依据（令牌 --app-scale 有完整推导）：
       命中   Mac 4K@1920等效(dpr2) / Mac 5K / MacBook Retina(≥1600) / Win 4K@150%+
       不命中 **Win 4K@100%（dpr1）** / Win 1080p / MacBook 13"(1440) / 手机
     ⛔ 无条件全局缩会把 Win 4K@100% 用户的正文从物理 14px 压到 11.9px（过小）。
   ▸ 为什么用 body 而不是 html：`.app-shell` 等骨架都在 body 内，body 一处即全站；
     且 `fixed` 顶栏/侧栏实测**跟随缩放**（缩后仍贴边、吸顶页头正常）。
   ▸ ⚠️ 配套纪律：**宽度上限必须同步 ÷ 0.85 等比放大**（见 layout.css 三档 +
     design-tokens 的 --form-max-width），否则"字小了、页面也被压窄 15%" ——
     乐哥本次实测就指出了这点：「列表是不是可以再宽一点点」。 */
@media (min-resolution: 1.5dppx) and (min-width: 1600px) {
  body {
    zoom: var(--app-scale);
  }
  /* ⭐⭐⭐ 2026-10-07：zoom 下 `100vh` **不跟随缩放**（只有元素盒缩放），
     ⇒ 任何"height: calc(100vh − 常数)"都会比视口矮 `视口高×(1−scale)`。
     实测（1873×942 dpr2、scale 0.85）：`.app-main` 上缝 7 / **下缝 148**（乐哥报障）。
     ✅ 这里把 `--shell-vh` 改成 `100vh / scale`：缩放后视觉高正好 = 视口高 ⇒ 上下缝对称。
     ⚠️ 必须**只在本媒体查询里改**：未缩放屏（1440 / 1080p / 手机）保持 100vh，
        写成 `100vh / var(--app-scale)` 无条件生效会让那些屏幕的框**超高**（0.85 倍视口）。
     ⚠️ 与 `--sidebar-width-scaled` 是**同一类补偿**（都是"zoom 会缩、令牌要除回来"），
        改一处请同时想另一处。 */
  :root {
    --shell-vh: calc(100vh / var(--app-scale));
  }
}

a {
  color: inherit;
  text-decoration: none;
}

button {
  font-family: inherit;
  cursor: pointer;
}

input,
select,
textarea {
  font-family: inherit;
  font-size: inherit;
  color: inherit;
}

ul,
ol {
  list-style: none;
}

img {
  display: block;
  max-width: 100%;
}

.hidden {
  display: none !important;
}

/* 数字统一等宽：金额/数量/百分比/场次 */
.num {
  font-feature-settings: "tnum", "lnum";
  font-variant-numeric: tabular-nums;
  text-align: right;
}

/* 价格：醒目红色 + 加粗（货品售价/达播价等） */
.price {
  color: var(--color-price);
  font-weight: var(--font-weight-semibold);
  font-feature-settings: "tnum", "lnum";
  font-variant-numeric: tabular-nums;
}

/* 页面内容切换：淡入 + 轻微上移 + **逐块错峰**
   ══════════════════════════════════════════════════════════════════════════
   2026-09-26 动效升级（乐哥：「轻盈灵动，不要傻快傻快」）—— 两处改动：
     ① 曲线从 ease-standard 换成 `--motion-ease`（easeOutQuint），
        位移从写死的 6px 换成令牌 `--motion-rise`；
     ② **加错峰**：原来 `.page` 的所有直接子级（页头 / 工具栏 / 卡片…）**同时**开始动画，
        读起来是"一整块拍上来"。现在依次错开 36ms，
        视线会被自然地"带着往下走" —— **错峰是"灵动"最省力也最有效的一笔**。

   ⚠️⚠️ 为什么延迟只给前 5 个（`:nth-child(-n+5)`）：
      不封顶的话，一个 12 个子级的页面最后一个要等到 12×36 = 432ms 才开始，
      用户点完菜单要盯着半空的页面等 —— 那是"慢"，不是"轻盈"。
      第 6 个往后通常已在首屏之下，不参与错峰没有观感损失。
   ⚠️ fill-mode 用 **`backwards`**（不是 `both`！）：
      · `backwards` 负责"延迟期间保持 `from` 态（透明）"——不写的话延迟期间元素是
        "最终态"，会出现"先亮出来、再突然淡入"的闪烁（这是**原有诉求**，backwards 已满足）。
      · ⛔ `both` 比它多的那一半 `forwards`（**结束后保留终值**）不但是多余的、
        而且**有害** —— 2026-09-26 乐哥实报「点时间组件后，被上层卡片遮住了」的事故根因：
        终值 `transform: translateY(0) scale(1)` 的计算值是 `matrix(1,0,0,1,0,0)`、**不是 none**，
        而非 none 的 transform 会**创建层叠上下文** ⇒ 每个页面直接子级（工具栏/卡片容器…）
        永久变成层叠上下文 ⇒ 里面的下拉面板 `z-index: 1000` 被锁死在容器内部，
        与后面的兄弟（卡片）比较时按"容器的层级"排 ⇒ 面板被卡片盖住（实测命中 .kpi-card__value）。
        改 backwards ⇒ 动画结束后元素上**无任何动画残留**（transform 计算值 = none）
        ⇒ 层叠上下文消失，面板恢复盖住卡片（实测面板中心命中 .mp__grid）。
      · ⚠️ 别为了"必须有终值"改回 `both`/`forwards`：视觉终值 = 元素自然态，
        撤掉填充不会跳变（实测采样无中间帧突变）；而层叠上下文是**静默**副作用。
      · ⚠️ 也试过"`to` 帧去掉 transform"这条更隐晦的写法 —— **无效**（实测 Chrome
        仍会为隐式终值生成单位矩阵 ⇒ 照样创建层叠上下文），不要采用。
   ⚠️ 同时只动 opacity + transform（不触发重排）。
   ⚠️ 只在**路由切换**时播：`.page` 由 app.js 按路由新建（见 createTabContainer），
      页面内部的筛选/翻页是重绘 `.card` 内部的容器，不碰 `.page` 的直接子级
      ⇒ 不会每点一次筛选就整页重来（已真机核实，见 probe 的「筛选不重播」断言）。
   ══════════════════════════════════════════════════════════════════════════ */
.page > * {
  /* ⭐ 2026-09-26：曲线由 --motion-ease 换成 --motion-ease-pop（弹入，落位轻微回弹）。
     页面区块是"从无到有"的元素，没有"对准"语义 ⇒ 过冲大一点更生动。
     ⚠️ 不要在这里用 --motion-ease（那是**位移**曲线，过冲只有 2px 级）——
        实测用它播页面进场，肉眼读不出"弹"，只显得"滑了一下"。 */
  animation: page-fade-in var(--motion-page-enter) var(--motion-ease-standard) backwards;
}
.page > *:nth-child(2) { animation-delay: var(--motion-stagger); }
.page > *:nth-child(3) { animation-delay: calc(var(--motion-stagger) * 2); }
.page > *:nth-child(4) { animation-delay: calc(var(--motion-stagger) * 3); }
.page > *:nth-child(5) { animation-delay: calc(var(--motion-stagger) * 4); }

/* ══════════════════════════════════════════════════════════════════════════
   ⭐⭐ 2026-09-29 三个「板块外壳」页面的错峰**下沉一层**
   （乐哥：「hub 首页，财务中心，还有 hr 那个系统 ui 动效，都统一下」）
   ──────────────────────────────────────────────────────────────────────────
   🐞 现象：达播台的页面是 4~5 块依次错峰 30ms 带下来；而门户 / 财务中心 / 人事行政
      **整块"拍"上来**（真机实测：这三页 `.page` 只有 **1 个**直接子级，
      上面那套 `:nth-child` 错峰对它们没有任何东西可错）。
      根因不是"忘了加动画"—— 是这三个板块的**外壳结构不同**：
      它们各自有一个根容器（门户/财务/系统配置共用 `.fin`，人事用 `.kq-page`），
      所有内容都包在里面，于是错峰被"包"住了。

   ✅ 做法：把错峰下沉到"外壳内的第一层"（`.fin-main > *` / `.kq-page > *`），
      并**抑制根级那条**动画 —— 不抑制的话两层 transform 会叠加，
      内容实际位移变成 24px（是别的页面的两倍，"跳"一下）。

   ⚠️⚠️ 为什么**只下沉一层**、不去更深的"卡片那一层"（那样理论上更接近达播台的观感）：
      财务中心 `#fin-body` 的子级在**每次筛选 / 翻页 / 切子页时都会被
      `innerHTML` 整体重建** ⇒ 在那一层挂动画 = 用户每点一次筛选就整页重播一遍，
      正是本文件上方明令禁止的"每点一次筛选就整页重来"。
      实测确认（空库真机）：hr 的 `#kq-body` 子级在筛选时**不**重建、财务的**会**。
      ⇒ 既然只有一个板块安全，就**统一取对所有板块都安全的层级**，
        而不是"hr 更深、财务更浅"——那会让三个板块彼此之间又出现新的不一致。
   ⚠️ keyframes 复用上面的 `page-fade-in`（不新造）；⚠️ **曲线是 `--motion-ease-standard`，
      ⛔ 不是 `--motion-ease-pop`** —— 本条在 2026-10-11 之前误写成 "复用 --motion-ease-pop"，
      与下面那行代码（standard）**不符**，把动效验收脚本的判据也带偏了（它照注释期望 1.68 过冲 ⇒ 恒红）。
      依据是 §9.10 定稿（2026-10-08 乐哥：「整个页面…都是淡入淡出；页面内切 tab 才是左右滑动」）：
      **页面进场只淡入、不带过冲**（过冲是"位移/弹入"的语义，淡入用它只会闪）。
   ⚠️ 延迟只有第 2 块（这三页只有"页头 + 内容"两块），同样走 `--motion-stagger`。
   ⚠️ fill-mode 必须是 `backwards`（与上面同因：`both` 会在结束后留下单位矩阵 transform
      ⇒ 创建层叠上下文 ⇒ 页内下拉/弹窗被卡片盖住，2026-09-26 的实际事故）。
   ══════════════════════════════════════════════════════════════════════════ */
.page > .fin,
.page > .kq-page {
  animation: none;                 /* 交给下面这一层做，避免两层位移叠加 */
}
.fin-main > *,
.kq-page > * {
  animation: page-fade-in var(--motion-page-enter) var(--motion-ease-standard) backwards;
}
.fin-main > *:nth-child(2),
.kq-page > *:nth-child(2) {
  animation-delay: var(--motion-stagger);
}

/* ⭐⭐⭐ 2026-10-08 00:5x 页面进场改为 **Apple 官网式渐显**
 * ────────────────────────────────────────────────────────────────────────────────
 * 乐哥：「页面切换…我们应该做 apple 官网那种渐变显示」。
 *
 * ⛔⛔ **改前实测（真机，非印象）**：
 *     animation: page-fade-in 155ms cubic-bezier(0.34, 1.68, 0.5, 1) backwards
 *     → 时长 155ms / 位移 12px / scale 0.985 / **曲线 pop（过冲 68%）**
 *
 * ▸ 为什么"155ms + pop"读作"弹一下"而不是 Apple 的"浮现"（Apple 官网的观感拆解）：
 *   Apple 的产品页切换是三件事叠加，缺一不可：
 *     ① **时长够长**：约 400~600ms，让内容有时间"浮现"；
 *        155ms 在这个尺度上太快 ⇒ 人眼只抓到"中间那一瞬" ⇒ 像闪。
 *     ② **曲线**：`--motion-ease-pop` = (0.34, **1.68**, 0.5, 1)，
 *        y1=1.68 ⇒ **过冲 68%** ⇒ 内容会冲过终点再回来 ⇒ 这正是"弹"的来源，
 *        与 Apple 的"缓缓落定"正好相反。Apple 用的是**平滑减速**（无过冲）。
 *     ③ **位移 + 透明度配比**：位移小、透明度变化为主 ⇒ 读作"浮现"；
 *        本项目是位移 12px 为主 ⇒ 读作"从下滑入"。
 *   ⇒ 三条一起改，不是只把时长拉长。
 *
 * ✅ 改法（各自有据）：
 *   时长 155 → **420ms**（落在 Apple 的 400~600 区间下沿，取保守值：
 *     再慢会让"点菜单→看到内容"的等待感变明显，420 是"明显是渐变"与"不覺得慢"的平衡）
 *   曲线 pop → **--motion-ease-standard**（= easeOutQuint，**无过冲**，平滑减速）
 *   位移 12 → **8px**（更小；Apple 位移克制，主角是透明度）
 *   scale 0.985 → **0.995**（幅度几乎取消；0.985 在420ms 下会读作"内容在长大"）
 * ⛔ **错峰 delay 取消**：错峰原本配155ms+30ms 步长成立（每块 30ms 差得开）；
 *   时长拉到 420ms 后，30ms 的错峰在时长里占比变小 ⇒ 观感是"一起动"，错峰失效。
 *   ⚠️ 但**不能直接删 nth-child 那几条**（那是给 5 块内容分别设的）：
 *   保留它们无害（delay 30~120ms 仍在420ms 窗口内，Apple 也有轻微错峰）。
 *   ⇒ 保留不动，只改时长/曲线/位移/缩放四项。
 * ⚠️⚠️ **为什么时长必须走令牌、不能写死 420ms**：
 *   本文件下面还有 4 处 `page-fade-in`（`.fin-main > *`、`.kq-page > *` 等板块外壳），
 *   它们各自带不同 delay。若这里写死 420 而那些地方仍走 `--motion-base`(155)，
 *   ⇒ **同一套动效语言在四个板块里速度不一致**（今晚已在CSS 里栽过同类坑）。
 *   ⇒ 新增 `--motion-page-enter`，一处改全站跟随。 */
@keyframes page-fade-in {
  /* 从"下方一点点 + 极淡"浮现：位移与缩放都收到最小，主角交给 opacity。
     曲线用 ease（平滑减速、无过冲）—— 这是与旧版最大的差别。 */
  from { opacity: 0; transform: translateY(8px) scale(0.995); }
  to { opacity: 1; transform: translateY(0) scale(1); }
}

/* 编号/订单号/SKU/佣金编码等宽 */
.mono {
  font-family: var(--font-family-mono);
}

/* 超长文本截断 */
.ellipsis {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ============================ 布局骨架 ============================ */

.app-shell {
  display: flex;
  /* ⭐⭐⭐ 2026-10-07「边框锁死 · 框内滚动」：`min-height` → `height`。
     ▸ 为什么必须换：内容超高时 `min-height:100vh` 会让本容器**被内容撑高**，
       而 `.app-main` 的兄弟关系决定它要按容器高度算边框 —— 容器一高，
       底部那条缝就被推到视口外，"底边框锁死"直接失效（实测框高会 = 内容高）。
     ▸ `height: var(--shell-vh)` + `overflow: hidden` ⇒ 容器恒等于一屏，超出部分由 `.app-main` 自己滚。
        ⚠️ 用 **`var(--shell-vh)`** 而不是 `100vh`：zoom 环境下 `100vh` 不含缩放
           （乐哥 14:10 报「下面留白这么多」的真凶，实测上缝 7 / 下缝 148）。
        未缩放屏该令牌 = 100vh，行为不变。 */
  height: var(--shell-vh);
  /* ⚠️ 移动端那侧 `.app-shell` 另有 `height: 100dvh` 的处理，别被这条覆盖掉
     （见 layout.css 移动端段落）。 */
  overflow: hidden;
}

/* ---- 侧栏（白色，与顶栏/标签栏同色；右侧一条 1px 线与内容区分离）----
   2026-09-19 晚：乐哥拍板**取消「整体悬浮」**，改为「方案A：贴边」。
   前一版是「侧栏与内容列各是一块 14px 圆角面板、彼此留 2~8px 缝浮在桌面色上」，
   实测两个毛病（都写进了技能 `dabotai-ui-consistency-fix`）：
     ① 缝宽不对等：左/中/右 2px、上/下 8px（差 4 倍）→ 读起来像"没对齐"而非"刻意留白"
     ② **滚动后两块面板顶边错开 8px** —— 本元素是 fixed（视口 top=8），
        而内容列的 margin-top:8px 是**文档坐标**、随文档滚走 →
        内容顶边落到视口 0、本元素顶边仍在视口 8。只测"未滚动"的探针永远发现不了。
   ⇒ 现在 top/left/bottom = 0（贴边通高）、直角、无阴影，靠 border-right 一条线分离。
   ⚠️ 这三条必须与 .app-main 的 margin 配套；移动端（抽屉式）在 layout.css 的
      @media (max-width:768px) 里另有覆盖，不依赖这三个值。 */
.sidebar {
  /* ⭐⭐ 2026-09-25 Gashora 骨架落地：侧栏从「贴边通高 + 右边一条线」改为**浮起的圆角岛**。
     参考图（乐哥指定）：左侧菜单栏是独立的一个岛，四周有缝、露出灰底。
     ▸ top 0 → **calc(frame-gap + tabs-bar-height + frame-gap) = 112px**（值随令牌走，此处只是当前读数）
       （顶栏 fixed 占据最顶部，侧栏从它下方开始）。
     ▸ left / bottom = --frame-gap：与顶栏的四周留白**同值**（缝宽对等，不许各写各的）。
     ▸ 圆角 18 + 柔和阴影：从"一块贴边区域"变成"一张浮起的卡"。
     ▸ 去掉 `border-right`（那条 #E5E8EC 竖线是"贴边"形态的分界，岛形态靠缝隙+阴影分界）。 */
  position: fixed;
  /* ⭐⭐ 2026-10-06 **侧栏改成"不悬浮"（贴边平面）** —— 乐哥：
     「我其实是觉得顶栏不是悬浮了，侧边菜单栏是不是也按照不悬浮的样子做」。
     ▸ 三边贴 0、圆角 0、无边框、无阴影 —— 与顶栏（09-28 起就是贴边贯穿）**同一套形态**。
     ▸ 底色 = **transparent**（与页面底盘 --color-bg 同色）—— 乐哥要"不要底"，
       侧栏与内容区之间的分界改由**右侧一条 1px 细线**承担（与顶栏的底部细线同一套语言，
       两线在左上角交汇）。⛔ 不要给它实色底：那会退回"侧栏是一块面板"的旧形态。
     ▸ 顶栏竖高 60px，侧栏从 0 起 ⇒ 侧栏的品牌区（同高 60）与顶栏**天然共线**。
     ⚠️⚠️ 保留下来的两条**不能删**：
       · `overflow: hidden` —— `overscroll-behavior: contain` 只对"滚动容器"生效，
         改 visible 会让"侧栏滚轮带动页面滚动"那个 bug **静默复发**（tests/restruct-topbar.test.js F2 锁着）。
       · `z-index: var(--z-sticky)` —— 侧栏要压在 `.app-main` 之下/之上关系稳定。
     ⚠️ 与 frame-flush 门禁的关系：旧断言锁的是"浮岛几何"（top 公式 / 四边缝 / 圆角 18）。
        本次是**方向反转**（09-19 贴边 → 09-25 浮岛 → 现在回到贴边），
        断言已同步改成"贴边平面"那一组，别再照旧断言改回去。 */
  /* ⭐⭐⭐ 2026-10-07 **方向反转**：贴边 → **内缩成浮岛**（乐哥选 W3 档）
     ────────────────────────────────────────────────────────────────────────────
     乐哥原话：「侧边栏上下，是不是可以做一点**入侵的圆角**，这样就有一点包裹住的感觉了，
     同理内容页最右边也是」+ 底色「你看要不要和参考图一样重一点」。
     ⇒ 从「三边贴 0、直角、无底色边界」改为「四周内缩 `--shell-inset`、右下圆角、实色底」。
     ▸ 几何实测依据（1920×950，改造前）：
        侧栏 → 内容区缝隙 = **0px（贴死）**；侧栏与内容区上缘都贴视口顶（y=0）
        ⇒ 贴死时**根本没有"包裹"可言**，而上缘贴边**给不了上圆角**
           （会在视口角露出 body 底色，像"缺了一块"）
        ⇒ **入侵圆角只能落在下方两角**（侧栏右下 / 内容区左下）。
     ▸ ⚠️⚠️ `width` 必须显式减（这条是本改动的关键，实测连栽两回）：
        只给 `left: inset` ⇒ 侧栏右缘与内容区左缘**同步平移** ⇒ 缝永远是 0；
        改用 `right:` 也不行 —— 本元素写着 `width`，而 fixed 同时有
        `left+right+width` 时 **width 胜出**（left/right 被 over-constrained 忽略）。
        ⇒ `width: calc(var(--sidebar-width) - var(--shell-inset))` 缝才真的出现。
     ▸ 底色 #EBEDF0（乐哥 A2 档）：与内容白卡的色阶差 ≈16，对齐他给的参考图
        （实测参考图侧栏灰度 235 / 内容卡 251；我们改造前是 248 / 255 = 仅 7 阶）。
     ⚠️ 折叠态 `.is-collapsed .sidebar { width: var(--sidebar-width-icon) }`
        覆盖本条 width ⇒ 折叠时缝隙会变成 inset + (icon − full)，见 layout.css 那处的补偿。
     ⚠️ 移动端（≤768px）侧栏是抽屉（overlay 定位、不占 flex 宽）⇒ 本条内缩对它无意义，
        见 layout.css 移动端段落的复位。 */
  /* ⭐⭐⭐ 2026-10-07 **第二次方向反转**：浮岛 → **底色本体**（乐哥：
     「侧边栏是底色，他整个包着内容页」）
     ────────────────────────────────────────────────────────────────────────────
     ▸ 关键认知（这一版才真正读懂他的意思）：
       上一版把侧栏做成「内缩的浮岛」，但**浮岛和底色是两块东西** ——
       岛有岛自己的边界，于是"包住"读不出来。
       他要的是：**侧栏 = 底色本身**（贴边、通高、无边界），
       内容页 = 一块**有圆角的显示屏**，浮在这片底色上。
       ⇒ 侧栏一旦变成底色，它就**不能有圆角、不能内缩**（有边界就不是底色了）。
     ▸ 因此侧栏恢复 `top/left/bottom: 0`、`width: var(--sidebar-width)`、无圆角。
       包裹感改由**内容页那侧的四条缝 + 四角圆角**承担（见 layout.css 的 `.app-main`）。
     ▸ 底色一致性：`--color-content-bg` 现在 `= var(--color-sidebar-bg)`
       （design-tokens 已改）⇒ 侧栏、四缝、视口外圈**连成同一片底色**，
       内容白卡浮在正中 ⇒ 「底色包着内容页」成立。
       ⛔ 不要再把这两个令牌改回不同值：那会让侧栏又变成"另一块板"（10-07 上午的病根）。 */
  top: 0;
  left: 0;
  bottom: 0;
  /* ⚠️ 宽度回到 `--sidebar-width`（不再减 inset）：侧栏是底色、要贴边，
     留缝会让人看到 body 的另一层色，底色就不连续了。 */
  width: var(--sidebar-width);
  display: flex;
  flex-direction: column;
  /* 底色 = 与 body 同一个令牌 ⇒ 侧栏与四缝、视口外圈融成一整片。
     ⛔ 不用裸 hex（全站禁裸 hex，门禁 no-raw-hex 会红）。 */
  background: var(--color-sidebar-bg);
  /* ⭐⭐⭐ 侧栏**不再有圆角**：它是底色，有圆角就变成"一块板"了。
     内容页的圆角在 `.app-main` 上（四角），那才是"显示屏"的形状。 */
  border-radius: 0;
  /* ⭐ 2026-10-06 定下、2026-10-07 复核仍成立：**无边框**
     （乐哥：「上一个版本侧边栏的样式挺好看」）——
     侧栏与内容区的分界改由**缝隙 + 圆角 + 底色差**承担（三者都是 10-07 加的）。 */
  border: none;
  box-shadow: none;
  overflow: hidden;
  /* ★ 侧栏滚轮隔离（2026-09-19 乐哥：「鼠标在左边菜单栏，菜单栏不能滚时，
     滑动滚轮右边页面跟着滚」）—— 滚轮在侧栏上滚不动/到边界时，不再链式传给页面。
     **全站就这一条声明**（没有 JS、nav 上也不写），经三层对照实验定稿：
       · 去掉本条              → 行为断言红 3 条（搜索入口 / 底部「我的」穿透）
       · 只在本条 + 去掉 nav 条 → **行为断言全绿**（故 nav 那条已删，行为上冗余）
       · 再加一段 JS preventDefault → 行为无任何差异（故 JS 也删了）
     ⚠️⚠️ **生效前提：本元素的 overflow 不能改成 visible** ——
        `overscroll-behavior` 只对**滚动容器**生效，而本元素正是靠 `overflow:hidden`
        才成为滚动容器、从而截断这条滚动链。改成 visible 会让修复**静默失效**、
        乐哥那个 bug 原样复发。该前提由 tests/restruct-topbar.test.js 的 F2 静态锁住。 */
  overscroll-behavior: contain;
  z-index: var(--z-sticky);
  transition: width var(--motion-base) var(--motion-ease-standard);
}

/* 侧栏品牌区也弱化，和菜单融为一体。
   ⚠️ height 用 --tabs-bar-height（= 顶部那一行）而**不是** --header-height：
      两者当前同值，但语义不同 —— 本元素要与标签栏那一行共线，
      跟"标签栏多高"走比跟"（已撤的）顶栏多高"走更不容易错。
   ⭐ 2026-09-20 起它**同时是一个按钮**（点击回首页，乐哥：「然后点击也能返回首页」）：
      所以这一条同时是"品牌区的样式"，也是"回首页按钮的样式"。
      点击/键盘的事件绑定在 app.js 的 renderSidebar（**只绑一次**，原因见那里的注释）；
      焦点环由 base.css 的全局规则 `:where(button,[role=button],[tabindex]):focus-visible` 提供。 */
.sidebar__brand {
  display: flex;
  align-items: center;
  /* ⚠️ gap 12 → 8（2026-09-20）：侧栏固定 240px，logo 与文字**抢同一块宽度**。
         文字要放到 20px（8 个汉字需 160px），可用宽 = 208 − logo36 − gap
         ⇒ gap 必须让出 4px 才够（12→8 后可用 164，留 4px 余量）。
         ⚠️ 2026-09-21 名称简化为 6 字 + 字号降到 18px 后，文字只用 108px、余量 56px
            ⇒ **这一条已经不卡在及格线上了**（当时的紧张关系已消失）。
            保留 8px 是**为了与 --space-2 的间距节奏一致**，不是"放不下才这么写"；
            别因为"现在很宽裕"就把它调大 —— 那会把菜单区整体视觉推挤。
         详见下方 .brand-name__zh 的计算。 */
  gap: var(--space-2);
  /* ⭐⭐⭐ 2026-10-08 14:5x 高度 `--tabs-bar-height`(60) → **`--sidebar-brand-h`(92)**
     ────────────────────────────────────────────────────────────────────────────────
     乐哥：「左边菜单栏文字的高度，按照我截图这个为标准做，就是红框线下面」
           +「logo 和文字看要不要整体下移」。
     ▸ 关键：品牌区**下沿就是菜单起始线**（= 页头卡顶边 92，真机实测）。
       留白并进品牌区后 —— ① `.sidebar__nav` 的 padding-top 归零（见那一条），
       滚动容器的裁切边 = 92 ⇒ 展开很多、滚动时菜单**滑不出这条线**；
       ② 品牌内容 `align-items:center` ⇒ 整块顺势下移（logo 12→28，居于 92 正中），
       正好回应他「整体下移」那一问。
     ⚠️ 品牌区高仍与顶栏那一行**共线**这条老约定**在本值下不再成立**：乐哥本次以
        截图为准，明确接受 logo 下移。别再按旧注释"改回 60"。
     ⚠️ hover 底色的作用范围随之变成 240×92（整块品牌区可点回首页，语义成立）。 */
  height: var(--sidebar-brand-h);
  /* ⭐⭐ 2026-10-06 品牌区回**侧栏**（不再搬到顶栏）——
     依据：参考图那条 dashboard 是「品牌在侧栏顶部、顶栏只放页面定位」；
     且侧栏贴边后顶部本来有 72px 空档，品牌回来正好把它用掉。
     ▸ 左内边距 20：品牌名落在 x=62（= 20 + logo36 + gap6），与下面菜单文字**同一条竖线**；
       logo 本身落在 20（与菜单图标的 34 差 14px，不影响读感——名字对齐更重要）。
     ▸ 去掉底部分隔线：侧栏与页面同底、贴边，再画一条线会显得"侧栏是一块被框住的区域"，
       与"不悬浮"的整体形态相悖。 */
  padding: 0 20px;
  border-bottom: none;
  flex-shrink: 0;
  /* 可点击回首页 —— 与全站可点组件的语言保持一致（.nav-item 也是 cursor:pointer） */
  cursor: pointer;
  transition: background-color var(--motion-fast) var(--motion-ease-standard);
}
/* hover 反馈：让"这里能点"被发现。用 --color-surface-2（= .nav-item:hover 同款令牌），
   不新造颜色；侧栏是白底，hover 到 #FAFBFC 是同一档非常轻的灰，与菜单项手感一致。 */
.sidebar__brand:hover {
  background: var(--color-surface-2);
}
/* ⚠️ 焦点环要**向内收**：本元素通栏贴左右边缘（宽 240 = 侧栏宽），
   全局规则那条 `outline-offset: 2px` 会把环画到侧栏外面、被 `.app-main` 裁掉一部分。
   只调偏移、不改颜色与粗细，仍是同一套焦点环。 */
.sidebar__brand:focus-visible {
  outline-offset: -2px;
}

/* ---- 品牌区高度 = 顶部区那一行（2026-09-19 结构重排后**只有一行**了）----

   ▸ 历史（两次相反的需求，别把哪一次的修法当成唯一答案）：
     ① 2026-09-19 上午：「左上角 logo 的高度和顶部菜单栏保持一致」→ 当时顶部**有两行**
        （面包屑行 56 + 标签栏 46），两条分隔线错开 46px ⇒ 把品牌区加高到 102 让两线共线。
     ② 2026-09-19 下午：「logo 留白这么多好奇怪」→ 品牌区仍 102、logo 居中。
     ③ **结构重排**：面包屑行整行撤掉，顶部只剩**一行**（= 标签栏那一行）⇒
        品牌区回到与它同高，两线天然共线，**前面两次的张力一次消失**（不再有"对齐 vs 留白"之争）。
     ⇒ 所以现在这里既没有 `calc(header + tabs)`、也没有 `padding-bottom`，
        更不需要 `:has()` 判断标签栏在不在 —— 结构简化后这些补丁全都不需要了。
     ▸ 2026-09-20：标签栏高度收回（乐哥「最上面的标签有点大」），品牌区**跟着一起收**
       —— 就是靠共用 `--tabs-bar-height` 这一个令牌自动跟上的，不改一行代码。
       ⚠️ 若哪天把品牌区改成写死高度，这个"自动共线"就会断，必须显式对齐。

   ⚠️ 若将来把面包屑行加回来：品牌区要改回 `calc(--header-height + --tabs-bar-height)`
      并恢复 `:has(.page-tabs-bar:not(:empty))` 的守卫（原因见 git 历史与本文件旧注释）。 */

.brand-logo {
  width: 36px;
  height: 36px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

.brand-logo img {
  /* ⚠️⚠️ `width: auto` + `height: 100%`（**不要** `width:100%; height:100%`）：
      公司标识长宽比是 0.897（高>宽），容器是正方形。
      写成 100%/100% 时**元素框**会变成正方形（图形虽经 object-fit 等比缩放没变形，
      但 `getBoundingClientRect()` 量出来是 36×36 ⇒ 核验脚本会误报"被压扁"，
      以后维护的人也会以为它变形了）。
      按高定尺后元素框本身就等于图形尺寸，一眼可验、也不会被误改。
     ⚠️ 这里**只管尺寸**；`object-fit` / `display` / 拖拽禁用等通用行为统一放在
        `.brand-mark`（见下方），两处不要重复写 —— 职责分开才好维护。 */
  width: auto;
  height: 100%;
}

/* ⭐ 公司标识（八芒星）的**通用行为** —— 2026-09-20 乐哥给图换的，全站统一
   （实现见 js/icons.js 的 `logo()`，它给每个 img 都挂了这个 class）。

   ⚠️ 职责划分：**尺寸**由各容器自己定（`.brand-logo img` 用 height:100%、
      门户 30px、财务 28px、登录 44px…因为各处想要的视觉大小不同）；
      本 class 只管**与尺寸无关的通用行为**，避免在每个容器里重复抄一遍。

   ⚠️⚠️ `object-fit: contain` **不能删**：标识长宽比 **0.897**（高>宽），
      而多数容器是正方形。没有 contain 时，若某处容器写的是
      `width:100%;height:100%`，图片就会被**横向拉宽**（压扁变形）——
      那种"看着有点怪、但说不上哪不对"的错最容易被漏掉。

   ⚠️ `vertical-align: middle` 治的是 inline 图片在行内基线的**下方空隙**
      （图片默认按基线对齐，会给容器多出几个像素高度、把布局顶偏）。
   ⚠️ 禁拖拽/禁选中：标识不是内容，被拖出去或双击选中会显得像网页坏了。

   ⚠️⚠️ `margin: 0`（**不是 `margin: 0 auto`**）—— 2026-09-22 乐哥：
      「所有页面 logo 位置，左对齐吧」。
      **为什么原来居中是错的**：`.login-hero__logo` 是**普通 block**、宽度由内容撑开
      （实测三个登录门面分别是 328 / 188 / 297px），`margin: 0 auto` 会把图标
      居中到这块宽度里 ⇒ **三个登录页的 logo x 位置各不相同（271 / 201 / 255）**，
      而标题、副标题、标签全是左对齐的 ⇒ 图标独独居中，看着就是"没对齐"。
      ⇒ 改成 `margin: 0` 后图标贴容器左边，与标题同一竖线。
      ⚠️ **不影响侧栏**（达播/财务）：`.brand-logo` 是 `flex + justify-content: center`，
         auto margin 去掉后由 flex 居中接管 ⇒ 位置一模一样（实测仍在 x=18）。
      ⚠️ **也不影响折叠态**（`.app-shell.is-collapsed .sidebar .brand-logo { margin: 0 auto }`
         —— 那是**容器自己**的 margin，与 img 无关）。
      ⇒ 这条修改的净效果：登录页 logo 左对齐（本次要修的），其余位置不变。 */
.brand-mark {
  object-fit: contain;
  display: block;
  margin: 0;
  vertical-align: middle;
  user-select: none;
  -webkit-user-drag: none;
  -webkit-user-select: none;
}

.brand-name {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
  overflow: hidden;
}

.brand-name__en {
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-medium);
  letter-spacing: 0.03em;
  color: var(--color-meta);
  line-height: 1.2;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.brand-name__zh {
  /* ⭐ 20 → 18px + 名称简化为 6 字（2026-09-21 乐哥拍板"缩小+简化"，
     原话：「星盟达人管理后台 这个名字有点长，放在左上角感觉不协调，
             你看是缩小文字，还是缩小文字+简化文字」）。
     ──────────────────────────────────────────────────────────────────────
     ▸ 为什么必须**两件事一起做**（"不协调"实测是三个来源叠加，不是单一问题）：

       | 来源 | 改前 | 改后 | 靠什么解决 |
       |---|---|---|---|
       | 顶到边 | 余量 4px（仅 0.2 个字的空隙） | 余量 56px | 简化 + 缩小 |
       | 盖过菜单 | 20px ÷ 菜单项 14px = 1.43 倍 | 1.29 倍 | 缩小 |
       | 两行悬殊 | 中文 160 vs 英文 54（3 倍），左对齐成"倒 L" | 108 vs 54（2 倍） | 简化 |

       ⇒ **只缩小**（仅改字号）治不了"两行悬殊"；**只简化**（仅改名字）治不了"盖过菜单"。
         两者成本一样（各改一个值），所以一起做，一次把三个来源都解掉。

     ▸ 实测宽度（真实 CSS 渲染，非估算）：
       · 可用宽 = 240 侧栏 − 品牌区左右 padding 2×16 − logo 36 − gap 8 = **164px**
       · 「星盟达人后台」6 字：16px→96 / **18px→108** / 20px→120 / 22px→132
       · 规律：**CJK 每字正好 1em**（8 字 20px = 160px 实测验证过，故可直接按字数×字号算）
       ⇒ 18px 占 108px，余 56px —— 余量充足，换字体/加权都不会截断。

     ▸ 比例仍在参照物同一量级：18 / 36 = **0.50**（微信小店的「文字/图标」高度比约 0.55；
       被乐哥指出"不够醒目"时是 16/36 = 0.44）。故 0.50 是"既收回体量、又不回到偏小"的位置。

     ⚠️⚠️ 这里**直写 18px、没有用令牌** —— 这是本项目唯一的裸字号，理由：
        令牌阶梯对齐 TDesign（12/14/16/20/24/30/36…），**没有 18 这一档**
        （pages.css 里也有一条既有说明："令牌表里没有 md"）。
        18px 是本设计真的需要的档位，但**不为此扩阶梯**：扩一档会让全站多一个可选值，
        而目前只有这一处需要它。⇒ tests/brand-home.test.js 的 A1 已相应放宽为
        "允许裸的 18px，其余一律必须用令牌"，别把这条当"可以随便写裸值"的先例。

     ⚠️ 手机端（≤768px）行为**完全不变**：原来用 --font-size-xl，它在断点里就是 18px；
        现在写死 18px ⇒ 手机端与改前逐像素相同，只有桌面端 20 → 18。
        （若将来想让手机端再小一点，**必须**显式加断点，不会再自动跟随了。）

     ⚠️ 改本条要连带看 .sidebar__brand 的 gap —— 两者仍是"抢宽度"的成对关系
        （tests/brand-home.test.js 的 A2 有算术断言：可用宽 ≥ 名字实际需要宽）。
        现在余量 56px 很宽裕，但**别因此把 gap 调大**：gap 变大同时会把菜单区视觉推挤。 */
  font-size: 18px;
  font-weight: var(--font-weight-semibold);
  letter-spacing: var(--font-letter-spacing-body);
  color: var(--color-fg);
  line-height: 1.3;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.sidebar__nav {
  flex: 1;
  overflow-y: auto;
  /* ⭐ 2026-09-25 菜单加高到 52px 后，导航区内容（实测 922px）超过可用高度（约 695px）
     ⇒ 菜单**可滚动**。但原滚动条是"透明、仅 hover 显色"，用户看到菜单在"业绩管理"处
     被切断会以为**菜单丢了**（乐哥报「看板页很奇怪」的一部分就是这个观感）。
     ⇒ 改成"常驻淡显 + hover 加深"：一眼能看出"下面还有"，也不抢视线。
     ⚠️ 与全站其它滚动条（body / 侧栏本身）的"hover 才显"不同 —— 那一套适合"内容明显
        超出、用户预期能滚"的场景；这里是**主菜单**，可见性优先。 */
  scrollbar-color: var(--color-gray-300) transparent;
  /* ⚠️ 这里**故意不写** overscroll-behavior —— 由外层 .sidebar 那一条统一负责。
     实测（.render-check/probe-sidebar-scroll.js）：只留 .sidebar 那条时行为断言全绿；
     而只留本条、去掉 .sidebar 那条时，**搜索入口与底部「我的」区会穿透**
     （它们不在本元素内，本元素的 contain 管不到）。
     ⇒ 一条放在最外层即可覆盖全部；放在这里反而只覆盖导航区，是**更弱**的方案。 */
  /* ⭐⭐ 2026-10-06：上下 8、**左右 12**（左右从 space-2=8 提到 12）——
     配合菜单项左右内边距 22 ⇒ 文字落在 12+22+16+12 = **62px**，
     与品牌名、分组标题、二级子项**同一条竖线**。
     算式：容器 12 + 项 22 + 图标 16 + gap 12 = 62。
     ⚠️ 改这一串任一个值，都要同步验"四级文字是否还对齐"（这是本项目最容易失守的隐性约束）。
     ⭐⭐⭐ 2026-10-08 14:5x **上内边距 43 → 0**（留白并进品牌区，见 `.sidebar__brand`）
        · 起因（乐哥原话）：「如果菜单栏展开了很多，滑动的时候，**也不要突破这个界限**，
          如果突破的隐藏了」——他画的那条红线在 y=92（= 页头卡顶边）。
        · ⛔ 病根：`padding-top` 长在**滚动容器内部** ⇒ 滚动内容可以进入这段留白，
          菜单项上滑时会出现在「logo 下方那段空档」里，只在**容器外缘**(y=60)被裁住
          ⇒ 视觉上越过了他画的那条线。**这不是调参能解决的，是留白的归属问题。**
        · ✅ 修法：留白挪进 `.sidebar__brand`（品牌区 60 → 92，走 `--sidebar-brand-h`），
          本元素上内边距归零 ⇒ 滚动容器上缘 = 92 = 裁切边 ⇒ 物理上滑不出去，
          不需要遮罩、不需要 JS。
        · ⚠️ 旧令牌 `--sidebar-nav-pad-top` 已删除；`margin-top` 也不要加（等价于把
          留白又放回容器外，但会让"菜单起始线"与"品牌区下沿"变成两个可漂移的数）。
        · ⛔ **左右 12px 一个字没动**：它撑着「竖向基线 62」，动了四级文字会错位。 */
  padding: 0 12px var(--space-2);
  scrollbar-width: thin;                       /* Firefox */
  scrollbar-color: transparent transparent;
}
.sidebar__nav:hover {
  scrollbar-color: var(--color-gray-300) transparent;
}
.sidebar__nav::-webkit-scrollbar { width: 6px; }
.sidebar__nav::-webkit-scrollbar-thumb { background: var(--color-gray-300); border-radius: 3px; }
.sidebar__nav:hover::-webkit-scrollbar-thumb { background: var(--color-gray-400); }
.sidebar__nav::-webkit-scrollbar-track { background: transparent; }

.nav-item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  /* ⭐⭐ 2026-10-06 紧凑档（乐哥：「每个菜单选项都有点太高了，但是做太矮的话和呼吸感又冲突」）：
     ▸ 高度 52 → **44**（行高降下来，一屏能放更多项）；
     ▸ 左右内边距 12 → **22**（**呼吸感由横向留白承担**，不是行高）。
     ⭐ 算式：侧栏容器 padding-left 12 + 本项 22 + 图标 18 + gap 12 = **62**
        = 品牌名/分组标题/二级子项文字的同一竖线（改任一项都要重算这一串）。
     ⚠️ 52 是 09-25 按"参考图更舒展"定的；本次参考图的菜单更密（36~42），
        且我们侧栏有 9 组 29 项 —— 52px 会让"展开一组就滚动"。 */
  /* ⭐⭐⭐ 2026-10-07 22:55 **44px → 42px**（乐哥：「菜单栏的文字，就是首页那个，
     按照右边悬浮 tab 栏的高度来吧，我红线有标」）
     ────────────────────────────────────────────────────────────────────────────────
     ▸ 实测参照物（真机，不是印象）：
        右侧 `.table-tabs--in-header` = **42px**（19:45 那批刚统一的档）
        页头 `.page-actions .btn`     = **42px**（同一批）
        侧栏一级项原来              = **44px** ⇒ 差 **2px**
     ⇒ 改 42 ⇒ 一级项与 tab 栏、主按钮**三者完全一致**。
     ⚠️ 为什么不是反向把 tab 栏改成 44：那个高度 19:45 刚统一过
        （搜索框 / 月份框 / 按钮四个控件全按 42 做的），改它要连带三个控件一起动。
     ⚠️ 走**新令牌** `--nav-item-h` 而非复用 `--control-height-row`：
        后者语义是「页头筛选行控件」，混用会让侧栏项高被页头控件的调整带走。
     ⚠️ 竖向基线 62（容器12 + 项22 + 图标16 + gap12）是**横向**的，不受本条影响。 */
  height: var(--nav-item-h);
  padding: 0 22px;
  margin-bottom: 0;
  border-radius: var(--radius-md);
  color: var(--color-fg-2);
  /* ⭐⭐⭐ 同批 22:55 字号 14 → **15px**（乐哥：「依次菜单文字字号大一点，现在太小了」
     22:55 拍板走「**整体变大**」：分组 13→14、一级 14→**15**、二级 13→14）
     ⚠️ 为什么一级从 `var(--font-size-base)` 改成字面量 15：
        令牌阶梯 12/14/16 里**没有 15**，而 `--font-size-lg` 是 16（用于卡片标题，
        侧栏菜单用 16 会太重、且它还被 `.brand-name__zh` 等占用）。
        ⛔ 禁止为此新造 `--font-size-15`：design-tokens 注释明写
           「字号阶梯对齐 TDesign，禁止再降到 13px」—— 那是**正文**下限，
           但新造一档会让 15px 在两个含义间漂移（同 13px 那次一样的坑）。
        ⇒ 行内字面量 + 本注释说明，判据按**值**锁。 */
  font-size: 15px;
  font-weight: var(--font-weight-medium);
  cursor: pointer;
  white-space: nowrap;
  transition: background-color var(--motion-fast) var(--motion-ease-standard),
    color var(--motion-fast) var(--motion-ease-standard);
}

.nav-item:hover {
  background: var(--color-surface-2);
  color: var(--color-fg);
}

/* 激活态：浅主色底 + 主色文字（克制，不抢焦点）
   ⭐ 2026-09-25 Gashora 像素级试妆：参考图的当前项是「浅灰圆角背景 + 黑字 + 右侧紫色竖条」。
   侧栏是白底，故当前项用**界面底色**（#F8F8F8，比白稍灰）才能显出；近黑字 + 右侧品牌色竖条。 */
/* ⭐⭐ 2026-10-06 选中态（首页 / 我的提成这类"一级平铺项"）：整行浅蓝胶囊 + 主色文字。
   同 `.nav-item--sub.is-active` 的理由（侧栏无底后必须换色，否则看不见）。 */
/* ⭐⭐⭐ 2026-10-07 15:20 选中态：**白卡浮起**（乐哥选「白卡浮起（同飞书，推荐）」）
   ────────────────────────────────────────────────────────────────────────────────
   ▸ 改前 = `--color-accent-bg`（淡蓝底）+ 主色文字（10-06 那版）。
   ▸ 改后 = **白底 + 轻阴影 + 近黑字**，与飞书一致。
      为什么现在才换：底色从中性灰改成**清新蓝**（#E3EEFB）之后，
      淡蓝选中底与蓝底色**同色系、贴在一起** ⇒ 选中项的"胶囊"读不出来
      （实测：那会儿选中块几乎融进侧栏）。白卡是唯一能与蓝底拉开层次的选择。
   ⚠️ 三处必须一起改（否则"选中"在三个位置表现不一致）：
      ① 本条（一级平铺项，如"首页"）
      ② `.nav-item--sub.is-active`（二级项，在下面）
      ③ 分组标题 `is-active` 那态（`.nav-group.is-active .nav-group-label`）
   ⚠️ 阴影刻意很淡（.09）：侧栏底色本身已是浅色，白卡再重影会显脏。
   ⚠️ `::after` 那条右侧竖条继续保持 `display:none`（10-06 已删，见下）。 */
.nav-item.is-active {
  background: var(--color-surface);
  color: var(--color-fg);
  /* ⭐⭐⭐ 同上（2026-10-08 15:1x）：选中态由"浮起"改**平卡** + 字重 600 → 500。
     依据：真机量飞书选中项**没有投影**，且**不加粗** ——
       墨密度实测：选中「消息」0.359，未选中「云文档/日历/任务/邮箱」0.273/0.305/0.301/0.406
       ⇒ 选中那个**落在未选中区间之内**，说明飞书不靠加粗表达选中（靠"深色字 + 近白平卡"）。
     ⚠️ 一级（15/500）与二级（14/400）的**层级仍靠字号+基准字重表达**，不受本条影响。 */
  font-weight: var(--font-weight-medium);
  box-shadow: none;
  position: relative;
}
/* ⚠️ 2026-10-06：**右侧蓝色竖条已删** —— 参考图的选中项是"整行胶囊"，没有右侧竖条；
   有胶囊之后再挂竖条会变成两个选中信号（本项目纪律：同类状态只用一种表达）。 */
.nav-item.is-active::after {
  display: none;
}

.nav-item .nav-icon {
  flex-shrink: 0;
  display: inline-flex;
}

.nav-item .nav-text {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ⭐ 「返回门户网站」入口（2026-09-21 新增）—— 侧栏底部，达播与财务都有。
   ⚠️ 为什么单独一个类、不复用 `.nav-item`：
      它**不是本应用的页面**，而是"离开本应用"（门户是真实路径 ⇒ 整页跳转）。
      混进 `.nav-item` 会被侧栏那套 `[data-nav]` 绑定挂上 `navigate()` ——
      而 navigate 只改 hash ⇒ 变成 `#/hub`，**点了没反应**（本项目已明确否掉那个形态）。
   ⚠️ 盒子模型与 `.nav-item` **逐项一致**（gap 12 / 高 40 / 左右内边距 12 / 圆角 8），
      只有**颜色更轻**：它是次要操作，不该抢菜单的注意力。
   ⚠️⚠️ 颜色两次调整的账（2026-09-21 乐哥：「当前文字颜色好暗」）：
      原用 `--color-meta`(#8595A4) —— 在白底上对比度只有 **3.07:1**，
      **低于 WCAG AA 正文要求的 4.5:1** ⇒ 发灰、"糊"。
      现用 `--color-muted`(#5D6C7B) = **5.32:1**，达标。
      ⇒ 判"文字是不是太暗/太灰"要**算对比度**，不要凭观感 ——
        3.07 与 5.32 在截图上看差别不大，但一个是"没达标"、一个是"达标"。
   ⚠️ `text-decoration: none` 不能删：它是 `<a>`，默认带下划线。 */
.sidebar-portal {
  display: flex;
  align-items: center;
  gap: var(--space-3);                     /* = .nav-item 同款，供左侧图标与文字之间用 */
  height: 40px;
  padding: 0 var(--space-3);
  margin-bottom: 2px;
  border-radius: var(--radius-md);
  color: var(--color-muted);
  font-size: var(--font-size-sm);
  text-decoration: none;
  white-space: nowrap;
  transition: background-color var(--motion-fast) var(--motion-ease-standard),
              color var(--motion-fast) var(--motion-ease-standard);
}
/* 左侧图标（2026-09-21 第三次改加回）——
   ⚠️ 尺寸必须是 **16px**：18px（= 侧栏一级菜单）在本行会**与下方「我的」的 32px 头像
      在体量上打架**（这是上一次被否的真正原因）。16px 的中心正好与头像中心同轴（都在 x=28），
      既对齐又不抢戏。
   ⚠️ 颜色跟随文字（`currentColor`），hover 时随整行一起变深 —— 不要单独给图标写颜色。 */
.sidebar-portal__icon {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  color: var(--color-meta);   /* 比文字再轻一档：图标是辅助，文字才是要读的 */
}
.sidebar-portal:hover {
  background: var(--color-surface-2);      /* = .nav-item:hover 同款令牌，不新造颜色 */
  color: var(--color-fg);
}
.sidebar-portal:hover .sidebar-portal__icon { color: var(--color-muted); }
/* ⚠️ 焦点环：显式写一条，**不依赖**全局那条
   `:where(button,[role=button],[tabindex]):focus-visible` ——
   它是 `<a href>`（天然可聚焦），不在那条选择器的覆盖范围里。
   用了全站同一套颜色与粗细，只是偏移向内收（与侧栏其他通栏元素一致）。 */
.sidebar-portal:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: -2px;
}

/* ⚠️⚠️ 文字**不参与收缩、也不裁剪** —— 这条是本轮 UI 问题的根治点，别改回去。
   ──────────────────────────────────────────────────────────────────────────
   症状（乐哥截图）：文字显示成「返…」（4~6 个字被截掉），且线上图标与文字紧贴。
   实测根因（脚本量出来的数据）：
     · 文字「返回门户网站」的**自然宽 = 84px**（6 个全角字 × 14px）
     · 但它的盒子在某些渲染时机只被给到 **~77px** ⇒ `scrollWidth(84) > clientWidth(77)`
       ⇒ `text-overflow: ellipsis` 生效 ⇒ 截成「返…」。
       原因就是这里原来写的 `flex: 1 1 0%` **让文字跟着 flex 收缩**：
       本行总宽 223 − 左右 padding 24 − 尾部箭头 16 − ……，留给文字的余量会被挤。
   ⇒ 改成 `flex: 0 0 auto`：文字按自己的自然宽（84px）占位，**永远放得下** ——
      本行可用宽约 199px，装 84px 文字 + 16px 箭头绰绰有余。
      同时 `overflow: visible` 兜底：万一将来标签改长，宁可让它溢出（容易发现），
      也不要静默变成「…」（本轮就是"静默截断"骗过了所有只看 DOM 数值的检查）。
   ⚠️ 这也解释了为什么"只比 md5 / 只 grep 文件内容"发现不了问题：
      样式文件是对的、DOM 也是对的，只是**渲染被裁了**。
      接口分界线：这类问题必须**看渲染结果**（截图取样）才发现。 */
.sidebar-portal .nav-text {
  flex: 0 0 auto;
  overflow: visible;
  text-overflow: clip;
  white-space: nowrap;
}

/* 尾部箭头（`›`）推到最右 —— 与正下方「我的」那行的尾部箭头同构，
   表达"去往下一层 / 离开这里"，正是乐哥提的形态（「放个 > 之类」）。 */
.sidebar-portal__arrow {
  margin-left: auto;
  display: inline-flex;
  color: var(--color-muted);
}

/* ⭐⭐ 「切换系统」下拉（2026-09-30）—— 乐哥：「点左下角，能快速切换其他网站吗」
   ──────────────────────────────────────────────────────────────────────────
   与正下方「我的」**同构**：都是"侧栏底部、点开**向上弹**"的小面板
   （底部区域没有向下弹的空间，向下弹会出屏幕）。
   ⚠️ 触发器由 `<a>` 改成了 `<button>`：它的行为已经不是"跳走"而是"展开菜单"，
      元素语义必须跟行为一致（`<a href>` 会让中键/右键给出一个误导的地址）。
      **菜单项才是真实 `<a href>`**（跨板块是整页跳转）—— 见 app.js 的说明。
   ⚠️ 为什么不复用小 `.profile-menu` 那套类名：两个面板是**并列**关系
      （互斥显示、各自的关闭判据不同），共用类名会让"改一个波及两个"；
      但**度量与配色刻意逐项对齐**（同 bottom 偏移、同圆角、同阴影、同行高）。 */
.sysswitch {
  position: relative;   /* 菜单的定位基准（=「我的」那边的 .profile-wrap 同款） */
}

/* ⚠️ 触发器换成 button 之后必须清掉浏览器默认样式，否则是"灰底 + 居中文字 + 内边框"，
   与相邻的侧栏菜单行完全不是一套（这是这类替换最容易漏的一步）。
   只重置 button 特有的那几项；**尺寸/配色仍由 `.sidebar-portal` 给**（不要去改写它）。 */
.sysswitch .sidebar-portal {
  width: 100%;
  border: 0;
  background: none;
  font-family: inherit;   /* button **不继承** font-family（默认是系统 UI 字体），必须显式继承 */
  cursor: pointer;
  text-align: left;
}

/* 菜单容器：向上弹、左对齐铺满侧栏 —— 与 `.sidebar__footer .profile-menu` 同值
   （改这一处时想起那边也有一份，别只改一边）。 */
.sysswitch__menu {
  position: absolute;
  top: auto;
  bottom: calc(100% + 8px);
  left: 0;
  right: auto;
  width: 100%;
  min-width: 208px;
  padding: var(--space-2);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
  z-index: var(--z-dropdown);
  /* ⚠️ 入场动效用 §9.9 定的**小浮层基线**（`panel-in` + `--motion-base` + sheet 曲线）；
     收尾用 `backwards` 而不是 `both`（§8c：`both` 会残留 transform ⇒ 给容器留层叠上下文
      ⇒ 内部元素被外层卡片盖住，本项目全站踩过 6 个页面）。 */
  animation: panel-in var(--motion-base) var(--motion-ease-sheet) backwards;
}

/* 菜单项：度量与 `.profile-menu__item` **逐项一致**（同内边距 / 同圆角 / 同字号），
   差别只有一个 —— 尾部多一个 `›`（表达"去另一个站点"），因为它是链接不是动作。 */
.sysswitch__item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  font-size: var(--font-size-sm);
  color: var(--color-fg);
  text-decoration: none;        /* 它是 `<a>`，默认带下划线 */
  transition: background-color var(--motion-fast) var(--motion-ease-standard);
}
.sysswitch__item:hover { background: var(--color-surface-2); }
/* ⚠️ 焦点环要**显式**写一条：全局那条只覆盖 `button / [role=button] / [tabindex]`，
   而本元素是 `<a href>`（天然可聚焦、但不在那条选择器范围内）。
   用了全站同一套颜色与粗细，偏移向内收（与 `.sidebar-portal` 及侧栏其他通栏元素一致）。 */
.sysswitch__item:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: -2px;
}
/* ⚠️ 文字**不参与收缩、也不裁剪** —— 与 `.sidebar-portal .nav-text` 同一条纪律：
   宁可将来溢出（一眼能看出来），也不要静默截成「…」（本项目为此专门栽过一轮）。 */
.sysswitch__item .nav-text {
  flex: 0 0 auto;
  overflow: visible;
  text-overflow: clip;
  white-space: nowrap;
}
.sysswitch__arrow {
  margin-left: auto;
  display: inline-flex;
  color: var(--color-muted);
}
/* 数据未到位 / 没有其他可进的板块时的一行说明（**不可点**）。
   给说明而不是空面板：空面板看起来就是"点了没反应"。 */
.sysswitch__hint {
  padding: var(--space-2) var(--space-3);
  font-size: var(--font-size-xs);
  color: var(--color-muted);
}

.sidebar__footer {
  padding: var(--space-3) var(--space-2);
  border-top: 1px solid var(--color-border-soft);
  flex-shrink: 0;
}

/* 折叠态 */
.app-shell.is-collapsed .sidebar {
  /* ⭐⭐ 2026-10-07「侧栏=底色」：折叠态直接用 `--sidebar-width-icon`，
     **不再减 `--shell-inset`** —— 侧栏已恢复贴边通高（底色本体），
     减 inset 会让折叠态左边凭空多出一条底色缝，底色就"漏"了。
     ⚠️ 成对：`.is-collapsed .app-main` 那条是 `icon + inset`（仍在 layout.css），
        两者相减 ⇒ 折叠态的中缝仍恰好 = `--shell-inset`。 */
  width: var(--sidebar-width-icon);
}

.app-shell.is-collapsed .sidebar .brand-name,
.app-shell.is-collapsed .sidebar .nav-text,
.app-shell.is-collapsed .sidebar .nav-group-label,
.app-shell.is-collapsed .sidebar .user-meta {
  display: none;
}

.app-shell.is-collapsed .sidebar .nav-item {
  justify-content: center;
  padding: 0;
}

.app-shell.is-collapsed .sidebar .brand-logo {
  margin: 0 auto;
}

/* 折叠态（图标模式）：隐藏二级引导线 ——
   ⚠️ 折叠时一级项（.nav-group-label，含图标）整体 display:none，而这条线是"从一级图标垂下来"的语义
      ⇒ 图标不在了，线还挂着就没有归属、只是一道莫名竖线（且折叠栏仅 64px 宽，很显眼）。 */
.app-shell.is-collapsed .sidebar .nav-subitems::before {
  display: none;
}

/* === 二级分组菜单：8 个一级分组，每组可折叠 === */
/* 参考微信小店后台：无分隔线、子项单列无图标、文字缩进对齐一级菜单文字起点 */
.nav-group {
  /* ⭐ 2026-10-06：6 → **12px**（行高压到 40~44 之后，靠**分组空档**把"分组"这一层重新撑出来
     —— 这是"降行高但不丢呼吸感"的另一半）。 */
  /* ⭐⭐⭐ 2026-10-07 22:55 乐哥：「我的、首页、还有下面其他菜单中间，
     就是所有一级菜单的**间距保持一致**」—— 真机实测只有**一处**不统一：
       · 一级项「首页」→ 下一个分组标题：**25px**（= 首页 44 − 2 + 分组前无间距… 实测值）
       · 分组标题 → 首个子项、同组子项之间：**0px**（都没 margin）
     ⚠️ 所以只需处理**分组之间的那条缝**：给它一个明确的间距值，
        与其它"项与项之间"的 0 形成**明确的层级语言**（分组之间留白、组内不留白），
        而不是把所有缝隙都拉成一样（那会抹掉"分组"这一层，正是 09-25 立起来的）。
     ⇒ 这里**保留 12px 不改**：它是"组间留白"，改 0 会让分组消失；
        乐哥看到的"不一致"其实是 25 vs 0，而 25 = 分组标题本身高 34 造成的视觉错觉。 */
  margin-bottom: 12px;
  padding-bottom: 0;
  position: relative;
}

/* 一级菜单（分组标题）：icon + 名称 + (可选红点) + chevron。
   ⭐⭐⭐ 2026-10-07 定稿：**一级加重（15px/w600/深墨）+ 去掉二级引导线**（乐哥拍板 B 档）
   ────────────────────────────────────────────────────────────────────────
   ▸ 历史链（**别再倒回去**，两次都踩过）：
     · 2026-09-25：治"一级 : 二级 层级倒挂"，定了「一级 : 二级 等高（46/46）」。
     · 2026-10-06：仿参考图改成「**章节名**体量」——一级 46→30 高、14→12.5 字号、灰字
       （当时那批参考图的分组标题都是**纯文字小灰字**）。
     · 2026-10-07：**推翻了上一条**。实测该状态下**一级 12.5px < 二级 14px**
       ⇒ 层级倒挂反而更严重；乐哥反馈「侧栏的风格和当前 ui 不搭」。
       现改回「一级更重」：15px / w600 / 深墨（见下方 .nav-group-label）。
     · ⛔⛔ **2026-10-07 20:05 乐哥又拍板推翻了 15/600** ⇒ **13/500**。
       同一天两次改同一处，理由完全不同（见 .nav-group-label 里 20:05 那段）：
         09:00 治的是「二级 14 > 一级 12.5」；20:05 治的是「**分组标题比它管的项更重**」。
       ⇒ **本段历史里最有效的一条结论是「不要只看字号，要看整条链的层级关系」**：
         三档必须互不相同且方向一致，任何一档单独变动都会造成倒挂。
         ⚠️ 别照着上面「15px / w600」那行改回去 —— 它已被 20:05 作废。
   ⛔ **那条"章节名"做法不适用于我们**：它成立的前提是**分组只有文字、必须弱于内容项**。
     我们的分组带 **16px 图标 + 7 色业务域**（09-25 乐哥选定"靠颜色找菜单"）
     —— 压到 12.5px 灰字会让菜单入口比它下面的内容项更弱，读成"注释"，用户找不到入口。
   ⭐ 竖向基线 **68**（容器 padding-left 12 + 项 padding-left 22 + 图标 **22** + gap 12）
     = 品牌名 / 分组标题 / 一级项文字的**同一竖线** ✓
     ⚠️ 改内边距 / 图标尺寸时**整串一起算**，否则四级文字错位。
     ⚠️ 二级子项**不在这条算式里**（无图标）：它靠 `margin-left 35 + padding-left 21` 对齐到 68
        —— 见 `.nav-item--sub` 的注释。2026-10-10 之前是 `padding-left: 50` ⇒ 只到 62，**倒挂 6px**。
     📌 本文件多处旧注释仍写"基线 62 / 图标 16"（都是 10-08 图标 16→22 之前的），
        以本条 + DESIGN-SPEC「侧栏竖向基线」为**现行值**。
   ⚠️ 图标保留 **16px**（不能更小 —— 实测压到 14 会出现"一级图标比二级还小"的层级倒挂）。
   ⚠️ 呼吸感靠**组间距**（.nav-group margin-bottom 12）与横向留白，不靠行高。 */
.nav-group-label {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  /* ⭐⭐⭐ 2026-10-07（乐哥：「侧栏的风格，和当前 ui 不搭…包括一级菜单二级菜单字号」）
     ────────────────────────────────────────────────────────────────────────
     ▸ **根因（真机实测，不是审美之争）**：改前一级 12.5px / w500 / 灰，二级 **14px** / w400 / 黑
       ⇒ **一级比二级还小** = 层级倒挂。这是"不搭"的直接来源。
     ▸ 改法：一级提到 **15px / w600 / 深墨**，二级保持 14px 但**降到中灰**
       （14 灰 + 15 深墨 = 字号与颜色两个维度都在说"一级更重"）。
       乐哥 2026-10-07 选定 **B 档**（一级加重 + 去掉引导线）。
     ▸ 为什么**不再**做 9-26 那个"章节名"体量（小号灰字）：
       那套的前提是**分组只有文字、字号必须小于内容项**（当时照搬的是纯文字分组的产品图）。
       我们的分组带 **16px 图标 + 7 色业务域**（09-25 乐哥要"靠颜色找菜单"），
       一旦压到 12.5px灰字，**它比下面的内容项更弱** ⇒ 读成"注释"，用户找不到入口。
     ⚠️ 高度 30 → **34**：字号 12.5→15 需要 2px 补上（否则字被裁/上下贴边）。
       ⚠️ **竖向基线 62 不变**：容器 padding-left 12 + 本行 padding-left 22
          + 图标 16 + gap 12 = 62 ⇒ 品牌名 / 分组标题 / 二级子项仍落在**同一条竖线** ✓
          （本行改的是 height 与字号，没动 padding / 图标 / gap ⇒ 基线不受影响）
     ⚠️ 图标保持 **16px** 不变（不能再小 —— 会与二级图标倒挂，09-25 已踩过）。 */
  /* ⭐⭐⭐ 2026-10-07 20:05 乐哥拍板上侧栏这版（「侧栏这版先上」）：
     15px / w600 → **13px / w500**（一级 `.nav-item` 保持 14px / w500 不动）
     ────────────────────────────────────────────────────────────────────────
     ▸ **本条推翻了同一天 09:00 定的「一级加重 15/600」**（不是改错，是方向变了）：
       09:00 那一版的理由是「二级 14px > 一级 12.5px = 层级倒挂」⇒ 把一级提到 15。
       但 20:00 真机全量复核后判定**真正的倒挂在另一处**：
         · 分组标题 15/600 **比它管的二级项 14/400 更粗更大** ⇒ 喧宾夺主，
           41 个一级项 + 40 个二级项的**视觉重量被 7 个分组标题压住**；
         · 二级项与一级项**同为 14px**，只靠 500/400 字重区分，
           而 PingFang SC 只有 400/500/600 三档（见 design-tokens 注释），
           500 vs 400 在中文下**差别很弱**（乐哥 09:00 选它就是权宜之计）。
       ⇒ 现在改成**靠字号分层级**（13 / 14 / 13），不靠字重 —— 14→13 是明确的一档差。
     ▸ 为什么分组标题 13 而不是 14：它是**分类标签**不是页面入口，
       与具体页面项同号会读成平级；13 让它退到"标签"位。
       ⚠️ 与 09:00 那条注释里「分组带 16px 图标 + 7 色业务域，字号一小就找不到入口」的
         顾虑如何共存：**图标 16px 保持不变**（未降到 14），
         分组入口靠**图标 + 域色 + 组间距 12px** 识别，不靠字号 ——
         真机实测 13px 文字 + 16px 图标仍清晰可辨（见 verify-sidebar-type-scale.js）。
     ⚠️⛔ **本条推翻 ui-1010-three-fixes.test.js 里「必须 15px / 必须大于二级」那条判据**
        —— 那条是 09:00 B 档的产物。已同步改判据（改为锁「层级关系」而非锁死数字）。
     ⚠️ 竖向基线 **62 不变**（容器 12 + 本行 padding 22 + 图标 16 + gap 12），
        高度 34 / padding / 图标 / gap 一个都没动 ⇒ 四级文字仍落在同一条竖线。
     ⚠️ 字号 15→13 写在**行内字面量**而非 `var(--font-size-*)`：
        13px 在令牌阶梯里**不存在**（xs 12 / sm 14 / base 14 / lg 16…），
        `--font-size-xs` 是 12px（用于表头/时间戳），语义不符。
        ⛔ 禁止为此新造 `--font-size-13`：design-tokens 注释明写
           「字号阶梯对齐 TDesign，禁止再降到 13px」—— 那是**正文**下限，
           侧栏分组标签属辅助文字，但新造一档会让「13px 在两个含义间漂移」。
        ⇒ 因此用行内值 + 本注释说明，判据按「值」锁而不是按「令牌」锁。 */
  /* ⭐⭐⭐ 同批 22:55 字号 13 → **14px**（与一级项 15 拉开 1 档；
     原来与二级项同为 13 ⇒ 标签与页面项读起来平级，乐哥 20:42 反馈过"没差异"） */
  height: 34px;
  padding: 0 22px;
  /* ⭐⭐⭐ 2026-10-08 00:2x 字号 14 → **15px**（乐哥：「一级菜单字号大于二级菜单」）
   * ────────────────────────────────────────────────────────────────────────────────
   * ⛔⛔ **上面那段注释已经叠了 5 轮改动（09:00 / 20:00 / 20:05 / 22:55）且互相矛盾**：
   *   · 09:00 说「一级提到 15/600」；
   *   · 20:00 说「分组标题 15/600 喧宾夺主 ⇒ 降到 13/500」；
   *   · 20:05 说「13 与二级同为 13 读成平级」；
   *   · 22:55 改成 14（结果 **14 == 二级 14**，等于没分层）。
   *   ⇒ 当前值 14 落在一个**哪个诉求都不满足**的中间态：
   *     既不是"最重的页面入口"（那是首页项 15），也没和二级拉开。
   *   ⚠️ **真机实测（00:1x）证实**：一级 14/15px 二级 14px ⇒「一级不大于二级」，
   *     这正是乐哥 23:54 说「我看都没解决」的**第② 条**（他原话"一级菜单字号大于二级"）。
   *
   * ▸ 为什么是 **15**（不是 14+1=15 的巧合，而是三个约束交汇）：
   *   ① 二级是 14px ⇒ 一级必须 **> 14**，令牌阶梯里下一个可用值就是 15
   *      （16 = --font-size-lg，是卡片标题档，用在这里会让分组标题压过页面标题）；
   *   ② 与个人区一级项「首页」的 **15px 对齐** ⇒ 一级项内部一致
   *      （此前是 首页15 / 分组标题14 —— 实测"一级字号 15/14 不统一"）；
   *   ③ 34px 行高装 15px 有充足余量（实测分组标题高 34，字号 15 后仍居中不贴边）。
   * ⚠️ **字重保持 medium(500) 不动**：PingFang 只有 400/500/600 三档，
   *    分组标题已是 500 ⇒ 与二级的 400 拉开一档；再上 600 就是 20:00 那轮
   *    判定过的「喧宾夺主」。**层级这次靠字号 + 图标 18px 两处表达，不动字重。**
   * ⚠️ 15px 在令牌阶梯里不存在（xs12/sm14/base14/lg16）⇒ 沿用**行内字面量**，
   *    ⛔ 不新造 `--font-size-15`：design-tokens 明写「字号阶梯对齐 TDesign」，
   *    新造一档会让 15px 在两个含义间漂移（同 13px 的教训）。
   *    ⇒ 因此判据按**层级关系**锁（`一级 > 二级`），不锁死这个数字。
   * ⚠️ 竖向基线 **62 不变**：容器 12 + 本行 padding 22 + 图标 18 + gap 12 = 64 ⚠️
   *    ⚠️⚠️ **本行图标从 16 改成 18（见 .nav-group-icon 那处）⇒ 基线算式由 62 变 64**：
   *       12 + 22 + 18 + 12 = **64**，而二级子项（无图标）仍是 12+22+12 = **46**。
   *    ⇒ 分组标题与品牌名的图标中轴会右移 2px。**这是刻意的**：
   *       一级图标 18 是乐哥要的大一号；文字起点由 **padding 22** 决定、未动
   *       ⇒ **四级文字仍落在同一条竖线**（基线由文字起点决定，不由图标宽度决定）。
   *       ⚠️ 别"顺手把 padding 22 改成 20 来凑62"—— 那会把**文字**推歪，是错的方向。 */
  font-size: 15px;
  font-weight: var(--font-weight-medium);
  color: var(--color-fg);
  letter-spacing: 0;
  background: transparent;
  border: 0;
  border-radius: var(--radius-md);
  text-align: left;
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  transition: color var(--motion-fast) var(--motion-ease-standard),
    background-color var(--motion-fast) var(--motion-ease-standard);
}

.nav-group-label:hover {
  color: var(--color-fg);
  background: var(--color-surface-2);
}

/* ⭐⭐ 静态分组标题（**不做折叠**的板块：系统配置 / 工作台 / 人事行政）
   ────────────────────────────────────────────────────────────────────────────────
   ⚠️⚠️ 这条类是 2026-10-11 才补上定义的 —— 在此之前 **`--static` 全站没有定义**，
      而 `admin.js` / `workbench.js` 早就在用（HR 本次也接上）⇒ 三处的分组标题都继承了
      上面那条 `:hover`（`cursor: pointer` + 悬停亮底色）⇒ **读作"能点"，点下去没反应**。
      （同族坑项目里记过：`.nav-group-label` 的 hover/cursor 是**折叠按钮**的语义。）
   ⭐ 判据：**"静态标题"与"折叠按钮"必须在视觉上可分** ——
      能点的给 hover 反馈，不能点的**一点反馈都不要给**（否则用户会反复点）。
   ⚠️ 只影响带 `--static` 的元素（可折叠的板块不受影响，它们的标题是 `<button>`）。 */
.nav-group-label--static {
  cursor: default;
}
.nav-group-label--static:hover {
  background: transparent;
}

.nav-group-icon {
  display: inline-flex;
  flex-shrink: 0;
  color: var(--color-fg-2);
  transition: color var(--motion-fast) var(--motion-ease-standard);
}
/* ⭐⭐⭐ 2026-10-08 00:1x 图标尺寸 16 → **18**（乐哥：「一级菜单图标按飞书那种风格做」）
 * ⚠️⚠️ 2026-10-08 23:4x **这条已被 18 → 22 取代**（见下方 `.nav-group-icon svg` 那条）。
 *    下面整段"为什么 18 是对的"里的 ①②③ 三条理由仍然有效（它们论证的是
 *    "要对齐调用值 / 别比二级弱 / 层级靠图标+字号+字重"，与具体数值无关），
 *    但**结论数值从 18 改成 22**。保留原段是为了不改写历史（谁哪天想回到 18，先读下面那条）。
 * ────────────────────────────────────────────────────────────────────────────────
 * ⛔⛔ **先说清"原注释的理由为什么过期了"**（这是本条最容易被回改的地方）：
 *   上面那段 10-06 注释写的理由是「分组标题压到 **30px** 高之后，18px 图标会顶满行高、
 *   看着挤」⇒ 才压到 16。
 *   ⛔ **但分组标题早已回到 34px**（今天 22:55 那批的 34，与基线 62 一起算过），
 *     行高从 30 变成 34 ⇒ **「顶满行高」这个前提早就不成立了**，
 *     而16px 的值却没跟着回⇒ **注释的依据失效、值被留在旧档**。
 * ⚠️ 这与今天挖到的 `.nav-item--home` 盖掉基类是**同一类病**：
 *   「值改了、注释也更新了，但配套的另一个值忘了一起回来」。
 *
 * ▸ 为什么 18 是对的（三条，都不是"看着顺眼"）：
 *   ① **与调用值对齐**：app.js:1494 就是 `I.icon(g.icon ||'folder', 18)`，
 *      而这条 CSS 把它压成 16 ⇒ **JS 说的和 CSS 做的不是一回事**，
 *      后来者改 JS 尺寸会没效果（同一个坑今天已栽过一次）。
 *   ② **与个人区一级项同尺寸**：「首页」的 `.nav-icon` 走 `I.icon(..., 18)`
 *      且**无 CSS 覆盖** ⇒ 同为一级项却 18 vs 16，本身就不齐。
 *   ③ **飞书侧栏的一级图标就是比二级大**：18 : 16 ≈ 1.13，
 *      而层级靠"图标 + 字号 + 字重"三处一起表达才稳。
 * ⚠️ 二级子项是 `display:none`（纯文字无图标，刻意设计，见 `.nav-item--sub .nav-icon`），
 *    ⇒ 这里**不需要**再维持"与二级同尺寸"那条旧约束。
 * ⛔ 别再往下压到 14：会出现"一级图标比二级文字还弱"的层级倒挂（同注释原话，仍然有效）。 */
/* ⭐⭐⭐ 2026-10-08 23:4x 18 → **22**（乐哥：「图标做到22」），同时换**双色实心**
 * ────────────────────────────────────────────────────────────────────────────────
 * ▸ 与 JS 对齐：app.js 的 `NAV_ICON_SIZE = 22`（navIconSolid 传的就是 22）。
 *   ⛔ 这条 CSS 是**覆盖**作用 ⇒ 改 JS 不改这里 = 白改（18:16 那轮踩过的同一个坑）。
 * ▸ 为什么 22 站得住：一级项行高 34/40，22 的图标仍有上下呼吸（不是"顶满行高"）；
 *   与二级纯文字的 14px 拉开层级；飞书侧栏一级图标同样明显大于行内文字。
 * ▸ 22 只影响**一级**（分组标题 + 个人区平铺项，见下面那条 .nav-item--home 规则）；
 *   二级子项本来就没有图标（display:none，纯文字设计），不受影响。
 * ⛔ 别再往下压：14 会出现"一级图标比二级文字还弱"的层级倒挂。 */
.sidebar .nav-group-icon svg { width: 22px; height: 22px; }
/* 个人区平铺项（首页 / 我的报销 / 我的提成 / 审批中心）同尺寸 ——
   它们之前靠 JS 传 18 且**无 CSS 覆盖**，现在 JS 传 22，这条是为了
   ① 与分组图标严格同尺寸 ② 万一有人只改 JS 忘了 CSS 也能从这里看出来。 */
.sidebar .nav-item--home .nav-icon svg { width: 22px; height: 22px; }
/* 收起箭头 12 → **14**：它与图标同属一行，18:14 比18:12 更均衡
   （12 在 18 的图标旁边显得过小、像附属噪点）。⚠️ 只动这一处，别顺手动别的。 */
.sidebar .nav-group-chevron svg { width: 14px; height: 14px; }

/* ══════════════════════════════════════════════════════════════════════════
   ⭐⭐ 2026-09-25 乐哥选定的「业务域分色」（侧栏一级菜单图标）
   ──────────────────────────────────────────────────────────────────────────
   目的：靠颜色找菜单 —— 6 个业务域各一色，颜色本身在说"这是哪一类活"。
   色值全部走令牌（`--nav-domain-*`，见 design-tokens.css，那边有完整设计依据）。
   ⚠️ domain 值由 **app.js 的 NAV_DOMAIN 映射**集中产出（`data-domain`）——
      这里**故意不按中文组名写选择器**：组名会改（2026-09-24 就把「经营看板」组
      改成了「经营分析」），按中文名写会在改名那天**静默失效**（没报错、就是没色了）。
   ⚠️ **只给图标上色**：文字（.nav-group-text）仍走正文灰 ⇒ 彩色面积小、不抢内容。
      这是"协调"的关键 —— 11 个彩色图标 + 11 行彩色文字会很吵，图标单独上色才克制。
   ⚠️ 未归类分组无 data-domain ⇒ 不命中任何规则 ⇒ 保持默认灰（安全兜底）。
   ══════════════════════════════════════════════════════════════════════════ */
.sidebar .nav-group[data-domain="analysis"] .nav-group-icon { color: var(--nav-domain-analysis); }
.sidebar .nav-group[data-domain="ops"]      .nav-group-icon { color: var(--nav-domain-ops); }
.sidebar .nav-group[data-domain="goods"]    .nav-group-icon { color: var(--nav-domain-goods); }
.sidebar .nav-group[data-domain="finance"]  .nav-group-icon { color: var(--nav-domain-finance); }
.sidebar .nav-group[data-domain="risk"]     .nav-group-icon { color: var(--nav-domain-risk); }
.sidebar .nav-group[data-domain="tool"]     .nav-group-icon { color: var(--nav-domain-tool); }
.sidebar .nav-group[data-domain="personal"] .nav-group-icon { color: var(--nav-domain-personal); }
/* 「首页」（个人区首项，不属于任何业务域）：用品牌主色的浅档 —— 与域色同一彩度水位 */
.sidebar .nav-item--home .nav-icon { color: var(--color-primary-400); }

/* 当前所在分组：图标与文字用主色、加粗
   ⚠️ 选择器带 `[data-domain]` 把特异性提到 0,5,0 —— 必须**盖过**上面那组域色规则
      （否则"我在哪"这个最重要的状态会被业务域色吃掉，失去导航定位）。 */
.nav-group.has-active[data-domain] .nav-group-icon,
.nav-group.has-active .nav-group-icon,
.nav-group.has-active .nav-group-label {
  color: var(--color-accent-text);
}
.nav-group.has-active .nav-group-label {
  font-weight: var(--font-weight-semibold);
}

.nav-group-text {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* 通知红点（参考微信小店一级菜单右侧的小圆点；dot:true 启用） */
.nav-group-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--color-danger);
  flex-shrink: 0;
  margin-right: 4px;
  align-self: center;
}

.nav-group-chevron {
  display: inline-flex;
  color: var(--color-muted);
  /* ⭐ 2026-10-17：110ms 太快，箭头"啪"地翻过去、与 360ms 的高度形变脱节
     ⇒ 换成同一档（`--nav-fold-dur-close`，290ms）与同一条曲线，箭头和内容同一个节奏。 */
  transition: transform var(--nav-fold-dur-close) var(--nav-fold-ease);
}

/* 统一用 chevronDown，折叠时旋转 -90° 指向右侧 */
.nav-group.is-collapsed .nav-group-chevron {
  transform: rotate(-90deg);
}

.nav-subitems {
  /* ⭐⭐ 2026-09-26 动效修复：**这一条原来等于没有动画**（逐帧采样实测）。
     ────────────────────────────────────────────────────────────────────────
     ⛔ 改前：`max-height: 2000px` ⇄ `0`。而这一组的真实内容只有 114px ⇒
        max-height 在**第 1 帧就越过 114** 了，渲染高度序列是
          `0 → 114 → 114 → 114 …`
        也就是说菜单是**"啪"地瞬开**，那 240ms 的 transition 全部空转在一个
        看不见的属性上（乐哥要的"不要傻快傻快"——这就是最典型的傻快，
        而且比"快"更糟：它压根没动）。
     ✅ 改后：`display: grid` + `grid-template-rows: 0fr → 1fr`。
        跨度 = 真实高度，渲染序列
          `0 → 33 → 60 → 79 → 92 → 100 → 106 → 109 → 112 → 114`
        全程可见，且**不需要写死任何像素**（加一个菜单项不用改 CSS）。
     ⚠️ 代价：需要一层内层容器承载 `overflow: hidden`（见 JS 里的
        `.nav-subitems__inner`）。这是 grid 折叠法的固有要求 —— `0fr` 要有一个
        grid item 才能被压到 0 高。
     ⚠️ 为什么不用更省事的 `interpolate-size: allow-keywords`（`height: auto` 直接可动画）：
        实测 Chrome 153 支持、且零改动，但 **Safari 不支持** ⇒ 移动端会退化成
        "瞬开"（正是本次要修的问题）。grid 方案 Chrome/Safari/Firefox 全支持。
     ⚠️ 为什么不用 `:has()` 数子项个数去算 max-height：
        要写死"最多支持 N 项"，将来加一项就被裁掉一截，属自找的维护坑。 */
  display: grid;
  grid-template-rows: 0fr;
  /* ⚠️ 曲线用 in-out（两端缓）而不是进场的 ease：展开是**高度形变**，
      不是"元素飞进来"，两端缓才不会有"顶上去"的冲劲。 */
  /* ⭐ 2026-10-17 改 iOS 档（乐哥：「动画傻快傻快的，要 iOS 那种优美的感觉」）——
     原 155ms + 带 3.85% 过冲的曲线，用在"高度形变"上读作"顶一下再落回来"。
     现 360ms + iOS smooth 曲线（起手快、收尾缓、不过冲）；收起单独 290ms，收得利落些。
     ⚠️ 三个值都来自 design-tokens 的 `--nav-fold-*`（那边有完整依据）。 */
  transition: grid-template-rows var(--nav-fold-dur) var(--nav-fold-ease);
  /* ⭐ 2026-09-25（乐哥选定的方案）**二级引导线** ——
     在子项左侧画一条 1.5px 浅竖线，把"这一组属于上面那个一级项"画出来。
     ▸ 为什么用引导线而不是"浅色块容器"或"字号字重递减"：
       乐哥明确要**呼吸感**（不接受压缩、不接受加视觉重量）。
       引导线是三种做法里**唯一不增加视觉重量**的（1.5px、--color-border 浅灰），
       却能把从属关系说清楚；另两种要么偏重（色块）、要么让子项发灰（递减）。
     ▸ 定位 `left: 21px` = 一级项 padding-left(12) + 图标宽(18)/2 ⇒ **正对一级图标的中轴**，
       视觉上像"从那个图标垂下来的线"。
     ▸ 上下各留 2px：线不顶到相邻项，保持呼吸感（与 .nav-subitems 的动画裁切也兼容）。 */
  position: relative;
}
/* 展开态 */
.nav-subitems:not(.is-hidden) {
  grid-template-rows: 1fr;
}
/* 收起态：比展开快一点（iOS 的收起普遍比展开利落） */
.nav-subitems.is-hidden {
  transition-duration: var(--nav-fold-dur-close);
}
/* ⚠️ 内层容器（JS 注入）：承载 overflow:hidden 的那个"被压缩的格子"。
   必须在它上面裁切 —— 裁在 `.nav-subitems` 上会把引导线 ::before 一起裁掉
   （它是绝对定位、相对 .nav-subitems 的，纵向超出会被切）。 */
.nav-subitems__inner {
  overflow: hidden;
  min-height: 0;   /* grid 项默认 min-height:auto 会拒绝被压到 0（关键，漏了就等于没动画） */
  /* ⭐ 2026-10-17：收起态把内容整体上提 8px、展开时落回 0 —— 配合 360ms 的缓出，
     读起来是"抽屉拉开、里面的东西跟着滑到位"（iOS 列表展开的观感）。
     ⚠️ **只在收起态写位移、展开态写 `none`**：这样首屏（本来就是展开态）不会播一次
        多余的滑入；收起时 inner 已被 grid 压到 0 高，位移不可见。
     ⚠️ transform 会创建包含块 —— 这里安全：子项 `.nav-item` 的右侧竖条是相对它自己的
        绝对定位；二级引导线挂在 `.nav-subitems`（不是 inner）上，不受影响。 */
  transform: translateY(-8px);
  transition: transform var(--nav-fold-dur) var(--nav-fold-ease);
}
.nav-subitems:not(.is-hidden) .nav-subitems__inner {
  transform: none;
}
/* ⛔⛔⛔ 2026-10-07（乐哥：「还有那个引导线（我觉得可以不要）」）—— **二级引导线已移除**
   ────────────────────────────────────────────────────────────────────────
   ▸ 为什么不留：它原本承担"这一组属于上面那个一级项"的从属关系，
     但 **2026-10-07 一级改成 15px/w600/深墨后，从属关系已经由「字号 + 字重 + 颜色」
     三个维度说清楚了** ⇒ 线成了**冗余的第二套语言**，且它挂在"一级图标中轴"上，
     一级变粗后那条线显得更细、更像"划痕"。
   ▸ 为什么这次能直接去掉（上次不能）：上一轮（09-25）一级被做成 12.5px 灰"章节名"，
     **比二级还弱** ⇒ 从属关系只能靠线撑；现在一级是最重的，从属靠体量就成立。
   ⚠️ **2026-10-10 乐哥又要求恢复**（「菜单栏是不是可以参考这个增加引导线」）——
      本次是**做法不同**才恢复的，不是推翻上面那条结论（上面否掉的是"线被卡吞掉"的旧做法）。
      新做法三条要点见下面 `.nav-subitems::before` 的注释。

   ▸ 保留整段历史（都是真踩过的坑，将来若要恢复"线"可以少走弯路）：
     · left: 42 的算式 = 容器 padding-left 12 + 分组标题 padding-left 22 + 图标宽 16/2。
       旧算式 21 是 46px 高、18px 图标、12px 内边距时代的值，已过时。
     · 2026-09-25 乐哥报障「二级菜单选中后，挡住引导线了」——
       成因不是线太浅，而是**被盖住**：选中项 `position:relative`（要放右侧蓝竖条）
       必然绘制在非定位的引导线（`::before`, z-index:auto）之上。
       当时两条修法：① 线提到 `z-index:1`；② 组内有选中项时用 `:has()` 把线加深为
       `--color-gray-400`（浅线压在选中项浅灰底上对比度仅 1.04:1，等于白修）。
     · 不用主色线的原因：视觉重量变大，且与选中项右侧品牌蓝竖条"双蓝打架"。
   ▸ 若将来真要恢复，**实现要点**：保留 `position:absolute` + `z-index:1`
     （否则会被选中项背景盖住），并保留 `:has()` 加深那条。 */

/* ⭐⭐⭐ 2026-10-10 **恢复二级引导线**（乐哥：「菜单栏是不是可以参考这个增加引导线」→「t1 可以」）
   ────────────────────────────────────────────────────────────────────────────
   ⚠️ 与 09-25 那版**做法完全不同**，这是它能成立的原因（09-25 的坑正是"线被卡吞掉"）：

   ▸ ① **线的位置** = 一级图标中轴
        `left: 33px` 是相对 `.nav-subitems`（其左缘在侧栏的 12）⇒ 线落在**侧栏 x=45**。
        算式：容器 padding-left 12 + 一级项 padding-left 22 + 图标宽 22 ÷ 2 = **45** ✓
        （09-25 注释里的 21 / 42 是「46px 高、图标 16~18px」时代的旧算式，**已过时，别照抄**。）
   ▸ ② **线在卡的外侧** ← 本次核心
        真机实测（改前）：二级卡与一级卡是**同一个盒子**（都 12 → 251.5），
        线无论画在 45 还是 56，都必然落在卡内部 ⇒ 乐哥「选中条超过引导线就很奇怪」。
        现让二级卡整体右移（见 `.nav-item--sub` 的 `margin-left`），卡左缘 53 > 线右沿 47
        ⇒ **两者不重叠、且留 6px 间隙**：线读作"整组的脊柱"，而不是"卡的边框"
          ⇒ 不需要 `z-index` 抢救、也不需要 `:has()` 加深（09-25 那两条硬压手法全部作废）。
        ⚠️ 间隙 2026-10-11 由 **0 → 6**：零间隙时线紧贴卡左缘，被读成"卡的左边框"
           （我们白卡对比强 —— 见 `.nav-item--sub` 那段的三条结论）。
        依据：飞书参考图逐像素实测 —— 线 x=32（2px 宽）/ 高亮卡 x=34 起 ⇒ 线在卡外紧贴 2px。
   ▸ ③ **颜色走新令牌** `--nav-guide-line`：不裸 hex，也不复用中性灰（理由见令牌处注释）。
   ▸ 上下各留 2px：不顶到相邻项，保持呼吸感（也与 `.nav-subitems` 的 grid 折叠裁切兼容）。
   ⭐ **线宽 1px**（改前是 2px）。乐哥 2026-10-11「侧边栏引导线太粗了，不协调」。
     ⚠️ 2px 的来源是照飞书参考图实测（x=32/33 两列同色 ⇒ 判它 2px），**但那不适用于我们**：
        飞书那条线是 `#B3B3B3` 压**白底**；我们是 `#A9B7CC` 压**蓝底**（对比 1.74:1 > 飞书的 1.62:1），
        且线上是缩放屏（`zoom 0.85`）⇒ **"更重的颜色 + 2px 宽度"叠加**，观感比飞书粗一档。
     ⭐ 1px 是这类"缩进标记线"的通行档（编辑器缩进线 / 树形控件导轨都是 1px）。
     ⛔ 不要再拿"飞书是 2px"当理由改回去 —— 底色与缩放都不同，不可直接照搬宽度。
     🖼 三档对比：`deliverables/linewidth-3up.png`（真实 1:1 + 放大 2.2×）。
   ▸ 折叠态已由 `.app-shell.is-collapsed .sidebar .nav-subitems::before { display:none }` 关掉，
     无需另写规则（那条是"从一级图标垂下来"的语义 —— 折叠时一级项整体隐藏）。 */
.nav-subitems::before {
  content: '';
  position: absolute;
  left: 33px;                /* = 侧栏 45 − 容器 12（一级图标中轴） */
  top: 2px;
  bottom: 2px;
  width: 1px;                /* 见上方说明：2px 是因照搬飞书、未计入底色与缩放差异 */
  background: var(--nav-guide-line);
  border-radius: 1px;
  pointer-events: none;
}

/* ⚠️ 2026-09-26 动效修复：这条规则的内容**已由上面的 grid 方案接管**
   （`.nav-subitems { grid-template-rows: 0fr }` 就是折叠态），
   原来这里写的 `max-height: 0` 是那份"没有动画"的旧实现的另一半。
   保留空规则会让人以为还有第二套机制在用 ⇒ **整条删除**（本项目的规矩：
   死规则要删，见技能 dabotai-ui-consistency-fix 铁律「死规则要删」）。
   ⚠️ 但 `.is-hidden` 这个**类名不能删** —— JS 靠它表达折叠状态，
      新规则 `.nav-subitems:not(.is-hidden)` 就是读它来展开的。 */

/* 首页项：与一级菜单同规格（高 34px ｜ medium ｜ 图标 18 ｜ 左右内边距 22），视觉一体
   ⚠️ 2026-10-08 15:3x 起 42 → 34（与二级项同高，见 design-tokens 的 --nav-item-h）。 */
.nav-item--home {
  gap: 12px;
  padding: 0 22px;
  /* ⭐⭐⭐ 2026-10-07 23:4x **修「一级字号改了不生效」的根因**（不是错觉，真有 bug）
   * ────────────────────────────────────────────────────────────────────────────
   * 症状：22:55 把 `.nav-item` 的 font-size 改成 15px，真机 computed **仍是 14px**。
   * 原因：**这里这条 `font-size: var(--font-size-base)`（=14px）把基类盖掉了** ——
   *        `.nav-item--home` 的特异度(0,2,0) > `.nav-item`(0,1,0)，且写在后面。
   *        ⇒ 「首页」这一项在一级菜单里占 1/N 的量，改基类对它无效 ⇒ 看起来"完全没生效"。
   * ⚠️ 这就是「改外观却没生效却查不出原因」的典型：**必须回真机读 computed，不能只扫 CSS 源**。
   * ⚠️ 同理，高度也要跟着基类（基类的 var(--nav-item-h) 它同样会继承；
   *    2026-10-08 15:3x 起该令牌 = **34**，与二级项同高），
   *    这里不重复；但**凡本类新增属性，都要确认基类那条不会被它盖掉**）。 */
  font-size: 15px;
  font-weight: var(--font-weight-medium);
  /* ⭐⭐⭐ 2026-10-11 乐哥「一起加」⇒ 一级平铺项（首页 / 我的提成）与二级项**对齐同一套选中几何**
     ────────────────────────────────────────────────────────────────────────────
     ▸ `margin-right: 22` ⇒ 卡右缘 251.5 → **229.6**，与二级卡右缘一致（两级的彩条才会落在同一竖线）。
     ▸ ⚠️ 本类**没有 chevron**（`.nav-group-chevron` 只挂在分组标题 `.nav-group-label` 上）
        ⇒ 收窄它**不会移动任何箭头**，没有副作用。
        （曾误判"会把 chevron 左移 22px"——那是分组标题的事，与 `.nav-item--home` 无关。）
     ▸ 竖彩条见下面 `.nav-item--home.is-active::after`。 */
  margin-right: 22px;
}

/* 一级平铺项的竖彩条：与二级项**共用同一条规则**（见下面分组选择器），此处只补定位上下文。
   ⚠️ 一级项卡片高度同为 34（`--nav-item-h`），所以条的 `top/bottom: 7px` 与二级完全一致。 */
.nav-item--home.is-active {
  position: relative;
}

/* 子项：单列、无图标、缩进对齐一级文字起点 */
.nav-item--sub {
  /* ⭐⭐ 2026-10-07（乐哥选 B 档）：一级加重后，二级**降一级**。
     ────────────────────────────────────────────────────────────────────────
     ▸ **色值核实过**：`--color-fg-2` = #3F4753（正文深灰），与一级 `--color-fg`(#1C1E21)
       只差 **11 阶** ⇒ 两层分不开（我第一版写的就是它，等于"改了等于没改"）。
       ⇒ 改用 **`--color-muted` #546579**（次级/副标题），与一级差 **31 阶**，肉眼可分。
       ⚠️ **改颜色前先核令牌实际值**，别凭变量名想象深浅（#3F4753 名字叫 fg-2 但不是灰）。
     ▸ 这样分层后「选中态变主色蓝」才跳得出来；此前二级接近黑、选中差别不明显。
     ⚠️ 字号（`--font-size-sm` = 14）、高度 40、左内边距 50 **都不动** ⇒
        **竖向基线 62 仍成立**（容器 12 + 50 = 62 = 品牌名 / 一级文字的同一竖线）。 */
  /* ⭐⭐⭐ 2026-10-07 20:05 乐哥拍板：14px → **13px**（与分组标题同档，见上面 .nav-group-label 注释）
     ⚠️ 字重 400 / 颜色 --color-muted / 高度 40 / 左内边距 50 **全不动**
        ⇒ 竖向基线 62 仍成立，一屏仍装得下 41 项。
     ⚠️ 原来是 `var(--font-size-sm)`（14px 表格正文档）—— 二级项不是表格正文，
        语义本来就对不上；13px 令牌不存在（同上），用行内值 + 注释说明。 */
  /* ⭐⭐⭐ 同批 22:55 字号 13 → **14px**（与分组标题同档；一级 15 仍最大）
     ⚠️ 字重 400 / 颜色 --color-muted / 高度 40 / 左内边距 50 **全不动**
        ⇒ 竖向基线 62 仍成立。 */
  /* ⭐⭐⭐ 2026-10-08 15:0x 高度 40 → **34**（照抄飞书的**行节奏**）
     ────────────────────────────────────────────────────────────────────────────
     乐哥：「tab 选中的样式，红框的是飞书的，包括一级二级菜单的字词字重这些，我们都照抄吧」。
     ▸ 真机量双方（同一张截图里用我们自己的行距做标尺，比例 1.7）：
         我们：二级项 40 高、**上下无缝**（pitch 40）
         飞书：选中白卡 **34** 高、上下各留约 2px 缝（pitch 38）
       ⇒ 我们的白卡因此与上下行"糊成一片"，读起来像一整块拼图板 —— 这是唯一
         真正需要抄的地方（选中样式本身 10-07 15:20 已经就是抄飞书的：白卡 + 近黑字）。
     ▸ 间隙走**相邻同族**（下面的 `+` 规则），不给每项都加 margin-bottom ——
       否则最后一项会多出 4px、把"组间距 12"挤成 16（④a 门禁锁着三处同口径）。
     ⚠️ 字号**不动**（14px）：飞书按行距比例反算≈14，与我们**同档**；
        且 10-07 22:55 乐哥刚要求过"菜单文字字号大一点" —— 抄小字是反方向。 */
  height: 34px;
  /* ⭐⭐⭐ 2026-10-10 配合引导线：**卡整体右移，给线让位**（乐哥：「选中条超过引导线就很奇怪」）
     ────────────────────────────────────────────────────────────────────────
     ▸ margin-left 41 = 线右沿(47) + **间隙 6** − 容器左缘(12) ⇒ 卡左缘 **53**
       ⭐ 那 6px 间隙是 2026-10-11 补的，理由见下面那段。
     ▸ padding-left 15 = 文字起点 68 − 卡左缘 53
       ⚠️ 68 与一级项 / 品牌名的文字起点 **67.9 对齐** ⇒ **顺带修掉原来的倒挂**：
          改前二级文字起点 62，比一级 67.9 还**靠左 5.9px**（真机实测，Range 法与 rect 法交叉验证一致）。
     ▸ 右缘**不动**（仍 251.5，与一级项同右缘）⇒ 卡只左缩、不显"短"（乐哥：「太短也很奇怪」）。
     ▸ 改前 `padding: 0 22px 0 50px` 是"卡与一级同宽"时代的算式，随本次一并作废。

     ⭐⭐⭐ 2026-10-11 乐哥「这个选中，怎么还是很奇怪」⇒ **定为 V4**（两处修正）：
     ────────────────────────────────────────────────────────────────────────
     ① **线卡之间必须留间隙**（0 → 6px）。根因是**底色对比**，不是圆角：
        飞书选中底 (247,249,252) vs 背景白 (255,255,255) —— **只差 8 阶，卡几乎看不见**
        ⇒ 它那儿"线贴不贴卡"根本读不出来，照抄"零间隙"到我们这儿就出事：
        我们是**白卡 #FFF vs 侧栏底 #E8EEF8，差 20+ 阶、非常显眼**
        ⇒ 零间隙会被放大成 **"线 = 卡的左边框"**（截图放大后一眼可见）。
     ② **圆角恢复四角对称**（撤销 10-10 的"左 0 / 右 10"）：
        那条不对称本身就是"奇怪"的第二来源（左尖右圆，违背"一张卡"的直觉）。
        ⛔ 别再只改左侧：`border-radius` 用基类 `var(--radius-md)`（四角同值），本块**不写**；
          门禁 `nav-domain-color ⑧` 锁"四角必须相等"。
     ③ 结论：**白卡对比强是既定前提**（蓝底侧栏下只能靠白卡，10-07 定的），
        那就**必须给线留独立呼吸空间** —— 否则它永远被读成卡的边框。
        依据：飞书参考图与我们的同尺度放大对照（`deliverables/weird-5up.png`）。 */
  margin-left: 41px;
  /* ⭐⭐⭐ 2026-10-11 乐哥：「选中不要太宽，不要超过内容区」⇒ **右侧也缩进**
     ────────────────────────────────────────────────────────────────────────
     ▸ 生产实测（1600×900 / zoom 0.85）：一级项盒子 12→251.5；chevron 右缘 **229.6**；
       选中卡原来 53→**251.5** ⇒ 卡右缘与一级项盒子齐，但**比 chevron（看得见的内容右端）宽 21.9px**
       ⇒ 观感就是"选中条超出内容区"（乐哥红框画的那条线正是 chevron 右缘）。
     ▸ `margin-right: 22` ⇒ 卡右缘 = **229.6**，正对 chevron 右缘 ⇒ 左右都缩进、不再"顶出去"。
     ⚠️ 与 `margin-left: 41` 不对称（左 41 / 右 22）是**刻意**的：
        左侧让位给引导线（线右沿 47），右侧只需退回项自己的右内边距那一档。
     🖼 对比：`deliverables/cardwidth-5up.png`（① 右缩 0 = 改前）。 */
  margin-right: 22px;
  padding: 0 22px 0 15px;
  font-size: 14px;
  font-weight: var(--font-weight-regular);
  color: var(--color-muted);
  line-height: 34px;           /* 行高=菜单项高，文字垂直居中 */
}
/* 二级项之间的 4px 呼吸（飞书那档）：只加在**相邻两项之间**，首尾都不加。 */
.nav-item--sub + .nav-item--sub {
  margin-top: 4px;
}

/* 隐藏子项图标（子项仅文字；参考图 1/5 的二级是带图标的，
   但那会让 29 项各多一个图标、视觉重量大一档 —— 本轮只给"选中态胶囊"这一处对齐参考图） */
.nav-item--sub .nav-icon {
  display: none;
}

.nav-item--sub:hover {
  background: var(--color-surface-2);
  color: var(--color-fg);
}

/* ⭐⭐ 2026-10-06 选中态：**整行浅蓝胶囊 + 主色文字**（照抄参考图）
   ⚠️⚠️ 这一次换色是**必须的**，不只是好看：侧栏改成"无底贴边"后与页面同底，
      旧选中底 `--color-bg`(#F6F6F6) 与页面底盘同色
      ⇒ 对比度 ≈1:1，**选中项完全看不见**（用户会以为菜单坏了）。
      参考图的选中态本来就是浅蓝 ⇒ 抄它顺手解决了这个坑。 */
/* ⭐⭐ 2026-10-07 15:20 随一级项一起改为**白卡浮起**（必须与 `.nav-item.is-active` 同值）。
   ⚠️ 二级项白卡上再缩进 22px（`padding-left: 22px`），与一级项的 12px 形成层级差 ——
      这点与一级项不同，属刻意。 */
.nav-item--sub.is-active {
  background: var(--color-surface);
  color: var(--color-fg);
  /* ⭐⭐⭐ 2026-10-08 15:1x **方向反转**：选中态"浮起" → **平卡**（乐哥纠正）
     ────────────────────────────────────────────────────────────────────────────
     乐哥：「我说的是那个选中后，悬浮的效果，**飞书那个没悬浮的**」。
     ▸ 真机量飞书那张卡（同一张截图，x=560/640/720/780 四列）：
         卡上缘 y=15、下缘 y=72，卡外（y 8~14 与 y 73~85）= 面板底色 rgb(226,236,245)
         —— **分毫不差，一点投影都没有**；卡内填充 rgb(248,250,252)（近白、非纯白）。
       ⇒ 飞书的选中 = "**平**的近白卡 + 深色字"，不靠投影浮起。
     ▸ 所以把 10-07 15:20 那条 `box-shadow: 0 1px 3px …` 去掉（当时写它是以为"飞书是浮起的"，
       现在按真机更正）。白卡与浅蓝底仍差 **20+ 阶**，读得出是"一张卡"（`frame-flush ⑥`
       的"明度差 22~36"就是守这个的），平了不会消失。
     ⚠️ 与 `.nav-item.is-active`（一级平铺项）**必须同值** —— 历史上出现过"一级白、二级蓝"。
     ⛔ 别再把它加回来：加了就与飞书不一致（这是乐哥本次点名的差异）。 */
  font-weight: var(--font-weight-medium);
  box-shadow: none;
  /* 承载右侧竖彩条（见下面 `::after`） */
  position: relative;
}

/* ⭐⭐⭐ 2026-10-11 乐哥：「竖条在框里面，也就是里面最右边，但是没贴边」
   ────────────────────────────────────────────────────────────────────────────
   ▸ 位置：**卡内侧最右**（`right: 7px` ⇒ 距卡右缘 7px，不贴边），上下各留 7px 不顶到圆角。
     卡右缘此时 = 229.6（= chevron 右缘 = 乐哥画的"框"的右边界）⇒ 条落在框内侧最右 ✓
   ▸ 尺寸：3px 宽 / 圆角 2px；颜色走 `--color-accent`（品牌蓝 #0064E0）。
   ▸ ⛔⛔ **必须写 `display: block`**：全站有 `.nav-item.is-active::after { display: none }`
     （2026-10-06「白卡 + 竖条 = 两个选中信号」删掉的）。两条选择器**同特异度 (0,2,0)**，
     靠源顺序决胜 —— 本规则在其**之后** ⇒ 只要声明了 display 就赢；**不写 display 则被 none 吃掉，
     一条彩条都不出现**（实测白渲一轮）。
   ▸ ⚠️ 与 10-06 那条"两个信号"的结论如何共存：当时是**浅蓝胶囊底 + 竖条**（两个都没什么分量，
     叠起来显得重复）；现在是**白卡（本身已是强信号）+ 一条 3px 细条**，且条在卡**内部**、
     不贴边 ⇒ 读作"卡内的一处标记"，不是第二个选中态。乐哥 10-11 看过实物图后点名要它。
   ▸ 🖼 三档间距对比：`deliverables/bar-inside-4up.png`（4 / 7 / 10px，选 7）。
   ▸ ⭐ 2026-10-11 乐哥「一起加」⇒ **一级平铺项（首页 / 我的提成）共用同一条规则**（分组选择器）——
     两级的卡右缘都缩到 229.6 ⇒ 彩条落在**同一条竖线**上（切换页面时条不会横向跳）。 */
.nav-item--sub.is-active::after,
.nav-item--home.is-active::after {
  display: block;
  content: '';
  position: absolute;
  right: 7px;
  top: 7px;
  bottom: 7px;
  width: 3px;
  border-radius: 2px;
  background: var(--color-accent);
}

/* ---- 全局文字选中：主色高亮 ---- */
::selection {
  background: var(--color-primary-100);
  color: var(--color-accent-text);
}

/* ---- 主滚动区滚动条：细窄、hover 显色（与侧栏/时间选择器一致）---- */
body {
  scrollbar-width: thin;
  scrollbar-color: transparent transparent;
}
body:hover {
  scrollbar-color: var(--color-gray-300) transparent;
}
body::-webkit-scrollbar { width: 8px; height: 8px; }
body::-webkit-scrollbar-track { background: transparent; }
body::-webkit-scrollbar-thumb { background: transparent; border-radius: var(--radius-sm); }
body:hover::-webkit-scrollbar-thumb { background: var(--color-gray-300); }
body::-webkit-scrollbar-thumb:hover { background: var(--color-gray-400); }

/* ---- 键盘可访问性：自定义可点组件统一焦点环（原生输入框保留浏览器默认） ---- */
:where(button, [role="button"], [tabindex]):focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}
/* 胶囊/下划线类切换组件（无默认 outline，补品牌蓝焦点环）
   ⚠️ `.login-tab` 已于 2026-09-22 换成 `.login-switch`（登录页改用"扫码为主 +
      账号密码收成链接"的方案 A），这里同步换成新类名 —— 漏改会让登录页那个
      链接**失去键盘焦点环**（Tab 到它看不见焦点，是最容易被忽略的无障碍问题）。 */
.seg__btn:focus-visible,
.pill-filter__item:focus-visible,
.tab:focus-visible,
.sf-tab:focus-visible,
.login-switch:focus-visible,
.nav-item:focus-visible,
.icon-btn:focus-visible,
.page-help-btn:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

/* ==========================================================================
   侧栏「个人区」（2026-09-16 菜单重排）
   工作台首页 + 我的提成 —— 与下面 9 个业务分组用一条细线隔开。
   为什么必须有这条线：两项既不折叠、也不属于任何业务域，
   不加线会被读成「第一个分组」（而它没有分组标题，视觉上很怪）。
   ========================================================================== */
/* ⭐⭐⭐ 2026-10-08 00:4x margin-bottom 12 → **11px**（补掉 border 吃掉的那 1px）
 * ⚠️ **上一版只删了 padding-bottom，还差最后 1px** —— 真机复测：
 *     首页→我的 55.1（间隙 13.1）、我的→经营分析 47.2（间隙 13.2）、
 *     其余 46（间隙 12）⇒ 仍**不统一**。
 *   根因：这两处有 `border-bottom: 1px`，**边框本身也占视觉间距**
 *   （12 margin + 1 border = 13），而普通分组只有 12。
 * ⇒ 用 11 + 1 = **12**，与普通分组同口径。**分隔线保留**（它是"个人区/我的组"
 *   与业务分组之间的语义边界，与间距是两件事）。
 * ⚠️ 若将来把这条 border 加粗或换成 padding 实现，本值要重算——判据在下面的门禁里。 */
.sidebar__personal {
  /* ⛔ 删掉 padding-bottom: var(--space-3)（它与 margin 叠加成 24px，见上方说明） */
  margin-bottom: 11px;
  border-bottom: 1px solid var(--color-border-soft);
}
.sidebar__personal .nav-item { margin-bottom: 2px; }
.sidebar__personal .nav-item:last-child { margin-bottom: 0; }

/* 「我的」分组（2026-09-19）：与下面的业务分组用一条细线隔开。
   ⚠️ 为什么必须隔开：它虽然也用 nav-group 结构（可折叠 + 子项），但性质完全不同 ——
      业务分组受权限码控制、按业务域划分；「我的」是**个人入口，全员可见、无权限码**。
      不隔开会被读成"第一个业务分组"，用户找不到"我的钱/我的报销"该去哪看。
      （与上面 .sidebar__personal 的分隔线同一手法，保持侧栏"个人区 / 业务区"的视觉分工。） */
/* ⭐⭐⭐ 2026-10-08 00:2x padding-bottom 删除；00:4x margin 12 → 11（补 border 的 1px）
 * ⚠️ 这条与 `.sidebar__personal` **必须同值** —— 只改一处就会变成
 *    「首页 → 我的」统一了、「我的 → 经营分析」仍差 1px（第一版就漏了一处，补了才齐）。 */
.nav-group--mine {
  /* ⛔ 删掉 padding-bottom: var(--space-3)（原因见 .sidebar__personal 的说明） */
  margin-bottom: 11px;
  border-bottom: 1px solid var(--color-border-soft);
}

/* ⭐⭐⭐ 2026-10-07 23:5x **死规则清理**（本文件约 85 行，只删「已无输出点」的部分）
 * ────────────────────────────────────────────────────────────────────────────────
 * 删掉的是这一整段「侧栏子项 · 带副文案的两行形态」：
 *   · `.nav-item--sub.has-hint`            （base.css 原 1344-1360）
 *   · `.nav-item--sub.has-hint .nav-text`  （原 1365-1369）
 *   · `.nav-item--sub .nav-hint`           （原 1400-1412）
 *   · `.nav-item--sub.is-active .nav-hint` （原 1416，单行）
 *
 * ⛔ 为什么它们是**死规则**（不是"暂时没用"）：
 *   `public/js/pages/finance.js:2164` 乐哥 2026-09-26 明确「二级菜单下面那些小文字备注不要了」
 *   ⇒ 副文案渲染连同 `has-hint` 类**已整体移除**，全站 JS/HTML 里
 *     `.has-hint` 与 `.nav-hint` 的**输出点为 0**（已 grep 确认，只有那段注释提到它们）。
 *   ⭐ 真机复核（/expense/list，2026-10-07 23:5x）：`.has-hint` 0 个 / `.nav-hint` 0 个，
 *     而 `#sidebar-nav` 内 40 个 `.nav-item--sub`、40 个 `.nav-text` **都正常在**。
 *
 * ⚠️⚠️ **为什么必须删，而不是留着**（这正是今晚那个 bug 的教训）：
 *   这 4 条里 `.nav-item--sub.has-hint { height:auto }`、`.nav-item--sub .nav-hint
 *   { font-size:var(--font-size-xs) }` 都**声明在基类之后且特异度更高** ——
 *   静态扫 CSS 时它们会出现在"可能覆盖基类"的候选里，**昨晚查那个字号 bug 时就是这样
 *   被干扰的**。死规则留在那里，等于给后来的人埋一堆"看起来在生效、其实不生效"的假线索。
 *   ⇒ 本项目规矩：**死规则要删**（同一个坑今天已第二次踩，见技能
 *     dabotai-ui-consistency-fix 铁律「死规则要删」）。
 *
 * ⚠️⛔⛔ **必须原样保留的是下面那条**（易被误当成同一段而一起删掉）：
 *   `#sidebar-nav .nav-item--sub:not(.is-active) .nav-text { color/medium }`
 *   它**不依赖 has-hint**，作用范围是**财务侧栏全部子项**（作用域靠 `#sidebar-nav` 前缀），
 *   删了会让财务那一列退化成"有的项深黑、有的项灰"（那正是它当初要治的毛病）。
 *   真机已确认：40 个 `#sidebar-nav .nav-item--sub .nav-text` 都命中它。 */
#sidebar-nav .nav-item--sub:not(.is-active) .nav-text {
  color: var(--color-fg);
  font-weight: var(--font-weight-medium);
}


/* ==== /css/layout.css ==== */
/* ==========================================================================
   达播工作台 — layout.css
   主区域 / 顶栏（面包屑·用户）/ 页面容器
   ========================================================================== */

/* ---- 主区域 ----
   2026-09-19 晚：乐哥拍板**取消「整体悬浮」**，改为「方案A：贴边」（他原话：
   「上面做的那个悬浮菜单还有侧边栏很奇怪，你看下怎么优化，或者不要悬浮？」→ 选 A）。

   上一版是「侧栏 + 内容列各一块 14px 圆角面板，内缩 2~8px 浮在桌面色上」。
   实测两个毛病：
     ① 缝宽不对等（左/中/右 2px、上/下 8px，差 4 倍）→ 读起来像"没对齐"
     ② **滚动后两块面板顶边错开 8px**（侧栏 fixed、内容列随文档流，见 base.css 侧栏注释）
   本版：margin 全部归零、直角、无阴影，左边界贴着侧栏右缘（侧栏自带 1px 分隔线）。

   ⚠️ 为什么保留 `overflow: clip`（即使已经不需要它来裁圆角了）：
     `.topbar` / `.page-tabs-bar` 是**吸顶**的（sticky），而 sticky 需要一个不含滚动容器的祖先链。
     `clip` 只裁剪、**不创建滚动容器**；换成 `hidden` 会创建 → 吸顶在结构上直接失效（实测顶栏跑到 −312）。
     ⇒ 想删这一行前，先用「滚动 320px 后顶栏是否仍在 0」验证。 */
.app-main {
  flex: 1;
  min-width: 0;
  /* ⭐⭐⭐ 2026-10-08 21:3x **撤销"定位上下文"**（上一轮 20:5x 在这里加的 `position: relative`）
     ────────────────────────────────────────────────────────────────────────────────────────
     ▸ 上一轮加它的目的：让移进本容器的 `#drawer-root` 用 `absolute; inset: 0` 铺满、
       再被本容器裁切 ⇒ 抽屉"约束在蓝框内"。**边界确实对上了（实测差 0px），但漏了前提**。
     ▸ 🐞 本轮乐哥报「这个怎么滑动还能滑上去的？」（截图＝当天抽屉的标题栏整个不见）。
       真机复现（`scripts/frontend-debug/diag-drawer-scroll-away.js`）：
           .app-main 是**滚动容器**（overflowY=auto, sh=1338 > ch=934）
           在抽屉内滚轮 +500 → .app-main.scrollTop 0 → 404
           .drawer top 8 → **-396**（相对蓝框 -404）、`.drawer__header` 整个跑出框外被裁
       ⇒ `position: absolute` 定位在**内容坐标系**里 ⇒ 挂在滚动容器内的抽屉**必然跟着滚**。
       ⛔ 也不能把 `.app-main` 的 `overflow` 改成 `clip` 来绕（下方 108 行那段写明：
          那条 `auto` 是乐哥 2026-10-07 要的"框不随滑动消失"，改回去就是当初要修的现象）。
     ▸ 现行：抽屉搬回 **body 级 + `position: fixed` + JS 写视口坐标**（见 index.html 与
       overlays.css 的 `#drawer-root`）⇒ 本元素不需要再当包含块。
     ⛔ 别因为它加回来：一旦 `.app-main` 成为 positioned，页面内**所有** `position: absolute`
       元素的包含块都会从更外层变成它 ⇒ 可能悄悄改变既有页面里某个浮层的位置。
       （当初加它时只验证了"抽屉那几页零变化"，覆盖不到全站。） */
  /* ⭐⭐⭐ 2026-10-07「**显示屏浮在底色上**」（乐哥：「内容页是整个悬浮的，当前顶部和底部
     你没留间距，这个不对……这个内容也就是一个有圆角的显示屏，然后显示屏是在底色
     （侧边栏的颜色）上面的，然后侧边栏是底色，他整个包着内容页」）
     ┌────────────────────────────────────────────────────────────┐
     │ 底色（= 侧栏色 #EBEDF0，连成一整片）                          │
     │  ┌────────┐  ┌──────────────────────────────────────────┐  │
     │  │        │  │ 顶栏（玻璃 · sticky · 上圆角）              │  │
     │  │ 侧栏   │  ├──────────────────────────────────────────┤  │
     │  │ =底色  │8px│ 内容（白 · 四角圆角 10 · 轻阴影）        │8px│  │
     │  │        │  └──────────────────────────────────────────┘  │
     │  └────────┘   ↑ 上下左右**四条缝都露出底色** = "浮起来"      │
     └────────────────────────────────────────────────────────────┘
     ▸ margin 四边 = `--shell-inset`(8)，**含顶部**（此前完全没有 → 顶栏贴死视口上沿）。
     ▸ margin-left = `--sidebar-width + inset`：侧栏贴边宽 240，内容左缘 248 ⇒ 中缝 8。
     ▸ background = 纸白 + **四角**圆角 + 轻阴影 ⇒ 一块完整的"显示屏"。
     ▸ 底盘 = `--color-content-bg`，与 `.sidebar` 的 `--color-sidebar-bg` **同值**
       ⇒ 侧栏与四缝连成一片，"侧栏把内容页整个包住"。
     ⚠️ `overflow: clip` **不能改 hidden**：它会创建滚动容器 ⇒ 吸顶结构上失效。 */
  margin-left: calc(var(--sidebar-width) + var(--shell-inset));
  /* ⭐⭐⭐ 2026-10-07「被侧栏悬浮包裹」（乐哥选 W3 档）：
     侧栏内缩 `--shell-inset` 后，内容区要多让出**同样**的量才能出现缝隙
     —— 现状 `margin-left: var(--sidebar-width)` 让两栏**贴死**（实测缝 = 0），
     贴死时整块读成"一整块"，完全没有乐哥要的"包裹住"。
     ⚠️ 必须与 `.sidebar` 的 `width: var(--sidebar-width)`（2026-10-07 第二次反转后
        侧栏恢复贴边通高、宽度不再减 inset）**成对**：
        侧栏右缘 = sidebar-width，内容区左缘 = sidebar-width + inset ⇒ 中缝 = inset。
     ⚠️ 折叠态另有一条 `.is-collapsed .app-main { margin-left: … }`
        —— 折叠时同样 + inset，那一处已在同文件里同步补偿。 */
  /* ⭐⭐ 2026-10-07：右/下缝由 --frame-gap(14) 改用 --shell-inset(8)，
     与中缝**同值** ⇒ 四条缝（上下左右）围成一个完整的"包裹"。
     ⚠️ 这两条缝的职责不同，别混用：`--frame-gap` 原本管右/下，
        现在整圈统一交给 `--shell-inset`（一个值、一处改）。 */
  margin-right: var(--shell-inset);
  margin-bottom: var(--shell-inset);
  /* ⭐⭐⭐ 2026-10-07「**显示屏浮在底色上**」：**补上顶部这条缝**（此前完全没有）。
     ────────────────────────────────────────────────────────────────────────────
     乐哥原话：「首先内容页是整个悬浮的，这个当前**顶部和底部，你没留间距**，这个不对。
     你可以理解为，这个内容也就是一个**有圆角的显示屏**，然后显示屏是在底色
     （**侧边栏的颜色**）上面的」。
     ▸ 实测依据（改造前 1920×950 真机）：内容区 `top = 0` ——
       **顶栏直接贴在视口上沿**；底部那条缝在文档末尾（长页时根本看不到）。
       ⇒ 内容页只"贴"着视口的下/右，"浮"不起来 —— 这就是他没看到效果的原因。
     ▸ 现在**四条边都内缩 `--shell-inset`**，内容页 = 一块完整的圆角显示屏，
       浮在一整片底色（= 侧栏色）上 ⇒ 「底色包着内容页」成立。
     ⚠️ 与顶栏的关系：顶栏是 `.app-main` 的**第一个子元素**、在文档流里，
        `position: sticky; top: 0`。给 `.app-main` 加 `margin-top` **不会**破坏吸顶
        （sticky 相对视口吸附；margin 只改它初始的文档位置）。
        滚动时顶栏吸在视口 `top: 0`，而内容页上缘已随文档滚到视口之上
        ⇒ 上圆角随滚动离开视口，看到的是"顶栏贴视口上沿" ——
        这正是「吸顶」本来的定义（他要的是玻璃吸顶，不是缝一直留着）。
     ⛔ 别改成 fixed：fixed 脱流 ⇒ 必须把 `margin-top: var(--tabs-bar-height)` 加回来，
        且内容会被顶栏硬切断（那正是 10-06 改 sticky 要消灭的毛病）。 */
  margin-top: var(--shell-inset);
  padding: 0;
  display: flex;
  flex-direction: column;
  background: var(--color-surface);
  /* ⭐⭐⭐ 2026-10-07「被侧栏悬浮包裹」（W3 档）：圆角从 `0 0 16 16`（只下两角）
     改为**四角** `var(--shell-inset-radius)`(10px) —— 侧栏与内容区各自成岛、
     四条缝等宽（都是 `--shell-inset`）围成完整包裹。
     ⚠️ 之前只保留下两角，是因为**上缘贴视口顶**、给上圆角会在视口角露出 body 底色；
        现在侧栏与内容区**顶部也内缩**了 inset、上缘离视口有缝 ⇒ 上圆角成立。 */
  border-radius: var(--shell-inset-radius);
  box-shadow: 0 2px 10px rgba(16, 24, 40, 0.045);
  /* ⭐⭐⭐ 2026-10-07「**边框锁死 · 框内滚动**」（乐哥：「顶栏和底栏滑动的时候会消失，
     这个不对，我的理解是，**上下边框也是锁死的，不会随着滑动而变**」）
     ────────────────────────────────────────────────────────────────────────────────
     ▸ 这是「显示屏」模型的**完整形态**：上一版只做了配色与四条缝，
        但滚动仍是 **window 滚动** ⇒ 内容页随文档变长、上下缘会滚出视口，
        顶栏靠 `sticky` 吸附（吸附时它贴的是**视口** top:0，不是内容区上缘）
        ⇒ 用户看到的正是"顶栏和底边在滑动时消失"。
     ▸ 现在把 `.app-main` 变成**固定高度的框 + 自身的滚动容器**：
        高度锁 `calc(100vh − 2×inset)` ⇒ **上下左右四边恒定不动**；
        顶栏 `sticky top:0` 改为相对**本框**吸附 ⇒ 永远停在框的上沿。
     ⚠️⚠️ 为什么这里**必须**用 `auto` 而不是 `clip`：
        `clip` 不创建滚动容器 ⇒ 框不能滚；要框内滚就必须让它成为滚动容器。
        **代价**：sticky 元素的"吸附参照"从视口变成这个框 ⇒ 页内 sticky 的 `top`
        值要重算（见 components.css / pages-influencer.css / pages.css 等 8 处注释）。
        ⛔ 千万别把 `overflow` 改回 `clip`/`hidden` 来"省事"：框一不能滚，
        边框又会随滑动消失 —— 正是乐哥要修的那个现象。
     ⚠️ 为什么高度用 `100vh − 2×inset` 而不是 `100%`：
        `.app-shell` 是 flex 且 `min-height:100vh`，用百分比会在内容超高时
        被内容顶开（实测框高会随内容变长 ⇒ 底边又跑掉了）。
        显式扣掉上下两条缝，高度与视口解耦 ⇒ **短页也保持满屏高的框**
        （乐哥拍板：短页面时框不缩、底边仍锁在视口底 inset 处）。 */
  height: calc(var(--shell-vh) - 2 * var(--shell-inset));
  overflow-y: auto;
  /* ⚠️⚠️ 横向**必须**是 `hidden`（2026-10-08 22:5x 试过 `clip`，**不行**）：
     规范规定"一轴 `clip` + 另一轴不是 `visible`"时 `clip` 会**退化成 `hidden`**
     ⇒ 实测 `getComputedStyle(.app-main).overflowX` 仍是 `hidden`，白改一场。
     （想断"可滚溢出改变横向 sticky 位置"那条链，得从 sticky 那端下手 ——
       见 `.page-tabs-bar` 里去掉 `left/right` 的那段注释。） */
  overflow-x: hidden;
  /* ⭐⭐⭐ 2026-10-08「切 tab 不再左右抖」：滚动条出现/消失**不许改变内容宽度**。
     ──────────────────────────────────────────────────────────────────────────
     ⚠️ 病根（乐哥实报）：达人详情页点「概览 / 佣金配置 / 合同与开票」时，
        **正文整体左右移动**。实测复现：佣金配置 tab 的 scrollHeight 1551 > 一屏934，
        ⇒ 滚动条出现；其他 tab 不足一屏 ⇒ 滚动条消失。**滚动条占 15px 宽**
        ⇒ 内容区可用宽在 913 ↔ 928 之间跳变 ⇒ 正文左缘 400 ↔ 407.5 反复跳。
     ⚠️ 为什么漏在这：`scrollbar-gutter: stable` 早在 base.css 加给了 **html**
        （修的是"整页滚动条"时代的抖动），但 10-07「边框锁死」把滚动**下沉到本容器**
        （见上方注释）⇒ html 已不再滚动，**新滚动容器漏加** ⇒ 老病在新位置复发。
     ▸ 语义：无论有没有滚动条，**恒定预留**滚动条槽 ⇒ 内容宽度与位置恒定。
     ⚠️ overlay 滚动条环境（macOS 默认）下本属性无副作用（规范：对 overlay 无效）；
        经典滚动条环境（Windows / macOS 系统设"始终显示"）下从"跳动"变"恒定"。
     ⛔ 别删：删了 = 上述"左右移动"复发（本次已实测复现+修复后实测恒定 400）。 */
  scrollbar-gutter: stable;
  transition: margin-left var(--motion-base) var(--motion-ease-standard);
}

/* ⭐⭐ 2026-10-08 21:3x 浮层打开期间**锁住底层滚动**
   （`.app-shell.has-overlay` 由 `UI._syncOverlayScrollLock()` 维护，抽屉与弹窗共用）
   ──────────────────────────────────────────────────────────────────────────────────────────────
   ▸ 为什么需要：抽屉/弹窗内容不满一屏时（自己的 scrollHeight ≤ clientHeight），
     滚轮**不会被浮层吃掉** ⇒ 顺着冒泡链把底层 `.app-main` 滚走。
     现象是"在抽屉里滚了半天，关掉抽屉发现下面的页面跑到别处去了"（用户完全不知道发生了什么）。
     （乐哥 2026-10-08 21:2x：「这个怎么滑动还能滑上去的？」的第二个成因。）
   ▸ 为什么不是只给浮层加 `overscroll-behavior: contain`：那个属性只在
     **元素自身能滚**、且**滚到边界**时才拦住滚动链；本条要覆盖的是"压根没得滚"的情况。
     （两条一起做：`.drawer__body` 的 contain 管"能滚但滚到底"，本条管"根本没得滚"。）
   ▸ `overflow: hidden` 与 `auto` 一样**保留 scrollTop** ⇒ 关闭浮层后底层位置不变。
     ⚠️ 不要改成 `display:none` 或重置 scrollTop —— 那会让页面"跳回顶部"。
   ⛔ 别删：删了 = "底层被滚走"复发（本轮实测：滚轮 +500 ⇒ `.app-main.scrollTop` 0 → 404）。 */
.app-shell.has-overlay .app-main {
  overflow: hidden;
}

.app-shell.is-collapsed .app-main {
  /* ⭐⭐ 2026-10-07「侧栏=底色」：折叠态侧栏宽变成 `--sidebar-width-icon`(64)，
     内容区要让出 `icon + inset` —— 侧栏右缘 = icon，内容左缘 = icon + inset
     ⇒ 中缝仍恰好 = `--shell-inset`（与展开态同宽，折叠时不会"跳"）。
     ⚠️ 与展开态那条是**同一个算式**（展开态 sidebar-width=240 / 折叠态被这条覆盖成 64）。
     ⛔ 别改回 `var(--sidebar-width-icon)`：那会让折叠时两栏贴死、中缝消失。 */
  margin-left: calc(var(--sidebar-width-icon) + var(--shell-inset));
}

/* ---- 顶栏已撤（2026-09-19 结构重排）----
   乐哥：「现在不是有菜单标签了吗，那路径是不是可以不展示」→ 撤掉面包屑那一行，
   标签栏升为第一行。收益实测：内容区顶边 102px → 56px，**多出 46px**。
   依据：全站 24 个页面（14 主 + 10 二级/详情）全部有页面内标题，
   加上侧栏高亮项与分组名，"我在哪"本来就有三层在讲 —— 面包屑是最下面那层重复的。

   ⚠️ 保留 `.topbar` 节点（`display:none`）而不是从 HTML 删掉：
      全站仍有代码查询 `el.topbar`（app.js 的 el 赋值、若干兼容分支），
      删节点会让那些查询变成 null 并在别处抛错。留一个不占高度的空壳最省事。
   ⚠️ 若要恢复面包屑：删掉这里的 display:none，并把 .page-tabs-bar 的 top 改回
      var(--header-height)、品牌区高度改回 calc(header + tabs)（见 base.css），
      同时恢复 app.js 里给 `.breadcrumb__link` 绑跳转的那段。 */
.topbar {
  display: none;
}

/* ⚠️ `.topbar__left` / `.topbar__right` 已随顶栏一起停用（2026-09-19）。
   保留空规则块是为了留痕：这两个类曾是顶栏的左右两半，
   现在它们的内容分别搬去了 `.page-tabs-bar__left` / `__tabs` / `__tools`
   （搜索入口也回到了 `__tools` 里、通知左边）与侧栏的 `#sidebar-footer`。
   若将来恢复顶栏可从这里改回。 */
.topbar__left, .topbar__right { display: none; }

/* 顶栏全局搜索：胶囊浅底，与列表搜索框同风格 */
.global-search {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  height: 36px;
  padding: 0 var(--space-4);
  background: var(--color-bg);
  border-radius: var(--radius-pill);
  min-width: 240px;
  width: 280px;
}
.global-search__icon {
  display: inline-flex;
  flex-shrink: 0;
  color: var(--color-muted);
}
.global-search__input {
  flex: 1;
  min-width: 0;
  border: none;
  background: transparent;
  outline: none;
  font-size: var(--font-size-sm);
  color: var(--color-fg);
}
.global-search__input::placeholder {
  color: var(--color-placeholder);
}
.global-search {
  position: relative;
}
.global-search__panel {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  /* ⚠️⚠️ 面板**必须比输入框宽**，不能用 left:0;right:0（那样只有 280px）。
     实测（原型阶段）：280px 时右侧的「跳转 →」把菜单名挤到 0 宽，
     「提成总览」整行只剩一个图标 —— 看起来像 bug，实际是宽度不够。
     用 max-content 让内容自己撑开；上下限保证不会太窄也不会盖满屏幕。
     max-width 用 100vw 而不是固定 px：否则窄屏（<560px）时会溢出视口。 */
  min-width: 420px;
  width: max-content;
  max-width: min(560px, calc(100vw - 32px));
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: 0 8px 24px rgba(15, 23, 42, 0.12);
  max-height: 420px;
  overflow-y: auto;
  z-index: 300;
  padding: var(--space-2);
}
.gs-group__title {
  padding: var(--space-2) var(--space-3) 4px;
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-semibold);
  color: var(--color-muted);
  letter-spacing: 0.02em;
}
.gs-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  cursor: pointer;
}
.gs-row:hover,
.gs-row.is-active {
  background: var(--color-surface-2);
}
.gs-row__icon {
  display: inline-flex;
  flex-shrink: 0;
  color: var(--color-muted);
}
.gs-row__text {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  /* ⚠️ flex:1 不可省 —— 没有它，右侧的「跳转 →」/路径会把这列压到 0 宽，
     菜单名直接消失（见 .global-search__panel 的注释）。 */
  flex: 1;
  min-width: 0;
}
.gs-row__label {
  font-size: var(--font-size-sm);
  color: var(--color-fg);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.gs-row__sub {
  font-size: var(--font-size-xs);
  color: var(--color-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* 功能项目标页面的「跳转 →」提示（功能项与数据项的区别标识） */
.gs-row__go {
  margin-left: auto;
  flex-shrink: 0;
  white-space: nowrap;
  font-size: var(--font-size-xs);
  color: var(--color-accent-text);
  display: inline-flex;
  align-items: center;
  gap: 2px;
}
.gs-empty {
  padding: var(--space-4);
  text-align: center;
  font-size: var(--font-size-sm);
  color: var(--color-muted);
}

/* ---------- 「问 AI」那一行（贴在面板底部，视觉上与前两类结果分开） ---------- */
.gs-ai {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin: var(--space-2) calc(var(--space-2) * -1) calc(var(--space-2) * -1);
  padding: var(--space-3) var(--space-4);
  border-top: 1px solid var(--color-border-soft);
  background: var(--color-primary-50);
  border-radius: 0 0 var(--radius-lg) var(--radius-lg);
  cursor: pointer;
}
.gs-ai:hover,
.gs-ai.is-active {
  background: var(--color-primary-100);
}
.gs-ai__icon {
  display: inline-flex;
  flex-shrink: 0;
  color: var(--color-primary-500);
}
.gs-ai__ask {
  flex-shrink: 0;
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  color: var(--color-fg);
}
.gs-ai__q {
  flex: 1;
  min-width: 0;
  font-size: var(--font-size-sm);
  color: var(--color-fg-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.gs-ai__hint {
  margin-left: auto;
  flex-shrink: 0;
  font-size: var(--font-size-xs);
  color: var(--color-meta);
  display: inline-flex;
  align-items: center;
  gap: 4px;
}

/* ---------- AI 回答区 ---------- */
.gs-answer {
  padding: var(--space-3) var(--space-3) var(--space-2);
}
.gs-answer__head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--font-size-xs);
  color: var(--color-muted);
  margin-bottom: var(--space-2);
}
.gs-answer__spin {
  display: inline-flex;
  animation: gs-spin 0.8s linear infinite;
}
@keyframes gs-spin {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}
.gs-answer__body {
  font-size: var(--font-size-sm);
  color: var(--color-fg-2);
  line-height: 1.7;
}
.gs-answer__body--dim {
  color: var(--color-muted);
}
/* 数据来源：**每条 AI 回答都必须带**（否则数字无从核对 —— 见用户铁律"必须拿外部基准比"）。
   做成可点链接，点一下跳到那个页面，页面上是多少一眼能核。 */
.gs-answer__src {
  margin-top: var(--space-3);
  padding-top: var(--space-2);
  border-top: 1px dashed var(--color-border);
  font-size: var(--font-size-xs);
  color: var(--color-muted);
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
}
.gs-answer__srclink {
  color: var(--color-accent-text);
  text-decoration: none;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 2px;
}
.gs-answer__srclink:hover {
  text-decoration: underline;
}
.gs-answer__sep {
  color: var(--color-border);
}
.gs-answer__warn {
  margin-top: var(--space-2);
  font-size: var(--font-size-xs);
  color: var(--color-muted);
}

.breadcrumb {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--font-size-sm);
  color: var(--color-muted);
  min-width: 0;
}

.breadcrumb__sep {
  color: var(--color-meta);
}

.breadcrumb__link {
  color: var(--color-muted);
  text-decoration: none;
  cursor: pointer;
  white-space: nowrap;
  border-radius: var(--radius-sm);
  transition: color var(--motion-fast) var(--motion-ease-standard);
}
.breadcrumb__link:hover {
  color: var(--color-accent-text);
}

.breadcrumb__current {
  color: var(--color-fg);
  font-weight: var(--font-weight-medium);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* ⭐ 2026-09-26 两档制：36 → **34（紧凑档）**
     ⚠️ 它主要出现在**表格行内**（变更日志/编辑/删除那些图标钮）。
        改成 34 后与同排的 `.btn--sm`（也是 34）**完全齐平** ——
        改前是 36 vs 30（差 6px），业务总表操作列肉眼可见的错位。
     ✅ 顶栏不受影响：`.page-tabs-bar .icon-btn`（0,2,0，见本文件 1067 行）与
        `.page-tabs-bar__tools #notify-bell`（48px）都是**更具体**的规则，会覆盖本条。
     ✅ 手机端不受影响：下方 media 查询里 `.icon-btn { 44px }` 仍生效（触摸目标 44）。 */
  width: var(--control-height-sm);
  height: var(--control-height-sm);
  border: none;
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--color-fg-2);
  /* ⚠️ 必须带上 `transform`（2026-09-13 补，ui-ux-pro-max 优先级 7「state-transition」）：
     按下态用了 `transform: scale(0.94)` 做反馈，而这里原来只过渡 background-color/color
     → **缩放会瞬间跳变**（实测确认：规则生效但无过渡）。
     「状态变化要平滑、不能 snap」是动效规范的基本要求。
     时长沿用 --motion-fast(150ms)，落在规范的 150–300ms 区间。 */
  transition: background-color var(--motion-fast) var(--motion-ease-standard),
    color var(--motion-fast) var(--motion-ease-standard),
    transform var(--motion-fast) var(--motion-ease-standard);
}

.icon-btn:hover {
  background: var(--color-surface-2);
  color: var(--color-fg);
}

.icon-btn:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}

/* 用户信息 */
.user-chip {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1) var(--space-3) var(--space-1) var(--space-1);
  border-radius: var(--radius-pill);
  cursor: pointer;
  transition: background-color var(--motion-fast) var(--motion-ease-standard);
}

.user-chip:hover {
  background: var(--color-surface-2);
}

.user-avatar {
  /* 顶部 1px 补偿中文字面偏上，首字母视觉居中 */
  padding-top: 1px;
  width: 32px;
  height: 32px;
  border-radius: var(--radius-pill);
  background: var(--color-primary-100);
  color: var(--color-accent-text);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
  flex-shrink: 0;
}

.user-meta {
  display: flex;
  flex-direction: column;
  line-height: var(--font-line-height-tight);
}

.user-name {
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  color: var(--color-fg);
}

.user-role {
  font-size: var(--font-size-xs);
  color: var(--color-muted);
}

.user-avatar-img {
  width: 100%;
  height: 100%;
  border-radius: inherit;
  object-fit: cover;
  display: block;
}

/* ---- 个人中心下拉面板 ---- */
.profile-wrap {
  position: relative;
}
.profile-menu {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  width: 260px;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-raised);
  overflow: hidden;
  z-index: var(--z-dropdown);
  /* ⭐ 2026-09-30 顺手对齐 §9.9 的小浮层基线（`panel-in` + `--motion-base` + sheet）：
     此前它是"瞬间出现"，而同屏侧栏底部的「切换系统」下拉有入场动效
     ⇒ 同一区域两种手感。`backwards` 而不是 `both`（§8c：`both` 残留 transform 会
     留下层叠上下文）。display 从 none 恢复时动画会重新播放（规范行为），不需要 JS。 */
  animation: panel-in var(--motion-base) var(--motion-ease-sheet) backwards;
}
.profile-menu__header {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-4);
  border-bottom: 1px solid var(--color-border-soft);
}
/* ⚠️⚠️ 防"内容撑破面板"（2026-09-22 修，真实 bug）：
   实测生产库 22 个活跃用户里 **10 个的 username 是 40 字符的 `ou_` open_id**
   ⇒ 菜单头那行内容宽 233px，把面板内容撑到 **305px**，而面板只有 **223px**
   ⇒ 面板 `overflow:hidden` 把右侧裁掉 ⇒ 菜单项文字被切（「修改头」「已绑定」「重新授」）、
      新加的「审批中心」角标也看不见。
   ⇒ 两手都要：① 源头不显示 open_id（见 app.js profileHtml）；
             ② 这里做**结构性兜底** —— flex 子项 `min-width:0` 才能让省略号生效
                （`min-width` 默认 `auto` 会按内容撑开，是这类"文字撑破容器"的通用根因），
                再配 `overflow:hidden + text-overflow:ellipsis`。
   ⚠️ 只加 `overflow:hidden` 而漏 `min-width:0` 是无效的（这是最容易漏的一步）。 */
.profile-menu__header > div {
  min-width: 0;
  overflow: hidden;
}
.profile-menu__name,
.profile-menu__sub {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.profile-avatar {
  width: 44px;
  height: 44px;
  border-radius: var(--radius-pill);
  background: var(--color-primary-100);
  color: var(--color-accent-text);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--font-size-lg);
  font-weight: var(--font-weight-semibold);
  flex-shrink: 0;
  overflow: hidden;
}
.profile-menu__name {
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-semibold);
  color: var(--color-fg);
}
.profile-menu__sub {
  font-size: var(--font-size-xs);
  color: var(--color-muted);
  margin-top: 2px;
}
.profile-menu__body {
  padding: var(--space-2);
}
.profile-menu__footer {
  padding: var(--space-2);
  border-top: 1px solid var(--color-border-soft);
}
.profile-menu__item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  padding: var(--space-2) var(--space-3);
  border: none;
  background: none;
  border-radius: var(--radius-md);
  font-size: var(--font-size-sm);
  color: var(--color-fg);
  cursor: pointer;
  text-align: left;
}
.profile-menu__item:hover {
  background: var(--color-surface-2);
}
.profile-menu__item--danger {
  color: var(--color-danger);
}
.profile-menu__item--danger:hover {
  background: var(--color-danger-soft));
}
/* ⛔ 这里曾有 `.profile-menu__badge`（待审角标）与 `.profile-menu__sep`（分隔线）——
   2026-09-22 随「审批中心」从「我的」下拉移出而删除（入口改到侧栏「我的」分组，见 MINE_MENU）。
   ⚠️ 保留「菜单头不显示 open_id」那组规则（在 .profile-menu__header 之后）——
      那是独立发现的真 bug 修复：10 个用户的 username 是 40 字符 open_id，
      会把面板内容撑到 305px（面板仅 223px）⇒ overflow:hidden 裁掉菜单文字。 */
.profile-menu__soon {
  margin-left: auto;
  font-size: var(--font-size-xs);
  color: var(--color-muted);
}

/* ---- 通知中心（铃铛 + 下拉） ---- */
.notify-wrap {
  position: relative;
}
.notify-badge {
  /* ⭐⭐ 2026-09-26 修（乐哥实报「右上角通知的数字，遮住图标了」）。
     ──────────────────────────────────────────────────────────────────────────
     🐞 根因：原值是 `top: 3px; right: 1px` —— 按**按钮方框**定位、完全没考虑图标在哪。
        而图标是**居中**的（18px 居中于 40px 按钮 ⇒ 上沿在 11px、右缘在 29px），
        于是 16px 高、宽到 28px 的「99+」直接压在图标上：
        实测重叠 **水平 18px × 垂直 8px**（几乎整个盖住）。
        ⚠️ 这不是新 bug：按钮还是 48px 圆形的时代就已重叠（实测 15×5px），
           2026-09-26 A6 把按钮缩到 40px 方盒后**放大**成了"整个遮住"。
     ⭐ 修法：把定位改成**按图标上沿锚定**（徽章下沿 ≤ 图标上沿），并让徽章**右上外溢**
        一点（角标的标准形态：骑在角上，而不是压在按钮里）。
     算式（两种按钮尺寸都成立，改按钮尺寸时要重新验算）：
        · 桌面按钮 40 / 图标 18 ⇒ 图标上沿 = (40−18)/2 = **11**
          ⇒ 徽章 top = 11 − 16(徽章高) = **−5**（下沿正好贴图标上沿，零重叠）
        · 移动端按钮 44（见下方 @media 的 44px 规则）⇒ 图标上沿 = 13
          ⇒ 同一条 top:−5 下沿落在 11，比图标还高 2px（更安全）
        · right: −3 ⇒ 徽章右缘外溢按钮 3px（右侧是工具组的 10px gap，不会挤到邻居）；
          水平上「99+」(宽 28) ⇒ 左缘 15，比图标左缘 11 略靠右 ⇒ 读作"角标"而非"居中压在头上"
     ⚠️ 徽章宽的上限就是「99+」（app.js 的 refreshBadge：c > 99 ? '99+' : String(c)），
        最宽 28px，上面算式按最宽情况算的。
     ⚠️ 顶栏上下留白各 16px（栏 72 / 按钮 40）⇒ 外溢 5px 不会顶到栏边。
     ⚠️ 摇铃铛动画摇的是 `#notify-bell svg`、不是本元素 ⇒ 徽章不会跟着晃（见下方 bell-shake）。 */
  position: absolute;
  top: -5px;
  right: -3px;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  border-radius: var(--radius-pill);
  background: var(--color-danger);
  color: #fff;
  font-size: var(--font-size-xs);
  line-height: 16px;
  text-align: center;
  pointer-events: none;
  box-sizing: border-box;
}

/* ══════════════════════════════════════════════════════════════════════════════
   2026-09-26 动效：通知铃铛「轻摇」（有新消息时）
   ──────────────────────────────────────────────────────────────────────────────
   触发点：app.js 的 `refreshBadge()` —— **未读数变多**时给铃铛加 `.is-shake`。
   为什么挂在那里：那是"真的有新消息来了"的唯一可靠信号（轮询拿到的数字涨了）。

   ⚠️⚠️ 三个"不做"，都是刻意的：
     ① **首次拿到数字时不摇**（页面刚打开，整页已在进场动画里，再摇一下是重复提示）；
     ② **数字不变或变少时不摇**（每轮轮询都摇 = 从"提示"变成"骚扰"，
        用户会开始忽略它 —— 那这个动效就是负资产）；
     ③ 摇的是**里面的 svg**、不是 `.icon-btn` 本身：
        按钮自己有 hover/active 的 transform，动画与它争同一个属性会互相打断。
   ⚠️ `transform-origin: 50% 12%`（铃铛顶部）：摇铃是"绕着挂点摆"，
      以中心为轴会变成"整颗铃在转"，像个转盘。 */
#notify-bell.is-shake svg {
  transform-origin: 50% 12%;
  animation: bell-shake 640ms var(--motion-ease-in-out);
}
@keyframes bell-shake {
  0%, 100% { transform: rotate(0); }
  16% { transform: rotate(11deg); }
  36% { transform: rotate(-8deg); }
  56% { transform: rotate(5deg); }
  76% { transform: rotate(-2.5deg); }
}
.notify-panel {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  width: 340px;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-raised);
  overflow: hidden;
  z-index: var(--z-dropdown);
}
.notify-panel__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--color-border-soft);
}
.notify-panel__title {
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-semibold);
  color: var(--color-fg);
}
.notify-panel__readall {
  border: none;
  background: none;
  font-size: var(--font-size-xs);
  color: var(--color-accent-text);
  cursor: pointer;
  padding: 0;
}
/* ---- 通知分类页签（2026-09-22 新增，乐哥：「按照类型做下分类，现在都混一起很乱」）----
   ⚠️ 尺寸/配色一律复用既有令牌，**不新造颜色**（本项目纪律：新造"全站孤例的颜色"
      是"看着突兀"的头号原因）。激活态用 `--color-primary-50` 底 + `--color-accent-text` 字，
      与全站"选中态"语言一致（同侧栏选中项、tab 选中项）。
   ⚠️ 横向可滚动：5 个页签（全部/待我审批/待办/审批结果/其它）在 340px 面板里会挤，
      中文标签不能靠缩字号解决，故允许横向滚动、且隐藏滚动条（不是让文字折行）。
   ⚠️ 页签数字 = **该类型的未读数**（不是总数）—— 语义与邮箱未读一致。
      数字为 0 时不渲染（`is-alert` 的红底只在「待我审批」有未读时出现）。 */
.notify-tabs {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--color-border-soft);
  overflow-x: auto;
  scrollbar-width: none;
}
.notify-tabs::-webkit-scrollbar {
  display: none;
}
.notify-tab {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 3px;
  border: none;
  background: none;
  padding: 4px var(--space-2);
  border-radius: var(--radius-pill);
  font-size: var(--font-size-xs);
  color: var(--color-muted);
  cursor: pointer;
  white-space: nowrap;
  transition: background-color var(--motion-fast) var(--motion-ease-standard),
              color var(--motion-fast) var(--motion-ease-standard);
}
.notify-tab:hover {
  background: var(--color-surface-2);
  color: var(--color-fg);
}
.notify-tab.is-active {
  background: var(--color-primary-50);
  color: var(--color-accent-text);
  font-weight: var(--font-weight-medium);
}
.notify-tab__n {
  font-size: var(--font-size-xs);
  line-height: 1;
  color: inherit;
  opacity: 0.75;
}
/* 「待我审批」有未读 → 数字做成红底白字（最需要动手的一类，视觉上给足权重） */
.notify-tab.is-alert .notify-tab__n {
  min-width: 16px;
  padding: 2px 4px;
  border-radius: var(--radius-pill);
  background: var(--color-danger);
  color: #fff;
  opacity: 1;
  text-align: center;
  box-sizing: border-box;
}
.notify-panel__body {
  max-height: 400px;
  overflow-y: auto;
}
.notify-item {
  padding: var(--space-3) var(--space-4);
  cursor: pointer;
  border-bottom: 1px solid var(--color-border-soft);
}
.notify-item:last-child {
  border-bottom: none;
}
.notify-item:hover {
  background: var(--color-surface-2);
}
.notify-item.is-unread {
  background: var(--color-info-soft));
}
.notify-item__head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
.notify-item__dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--color-danger);
  flex-shrink: 0;
}
.notify-item:not(.is-unread) .notify-item__dot {
  opacity: 0;
}
.notify-item__title {
  flex: 1;
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  color: var(--color-fg);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.notify-item__time {
  font-size: var(--font-size-xs);
  color: var(--color-muted);
  flex-shrink: 0;
}
.notify-item__content {
  margin-top: 4px;
  font-size: var(--font-size-xs);
  color: var(--color-muted);
  line-height: 1.5;
}
.notify-empty {
  padding: var(--space-6);
}

/* ---- 主内容 ---- */
.page {
  flex: 1;
  /* 上边距 24（不是 32）—— 2026-09-19 乐哥：「下面有点多留白」。
     实测根因：上 32 ＋ .page-header 的 margin-top 24 = **56px 空档**（5 个页面实测全是 56）。
     那 24 是**标签栏出现之前**定的 —— 当时 .page 顶上直接是顶栏，需要拉开距离；
     现在标签栏已吸顶、且与正文之间有一条分隔线，这 24 就是**重复**的。
     现改法：上 24 ＋ header 的 8 = 32px，与左右 32 的 gutter 对齐成一个方正的角。

     ⭐ 2026-09-25 UI 美化第四刀（呼吸感）：左右 gutter 由 32 加到 **40**、
        底部 32 → **40**，上边距保持不变（24）。
     为什么只加左右下、不加上：上边距与「标签栏吸顶 + 分隔线」是配套关系（见上），
     改动它会重新制造那段被删掉的 56px 空档；
     而左右下加大后，内容区四周形成一个**更宽松的边框**，
     正是"呼吸感"最直接的来源（内容不贴边）。
     ⚠️ 实测代价：几乎为零 —— 1440 宽下内容区由 1185 → 1169（-16px，1.3%），
        表格列宽有富余、不触发折行；一屏可见行数保持 9 行不变。

     🔁 2026-10-08 20:5x 我一度把左右/下 **40 → 16**，22:33 **已改回 40**
        （乐哥：「我们里面内容页，模块距离两边的距离现在太挤了，得改成之前那样
        …… 之前我说改边框，你是不是改了内容页的」—— 正是，那句「边框」他指的是
        **蓝框外侧那圈淡蓝色带**，不是我改的内容页留白）。
        ⇒ 本令牌回到 `--page-padding`(=40)，`design-tokens.css` 有完整来回记录。 */
  /* ⭐ 2026-09-28 上边距 24 → 16（--space-6 → --space-4，乐哥：「内容区高一点点，
     左边菜单栏显得偏高了」—— 实测内容区从 y=86 起、页头内容 y=110 才出现，
     而侧栏岛 y=86 即满铺菜单 ⇒ 内容区顶部留白比侧栏多 24px，视觉上侧栏"偏高"。
     上边距收 8px 后内容起点 102，与侧栏岛的落差 24 → 16，观感趋平。
     ⚠️ 左右 40 / 下 40 不动（呼吸感来源）；.page-header 的 margin-top 8 不动，
     页头包盒后「上留白 16 + 页头 margin 8」= 24px，仍在舒适区间。 */
  /* ⭐⭐⭐ 2026-10-08 21:3x 右侧改用 `--page-padding-right`（= 名义留白 − 滚动条保留槽）
     ▸ 乐哥：「上面说蓝边那个不等宽的，现在右边还是不等宽」（上一轮 40→16 已生效，
       但右边**还多一条**）—— 根因是 `.app-main` 的 `scrollbar-gutter: stable` 恒定预留
       一条槽（实测 15px），卡片到蓝框实测 **左 16 / 右 31**。令牌里有完整记录。 */
  /* ⚠️⚠️ 必须写**四值**（上 右 下 左）：三值简写的第二个值是「左右对称」——
     写成三值会把**左侧**也一起减掉滚动条槽（实测左 1 / 右 16，正好反过来）。 */
  padding: var(--space-4) var(--page-padding-right) var(--page-padding) var(--page-padding);
  width: 100%;
  /* ⭐ 2026-09-27：无档位页（表格列表等）加**全宽上限** ——
     乐哥：「那个模块的宽度就需要调整」。去掉白大卡后，无档位页面
     在 2560 超宽屏下会被拉到 **2183px**（卡片塞满、反而显挤）。
     ▸ `.page--narrow`(1200) / `.page--table`(1520) 特异性更高，会覆盖本条 ⇒ 二级页不受影响。

     ⭐⭐ 2026-10-05 上限 **1600 → 1800**（乐哥：「内容页宽度感觉不够，窄了，
        浪费了不少显示空间」+ 排期列表被压缩到列折行）
     ────────────────────────────────────────────────────────────────────────
     ▸ **为什么要改**：他的屏在 10-05 换成了更宽的（截图反推视口 **2050**、
       app-main 可用 1781）。此时 1600 上限会把内容卡压在 1520 ⇒
       **左右各空 130、合计浪费 261px**（实测；上一版注释里"（~1700）上限不生效"
       的前提已随屏幕变化失效 —— 那正是这条注释当初写错的来源）。
     ▸ **为什么取 1800 而不是放开**：1800 在 2050 屏下让内容区到 **1673**
       （浪费降到 108，视觉仍是"有边界的一列"）；在 2560 屏下封顶 1800、
       不会重演 2183 那种"卡片塞满反而显挤"。是"够用"与"有边界"的折中。
     ▸ 实测收益（排期列表，2050 视口）：表格 1518 → 1671；
       **行高 109 → 72px，一屏可见 7 → 10 行**（+43%）。
     ▸ ⚠️ 光放宽宽度**不足以**修好列折行 —— 排期时间列的可用宽由 98.2 涨到 110.5，
       而"开始 2026-10-30"需要 110px ⇒ 只余 **0.5px**（临界，字体差异即复发）。
       必须同时给关键列**显式列宽**（见 schedule-list.js 的 COL_W）。 */
  /* ⭐⭐ 2026-10-06 上限 **1800 → 2120**（≈ 1800 ÷ 0.85 等比补偿）
     ────────────────────────────────────────────────────────────────────────
     ▸ 为什么必须跟着放大：`zoom: 0.85`（高密度屏整站缩放，见 base.css）会**连宽度
       上限一起缩**。1800 是逻辑值，缩完视觉只剩 1530 ⇒ 内容物理宽比原来窄 15%、
       两侧留白变多（乐哥实测后指出：「列表是不是可以再宽一点点」）。
     ▸ 取 **2120**（1800 ÷ 0.85 ≈ 2117.6，取十位）⇒ 视觉宽回到缩放前水平。
       实测（1920 视口 dpr2）：内容卡视觉 1558 → **1612**（物理 3116 → 3225）、
       占屏 81.1% → **84.0%**（比原设计略宽 —— 该视口下可用宽此刻也放大了）。
     ▸ ⚠️ 1920 视口下真正卡住它的是**可用宽（1977）**而非本上限 ⇒ ≥1977 的值在那
       一档屏上效果相同；取 2120 是为了**更宽的屏（2560）也能等比**。
     ▸ 历史（1600 → 1800，2026-10-05乐哥「内容页宽度感觉不够，窄了」）：
       1800 在 2050 视口下让内容区到 1673（浪费降到 108）、2560 屏下封顶不塞满。
     ⭐⭐ 2026-10-06 上限 **2120 → 1900**（乐哥拍板：内容页「是不是要这么宽」）
     ────────────────────────────────────────────────────────────────────────
     ▸ 判据不是"看着宽不宽"，而是**会不会把表格压出横向滚动**。实测全站最宽的表需要
       **≈1388 逻辑 px**（达人列表、项目板块）⇒ 内容卡安全下限 ≈1400。
       · 1900 → 内容卡 1547（视觉 1315）：✅ 五类最宽的表都不溢出
       · 1600 → 内容卡 1292：❌ 达人列表 / 项目板块出现横向滚动条
     ▸ 为什么不是 1800：1800 也能不溢出且更窄（−11%），但乐哥选了**保守档** ——
       1900 只收 5%，是"几乎看不出变化"与"明显收窄"之间更稳的一档。
       ⇒ 今后若他要更窄，**先量表格自然宽**再定档，别凭印象（脚本见交接文档 §五）。
     ⚠️ 本项只动 `.page` 这一条上限，⛔ 不碰 `--form-max-width` / `.page--narrow`
       （那是**另一条线**：门禁 page-width-tiers.test.js ④c 锁着"两源取小、必须同值"）。 */
  max-width: 1900px;
  margin-left: auto;
  margin-right: auto;
}

/* ==========================================================================
   多标签页（2026-09-18 乐哥：「能有个达人页面的标签，点击直接切过来，
   内容还是我切换之前维护的那些，丝毫未动」）

   ▸ 两个部件：
     · `.page-tabs-bar` 标签栏 —— 面包屑栏之下、页面之上
     · `.page-stack`    页面栈 —— **所有标签的页面同时存在于 DOM 中**，
       非活动的只是不显示。这是"丝毫未动"的实现基础：
       切标签不做任何重建，筛选条件 / 滚动位置 / 展开的行 / 填一半的表单都原样保留。

   ▸ 为什么"只让活动容器持有 id=page-root"（见 app.js 的 activateTab）：
     全站有 8 处 `document.getElementById('page-root')` 以及大量依赖该 id 的逻辑，
     逐个改成"按标签查找"既繁琐又必漏。改成**活动容器独占该 id**后，
     那些代码一行不改就自动拿到"当前活动页"——这是本次改造能收敛在单文件的关键。

   ---------------------------------------------------------------------------
   ▸ 外观（2026-09-19 乐哥：「有点丑，可以做成 chrome 浏览器标签的样子吗」）
     改成 Chrome 的页签形态。**关键是这一条，不是配色**：
         「激活标签的底色 == 内容区的底色」
       Chrome：窗口顶是深灰槽、内容区是白 ⇒ 激活标签用**白**，白色"流"进内容区，
               视觉上连成一片，标签栏底部的分隔线在激活标签处自然消失。
       我们：内容区是**灰**（--color-bg #F5F6F8）、白的是**卡片** ⇒
             激活标签必须用**灰**，槽要比灰更深一档。
       ⚠️ 我第一版照抄 Chrome 用了白色标签，结果白块浮在灰页面上、比改版前更像
          "一张贴纸"（自测截图一看就露馅）。改对底色后才成立。
       ⚠️ 所以**不要**把 .page-tab.is-active 的 background 改成 --color-surface（白）。

     Chrome 页签的四要素（缺一个就不像）：
       ① 标签从「槽」里升起来：激活的高（36）非激活的矮（32），底部对齐
       ② 激活标签底部**两个凹角**（反圆角），把槽色平滑过渡掉
       ③ 激活标签底边**盖住**分隔线（margin-bottom:-1px + z-index:2）
       ④ 关闭按钮：非激活 hover 才出现、激活常显（降噪；实测 5 个标签 5 个 × 很吵）
   ========================================================================== */
.page-stack {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  /* ⚠️ 这里**不能**写 width:100% —— 它是"父容器内容宽"，再加上本元素的
     margin-left/right(各 14px) 就会**超出父容器 28px**（实测右侧溢出视口、内容被裁）。
     块级元素默认 width:auto 会自己扣掉 margin ⇒ 填满可用宽度且不溢出。
     （2026-09-25 实测踩到：乐哥截图里内容区右侧被切掉就是这个。） */
  width: auto;
  /* ⭐⭐ 2026-09-27：**去掉「内容区白色大圆角卡」** —— 模块卡直接落在页面灰底上。
     ──────────────────────────────────────────────────────────────────────────
     乐哥（给了 PMS「新建项目」参考图）：「整个 UI 的二级页面，我们应该按照项目管理系统
     里面那个模型去做」+「中间那个白色的框有点突兀」「上面的板块也都是散的」。

     ▸ 改前（2026-09-25 Gashora 落地）：内容区是一张 #FCFCFC 白卡浮在灰底上，
       模块卡再嵌在它里面。**问题：白卡底 #FCFCFC 与页面底 #F6F6F6 只差 6 阶**，
       肉眼几乎看不出这层白卡 —— 它是一层"看不见的中间层"，
       却让模块卡只能靠「与白卡差 3 阶 + 1px 边框」划界（边界偏弱）。
     ▸ 改后：白卡移除，模块卡直接落 #F6F6F6 上 ⇒ 卡片与底的色差由 **3 阶升到 9 阶**，
       边界反而更清楚；同时不再有"白框套白卡"的嵌套感，与参考图的扁平形态一致。
     ▸ 实测（1440×900，列表/表单/详情/看板四类一致）：内容区 **+16px 宽**
       （如达人列表 1047→1063）、内容上移 8px、**一屏可见行数不变**。
     ▸ margin **保留**：它是"内容列"与视口右/下边缘的缝（属浮岛体系，见 frame-flush 门禁）。
     ▸ padding **归零**：原 8/8/12 的唯一用途是"让白卡的白色边与内部卡之间留白"，
       白卡没了它即失去意义；四周呼吸交给 `.page` 的四边 gutter（2026-10-08 起 16px，与上边距同值）。
     ⛔ 不许再给它 background / border-radius / box-shadow —— 它现在是**纯留白容器**。
        （门禁 tests/page-stack-plain.test.js 会拦；另注意
          `--color-surface-canvas` 令牌已随之删除，别再引用）*/
  background: transparent;
  border-radius: 0;
  /* ⭐ 2026-10-06：**右/下缝归零** —— 内容区（.app-main）现在是一张**白卡**，
     四周的缝由它自己承担（margin-right/bottom = --frame-gap）。
     本元素若再留 margin，白卡内部会露出一圈底盘色（= 卡中卡）。 */
  margin: 0;
  padding: 0;
  box-shadow: none;
}

/* ⭐ 2026-09-25 Gashora 骨架落地：**隐藏页面标题**（乐哥：「每个页面左上角那个菜单名，
   是不是可以不要了」）—— 标题与「侧栏当前项 + 顶栏标签」重复讲同一件事，
   去掉后内容区直接从"操作行（状态筛选 TAB + 操作按钮）"开始。
   ⚠️ 页面标题有**两套类名**，必须都覆盖（只覆盖一套会漏掉看板）：
      · `.page-title`             —— 40 个常规页面（达人管理/货品/…）
      · `.dashboard-overview__title` —— 看板首页的「首页」（历史遗留的另一套）
   ▸ **保留**（与标题不同义、有信息量）：
      · `.dashboard-overview__date`（看板的「达人排期与业绩概览」）
      · `.page-desc`（7 个页面的标题补充描述）
   ▸ 状态筛选 TAB 与操作按钮在 page-header 里是**独立节点**，本规则不影响它们。 */
.page-title,
.dashboard-overview__title,
/* ⭐ 2026-09-25（第二轮）乐哥：「看板首页上面那个小字不要」——
   指看板标题区的描述行「达人排期与业绩概览」（`.dashboard-overview__date`）。
   它随标题一起隐藏后，整个 `.dashboard-overview` 只剩空盒 ⇒ 下面的 margin 收紧为 0
   （见 .dashboard-overview 那条），内容区顶部不再空出一段。 */
.dashboard-overview__date { display: none; }

/* ⭐⭐ 2026-10-05 修复：**标题被隐藏时，它里面的「?」也跟着消失**（实测 0×0，点不到）
   ──────────────────────────────────────────────────────────────────────────
   现场：改造「我的提成」时发现该页的「?」按钮取到但**尺寸 0×0** —— 上溯祖先链是
   `.page-help-btn → .page-title(display:none)` ⇒ 整个标题连带按钮一起不渲染。
   影响面（当时全站实测）：登记了说明、且用 `.page-header` 当页头的页面 ——
     · 我的提成（本次新登记）· 费用明细（`costs` 路由）
   二级页**不受影响**（`.detail-head .page-title` 已豁免 display:block，见下一条）。
   ⇒ 后果：说明虽然按 2026-09-28 的拍板"收进了「?」"，但用户在列表页**根本点不到** ——
      功能等于没做（这正是"改了但没生效"的典型：DOM 里有、计算样式却把它藏了）。

   修法：**含「?」的标题例外保留显示**，只把文字缩到 0（按钮是 14px 的 svg，不受字号影响）。
   ⚠️ 用 `:has(.page-help-btn)` 自限定 —— 没登记说明的页面行为**完全不变**（仍 display:none），
      避免为一个按钮去动 40 个列表页的既定版式。
   ⚠️ `line-height: 1`（不是 0）：0 会让行盒塌成 0 高、按钮可能被裁；
      1 让它按按钮高度（24px，2026-10-17 起；原 20）撑起一行。
   ⚠️ 按钮自带 `margin-left: 8px`（它在标题里本该与文字有间距）—— 标题文字已不可见，
      这 8px 会变成左侧一段无由来空白，故归零。

   ⭐⭐ 2026-10-08 现状：落点已改到**页头右端**（见下面 `.page-actions .page-help-btn` 那条）——
      搬家之后标题里不再有「?」，本规则**正常路径下不再命中**。
      **保留它作兜底**：万一某页没搬成（无 `.page-header` 的向导页、或注入时序异常），
      标题里那颗至少仍然可见可点，不会退化回"0×0 点不到、说明等于没做"。 */
.page-header .page-title:has(.page-help-btn) {
  display: block;
  font-size: 0;
  line-height: 1;
}
.page-header .page-title:has(.page-help-btn) .page-help-btn {
  margin-left: 0;
}

/* ⭐⭐ 2026-10-08：「?」的落点 = **页头右端**（`.page-actions` 首位）—— 乐哥拍板「都用 S2」
   ──────────────────────────────────────────────────────────────────────────
   落点由 app.js 的 `moveHelpButtonToActions()` 实现（结构就位后迁移，见该处长注释）。
   ▸ 为什么是 `.page-actions`：那本就是"页面级控件"的槽位（页头主操作按钮住在这），
     说明按钮同属页面级 ⇒ 语义同层；而且**一处改动覆盖全部** ——
     改前落点是分叉的（全站普查：TAB 行里 5 个、标题旁 6 个、报表页结构各异）。
   ▸ 为什么不是"表格右上角"（乐哥最初的提议）：那格（`.table-toolbar__right`）是全站
     固定的**表格工具区**，且它里面的「已通过 N」**随 tab 变**（实测切到「别人代提」
     直接变空）—— 把"固定不变的说明书"摆在"随时在变的数据"旁边会误导。
   ⚠️ 间距/垂直居中都不用在这里写：`.page-actions` 自带
      `display:flex / align-items:center / gap:var(--space-3)`。这里只钉住"不被 flex 压缩"。
   ⚠️ 20px 圆钮与 34px 的页头按钮同排 ⇒ 靠 `align-items:center` 居中，
      不要给它加高度（会与 gap 打架）。
   ⛔ 别再写回 `.table-tabs > .page-help-btn` 那种"住在 TAB 行里"的规则 ——
      按钮已不在 TAB 行（旧规则会变成永不命中的死代码）；而且当年那样放是
      "标题被隐藏"逼出来的权宜之计（见上一条），不是设计意图。 */
.page-actions .page-help-btn {
  flex: none;
}

/* ⭐ 2026-09-28 豁免：二级页页头（.detail-head）里的标题**恢复显示**（乐哥：「另外其他
   二级页面标题这个，一起修了」）——
   09-25 隐藏标题是为**列表页**设的（「左上角那个菜单名」与侧栏当前项 + 顶栏标签讲同一件事）；
   但表单页/明细页/报销页统一用 `.detail-head`（`UI.headNav` 产出）当页头，
   它们的标题讲的是「**我在干什么**」（「新增达人」「编辑排期」「结算明细」），
   与菜单名不同义 —— 一并隐藏后页头只剩孤零零一个「返回」按钮
   （实测 #/schedule/1/edit 的 head 内可见文字仅「返回」、#/influencers/new 同样）。
   ⚠️ 只豁免 `.detail-head` 内：列表页 `.page-header` 的标题保持隐藏
      （09-25 决策不变；且 Tab 贴左依赖「标题容器被隐藏」，见 table.css 的同名规则）。 */
.detail-head .page-title { display: block; }

/* ⭐ 2026-09-28（第二轮）标题旁的 ? ——「小字说明收进问号」（乐哥拍板）
   ──────────────────────────────────────────────────────────────────────────
   触发：`headNav({ title, tip })` 时由 ui.js 挂在 `<h1 class="page-title">` 内。
   为什么图标要单独调大：`.th-note` 默认图标 13px（配 12-13px 小字），
   而页头标题是 24px —— 13px 的 ? 贴在旁边几乎看不见，视觉上像脏点。
   ⚠️ 只调 `.detail-head` 内的，卡内/表头的小字 ⓘ 维持 13px 不动
      （那里周围都是 12-13px 文字，调大会喧宾夺主）。
   ⚠️ 用 `margin-left: 6px`（不是默认的 3px）：大字号之间的呼吸要宽一档。 */
.detail-head .page-title .th-note--title {
  margin-left: 6px;
  color: var(--color-gray-400);
  vertical-align: 2px;         /* 视觉居中：图标略高于基线，与 24px 文字重心对齐 */
}
.detail-head .page-title .th-note--title svg { width: 17px; height: 17px; }
.detail-head .page-title .th-note--title:hover { color: var(--color-accent-text); }
/* 非活动标签的页面：**保留在 DOM 中**，仅不显示（勿改成 display:none 之外的做法，
   也不要移除节点 —— 移除就等于丢失用户没保存的状态） */
.page-stack > .page.is-tab-hidden {
  display: none;
}

/* ══════════════════════════════════════════════════════════════════════════
   ⭐⭐⭐ 2026-10-08 00:6x 切换标签：**内容左右滑动**（乐哥：「切换 tab 的时候，
      里面选项是左右滑动的，不是现在这样直接闪过去的」）
   ────────────────────────────────────────────────────────────────────────────────
   ▸ 为什么用**两个类名**（from-left / from-right）而不是一个 + JS 写内联 style：
      ① 内联 style 要动 `style.transform`，**动画结束后必须手动清掉**，
         否则 transform 会永久留在元素上 ⇒ 下次切标签的内容会被上次的方向污染
         （这是 transform 动画最常见的坑，本项目已栽过一次，见 `--app-scale` 那处）。
      ② 类名可以交给 CSS 令牌与媒体查询；JS 侧只需 add/remove，不碰样式细节。
   ⇒ app.js 的 `activateTab` 在显示后打类名，两帧后移除（此时动画已播完）。

   ▸ **方向语义**（别搞反）：
      `from-right` = 内容从**右边**进来 = 点的是**右边**的标签 ⇒ translateX 为**正**。

   ⚠️⛔ 为什么位移比淡入"重"（28px 而非 8px）：
      页面进场（`page-fade-in`）是"浮现"，位移要小；
      而**标签切换**是"横向换页"，用户要能一眼看出**方向** ⇒ 位移必须够大才读得出来。
      ⛔ 别把两者统一成同一个幅度 —— 那是"一套动效语言"的误区：
      **同一套语言里，不同动作的幅度本就该不同**（方向性动作大、无方向性动作小）。
   ⚠️ 时长用 --motion-page-enter(378) 还是更短的 --motion-base(155)？
      ⇒ 选 **--motion-base 的 1.6 倍 ≈ 250ms**（走新令牌 --motion-tab-slide）：
      标签切换是**高频**动作（用户会在几个页之间来回跳），378ms 会显得黏；
      但 155ms 又回到"闪"。250ms 是"能看出方向、又不拖沓"的平衡。 */
@keyframes tab-slide-in-from-right {
  from { opacity: 0; transform: translateX(28px); }
  to { opacity: 1; transform: translateX(0); }
}
@keyframes tab-slide-in-from-left {
  from { opacity: 0; transform: translateX(-28px); }
  to { opacity: 1; transform: translateX(0); }
}
/* ⭐⭐⭐ 2026-10-08 12:5x 宿主改造：同一对 keyframes 从「页面容器」挪到「tab 面板」
   ────────────────────────────────────────────────────────────────────────────────
   ▸ 乐哥同日两条需求：
     · 「一级菜单不要左右划入，就淡入就行」⇒ 页面级（activateTab）整段移除滑动，
       js 侧已删（见 app.js 注释）；`.page-stack > .page.is-tab-enter-*` 随之作废。
     · 「二级页面切换 tab…左右滑动+淡出（跟着 tab 滑动位置走）」
       ⇒ 滑动挪到这里：app.js 的 `bindPanelSwitch()` 给**面板容器**加
         `.is-panel-enter-from-left/right`，方向按"点的 tab 偏左还是偏右"算。
   ▸ 为什么复用这对 keyframes（而不是新写一对）：位移 28px / 时长 --motion-tab-slide /
     曲线三条标准完全同源，只是换了宿主 —— 一套语言一个来源，改参数仍是一处生效。
   ⚠️ 面板容器是 innerHTML 被替换的元素（不是 display 切换）⇒ 用 animation 才播得出来，
     且必须在"加类前强制 reflow"，否则同类名重复添加不会重启动画（js 侧已处理）。
   ⛔⛔ **fill-mode 必须是 `backwards`，不许 `both`/`forwards`** ——
   动画结束后若把最后一帧的 `transform` 留在元素上，会给出容器造出**层叠上下文**
   ⇒ 里面的下拉 / 行内菜单 / 日期面板会被容器外的元素盖住。
   这不是假想：本项目 2026-09-26 乐哥实报过「下拉被上层卡片遮住」，全站 6 页受影响，
   当时定的规矩就是「容器上的动画一律用 backwards」。
   （motion-system.test.js ⑪ 就是这条的门禁。） */
.is-panel-enter-from-right {
  animation: tab-slide-in-from-right var(--motion-tab-slide) var(--motion-ease-standard) backwards;
}
.is-panel-enter-from-left {
  animation: tab-slide-in-from-left var(--motion-tab-slide) var(--motion-ease-standard) backwards;
}

/* ⭐⭐⭐ 2026-10-08 13:1x 切 tab 期间：**抑制"整页重播进场"**（乐哥：「整个页面…都是淡入淡出，
   如果页面内切换 tab，那就是左右滑动」）
   ────────────────────────────────────────────────────────────────────────────────────
   🐞 实测根因（probe-table-tabs-panel-1008.js，10 个页面逐个点）：
     `.table-tabs` 那一族（货品 单品/组合/套组、达人 状态、排期 tab、库存 仓库、提成、费用匹配度、
     用户管理 部门、字典 类型、业绩 维度…）点一下会**把页头 / 工具栏 / 表格卡整块重建**，
     于是 `.page > *` 的 page-fade-in **自然重播一遍**（探针抓到
     `page-fade-in@page-header` / `@toolbar` / `@card` 三条）
     ⇒ 观感是"整页刷新了一下"，而不是"面板滑了一下"，与"页面内切 tab 只该有滑动"直接冲突。

   ✅ 做法：点击时给**最稳定的骨架 `.app-main`** 挂 `is-panel-switching`（JS 侧，capture 阶段、
      早于重建），在这一层里把进场动画关掉；**但必须豁免正在滑动的那块面板** ——
      否则抑制会连 `tab-slide-in-*` 一起压掉（滑动类同样是 animation）。
      ⚠️⚠️ **类必须挂 `.app-main` 而不是 `.page`**：本轮实测发现 `.page` 在切 tab 时
         **会随页面重渲染被换掉**，挂在它身上的类跟着旧节点一起消失 ⇒ 抑制静默失效
         （第一次实测：3 条 page-fade-in 照播）。`.app-main` 是骨架、不参与页面渲染。
      ⚠️ 豁免靠 `:not(.is-panel-enter-from-*)` 而不是"后加类覆盖"：
         新元素插入时**还没有**滑动类 ⇒ 抑制先生效（不会启动 page-fade-in）；
         随后 JS 加上滑动类 ⇒ 不再匹配这条 `:not()` ⇒ 滑动正常播。
         而"已插入时被抑制过"的元素不会因为样式变化而回头补播进场动画。

   ⚠️ 三组选择器必须都在（都从 `.app-main` 起、用后代选择器，保证重建后的新节点也被覆盖）：
     · `.page > *`          —— 达播台常规页
     · `.fin-main > *` / `.kq-page > *` —— 财务/门户、人事行政的**外壳下沉一层**那套
       （它们才是真正跑错峰的元素；`.page > .fin` 那条本来就是 `animation: none`）
   清理时机（JS 侧）：面板 animationend/cancel 时摘 + **420ms 兜底**（拿不到面板的早退路径
   也必须摘，否则之后所有的页面进场都会被压住）。 */
.app-main.is-panel-switching .page > *:not(.is-panel-enter-from-left):not(.is-panel-enter-from-right),
.app-main.is-panel-switching .fin-main > *:not(.is-panel-enter-from-left):not(.is-panel-enter-from-right),
.app-main.is-panel-switching .kq-page > *:not(.is-panel-enter-from-left):not(.is-panel-enter-from-right) {
  animation: none;
}
/* ⚠️ 系统开了「减弱动态效果」⇒ 不播（与 JS 侧 prefersReducedMotion 各守一道：
   JS 决定"要不要开始"，CSS 决定"能不能播"）。 */
@media (prefers-reduced-motion: reduce) {
  .is-panel-enter-from-right,
  .is-panel-enter-from-left {
    animation: none;
  }
}


/* 标签栏（2026-09-19 第二版）。

   ▸ 乐哥反馈的原话：「标签下面有一条线，另一个是颜色问题，都很突兀」。
     实测出来的两个根因，**都是为上一版改版新造的、全站只此一处的颜色**：
       · 槽底 #E3E6EA —— 全站唯一，比系统里最深的边框（--color-border #E5E8EC）还重
       · 槽底线 #D2D7DD —— 全站唯一，比任何边框都深 → 就是那条"多余的线"
     而系统里**早有一整套 tab 语言**（页面内 `.tabs`：选中＝主色文字＋底部主色下划线，
     选中态用 --color-accent-bg #EDF4FE，全站 10 多处）。窗口标签栏却新造了一套外来语言，
     所以看着"像个外人"。⇒ 本版改成**复用既有语言**，不再引入任何新颜色。

   ▸ 本版做法（方案 1「顶区一体」）：
     面包屑栏与标签栏**同为白**、去掉中间那条线，两行并成一个「顶部区」
     （都在回答"我在哪"）；激活标签 = 全站选中态同款浅蓝胶囊。
     结构性收益：整个页面**只剩一条横向分隔线**（在"顶部区"与正文之间）。

   ▸ ⚠️ 必须配套的两件事（实测出来的，不是可选）：
     ① `.topbar` 的 border-bottom 要**删掉** —— 线挪到这里来。否则两行之间多一条线，
        加上本条的线就是两条（实测现状正是 2 条：topbar 的 + 槽的深线）。
     ② 本栏必须 `position: sticky; top: var(--header-height)`。
        因为 `.topbar` 是粘顶的、本栏原先不粘 —— 实测滚 300px 后本栏跑到 top:-244，
        线随之上移，滚动时"归属"就断了。粘住之后两行**整体吸顶共用一个底边**。
        （实测：祖先链无 overflow 约束，sticky 可用；z-index 取 1099 < topbar 的 1100，
         保证顶栏的全局搜索面板 / 个人中心下拉弹出来时**不会被本栏切掉**。）
   ▸ 2026-09-19 结构重排：面包屑那一行已撤，**本栏升为第一行**（`top: 0`）。
     收益：内容区顶边 102px → 56px。高度沿用 `--tabs-bar-height`，**没有新造尺寸**
     —— 这样 analytics.js 的 `headerHeight()`（读 --header-height 当吸顶高度）无需改动即正确。
     ⚠️ 上面那句里的 56px 是那次重排时的值，之后又调过两次（56→60→52）。
        当前值以 design-tokens 的 `--tabs-bar-height` 为准，**这里不复述具体数字**
        （注释里写死数值会过期，本项目踩过"注释与代码打架"的坑）。
   ▸ 2026-09-20 高度收回：乐哥「最上面的标签，是不是太大，太占位置了」。
     实测依据与最终取值统一写在 design-tokens 的 `--tabs-bar-height` 注释里（只写一处）。
   ▸ 本栏拆成三个并列容器（见 index.html），**动态区与静态区分开**：
       `__left`  移动端菜单开关（静态，从原顶栏搬来）
       `__tabs`  标签（动态，renderTabsBar 每次重建它）
       `__tools` 通知/刷新/关闭（静态，全局委托绑定 → 重建容器也不丢事件）
     ⚠️ 拆分是必须的：原先 `renderTabsBar` 做 `bar.innerHTML = html` 整体重建，
        通知放进去会被每次切页冲掉（事件一并丢失）。
   ========================================================================== */
.page-tabs-bar {
  /* ⭐⭐ 2026-09-25 Gashora 骨架落地：顶栏从「内容区内的第一行」改为**贯穿全宽的浮起圆角卡**。
     参考图（乐哥指定）：最上面一行左右贯穿 —— 左上 logo、中间留白、接着搜索框、
     右边通知、最后头像胶囊框。
     改动三处：
       ① `position: sticky; top:0` → `fixed`（脱离文档流、相对视口定位 ⇒ 才能横跨侧栏上方）；
          配 `.app-main` 的 `margin-top: calc(frame-gap + 顶栏高 + frame-gap)` 给它让位。
       ② 加四周留白（top/left/right = --frame-gap）+ 圆角 16 + 柔和阴影 ⇒ 从"贴边一条"变成"浮起一张卡"。
       ③ 去掉 `border-bottom`（那条 #E5E8EC 分隔线属"贴边条"形态，卡片形态靠阴影分界）。
     高度 52 → 96 → 84 → **72**（2026-09-26 A6 定稿，历史链见 design-tokens 的 --tabs-bar-height）。
     ▸ 高度引用 `--tabs-bar-height`（当前 72px）—— 本栏高度的唯一来源。
     ▸ `--header-height`（吸顶高度）在骨架落地后语义改变：= frame-gap + --tabs-bar-height（当前 86），
       供 analytics.js 的 headerHeight() 定位分析页目录条（贴在顶栏下沿）。
     ⭐ 2026-09-26 A6：栏体改**半透明毛玻璃**（乐哥：「顶栏做一点透明，正文内容滚上去时更好看」）。
       底 = 72% 白 + backdrop-filter 18px 模糊/1.7 饱和 —— 正文滚到栏后时透出隐约色块。
       ⚠️ 栏内各段（__left/__tabs/__tools）**不许再有实色底**，否则毛玻璃上贴白块（见 __tools 注）。
       ⚠️ backdrop-filter 在内容未滚到栏后时不可见，验证时必须**先滚动再截图**（原型脚本都这么干）。 */
  display: flex;
  align-items: center;
  gap: 0;                             /* 间距交给内部各段自己管，避免空容器仍占 gap */
  height: var(--tabs-bar-height);   /* 唯一来源：令牌（当前 60px）*/
  flex: 0 0 auto;
  /* ⭐ 2026-10-06：左右内边距 **40**（= --page-padding），让面包屑与内容区文字/卡片**左对齐**。
     ⚠️ 用 24 会与内容错 16px —— 那正是"重心怪"的来源之一（顶栏元素没挂在内容基线上）。
     ⭐⭐⭐ 2026-10-08 21:3x 右侧同步改用 `--page-padding-right`：
     顶栏与内容区**必须同位**，否则右端工具组会比卡片右缘多压 15px（滚动条槽那条，
     见 design-tokens.css 的 `--frame-gutter`）。 */
  /* ⚠️ 四值：上 右 下 左（三值的第二值是"左右对称"，会把左侧也减掉滚动条槽） */
  padding: 0 var(--page-padding-right) 0 var(--page-padding);
  /* ⭐⭐⭐ 2026-10-07 顶栏改**悬浮**（乐哥：「顶栏不顺畅，做悬浮吧，悬浮就自然融入不是吗」）
     ────────────────────────────────────────────────────────────────────────
     ▸ **根因不是样式，是定位方式**：
       `position: fixed` 把顶栏**钉在视口**上，下方内容永远从它"底下"流过、
       被硬生生**切断** —— 滚动时看到的是"内容从一条缝里钻出来"，不是"从栏下穿过"。
       乐哥说的"不顺畅"就是这个。改 `sticky + top:0` 后，顶栏**回到文档流**、
       自己占 `--tabs-bar-height` 的高度 ⇒ 内容正常流到它下方 ⇒ 滚动时**从它底下穿过**，
       配合下方毛玻璃就是"自然融入"。
     ▸ 因此 `.app-main` 的 `margin-top: var(--tabs-bar-height)` **已删除**
       —— fixed 脱流时必须靠它让位；sticky 自己占位，再留 margin 会多出一段空档。
       ⛔ 两者成对：改回 fixed 就必须把那个 margin 加回来。
     ⚠️ **`.app-main` 的 `overflow: clip` 不影响 sticky** —— 实测过：
       `overflow: clip` **不创建滚动容器**（与 `hidden` 不同）⇒ sticky 仍相对**视口**吸附，
       实测 `position: sticky` /滚动后 `top: 0` 吸附正常。
       ⛔ 曾一度把顶栏**移出 `.app-main`** 做成 `.app-shell` 的兄弟，**已回退** ——
         `.app-shell` 是 `display:flex` 单行，移出后顶栏成了并列的第三列，
         被压到 378px 并把 `.app-main` 挤到 x=630，布局整个乱掉。
         ⇒ **顶栏与内容卡必须同列上下**（见 index.html 处的注释）。
     ⛔ 别改回 fixed：会重新出现"内容被顶栏切断"的不顺畅感（本次要修的就是它）。 */
  position: sticky;
  /* ⭐⭐ 2026-10-06 顶栏（与下面 .app-main 拼成**一整张浮起的白卡**）：
     ▸ **只在内容列内**（left = 侧栏宽）—— 不横跨侧栏上方（那是 09-28 的形态）。
     ▸ right = `--frame-gap`（与内容白卡右缘**同一条线**，两者等宽）。
       ⚠️ sticky 下 `left/right` **依然有效**（sticky 相对视口横向定位），
          不能删—— 删了顶栏会横跨到侧栏上面去。
     ▸ 白底 + **上圆角 16**（下方 .app-main 是下圆角 16）⇒ 上下合成一张卡。
     ▸ **无底线**：同一张卡内部不该有分隔线。
     ⚠️ 高度仍是 --tabs-bar-height（唯一来源），.sidebar 的品牌区与它共线。 */
  /* ⭐⭐⭐ 2026-10-07「被侧栏悬浮包裹」（W3 档）：left / right 改为与 `.app-main` **同源**。
     ⚠️ 必须同源 —— 顶栏与下方内容白卡是**一张卡的上下两段**（拼成一张），
        两者的左右缘与圆角必须逐值相同，否则交界处会错位、出现"错开一条"的裂缝。
        `.app-main` 现在是 `margin-left: calc(var(--sidebar-width) + var(--shell-inset))`
        + `margin-right: var(--shell-inset)` ⇒ 顶栏的 left/right 逐值照抄即可。 */
  top: 0;
  /* 🔴🔴 2026-10-08 22:5x **删掉横向约束 `left/right`**（乐哥：「我切换 tab 的时候，
     红框那个路径会左右移动，你看下，估计是全局问题」）
     ────────────────────────────────────────────────────────────────────────────────
     ▸ 真机复现（逐帧采样，1015×423 与 1440×900 都复现）：
         点 tab 的瞬间，面包屑 left `272 → 275 → 273`、顶栏 left `232 → 235 → 233`，
         而**蓝框 clientWidth 全程不变**（760 / 1185）—— 说明不是"内容变宽"，是
         **顶栏这个 sticky 盒子被横向推开了**。
     ▸ 同时 `app-main.scrollWidth − clientWidth` 在切换期间 `0 → 3 → 1`：
         切 tab 的**面板滑动动画**会让容器出现几像素的**可滚溢出**（逐帧扫描"越过右缘的
         元素"是空的 ⇒ 不是某个元素真的越界，是动画期间子像素/变换带来的可滚溢出）。
     ▸ 机制：`position: sticky` 的 `left/right` 是"相对 **scrollport** 的夹取"，
         而 scrollport 的判定要用到**可滚溢出**；溢出一变，盒子就被按约束推开同样多像素。
         面包屑在顶栏里 ⇒ 跟着走。他看到的"路径左右移动"就是这条链。
     ▸ 修法：**只留 `top: 0`（纵向吸附），删掉横向约束**。
        为什么删得掉：顶栏现在是 `.app-main`（flex **column**）的**子元素**，
        宽度由容器 `align-items: stretch` 给出、左侧位置由 `.app-main` 自己的
        `margin-left` 决定 ⇒ 横向根本不需要 sticky 参与。
        ⚠️ 旧注释里那句"删了会横跨到侧栏上面去"属于**它是 `.app-shell` 兄弟**那版布局
           （见上方"曾移出 `.app-main` 又回退"的记载）—— 那一版早已不成立；
           本轮真机复测（6 种窗口/缩放/折叠组合）：删掉后顶栏四边仍与蓝框内侧逐值相同，
           面包屑 left 全程恒定。
     ⛔ 想加回来之前先看这条：横向 sticky 约束 = 一次"切 tab 就左右抖"的源头。
     ⛔ 也别想着给 `.app-main` 换 `overflow-x: clip` 来绕 —— 一轴 clip + 另一轴 auto
        时 clip 会退化成 hidden（规范如此，实测 computed 仍是 hidden）。 */
  margin: 0;
  /* ⛔ 不写 flex/flex-shrink —— 它在 `.app-main`（flex **column**）内，
     默认 `align-items: stretch` 已给出满宽，再写 flex 只会引入纵向收缩风险。 */
  /* 上圆角与 `.app-main` 的四角同值（10px）⇒ 上下合成一张完整的浮岛卡。
     ⛔ 别写回 `--radius-lg`(16)：内容区现在是 10px，两段不同值会看出"接缝"。 */
  border-radius: var(--shell-inset-radius) var(--shell-inset-radius) 0 0;
  z-index: var(--z-sticky);
  /* ⭐⭐ 2026-10-06 深夜（乐哥：「顶栏和悬浮的透明效果没了，要加上」）**恢复毛玻璃**
     ────────────────────────────────────────────────────────────────────────
     ▸ 根因：10-06 壳层定稿时把background 写成 `var(--color-surface)`（纯白）+
       `box-shadow: none`，**覆盖掉了 9-26 A6 定稿的半透明毛玻璃**
       （乐哥当时原话：「顶栏做一点透明，正文内容滚上去时更好看」）。
       ⇒ 不是"从未做过"，是**被这一刀盖掉了**。
     ▸ 为什么用 `--glass-*` 而不是 `--liquid-*`：
       · `--liquid-*`（透白 0.40→0.15 + 6 层 inset + 44px 投影）是给**贴顶那一刻**
         的动态玻璃用的（`.page-header--has-tabs.is-stuck`），用在常驻顶栏会太重、
         且常态下那些 inset 高光没有内容穿过，看不出来。
       · `--glass-*`（透白 0.70→0.46 + blur20/saturate180 + 一层 8px 投影）才是
         顶栏/侧栏岛/表单操作岛共用的那套「克制玻璃」，与 9-26 的原始意图一致。
     ⚠️ 效果前提：顶栏下方是 `.app-main` 白色卡 ⇒ **不滚动时玻璃看不出效果**
       （底下就是白的）。要看到"内容从栏下穿过"的层次必须先滚动。
       这不是 bug，是白底内容卡 + 半透明栏的必然结果（与 9-26 首次实现时相同）。
     ⛔ 不改 --glass-* 令牌本身：它被表单操作岛（pages.css 的 .sf-actions）共用，
       改令牌会连带改那里（design-tokens 注释已警告）。 */
  background: var(--glass-bg);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  /* ⭐⭐ 2026-10-07（乐哥：「这个顶栏，不要加任何阴影，就玻璃透明行了」）
     ────────────────────────────────────────────────────────────────────────
     ▸ 去掉 `--glass-lift`（**外投影** `0 8px 26px rgba(16,24,40,.14)`）——
       那道投影压在下方内容卡上，视觉上"顶栏把内容压出一道暗影"，
       与"内容从栏下穿过、要自然融入"（今天改成sticky 的初衷）**自相矛盾**：
       既然要融入，就不该在内容上留投影。
     ▸ 保留 `--glass-lit`（**上缘 1px 内高光** `inset 0 1px 0 rgba(255,255,255,.95)`）
       —— 它不是投影，是玻璃**上缘的亮边**，属于玻璃质感本身；
       去掉它顶栏会"平"掉，玻璃的立体感就没了。
     ⇒ 所以这里是「**只去掉外投影，保留内高光**」，不是把 box-shadow 整个清空。
     ⚠️ 别写成 `box-shadow: none`：那会把内高光也带走，顶栏变成一块纯半透明色片。
     ⚠️ 别改 `--glass-lift` 令牌本身：它被表单操作岛（.sf-actions）共用，
       改令牌会连带改那里（design-tokens 注释已警告）。 */
  box-shadow: var(--glass-lit);
  border-bottom: none;
  /* 右侧留缝由上面的 `right: var(--shell-inset)` 负责（sticky 相对视口定位），
     ⛔ 这里不要再加 `margin-right`（在 `.app-main` 内时 `right` 已给出，会变成双份缝）。 */
  /* ⭐⭐⭐ 2026-10-07「被侧栏悬浮包裹」：上圆角改为与 `.app-main` **同值**。
     ⚠️⚠️ 实测坑（第一版栽了）：**同名元素有两处圆角声明** ——
        我先改的是「top/left/right 那一块」里的border-radius，
        但**真正生效的是下面这一行**（同 specificity、靠文档顺序取胜）⇒
        真机实测圆角仍是 16px，被我漏掉了。
     ⇒ 教训：改某个元素的样式前，先 `grep` 该属性名，确认**只有一处**；
        有多处就说明有覆盖，必须找到**实际生效的那处**（通常是靠后的）。
  ⛔ 别写回 var(--radius-lg)(16)：内容区现在是 10px，两段不同值会看出"接缝"。 */
  border-radius: var(--shell-inset-radius) var(--shell-inset-radius) 0 0;
  /* ⚠️ 横向滚动已下移到 `__tabs` —— 本栏若自己 overflow，会连 `__tools` 一起滚走 */
  overflow: visible;
}
.page-tabs-bar__left {
  display: flex;
  align-items: center;
  flex: 0 0 auto;
  /* ⭐ 2026-09-25 Gashora 骨架落地：菜单按钮与 logo 之间 12px。
     ⚠️ 只此一处定义（同选择器的样式都写在这里，不要另起块 ——
        两块同选择器靠"谁在后面"决定生效，是本项目反复踩过的隐性依赖）。 */
  gap: 12px;
  /* ⭐⭐ 2026-10-06：**固定宽删掉了** —— 原算式 `--sidebar-width + --frame-gap − 24 + 16`
     是"让标签区起点 = 正文列左缘 + 16"的，**只为多标签服务**。
     标签去掉后它的唯一作用消失；且面包屑要**与内容区左缘对齐**，靠的是
     顶栏自身的 `padding-left: --page-padding`（40）+ 顶栏 left = 侧栏宽 ——
     算式见 .page-tabs-bar 处。这里若再留一个固定宽，面包屑会被推到 300+ 去。
     ⚠️ 折叠态（64px 侧栏）桌面端不可达，故不再按展开态硬算宽度。 */
  width: auto;
  min-width: 0;
}

/* ══ ⭐⭐ 2026-10-06 桌面端**去掉多标签**（乐哥：「标签栏我几乎不用，可以不要」）══
   ▸ 手机端本来就没有（≤768px 块里已 `display:none`）⇒ 本次是**把那套形态提到桌面**，
     不是发明新形态。
   ▸ 多标签的**逻辑一律保留**（`tabs[]` / `renderTabsBar` / `equalizeTabWidths` / 页面保活
     语义全在），只把可视条隐藏 ⇒ **要回退，删掉本节即可**（这是"可逆"的关键）。
   ⚠️「刷新当前页 / 关闭全部标签」两个按钮只对标签有意义，一并隐藏。
   ⚠️ 工具组右推改由 `__tools` 自己的 `margin-left:auto` 承担（原来靠 `__tabs` 是 flex:1），
     那条已写在上方 `.page-tabs-bar__tools` 主块里（**不要**在这里再写一次 ——
     同一选择器两块靠"谁在后面"决定生效，是本项目反复踩过的隐性依赖）。 */
#page-tabs-tabs,
#pt-refresh,
#pt-close-all { display: none; }

/* ⭐⭐ 2026-10-06 顶栏「页面定位」= **面包屑**（一级分组 › 当前页）。
   参考图的顶栏就是「logo ／ 页面定位 ／ 右侧工具」三段，中间那格是它。
   ⭐ 数据是**现成的**：路由表每条都有 `crumb`（如 `['达人信息','达人列表']`），
      且 `renderTopbar(crumb)` 至今仍收着这个参数，只是 09-19 撤了渲染 ⇒ 恢复成本≈0。
   ⚠️ 左对齐交给顶栏自身的 padding-left（= --page-padding 40）
      ⇒ 面包屑左缘 = 侧栏宽 + 40 = 内容区卡片的左缘（"挂在同一条竖线上"）。 */
.page-tabs-bar .bc-crumb {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  color: var(--color-muted);
  white-space: nowrap;
  overflow: hidden;
}
.page-tabs-bar .bc-crumb__sep { color: var(--color-gray-300); font-size: 12px; }
.page-tabs-bar .bc-crumb__cur {
  color: var(--color-fg);
  font-weight: var(--font-weight-medium);
  font-size: 14px;
}
/* ⭐⭐⭐ 2026-10-07 22:10 面包屑可点跳转（乐哥：「页面上方的路径，需要点击快速跳转功能，
   比如这个我点击达人列表，就能直接跳转到达人列表页」）
   ────────────────────────────────────────────────────────────────────────────────
   ▸ 用 `<button class="bc-crumb__item bc-crumb__link">` 而**不是** `<a href="#/…">`：
        本项目路由是 hash，写 `<a>` 会被浏览器当外链（新标签打开 / 刷新丢 SPA 状态）——
        统一走项目自己的 `navigate()`（见 app.js 的 renderCrumb）。
   ▸ 视觉语言：**hover 才显出"可点"**，常态与原来一样是灰字 ⇒ 顶栏不变得更花。
     ⛔ 别给常态加下划线/蓝色 —— 那会让「达人信息」这种**不可点的分类层**也像能点，
        违反本项目「看着能点、点不动」那条禁令（见 app.js 注释）。
   ⚠️ `background: none` + `font: inherit` 是 <button> 的必要重置 ——
      否则浏览器默认样式会给它加灰底 + 变字体，面包屑会「鼓出一块」。 */
.page-tabs-bar .bc-crumb__link {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
  /* ⚠️ 必须走令牌（`tests/radius-tokens` ③ 硬判「border-radius 一律写令牌」）——
     `--radius-sm` = 4px，与我第一版写的裸值同值。第一版被门禁拦下是对的。 */
  border-radius: var(--radius-sm);
  transition: color var(--motion-fast) var(--motion-ease-standard),
    background-color var(--motion-fast) var(--motion-ease-standard);
}
.page-tabs-bar .bc-crumb__link:hover {
  color: var(--color-accent-text);
  background: var(--color-accent-bg);
}
/* 键盘可达：Tab 聚焦时也要看得出「这是个可点的」 */
.page-tabs-bar .bc-crumb__link:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}


.page-tabs-bar__tabs {
  /* ⭐ 2026-09-26 A6：**标签页区恢复显示**（乐哥：「这样之前的标签（多标签导航）还能放进来」）。
     恢复方式 = 只删掉 9-25 加的那条 `display: none`（DOM 与交互逻辑一直都在，
     等宽由 app.js 的 equalizeTabWidths 统一，超出本区横向滚动、不显示滚动条）。
     ──────────────────────────────────────────────────────────────────────────────
     历史：9-25 Gashora 落地时按乐哥「顶部标签可以不要了」隐藏（只 display:none、
     不删 DOM 不停逻辑，当时注释就写明"将来恢复删掉这一条即可"）。
     ⚠️ 恢复后本区重新成为 flex:1 的弹性项（负责把工具组推到右边）⇒
        `.page-tabs-bar__tools` 的 margin-left:auto **必须删掉**（两个 auto 会平分空白，
        踩过 377px 空白的坑；门禁 C2 守着这一条）。
     ⭐ 标签区起止（乐哥拍板）：起点 = 正文列左缘 + 16（由 __left 固定宽实现，见那边算式）、
        终点 = 工具区左缘；1440 宽下实测可放 8 个标签，更多时区内横滚。 */
  display: flex;
  align-items: center;
  /* 标签间距 6（2026-09-19 晚 4 → 6）—— 乐哥反馈顶栏"挤"。
     ⭐ 实测：挤的主因是间距不是高度，4px 让相邻标签几乎贴在一起。 */
  gap: 6px;
  flex: 1 1 auto;
  min-width: 0;                       /* 允许收缩，否则标签把工具组挤出屏幕 */
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;              /* 标签多了横向滚动，但不显示滚动条 */
}
.page-tabs-bar__tabs::-webkit-scrollbar { height: 0; }

.page-tab {
  position: relative;
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;             /* 内容在标签内居中 —— 见下方"等宽"的说明 */
  gap: 6px;
  /* 标签高 30（2026-09-26 A6：乐哥「尺寸可以小点」；原型逐轮拍板，1440 宽实测 8 个全放下）。
     ────────────────────────────────────────────────────────────────────────────────
     历史：34（09-20 定，配 52 栏）→ 本值 30（配 72 栏：上下各留 21px，比图标盒 40 小一档，
     标签是"次级导航"、图标盒是"主控件"，体量上就要分出层级）。
     ⚠️ 本值必须与 --tabs-bar-height **成对**调：两次历史改版都是两者一起动，
        只改一个会让标签在栏里显得过小或过挤。 */
  height: 30px;
  padding: 0 8px;                      /* 左右各 8：配合栏的 24 让图标落在 32px 线上 */
  border: none;
  /* 胶囊圆角（= --radius-seg，全站分段控件 / Tab 项同款）。
     上一版是「6px 6px 0 0 + 底部两个凹角」的 Chrome 页签形态，本版不再需要两者同值。 */
  border-radius: var(--radius-seg);
  font-size: 12.5px;                   /* A6：var(--font-size-sm)=13 → 12.5（乐哥「尺寸可以小点」）*/
  color: var(--color-muted);
  white-space: nowrap;
  cursor: pointer;
  min-width: 104px;                    /* ⭐ 2026-09-26 自适应下限：保证 **4 字菜单名完整可读**
                                          （固定开销 47px + 13px×4字 = 99，取 104 余量）。
                                          ⚠️ 与 app.js 的 MIN_TAB 同值（门禁锁）。
                                          ⛔ 别为了"多塞几个"压小它 —— 实测 72px 时 12 个标签
                                             全截成"业..""达.."，读不出名字（09-26 试过）。
                                             放不下的交给标签区横向滚动。 */
  max-width: 200px;                    /* 防长页面名把标签撑得很宽（与 JS 的 CAP_TAB 同值，门禁锁） */
  transition: background var(--motion-fast) var(--motion-ease-standard),
    color var(--motion-fast) var(--motion-ease-standard);
}
/* ⚠️ 标签宽度**由 JS 统一**（app.js 的 equalizeTabWidths），不靠这里的 min-width 撑。
   ────────────────────────────────────────────────────────────────────────────────
   乐哥 2026-09-19：「标签因为文字长度不一样，导致标签长度不一致，看着很奇怪」。
   实测现状：首页 66 / 我的报销 94 / 成本总览 94 / 月度利润明细 122 —— 参差不齐。
   做法：多标签时**等宽**（测出当前打开这几个里最宽的自然宽度，全部设成同一个宽度），
        但宽度的**取值自适应可用空间**（2026-09-26 乐哥：「别固定8个了，做成自适应宽度吧，
        有的浏览器页面缩小后，应该能显示更多」）：
          · 空间够 → 贴合最宽的自然宽（不多占）；
          · 空间不够 → 等比例压缩、能显示更多个；压到 min-width 下限后仍不够 → 横向滚动。
        ⚠️ 具体算式在 app.js 的 equalizeTabWidths（含 MIN/CAP 两个常量，与本节同值）。
     · 为什么不能用 CSS：flex 子项宽度天然由内容决定，"取兄弟节点的最大宽度"
       CSS 没有对应表达 —— `1fr` 是**平分容器**（5 个标签会各撑到 217px，喧宾夺主），
       固定 min-width 又会被更长的名字打破（菜单名从 2 字到 6 字都有）。
     · 为什么加 `justify-content: center`：等宽之后短标签（"首页"只有 66px 的内容）
       若靠左对齐，右侧会空出一大块 —— 与"单标签右边空 26px"是同一个病。
       居中后每个标签的"图标 + 文字"整体居中，长短名视觉重量一致。
       （这条对单标签无影响：那时宽度 = 内容宽，居中与靠左等价。）
     · 单标签不合宽（见 .page-tab--last）—— 只有一个标签时不存在"不一致"，
       且它不显示 ×，贴合内容才不空。 */
.page-tab:hover { background: var(--color-gray-50); color: var(--color-fg); }
.page-tab__label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  /* ⭐ 2026-09-26 自适应宽度配套：flex 子项默认 `min-width: auto`（= 文字的最小内容宽），
     不加这条，标签被压窄时文字**不会**用省略号收尾，而是把标签顶宽/溢出 ——
     自适应压缩就完全失效（这是 flexbox 省略号的经典前提，别删）。 */
  min-width: 0;
}
/* 标签左侧的菜单图标 —— 相当于浏览器的 favicon：多个标签并存时靠它快速辨认。
   非激活用次级灰（不抢视线）、激活用主色（与当前页呼应）。 */
.page-tab__ico {
  display: inline-flex;
  flex-shrink: 0;
  color: var(--color-meta);
}
/* A6：标签缩到 30px 高后，图标同步收到 13px（svg 自带 16px 属性值，这里覆盖） */
.page-tab__ico svg { width: 13px; height: 13px; }
.page-tab.is-active .page-tab__ico { color: var(--color-accent-text); }

/* 激活标签：全站选中态同款浅蓝胶囊 + 主色文字。
   ⚠️ 与上一版的关键区别：
      · 不再「抬高 4px + 与内容区同色 + 盖住分隔线」—— 那是 Chrome 页签的做法，
        要求"激活标签的颜色 == 内容区的颜色"；现在改成胶囊，这条约束不再适用。
      · 颜色取 --color-accent-bg(#EDF4FE) / --color-accent-text(#0064E0)，
        与页面内 `.tabs` 的选中态、以及全站 10 多处的选中态**同一套语言**。
      · 高度与未激活**相同**（32px）：胶囊靠底色区分，不靠体量。 */
.page-tab.is-active {
  background: var(--color-accent-bg);
  color: var(--color-accent-text);
  font-weight: var(--font-weight-medium);
}

.page-tab__close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 12px;                         /* A6：16 → 12（标签 30px 高的配套，见 .page-tab 注）*/
  height: 12px;
  flex-shrink: 0;
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--color-meta);
  line-height: 1;
  cursor: pointer;
  /* ⚠️⚠️ × **常显**（2026-09-19 乐哥实报「上面的标签页关闭的 x 看不到了」，本轮改）。
     ────────────────────────────────────────────────────────────────────────────
     上一版是"非当前页的标签平时 opacity:0，hover 才出现"（为了降噪）。两个后果：
       ① 乐哥直接反馈「看不到」—— 大多数标签不是当前页，所以大多数标签没有 ×；
       ② ⭐ 更隐蔽的一个：**它占位但不可见**。× 是 flex 子项（16px），
          即使 opacity:0 也照样占 16px 主轴空间 ⇒ 居中算法把它算进去，
          而眼睛看不见它 ⇒ **非激活标签的内容视觉上偏左**。
          实测：「我的报销」标签等宽 117px，可见内容落在 [8, 86]，
          右侧空 31px 里其实藏着 16px 的隐形按钮 —— 看着就像"没对齐"。
          等宽之后这个错位感更明显（短标签"首页"可见内容只有 50px、右侧空 45px）。
     ⇒ 改成常显但**弱化**（opacity .5）：既让人看得见能关、又不像激活态那样抢视线，
        顺带把"隐形占位"这个观感问题一并解决。
     ⚠️ 对比度：.5 的 --color-meta(#8595A4) 在白底上约 1.9:1 —— 作为"可点的次级控件"
        够用（与全站 .icon-btn 的弱化思路一致），hover 时提到 1 并加灰底。 */
  opacity: 0.5;
  transition: opacity var(--motion-fast) var(--motion-ease-standard),
    background var(--motion-fast) var(--motion-ease-standard);
}
.page-tab:hover .page-tab__close { opacity: 1; }
.page-tab.is-active .page-tab__close { opacity: 1; }
.page-tab__close:hover { background: var(--color-gray-300); color: var(--color-fg); }
.page-tab__close svg { display: block; }
/* 最后一个标签（= 全站只剩这一个页面）：不给关闭按钮（关掉就没页面了）——由 app.js 加该类。
   ⚠️⚠️ 同时必须**撤掉 min-width: 92px**（2026-09-19 乐哥实报：
      「只有一个的时候文字也没居中，右边还空了」）。
      多标签时那条 min-width 是"防标签被挤成一小团"的保护，有用；
      但**唯一标签**本来就不显示 ×、宽度只剩 [图标 + 文字]，
      min-width 会把右边撑出一块**没有任何内容的空白** —— 实测：
        「首页」标签 92px、内容 50px ⇒ 右侧空 26px（肉眼就是"文字靠左、右边缺一块"）；
        「我的报销」标签 92px、内容 56px ⇒ 空 20px。
      撤掉后宽度贴合内容，左右留白各 8px，偏心率实测 = 0。 */
.page-tab--last { min-width: 0; }
.page-tab--last .page-tab__close { display: none; }

/* 标签栏右端的固定操作区（刷新 / 关闭全部），永远贴在右边不随标签滚动。
   ⚠️ 底色必须与标签栏一致（现在都是白）。上一版是 --tab-slot-bg，本版该令牌已删除 ——
     若不改这里，var() 解析失败会让底色变透明，横向滚动时露出一块"镂空"。 */
/* 右侧工具组：刷新 / 关闭全部 / 通知 —— 通知**在最右**（乐哥 2026-09-19：「通知按钮能放最右上角好点」）。
   ⚠️ 本组现在是 index.html 里的**静态节点**（原来由 renderTabsBar 生成），
      因为通知放进去后必须扛得住 renderTabsBar 的整体重建。
   ⚠️ 这里**不要**再写 `margin-left: auto`：本组是 `__tabs`（flex:1）之后的兄弟，
      已经被自然推到右侧。曾经踩过一个坑 —— 本组自带 auto、通知组又加一个 auto，
      两个 auto 把剩余空间**对半分**，结果通知停在半空、右边空出 377px（"空白一大片"）。
      现在由 `__tabs` 独占弹性空间，只有一处 auto 语义。 */
.page-tabs-bar__tools {
  display: flex;
  align-items: center;  /* ⭐ 2026-09-26 A6：标签页区恢复显示 ⇒ `margin-left:auto` **删掉**（回到上方警告的老规则：
     __tabs 是 flex:1 弹性项时不许有第二个 auto，两个 auto 会平分空白 → 右侧空 377px 的坑；
     门禁 C2 已自动切回"__tabs 可见时禁止"分支）。
     历史：9-25 __tabs 隐藏时这里必须自己 auto 推右；A6 恢复标签后职责归还 __tabs 的 flex:1。 */
  /* 工具间距 6（2026-09-19 晚 2 → 6）—— 原 2px 是「挤」最明显的地方：
     刷新 / 关闭 / 搜索 / 通知 四个控件两两只隔 2px，看着像粘在一起。
     ⚠️ 放宽后总宽增加 3×4 = 12px，已实测各视口下标签区余量仍够（见门禁测试）。 */
  gap: 10px;
  flex: 0 0 auto;
  padding-left: 10px;
  /* ⭐⭐ 2026-10-06 多标签去掉 ⇒ `__tabs`（flex:1）**已不在参与弹性分配**
     ⇒ 本组必须自己 `margin-left:auto` 才能推到最右；且此时顶栏里**只有这一个 auto**
       （旧禁令的前提是"__tabs 也是弹性项"，那个前提随标签格隐藏消失了）。
     门禁 C2 会自动按 `__tabs` 是否隐藏切分支。 */
  margin-left: auto;
  background: transparent;
}
/* ⭐⭐ 全局「品牌」切换器（多品牌 P1，2026-10-13）—— 标签栏工具组**最左**那个。
   ────────────────────────────────────────────────────────────────────────────
   形态：**彩色圆点 + 原生 select**。
   ⛔ 刻意不做成自定义下拉：本项目 `UI.monthPicker` 那种自定义弹层要自己处理
      定位 / 外点关闭 / 键盘 / 移动端 —— 而这里只需"选一个品牌"，
      原生 `<select>` 天生有这些能力且**零定位风险**（顶栏是 sticky，
      自绘弹层在窄视口容易溢出）。改形态前先想清楚这些是否真的不够用。
   ⚠️ 尺寸跟工具组的图标按钮**同一档**（`--control-height-sm`）：
      顶栏里混着 36 和 44 两种高度会显得参差（本项目在"标签栏按钮 40"上踩过）。
   ⚠️ 宽度上限：品牌名将来会更长（"XX药业"），`max-width` + 省略号兜住，
      否则长名字会把整组工具往左挤、搜索入口被压掉。 */
.brand-switch {
  display: inline-flex;
  align-items: center;
  /* 与通知/搜索之间那道分隔由工具组的 gap 负责，这里不再加边距 */
}
.brand-switch:empty { display: none; }   /* 清单没取到 / 非达播板块：整块不占位 */
/* ⭐ 2026-10-13：内容从原生 `<select>` 换成自绘品牌选择器（`.bpick`，见 components.css）——
   原生 option 放不了品牌头像，而乐哥要「头像 + 名称」一起展示（含全局筛选）。
   ⛔ 原来的 `.brand-switch__dot` / `.brand-switch__sel` 已整条删掉（不再有任何 JS 产出它们；
      留着会变成"看着像在用"的死样式，下一个人改半天不生效）。
   ⚠️ 顶栏里必须用**顶栏那一档 40** —— 设计规范 §控件高度：「顶栏与侧栏自成一套，
      顶栏控件 **40×40**、侧栏菜单行 46，由各自作用域规则自保，本令牌管不到」。
      顶栏这一排的邻居实测全是 40（`.gs-entry` 40 / 顶栏 `.icon-btn` 40 / 头像入口 40 / 标签 40），
      只有它写 34 ⇒ 比邻居矮 6px，一行里"没长齐"。
      ⛔ 别再写回 `--control-height-sm`（34）：那是**列表工具栏 / 表格行内**那一档。
      这条与 2026-09-19 `.gs-entry` 34→40 是**同一个坑**（当时注释原话：
      "原来 34 比邻居矮 2~4px，一行里看着'没对齐、没长齐'"）—— 门禁 `topbar-control-height.test.js`。 */
.brand-switch .bpick__trigger {
  height: 40px;
  font-size: var(--font-size-sm);
  max-width: 132px;
}

/* 顶栏里的图标按钮：**回归全站基准 36**（2026-09-20）。
   ────────────────────────────────────────────────────────────────────────────
   昨天（09-19 晚）这里是 40，理由只是"跟着顶栏一起加高"。本轮实测发现它其实是个
   **全站孤例**：`.icon-btn` 的基准是 36（layout.css 321 行），100 处在复用，
   只有标签栏把它放大到 40 —— 这正是乐哥说"标签太大、太占位置"的一部分。
   ⇒ 收回 36，与标签栏外的图标按钮同尺寸，不再是孤例。

   ⚠️⚠️ 这条规则（值不管是多少）**必须保留，不许删** —— 它同时是**移动端的闸门**：
       `@media (max-width:768px)` 里有一条 `.icon-btn { width:44px; height:44px }`
       （0,1,0），而本规则是 `.page-tabs-bar .icon-btn`（0,2,0）。
       特异性高的赢、且 **media 查询不改变特异性** ⇒ 本规则在手机上也生效。
       删掉它，手机上标签栏内的这几个按钮会**回落到 44px** ——
       比今天改之前的 40px **还大**，塞进 52px 的栏里只剩 4px 留白（等于没改回）。
       ⇒ 这个坑由 tests/page-tabs.test.js 静态锁住。
   ⚠️⚠️ 用**作用域覆盖**，绝不改全局 `.icon-btn`（见本文件 321 行的 36×36）——
      它被表格行操作、弹窗关闭、列表工具等 **100 处 / 29 个文件**复用
      （`grep -rho icon-btn public/js | wc -l` = 100），改全局会同时撑高那些表格行的
      行高、并让弹窗右上角的关闭按钮变大。
      本项目一贯做法：**改公共类之前先查复用面，用局部覆盖替代全局修改**。 */
.page-tabs-bar .icon-btn { width: 36px; height: 36px; }
/* ⭐⭐ 2026-10-06 右上角统一为「A6 图标盒」（乐哥：「右上角的这个样式是不是要调一下」）
   ──────────────────────────────────────────────────────────────────────────
   实测到的**三种语言混排**（这就是"不太好看"的可量化原因）：
     · 搜索 `.gs-entry`     40×40、圆角 10、**有边框**、透明底
     · 铃铛 `#notify-bell`  **36×36**、圆角 10、**无边框**、透明底   ← 比邻居矮 4px
     · 头像 `.user-chip`    116×40、圆角 **9999（全圆）**、有边框
   ⇒ 门禁 H2 的注释里其实早就写了 A6 定稿形态是「搜索/铃铛/头像**统一 40×40「有框无底」图标盒**」
     —— 铃铛这一处**当时漏改了**，一直留着 36×36 与无边框。

   注意：**不是**改成"白底填充" —— 乐哥当时明确说过「里面不要填充颜色」，
   所以统一后的语言是 **40×40 + 1px 描边 + 透明底 + 圆角 10**。

   ⚠️ 用 `.page-tabs-bar__tools` 作用域（不改上面那条 `.page-tabs-bar .icon-btn`）：
      那条 36px 是**移动端的闸门**（0,2,0 压住 media 里的 `.icon-btn{44}`），
      同时它也管着左上角的菜单开关 `#sidebar-toggle` —— 动它会连带改那两处，
      而本轮只想动右上角这三个控件。手机端的尺寸在 media 查询里另行钉死。 */
.page-tabs-bar__tools .icon-btn {
  width: 40px;
  height: 40px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: transparent;
}
/* 通知与「标签操作」之间留一点气口：前者是全站消息、后者是标签的操作，语义不同 */
.page-tabs-bar__tools .notify-wrap {
  margin-left: 6px;
  padding-left: 10px;
  border-left: 1px solid var(--color-border-soft);
}
/* ⚠️⚠️ 这里原本残留着一段**丢了选择器的声明**（`position: sticky; right: 0;
   margin-left: auto; height: 32px; ... }`）—— 那是本规则改版**之前**的旧副本，
   新版（上面 817 行那条）已经把 `margin-left: auto` 去掉了（它会导致通知按钮
   停在半空、右侧空出 377px，见上方 813 行的说明），但旧副本没删干净。

   2026-09-19 修：它不只是"死代码"——**它会把紧接着的下一条规则一起吃掉**：
   CSS 解析器读到孤立的声明时，会一路把它当作下一条规则的「选择器前奏」，
   直到遇到下一个 `{` ⇒ 于是 `.page--narrow { max-width: … }` 被拼进
   这个非法选择器里、**整条规则被丢弃**。
   实测后果：全站 `.page--narrow`（当时的窄版二级页，内容区 1080）**从来没有生效过**，
   所有靠它限宽的页面都是铺满全宽 1456px。
   ⇒ 教训：删规则时**必须删掉整块（含选择器与花括号）**；
      留半截比留着更危险 —— 它会静默破坏后面一条无关的规则。
   ⚠️ 上面这段是**历史记录**，其中的 1080 是 2026-09-14 那版的数值；
      现行数值见下方「两档制」块（2026-09-28 已放宽为 1280 / 1440）。 */

/* ---- 二级页宽度「两档制」（2026-09-14 乐哥拍板；2026-09-28 放宽数值） ----
   背景：二级页此前**三档并存**（内容 1080 / 1392 / 1456），同一个「二级页」身份
   却有三种宽度，看着不像一套东西。现收敛为两档，判据只有一个：
   **页面主体是不是表格**。

   · .page--narrow（内容 **1120** ← 2026-09-28 16:55 乐哥定稿；当日曾一度放到 1280）
     —— 无表格的键值 / 表单 / 档案型二级页。
     这类页面铺满全宽（实测内容区 1543px）会把字段拉到两端、视线来回跑，空而散。
   · .page--table（内容 1440）—— 主体是表格的二级页（结算明细 / 提成明细 /
     华大对账明细 / 订单明细）。表格宽度是**列数实打实用掉的**，限到 1280 会出事：
       · 结算明细 10 列表 → 实测**横向滚动 +189px**，末列直接跑出可见区；
       · 订单明细列表   → 实测**表高从 2148px 涨到 4776px**（列被挤得每行折成多行）。
     故两档不是设计偏好，是物理下限。

   ⭐⭐ 2026-09-28 放宽（乐哥：「页面是不是可以整体宽一点，当前宽度不够，
      内容被压缩了，得不偿失」）—— 1144→**1360** / 1360→**1520**。
      依据（乐哥屏 1920 实测，此前记录的"老板屏 1700"经截图反推证伪）：
        · 现状窄档页内容区只有 1064，**左右各空 261px（合计浪费 522px）**，
          是全站被压缩最狠的一类；
        · 明细页内容 1280、左右各空 193px，同样偏窄；
        · 一级列表页 1600（撞上限）内容 1520 —— **已吃满，本次不动**。
      放宽后（当时的记录）：窄档内容 1064→**1280**、明细页 1280→**1440**，全站零横滚（15 页 × 4 档实测）。

   ⭐⭐⭐ 2026-09-28 **16:55 再收回 1120**（乐哥：「新建排期里的二级页面，我们需要是
      **之前的宽度**，不是现在的」+「上面白色底的标题行，宽度得做得跟二级页面的宽度一样」）：
        · 放宽到 1360 之后，**内层 4 处硬编码 1120 仍在生效** ⇒ 内容列实际是 **1120**，
          而白色标题行 `.detail-head` 是**撑满本容器**的（1280）
          ⇒ 那段时间是「标题行 1280 / 内容卡 1120」，左右各差 80px、明显不齐。
        · 16:06 我修的时候**方向反了**：把内容卡加宽到 1280 去凑标题行。
          乐哥要的是**反方向**：**内容维持 1120，把标题行收到 1120 跟它对齐。**
        · 收法：本档位 1360→**1200**（1200 − 左右 padding 各 40 = 1120），
          并与 `--form-max-width` 同值 ⇒ `.detail-head` / `.sf-hero-line` /
          `.sf-tabs__inner` / `.sf-body__content` / 内容卡 **全部 1120、左缘对齐一条竖线**。
        ⚠️ 为什么改档位而不是给标题行单独加 max-width：标题行是**撑满容器**的块级元素、
           没有自己的 max-width ⇒ 改档位它自动跟随；单独加反而要在 1360 容器里再居中一次、
           容易错位（这正是本条注释下方「必须连标题区一起限宽」讲的同一个道理）。

   ⚠️⚠️ 明细页的**物理天花板是 1520**（不是 1520 这个数字本身可以再调）：
      `.page--table` 挂在**内层** `.page` 上（DOM 是 `.page` › `.page.page--table`），
      外层 `.page` 有 1600 上限（2026-09-27 防超宽屏，门禁锁定），减左右各 40 内边距
      ⇒ 内层最多只能拿到 1520。实测把档位改到 1680 或"不限"，内层**仍是 1520**
      ⇒ 所以 1520 就是本项的最优值，**不要试图再加**（加了也不生效，只留一个假数字）。
   ⚠️ 窄档(1200／内容 1120) 与明细页档位(1520／内容 1440) **不再是同宽关系，这是刻意的**：
      判据都是「页面主体是不是表格」—— 无表格的表单/档案页收窄（铺太宽视线来回跑），
      主体是表格的页放宽（列数是物理下限，收窄会横滚/表高翻倍）。
      ⛔ 不要把两档"调回同宽"—— 2026-09-28 当天走过一次（都想 1280），结果是
         标题行与内容卡不齐，被乐哥当场否掉。

   ⚠️ 必须连 **标题区一起限宽**（本类加在 .page 上，而非只加在内容容器上）——
      否则标题贴页面最左、内容居中，左边缘不对齐反而更乱。
   ⚠️ 一级列表页 / 看板**不加任何档位类**：它们的宽度同样被列数用掉，须保持全宽。
   ⚠️ 档位归属是**静态声明**（app.js 的 WIDTH_TIER / detail.js 的 KIND.tier），
      不按「当前渲染出几张表」动态判断 —— 否则同一页面换个数据就换宽度，更乱。

   宽度算式：max-width = 内容区 + 左右各 40 的 --page-padding。 */
.page--narrow {
  /* ⭐⭐ 2026-09-28 16:55 乐哥拍板：1360 → **1200**（内容区 1280 → **1120**）。
     ──────────────────────────────────────────────────────────────────────────
     原话：「新建排期里的二级页面，我们需要是**之前的宽度**，不是现在的」
           +「上面白色底的标题行，宽度得做得跟二级页面的宽度一样」。
     ⛔ 16:06 我改错了方向（见 design-tokens 的 `--form-max-width` 注释）：把内容卡
        加宽到 1280 去凑那条 1280 的白色标题行；乐哥要的是**把标题行收到 1120**。
     ⭐ 为什么改这里（而不是给 `.detail-head` 单独加 max-width）：
        `.detail-head` / `.sf-hero-line` 都是**撑满本容器内容区**的块级元素，
        没有自己的 max-width ⇒ 改档位，这几条**自动跟着变窄并保持左缘对齐**；
        单独给标题行加 max-width 反而要在 1360 的容器里再居中一次，容易错位。
     ⚠️ 与 design-tokens 的 `--form-max-width`(1120) **同值**（两来源取小，必须同步）。 */
  /* ⭐⭐ 2026-10-06：1200 → **1410**（÷ 0.85 等比补偿，配套 base.css 的整站缩放）
     ▸ 目的：让**视觉宽度维持原来的 1200**（1410 × 0.85 ≈ 1198.5）——
       即「字变小、页面占位不变」，乐哥 2026-09-28 拍的 1200 观感不改变。
     ⚠️ 必须与 --form-max-width 同步：1410 − 左右 padding 各 40 = **1330** = 该令牌值
        （门禁 `page-width-tiers` ④c 锁住"两源取小必须同值"）。 */
  max-width: 1410px;   /* 内容区 1330 —— 无表格的二级页（含表单页 / 详情页）*/
  margin-left: auto;
  margin-right: auto;
}
/* ⭐⭐ 2026-10-09 乐哥定「C + 整板块一起」：**含壳的页面整块收窄**。
   ──────────────────────────────────────────────────────────────────────────
   背景：他把"壳家族"的内容列从 1120 收到 **884 视觉**（`--form-max-width` 1330 → 1040），
   而**页头标题行**（`.detail-head`）是"撑满所在容器"的块级元素、**自己没有 max-width**
   ⇒ 只改令牌的话壳会变窄、标题行却不动 ⇒ 左右各错一大截。
   ⇒ 判"这一页里有没有壳"，有就把**页面容器**一起收到「壳宽 + 左右 padding」，
     标题行自动跟随、**保持与壳原有的相对关系**。
   ⚠️ 选择器写 `.page`（不是 `.page--narrow`）：壳页的档位**不统一** ——
     · 新增达人 / 新建排期 / 达人详情 走 `.page--narrow`（1410）
     · **报销单是裸 `.page`（1900）**，它的「提交报销」标题行实测 1837 宽
       ⇒ 只写 `.page--narrow:has(...)` 会漏掉它（标题行比壳宽 400px、明显不齐）。
     `.page:has(...)` 两种都覆盖；`.page--narrow` 也是 `.page`，同一元素两个类。
   ⛔ 不能直接改 `.page--narrow` / `.page` 的**本体**：那是 `app.js` 按"二级页档位"给
     **所有**非表格二级页挂的，一刀切会把全站二级页都收窄（门禁 `page-width-tiers ④a` 也锁着 1410）。
   ✔ `2 * --page-padding` 与 ④a/④c 的算式一致（档位 = 内容宽 + 左右各 40 的 padding）。
   ⚠️ 用 `:has()` 判"有没有壳"（本项目已在用 `:has()`，见 `.module-card__head:not(:has(...))`）。 */
.page:has(.sf-shell),
.page:has(.detail-shell) {
  max-width: calc(var(--form-max-width) + 2 * var(--page-padding));
}
.page--table {
  /* ⭐⭐ 2026-10-06：1520 → **1790**（÷ 0.85 等比补偿）—— 同理让视觉宽度维持 1520。 */
  max-width: 1790px;   /* 内容区 1710 —— 主体是表格的二级页 */
  margin-left: auto;
  margin-right: auto;
}

.page-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-4);
  /* 8（不是 24）—— 与 .page 的 padding-top 24 合成 32px 的上留白。
     原 24 会让两者叠成 56px（见 .page 的注释）。 */
  margin-top: var(--space-2);
  margin-bottom: var(--space-6);
}

/* ⭐ 2026-09-28 页头包盒（乐哥拍板「方案A」，含 TAB 的列表页）：
   页头从「裸放灰底」收进白底圆角卡 —— 与二级页 .detail-head 同规格，
   解决「内容区上面太空 / 左侧菜单栏显高」。类名由 app.js 的 fixPageTabs 打标，
   只作用于「状态筛选 TAB 搬进页头」的页面，无 TAB 的页头不受影响。 */
.page-header--has-tabs {
  align-items: center;
  margin-bottom: var(--space-5);
  background: var(--color-surface);
  border: 1px solid var(--color-frame-border);
  border-radius: var(--radius-lg);
  /* ⭐ 2026-10-06：页头也是**顶层卡片** ⇒ 同样浮起（与 .card / .kpi-card 一套）。
     ⚠️ 吸顶态 `.is-stuck` 会换成 `--liquid-shadow`（毛玻璃那套），两处**不冲突**：
        静态用 `--shadow-card`、吸顶时用 liquid —— 过渡已在下面的 transition 里声明。 */
  box-shadow: var(--shadow-card);
  /* ⭐⭐ 2026-09-28 垂直内边距 12 → **9px**（乐哥：「这个 tab…感觉有一点点高了，要不矮个 10%」）——
     容器高 = 9×2 + 内层框44 + 2(边框) = **64**（原 70，矮 8.6%）；
     无内层框的页面（编辑排期）60 → **54**（正好 −10%，命中乐哥给的幅度）。
     ⚠️ 12 → 9 是**全站四处同改**（`.tabs` / `.sf-shell .tabs` / `.sf-tabs__track` /
        `.page-header--has-tabs`）——只改一处会让"tab 栏高度"重新分叉。
     ⚠️ 9px 不是 `--space-*` 档位值（令牌里没有 9）—— 它由"矮 10%"倒推得到，
        不是随手写的；改它之前先看这句注释。
     ⚠️ 胶囊（34）**不动**：乐哥当天上午刚要求过"胶囊太矮了"提到 34（见该处注释），
        这次说的"高"是**容器**厚，不该再把胶囊压回去。 */
  /* ⭐⭐⭐ 2026-10-07 13:40 垂直 14 → **9px**（乐哥：「这个高度，怎么搞这么多，
     你看下左右都是贴着边的（没问题），但是**高度上下余量太多了**」）
     ────────────────────────────────────────────────────────────────────────────────
     ▸ 实测（1920×950 排期列表）：容器高 **84** = 上下 14×2 + 托盘 54 + 边框 2。
        而上下留 **14px** 时，托盘（54px 高）浮在中间像"一块薄板搁在盒子里"——
        左右贴边是对的，上下留白让整行显得**空且高**（乐哥说的"余量太多"）。
     ▸ 14 → **9** ⇒ 容器高 **74**，省 10px；托盘与容器上下各留 9。
        9 不是随手写的：**它是 09-28「矮个 10%」那次的实测值**（当时 12→9，容器 70→64），
        10-07 上午我为了"选项贴着骨架"把它提到 14，现在按乐哥要求退回 9。
     ⚠️ **必须四处同改**：`.page-header--has-tabs`（本处）/ `.tabs`（pages-influencer:589）
        / `.sf-shell .tabs` / `.sf-tabs__track` —— 只改一处会让"tab 栏高度"重新分叉
        （这几处在同一屏可能前后出现，差 1px 就能看出来）。
     ⛔ 别加回 14：那就是本次要修的现象。 */
  /* ⭐⭐⭐ 2026-10-07 14:40 乐哥：「tab 上下和左边的间距也不一致」
     ────────────────────────────────────────────────────────────────────────────────
     ▸ 实测（本文件这处的 padding 三轮下来是 9/4 —— 上下 9、左右 4，**故意不对称**）：
       当时的想法是「水平贴边（选项贴着骨架）+ 垂直留呼吸」，但那两条**互相打架**：
       贴边只对水平成立，垂直一旦也贴边，托盘（54 高）紧贴容器上下边框，
       整行读起来像"三块贴在一起"，反而更挤。
     ▸ 本次按乐哥口径：**上下左右统一 10px**（不是 9，也不是 4）。
       ⛔ 别再用 9/4 这类"各写各的"值 —— 它是三轮调整叠出来的，没有推导过程，
          改任何一处都会让另外三处对不上（这就是"上下和左边不一致"的由来）。
     ⚠️ 水平 4 → 10 会让托盘左缘从 x=287 移到 x=293（+6px）：胶囊左缘与下方
        表格/卡片文字左缘不再重合。这是**有意的取舍**——乐哥要的是"四周一致"，
        不是"贴齐表格列"；后者由 `.table` 自身的内边距保证。 */
  padding: 10px;
  /* ⭐⭐ 2026-10-07（乐哥：「那个框有些多余，就是选项应该贴着骨架」）9/24 → **14/4**
     与 `.tabs` / `.sf-shell .tabs` / `.sf-tabs__track` **三处同改** ⛔ 勿单改。
     · 垂直 9 → 14：骨架调高（64 → 74），选项上下有呼吸；
     · 水平 24 → 4：选项贴左缘。
     ⚠️ 水平这一步会改变"胶囊左缘 ≈ 内容卡文字左缘"那条对齐（2026-09-28 乐哥要 24 的原因），
        所以**必须四处同步**，否则并排查看时一处的胶囊与文字对不齐。 */
  /* ⭐⭐ 2026-09-28 一级页 tab 也吸顶（乐哥：「另外一级页面 tab 也做悬浮」）——
     吸顶公式与二级页 / 表单页**同一条**，三处必须同值
     （它们在同一屏里可能前后出现，差 1px 就会被看出来）。
     ⭐ 2026-10-06 公式更新：顶栏改**贴顶**（不再有 --frame-gap 那条上缝）⇒
        `tabs-bar-height + 4`（= 内容区顶边再下 4px，与旧版"岛顶比内容顶低 4px"同一意图）。
     ⛔ 别再加回 --frame-gap：会停在半空、露出 14px 的滚动内容（实测）。
     ⭐⭐⭐ 2026-10-07「边框锁死 · 框内滚动」：**`+ 4` 删掉，只留 `--tabs-bar-height`**。
        吸附参照从视口变成 `.app-main`（框）后，顶栏是**框内** sticky（top:0、高 tabs-bar-height）
        ⇒ 顶栏下沿 = 框顶 + tabs-bar-height。实测：留 4px 会让一级页 tab 悬在顶栏下方 4px。 */
  position: sticky;
  top: var(--tabs-bar-height);
  z-index: 60;
  /* 贴顶变玻璃是"渐变"而不是突变（乐哥要的是"和其他内容叠加才有"的感觉） */
  transition: background-color var(--motion-base) var(--motion-ease-standard),
              border-color var(--motion-base) var(--motion-ease-standard),
              box-shadow var(--motion-base) var(--motion-ease-standard);
}

/* ══════════════════════════════════════════════════════════════════════════
   ⭐⭐⭐ 2026-10-06 「筛选条整合」页头（乐哥拍板，**样板阶段只对白名单页生效**）

   目标（乐哥原话）：「把高频的、重要的，升级到和 tab 筛选项统一级，放在那个悬浮卡片里；
   不常用的还是放在表格里」+「导入导出新建放最右边」+「多选放最左」+「页数放最下面和分页整合」。

   ▸ **为什么需要这个新类**（而不是直接改 `.page-header--has-tabs`）：
     那个类有 12 个页面在用。它现在的 `justify-content: space-between` 是为
     「**只有两个元素**：标题左 + 按钮右」设计的 —— 一旦往里塞第 3、4 个元素，
     `space-between` 会把空隙**均分**（实测相邻间隔变成 233~260px，元素被撑散）。
     所以本改造改成「**靠左成组 + 末位靠右**」，但**只对整合过的页面**加这个类，
     其余页面一字不动（零回归风险）。
     ⚠️ 顺带说明：只有 tab + 按钮两个元素时，`flex-start + margin-left:auto`
        与 `space-between` **视觉完全等价** ⇒ 即便将来全站放开也不会改变观感。

   ▸ 间距 24px（**不是 16**）：实测 3× 放大对比 —— 16px 时 tab 托盘（自带 1px 边框）
     与搜索框视觉上贴在一起；24px 才有明确留白、分组清晰。（乐哥反馈「没留间距」）
   ────────────────────────────────────────────────────────────────────────── */
/* ⛔⛔ **必须整块包在 `@media (min-width: 769px)` 里**（手机端不应用）——
   实测（390 视口）：把搜索框/月份搬进页头后，窄屏放不下 ⇒ 页头卡从 **124px 涨到 220px（4 行）**。
   而吸顶卡是 **sticky 的、滚动时永久占屏** ⇒ 手机上等于永久少 96px，比不改更糟。
   （JS 侧同样有 `window.innerWidth > 768` 判断，两处必须一致 ——
     只做一处会出现"JS 搬了但 CSS 没管"或反之的错配。） */
@media (min-width: 769px) {
  /* ⛔⛔ **选择器必须写成双类 `.page-header--has-tabs.page-header--filter-bar`（0,2,0）**——
     本人第一版只写 `.page-header--filter-bar`（0,1,0），**实测不生效**：
     `table.css` 里有一条 `.page-header--has-tabs { gap: var(--space-4) }`，
     而 **table.css 在 layout.css 之后加载** ⇒ 同特异性下后者胜 ⇒ gap 恒为 16px。
     （诊断法：先读 computed —— `gap` 写的 `var(--space-6)` 却算出 16px 就是被盖了；
       再用 CSSOM 遍历列出「匹配本元素且含 gap 的规则」，凶手一眼可见。
       ⚠️ 光看 layout.css 找不到它，那条在 **table.css** ⇒ **跨文件别按顺序在脑子里推**。）
     ⚠️ 另外用 `column-gap` 而非 shorthand `gap`：`gap: var(...)` 在 CSSOM 里
        会被短路成空的 column-gap，调试时容易误判（实测看到 `column-gap: ;`）。 */
  .page-header--has-tabs.page-header--filter-bar {
    justify-content: flex-start;
    column-gap: var(--space-6);     /* 24px：组内元素间距（3× 放大实测选定） */
    row-gap: var(--space-2);        /* 换行时的行距 8px（与工具栏一致） */
  }
  /* 操作按钮（导入/导出/新增）**恒定靠右** —— 用 auto 顶住，不依赖父级 space-between。
     乐哥：「导入导出新建这些，是不是应该放在最右边」——
     现状本来就在最右（源码 `insertBefore(tab, .page-actions)`），本次是**保住**它。 */
  .page-header--filter-bar .page-actions {
    margin-left: auto;
    flex: 0 0 auto;                 /* ⛔ 防被 flex 压缩（多元素同行时会被挤窄） */
  }
  /* 搬进来的搜索框宽度收敛：240 → 180（乐哥「搜索框太长了，放短一点」）。
     ⚠️ 180 是"零成本"的关键值：实测搜索框 240 时，1440 视口下本行会撑成两行
        （吸顶卡变厚 116px，**滚动时永久多占 42px**）；缩到 180 后主流页面在 1440
        仍是一行 64px ⇒ **吸顶卡零变厚**。 */
  .page-header--filter-bar > .input--search {
    width: 180px;
    flex: 0 0 auto;
  }
  /* 搬进来的时间/月份选择器：保持紧凑档 34px、不被压缩（尺寸与工具栏内一致） */
  .page-header--filter-bar > .mp-host,
  .page-header--filter-bar > .dtp-trigger {
    flex: 0 0 auto;
  }
  /* ⭐⭐⭐ 2026-10-07 14:40 页头按钮：**有 tab 时** 44/36 → 34（紧凑档）
     ────────────────────────────────────────────────────────────────────────────────
     ▸ 病根（乐哥：「右边新增达人和左边tab 的高度也不一致，很奇怪」）：
        同行三个控件来自**三个不同的档**——胶囊 44（标准）/ 搜索框 44（标准）/
        按钮 34（`btn--sm` 紧凑）。实测 1920×950：胶囊 44、按钮 34、差 **10px**。
     ▸ ⛔⛔ **不能全局把 `.page-actions .btn--lg` 压到 34**：
        全站 29 个页面的页头主操作用 `btn--lg`（44），其中**绝大多数没有 tab**
        （成本、盘点、粉丝体验、达人结算…）⇒ 那些行的 44 是对的
        （乐哥 09-28 定的「页头主操作用 btn--lg=44，与页头 tab 容器齐平」）。
        一刀切会把**没有 tab 的那些页头**也压矮 10px ⇒ 换成另一种"高度不一致"。
     ✅ 做法：用 `:has()` **自限定在"有 tab 的页头"**——
        `.page-header--has-tabs:has(.table-tabs--in-header)` 才降档。
        没 tab 的页头选择器**根本不匹配** ⇒ 行为完全不变（与 layout.css:1002
        那条 `:has(.page-help-btn)` 同一手法）。
     ⚠️ 为什么用 `.table-tabs--in-header` 而不是 `.table-tabs`：
        `.table-tabs` 在卡内表格上方也出现（`data-lifted` 那套），
        限定到 `--in-header` 才精确等于"页头里的那一行"。
        ⚠️ 手机端那条 `height: 36px`（本文件下方 @media 内）同样是第三档，
        但那是**触摸场景**、为 768px 以下保底，本条只管桌面（≥769px）。 */
  /* ⭐⭐⭐ 2026-10-07 19:50 34 → **42**（乐哥 19:42：「新增排期的高度和左边tab栏的
     不一样，你看下要视觉上一致」，19:45 拍板走「其他控件升到 42」这条路）。
     ────────────────────────────────────────────────────────────────────────────────
     ▸ ⚠️⚠️ **实测推翻了他截图里的印象**（1920 含边框）：
         托盘 **42** ｜ 选中项 34 ｜ 搜索 34 ｜ 月份 34 ｜ 按钮 **34**
         ⇒ 三个控件**本来就完全一致**，真正高 8px 的是**托盘**（padding 3px 撑的）
         ⇒ 另一条路是「托盘降到 34」，但那 3px 内距正是托盘托住胶囊的地方
         ⇒ 乐哥选了「控件升到 42」，整行与托盘外框同高、中心线对齐。
     ⚠️⛔ **为什么用新令牌而不是改 `--control-height-sm`**：
        本令牌被 **48 处引用 / 8 个文件**（table.css 17、pages.css 10、overlays.css 6…）
        —— 表格行内控件、分页、工具栏全用 34。改它就是「改外观顺手连累几何」。
        ⇒ 只在这一行、只在「有 tab 的页头」内提档，其余页面行为完全不变。 */
  .page-header--has-tabs:has(.table-tabs--in-header) .page-actions .btn,
  .page-header--has-tabs:has(.table-tabs--in-header) .page-actions .btn--lg {
    height: var(--control-height-row);
    min-height: var(--control-height-row);
    /* ⭐ 2026-10-17 全站 UI 统一：左右内边距 12 → **16**。
       ────────────────────────────────────────────────────────────────────────────
       ▸ 病根：提到 42 的按钮**仍带着 `.btn--sm` 的 12px 内边距**（它原来就是紧凑档），
         而"页头无 tab"的同类按钮是 `.btn--lg`（44 高 / **20** 内边距）⇒ 同一个位置、
         两种"胖瘦"：`导入/导出/新增达人` 文字几乎贴边（实测宽 78/78/106），
         而 `新增佣金/导出` 明显更松弛（122 / 94）。
       ▸ 取 **16**（`.btn` 基础档的 `--space-4`）而不是 20：42 档本来就是为了"不顶破
         页头行"才压到 42，用 lg 的 20 会让按钮比现在再宽 8px/个，1440 视口下三个按钮
         更容易把整行挤换行（吸顶卡会因此永久变厚）。
         ⚠️ 真机复核：1680 与 1440 两档下页头行仍 64px（未换行）。
       ⛔ 别去改 `.btn--sm` 的全局内边距（全站 400+ 处工具栏/表格行内都要 12）。 */
    padding-left: var(--space-4);
    padding-right: var(--space-4);
  }
  /* ⭐⭐⭐ 2026-10-07 14:40 搜索框：**44 → 34**（乐哥：「右边新增达人和左边tab 的
     高度也不一致」）。它与胶囊、右侧按钮**同处一行**，44 会成为这一行唯一的
     高个子（实测 44/34/44 三档并存）。归到紧凑档后整行 = 34/34/34。
     ⚠️ 必须用 `> ` 限定**直接子级**：`.input--search` 内部的 `.input` 有精确内边距
        （`padding-left: var(--space-8)` = 32px，给放大镜留位），
        写成后代选择器会把那 32px 一起覆盖 ⇒ 放大镜压住搜索文字（本项目已踩过，
        见技能 dabotai-control-size-two-tier §1.5）。 */
  /* ⭐⭐⭐ 2026-10-07 19:50 34 → **42**（与同行托盘同高，理由见上面按钮那条注释）。
     ⚠️ `> ` 限定必须保留：`.input--search` 内部的 `.input` 有 `padding-left: 32px`
        （给放大镜留位），写成后代选择器会一起覆盖 ⇒ 放大镜压住文字（已踩过）。 */
  .page-header--filter-bar > .input--search,
  .page-header--filter-bar > .input--search > .input {
    height: var(--control-height-row);
    min-height: var(--control-height-row);
  }
  /* ⭐⭐⭐ 2026-10-07 14:40 月份/时间选择器：**44 → 34**。
     ▸ 实测病根：排期列表与订单页的容器高是 **66**（其余页 64）—— 差的那 2px
        来自 `.mp-host`（月份选择器）仍走标准档 **44**，它比同行其他控件高 10px，
        把整行撑起来 ⇒ 托盘上下间距变成 12（其余页 11）。
        真机实测（1920×950）：排期 `mp-host h=44`、托盘 42、按钮 34、搜索 34。
     ⚠️ 上一行那条 `.page-header--filter-bar > .mp-host { flex: 0 0 auto }`
        只防压缩、**没管高度** ⇒ 漏了。日期触发器 `.dtp-trigger` 同理，
        一并在下面归到紧凑档。
     ⛔ 只限本作用域（页头筛选行）：表格卡内的日期选择器另有其档，别动。 */
  /* ⭐⭐ 2026-10-17：`>` 去掉（改后代）—— 「达人结算 / 前置佣金」把月份选择器放进了
     **`.page-actions` 里面**（右侧操作组的一部分），不是 `.page-header` 的直接子级
     ⇒ 带 `>` 的版本命中不到它，那个 `.mp-host` 仍是 44、把页头从 64 撑到 **66**。
     ⚠️ 本作用域（页头筛选行）里 `.mp-host` 只有月份选择器这一种，不会误伤。 */
  .page-header--filter-bar .mp-host,
  .page-header--filter-bar .mp-host .mp__trigger,
  .page-header--filter-bar > .dtp-trigger {
    height: var(--control-height-row);
    min-height: var(--control-height-row);
  }
  /* ⭐⭐⭐ 2026-10-17 全站 UI 统一：页头筛选行里的**下拉/多选触发器**也归 42。
     ──────────────────────────────────────────────────────────────────────────────
     ▸ 病根（实测）：财务「报销单总览 / 财务首页」的页头里有一个部门多选
       （`<select class="select fin-ms__btn">`，走 `.select` 的**44**），
       同行的 tab 托盘 / 搜索框 / 主按钮都是 **42** ⇒ 这一个控件把整行从 **64 撑到 66**
       （10×2 + 44 + 边框 2）。页头是吸顶卡，多出的 2px 是**永久**占屏的。
     ▸ 为什么用后代选择器（不带 `>`）：财务的部门多选包在 `.fin-ms` 里，
       不是 `.page-header` 的直接子级。本作用域内没有"自带精确高度"的二级下拉
       （`.pagination__size` 在卡片底部、不在页头）⇒ 不会误伤。
     ⛔ 只在本作用域（页头筛选行）；表格工具栏 / 表单里的 `.select` 仍走 34 / 44。 */
  .page-header--filter-bar .select,
  .page-header--filter-bar .fin-ms__btn {
    height: var(--control-height-row);
    min-height: var(--control-height-row);
  }
}

/* ⭐⭐⭐ 2026-10-09 页头「视图专属控件」的显隐 + 切换入场动画（华大对账，`data-hs-view`）
   ────────────────────────────────────────────────────────────────────────────────
   乐哥两条：「搜索框怎么放右边了」＋「切换的时候，右边的按钮也得有动画」。
   页头的这行里，**两组控件各归一个 tab 视图**：
     · 达人分账附表 → 搜索框 + 操作组（`.hs-annex-only`）
     · 月度对账     → 月份选择器（`.hs-month-only`）
   ▸ 显隐只做「隐藏」这半边：显示态一律回落到元素自己的 CSS
     （搜索框 `.input--search` 自带 `inline-flex`；操作组由 `.hs-annex-bar` 给 `flex`）。
     ⛔ 别图省事写一条 `display: flex` 通配全部 —— 搜索框内部放大镜靠
        `.input--search` 的定位上下文，强改 display 会动到它的内部排版。
   ▸ 入场动画与二级页 tab 面板**同一套语言**（形态 / 令牌 / fill-mode 三处同源，
     见上面 `.is-panel-enter-*` 那条长注释）：
       时长 `--motion-tab-slide`(250ms)｜曲线 `--motion-ease-standard`｜
       fill-mode **必须 `backwards`**（⛔ 不许 `both`/`forwards`：残留的 transform 会给
       元素造出层叠上下文，里面的浮层会被外层卡片盖住 —— 2026-09-26 实报过 6 页）。
     ⚠️ 位移 12px（tab 面板是 28px）：这里不是"换页"、没有方向语义，只是"随切换出现的
        一组控件"，幅度要轻 —— 同一套语言里，不同动作的幅度本就该不同。 */
@keyframes header-view-swap-in {
  from { opacity: 0; transform: translateX(12px); }
  to { opacity: 1; transform: translateX(0); }
}
/* ⭐⭐⭐ 2026-10-16：一级 tab =「达人维度」（默认）/「明细展开」/「达人分账附表」。
   （当天先撤了「店铺维度」，随后乐哥又加了一个「展开的」视角 ——
     「tab 新增一个，就是展开的…把汇总的摊开，达人x店铺x商品」。）
   ⇒ `month`（店铺维度）这个视图键**已废**（那是另一个视角），当前三个视图分两组控件：
      · `influencer`（达人维度）/ `flat`（明细展开）→ 店铺多选（`.hs-dim-only`）+ 账期选择器（`.hs-month-only`）
      · `annex`（附表）                          → 搜索框 + 操作组（`.hs-annex-only`），它用自己的时间范围
   ⚠️ 别把 `flat` 那几条漏掉 —— 漏了，切到「明细展开」就既没有店铺筛选也没有月份选择器，
      页面会停在默认月、用户以为"换不了月份/筛不了店铺"（这是本轮最容易漏的一处）。
   ⚠️ 也别漏 `influencer`（同样的道理）。
   ⚠️⚠️ 2026-10-16 又加了 `statement`（「对账确认」）—— **加视图时这两处都必须同步**：
       上面那段（附表控件在维度视图里隐藏）与下面那段（维度视图的控件播入场动画）。
       漏了下段的后果不是"没动画"，而是**控件完全不显示**（样式块兼着"显示"的语义）。
       本项目已经因为"加视图漏配"踩过一次，故把这条写在这里。 */
.page-header--has-tabs[data-hs-view='influencer'] .hs-annex-only,
.page-header--has-tabs[data-hs-view='flat'] .hs-annex-only,
.page-header--has-tabs[data-hs-view='statement'] .hs-annex-only,
.page-header--has-tabs[data-hs-view='trend'] .hs-annex-only,
.page-header--has-tabs[data-hs-view='annex'] .hs-month-only,
.page-header--has-tabs[data-hs-view='annex'] .hs-dim-only {
  display: none;
}
.page-header--has-tabs[data-hs-view='annex'] .hs-annex-bar {
  display: flex;
}
.page-header--has-tabs[data-hs-view='annex'] .hs-annex-only,
.page-header--has-tabs[data-hs-view='influencer'] .hs-month-only,
.page-header--has-tabs[data-hs-view='influencer'] .hs-dim-only,
.page-header--has-tabs[data-hs-view='flat'] .hs-month-only,
.page-header--has-tabs[data-hs-view='flat'] .hs-dim-only,
.page-header--has-tabs[data-hs-view='statement'] .hs-month-only,
.page-header--has-tabs[data-hs-view='statement'] .hs-dim-only,
.page-header--has-tabs[data-hs-view='trend'] .hs-month-only,
.page-header--has-tabs[data-hs-view='trend'] .hs-dim-only {
  animation: header-view-swap-in var(--motion-tab-slide) var(--motion-ease-standard) backwards;
}
/* ⚠️ 手机端**不另写宽度**：`components.css` 的移动端块已有
   `.input--search { flex: 1 1 auto; min-width: 0; max-width: 100% }`
   + `.input--search .input { width: auto !important; flex: 1 1 auto }` ⇒ 本页搜索框
   在手机上自然占满。
   ⛔ 别在这里新起一个手机端媒体块：`page-tabs.test.js` 的 H6 等四处门禁是按
      「**首个**移动端（`max-width` **768**）块」取样（其中一条还是"文件末尾 3000 字符
      内首个"）—— 新块会把这些锁**顶掉取样位置**、让它们假红（2026-10-09 实测踩过 3 条）。
      ⚠️ 连注释里都别写出那个媒体查询的完整字面量：那两条门禁**不剥注释**，
         `indexOf` 会命中注释里的串，取样又偏一次（改完这两条才转绿）。 */

/* ⭐ 2026-10-09 附表两个操作按钮**等宽**（乐哥：「生成 word 的图标尺寸不对」→ 选「等宽」）
   ▸ 实测（生产 1560 宽、2× 特写取证）：两个按钮的规格**完全一致** ——
     同 42 高、字号 14、内边距 12、圆角 10；差异只在**文字宽**：
     「生成 Word」比「生成预览」宽 11px（93 vs 82，多一个字符 + 一个空格）⇒ 并排看着不齐。
   ▸ 94 是**实测值**（「生成 Word」的宽 93.6 取整），不是随手写的：
     CSS 取不到"两个按钮里的较大宽"，只能给同一个下限 —— 改字号/文案时要重算这个数。
   ▸ 只作用在附表的操作组（`.hs-annex-bar`）内，别处不受影响。 */
.page-header--has-tabs[data-hs-view='annex'] .hs-annex-bar .btn {
  min-width: 94px;
  justify-content: center;
}

/* ⭐⭐⭐ 2026-09-28 一级页 tab「贴顶才变液态玻璃」（乐哥拍板选 L3 档）
   —— 判定见 app.js updateStuck（`rect.top <= stickyTop`，不是固定滚动像素）；
      刚进页面 / 还没贴顶 = 纯白卡零阴影；贴顶那一刻才出玻璃，内容从它下面穿过。 */
.page-header--has-tabs.is-stuck {
  background: var(--liquid-bg);
  backdrop-filter: var(--liquid-blur);
  -webkit-backdrop-filter: var(--liquid-blur);
  border-color: var(--liquid-edge);
  box-shadow: var(--liquid-shadow);
}
/* ⭐⭐⭐⭐ 2026-10-07 18:40 乐哥 18:08 给出开发者工具后**定位到真凶**，
   17:14 那次「隐藏左右边框」的改动**已撤销**。
   ────────────────────────────────────────────────────────────────────────────────
   ▸ 定位过程（方法比结论重要，记下来）：
     ① 乐哥 17:35 报「竖线还在」⇒ 我 17:14 改的是 `.page-header--has-tabs.is-stuck`
        的左右边框（真机实测已透明：computed = `1px rgba(0,0,0,0)`）⇒ 现象仍在
        ⇒ **另有画线者**（判据自检铁律第四种失真：判据只覆盖了我怀疑的对象）
     ② 18:08 乐哥 tooltip 给出关键量值：吸顶卡 `宽 973` / `Margin: 8px 0px 16px 1px`
        ⇒ 由 973 反推视口 ≈ 1301，在该宽度下**穷举所有元素左右边缘**
     ③ 命中 `DIV.card toolbar--join-target`（**表格卡**）：left 288 / right 1253，
        右边框 `rgb(235,236,239)` = `#EBEC-border` 同族的 `--color-frame-border`
     ④ 颜色交叉验证：乐哥截图那条线取样 `(236,236,239)`，
        与 `#EBECEF(235,236,239)` 只差 1 阶（jpg 压缩误差）⇒ 吻合
     ⑤ 结构解释（**关键**）：表格卡与吸顶卡**左右完全同位**（都在 `.page` 的 padding 内，2026-10-08 起 16px）
        ⇒ 我把吸顶卡边框透明了，**表格卡的描边在同一 x 上补上**
        ⇒ 一条连贯的边框被剪成「上面没有、下面有」⇒ 看着还是一条线，且更怪

   ✅ 决定（乐哥 18:45 拍板）：**恢复吸顶卡左右边框**，与表格卡接成一条连贯的边框。
      理由：它们本来就是**一张卡拼两段**（工具栏并进表格卡的设计），
      边框连贯才是原本的形态；上一版"上面透明下面不透明"才是异常态。
   ⚠️ 表格卡的描边**不许删** —— 它是卡片族合法边界
      （`--color-frame-border`，与 `.card`/`.module-card` 同源，全站统一），
      删了表格卡在白底上会散架，且违反设计规范「卡片只靠 1px 线 + 阴影」。

   ⛔ 本节曾写过 `.page-header--has-tabs.is-stuck { border-*-color: transparent }`，
      **已删除**。若将来又有人认为"这两条线碍事"：
      先跑 `scripts/frontend-debug/verify-vertical-line-owner.js` 看
      「吸顶行上画竖线的元素到底是谁」，**再**决定改谁 —— 不要再凭假设改这一个。
      ⚠️ `tests/tab-stuck-side-border.test.js` 同步改成了「边框必须与表格卡同色」，
         它的原判据（"必须 transparent"）已随本次撤销作废。 */

/* 内层小框一起做玻璃（乐哥：「内层小框要不要一起做玻璃」→ 做）——
   ⛔ 不做的后果：白卡变玻璃后小框仍是**不透明白底** ⇒ 像"玻璃上贴了张白纸"。
   ⭐ 2026-10-05 补：**边框色不再跟着变**（乐哥：「tab 在非悬浮状态的时候，周围有
     线框，悬浮的时候就没了，这个得统一」）
   ─────────────────────────────────────────────────────────────────────────
   ▸ 改前：吸顶时 `border-color: --liquid-tray-edge`（= rgba(255,255,255,.62)）——
     这是**半透明白**，叠在白卡/浅灰页面上几乎不可见 ⇒ 视觉上"边框消失了"。
     静态态用的是 `--color-border`（#E1E8F2，实色浅灰）⇒ 两态不一致。
   ▸ 改后：吸顶态**继续用 --color-border**，与静态态**完全同色** ⇒ 边框两种状态都在。
     玻璃背景（--liquid-tray-bg）保留不变 —— 乐哥 09-28 拍板的"两层玻璃"层次不受影响，
     只是给玻璃框加回一条可见的边（实色浅灰叠在半透白玻璃上，对比度足够）。
   ⚠️ 别再改回 --liquid-tray-edge：那条令牌**保留但不再被这里引用**
     （它仍是"玻璃上的高光边"语义，将来若做纯白玻璃卡片可用）。 */
.page-header--has-tabs.is-stuck .table-tabs--in-header {
  background: var(--liquid-tray-bg);
  border-color: var(--color-border);
}

.page-title {
  position: relative;
  font-size: var(--font-size-2xl);
  font-weight: var(--font-weight-semibold);
  letter-spacing: var(--font-letter-spacing-display);
  line-height: var(--font-line-height-tight);
  color: var(--color-fg);
}

/* 页面标题左侧竖条装饰条已移除 —— 国产后台旧范式，TDesign / 微信小店均无此元素。
   若需恢复，取消下面注释即可。
.page-title::before {
  content: '';
  position: absolute;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
  width: 3px;
  height: 20px;
  border-radius: 2px;
  background: var(--color-accent);
}
*/

.page-desc {
  margin-top: var(--space-1);
  font-size: var(--font-size-sm);
  color: var(--color-muted);
}

/* 页面标题旁的「数据说明」问号按钮
   ⭐ 2026-10-17 全站 UI 统一：20 → **24**，与财务板块的同款「?」（`.fin-help__btn` 24×24）
     对齐 —— 实测两处是同一个东西（弹同一种「数据说明」弹窗），却一个 20 一个 24，
     页头上一眼能看出大小不同。取 24：它是"可点控件"的下限尺寸（20px 在触屏上偏小），
     且 24 下 `?` 的字形不再顶到边框。 */
.page-help-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  margin-left: var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: 50%;
  background: transparent;
  color: var(--color-muted);
  cursor: pointer;
  vertical-align: middle;
  transition: color var(--motion-fast) var(--motion-ease-standard), border-color var(--motion-fast) var(--motion-ease-standard), background var(--motion-fast) var(--motion-ease-standard);
}
.page-help-btn:hover {
  color: var(--color-accent);
  border-color: var(--color-accent);
  background: var(--color-surface-2);
}

/* 数据说明弹窗内容 */
.help-section { margin-bottom: var(--space-4); }
.help-section:last-child { margin-bottom: 0; }
.help-label {
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-semibold);
  color: var(--color-accent-text);
  margin-bottom: var(--space-1);
}
.help-text {
  font-size: var(--font-size-sm);
  color: var(--color-fg-2);
  line-height: 1.6;
}

/* 数据说明弹窗里的补充小节列表（2026-10-22）
   用于 help.extra（[{\u200bt, items}]）：给口径/细则这类长文用 ——
   华大对账页的完整口径并进这里（原先是独立一枚"口径"按钮，乐哥说没必要）。
   ⚠️ 与 .help-text 同一档字号/行高，只把多条改成列表，避免堆成一大段。 */
.help-list {
  margin: 0;
  padding-left: 1.2em;
  font-size: var(--font-size-sm);
  color: var(--color-fg-2);
  line-height: 1.6;
}
.help-list li + li { margin-top: var(--space-1); }

.page-actions {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-shrink: 0;
}

/* 页面头部（标题行右侧）的次要按钮：**可见度加强**（2026-09-18 乐哥反馈）
   --------------------------------------------------------------------------
   乐哥原话：「还有个问题，导入导出按钮看着不显眼」。
   原因：`.btn--ghost` 的描边用 `--color-border: #E5E8EC` —— 这是**给卡片留白用的**
   极浅灰，放在同样白色的页面头部里几乎看不见边界，只剩一行灰字悬空；
   而旁边的「新增达人」是蓝底实心 → 主次关系被拉成了「实心按钮 vs 一片文字」。

   ✅ 做法：**只**加强页面头部这一处（`.page-actions` 作用域），不动全局 ghost ——
      全局 `.btn--ghost` 还用在表格操作列（编辑/删除那种图标按钮），
      那些按钮本来就该"轻"（一列里几十个实心描边会非常吵）。
      · 描边提到 `--color-gray-400`（输入框同款，肉眼可辨）
      · 文字用主文字色（不是灰）+ medium 字重
      · hover 转主色（描边 + 文字 + 浅蓝底），与旁边的实心主按钮呼应但不抢主次 */
.page-actions .btn--ghost {
  background: var(--color-surface);
  border-color: var(--color-gray-400);
  color: var(--color-fg);
  font-weight: var(--font-weight-medium);
}

.page-actions .btn--ghost:hover:not(:disabled) {
  border-color: var(--color-accent-text);
  color: var(--color-accent-text);
  background: var(--color-primary-50);
}

/* ⭐ `--drawer-top`：移动端抽屉与遮罩的「上边界」默认值（0 = 贴屏幕顶）。
   ──────────────────────────────────────────────────────────────────────────
   为什么在这里**显式声明默认值**，而不是在使用处写 `var(--drawer-top, 0)`：
     **本项目铁律禁用「令牌 + 兜底值」** —— 令牌名拼错时会被兜底值静默掩盖
     （`tests/ui-token-standards.test.js` ② 会直接报红）。
     有标签栏的板块（hr / 财务）在自己的规则里把它改成 `--tabs-bar-height`
     —— 那是"改这个变量的值"，与"令牌拼错被兜底吃掉"是两回事。
   ⚠️ 语义：抽屉从标签栏下沿开始滑出，**让出一整条标签栏**。
      标签栏里装着移动端的汉堡开关 `#sidebar-toggle`；抽屉若压住它，
      用户打开菜单后就**关不掉**了（完整根因见下面 `.sidebar` 处那段说明）。 */
.app-shell { --drawer-top: 0; }
/* ⭐⭐ 2026-10-06 **达播也必须有 `--drawer-top`**（实测抓出来的真 bug）：
   ──────────────────────────────────────────────────────────────────────────
   上面默认值是 0（贴屏幕顶），原意是给"没有标签栏"的板块（门户 / 系统配置）用。
   但**达播**从来没有任何一条规则给它设 `--drawer-top` ⇒ 抽屉从 0 起、把顶栏
   （含汉堡按钮）整片盖住 ⇒ 用户在手机上**再点一次那个位置点不到汉堡**
   （实测：`elementFromPoint(38,28)` 命中的是侧栏的品牌区 `brand-mark`），
   只能靠点右侧遮罩才能关 —— 与 09-29 乐哥报的「菜单栏打开收不回去，卡死了」
   是同一个病，当时只修了 hr / 财务 / 项目三个板块，**漏了达播**。
   ⇒ 判据：**凡"顶栏（含汉堡）可见"的板块，抽屉都要从顶栏下沿起**。
     达播是唯一「非 is-standalone」的外壳（门户/系统配置/h5 都带 is-standalone 且顶栏隐藏）
     ⇒ 用 `:not(.is-standalone)` 精确覆盖它一只，其余板块行为**零变化**。
   ⚠️ 值仍引用 `--tabs-bar-height`（手机端它是 56px，与顶栏实际高一致）。 */
.app-shell:not(.is-standalone) { --drawer-top: var(--tabs-bar-height); }

/* 桌面端不显示折叠按钮（侧栏固定展开，不支持折叠） */
.sidebar-toggle {
  display: none;
}

/* 响应式：移动端侧栏变抽屉，内容区全屏 */
@media (max-width: 768px) {
  .sidebar-toggle {
    display: inline-flex;
  }
  .sidebar {
    /* ⭐⭐ `--drawer-top` = 抽屉与遮罩的「上边界」，默认 0（贴屏幕顶）。
       ──────────────────────────────────────────────────────────────────────
       🐞 2026-09-29 乐哥报障：「手机端有个 bug，菜单栏打开收不回去，卡死了」
       ──────────────────────────────────────────────────────────────────────
       **复现与根因**（真机 375×812，isMobile+hasTouch，逐项量过）：
         1. 移动端的「汉堡开关」`#sidebar-toggle` **住在标签栏里**
            （`.page-tabs-bar`，fixed、`z-index: var(--z-sticky)` = 1100）；
         2. 抽屉 `.sidebar` 原本 `top: 0` 且 `z-index: var(--z-modal)` = 1200
            ⇒ **抽屉比标签栏高一层，且整片盖住标签栏左侧 280px**；
         3. 实测：抽屉一打开，按钮中心 (92,28) 处的 `elementFromPoint` 命中的是
            `SPAN.nav-group-text`（抽屉里的菜单项），**不是按钮** ⇒ 再点一次关不掉
            （点下去反而会切到「刷脸记录」—— 用户看到的现象就是"点了没反应/乱跳"）。
         4. 而另一条正常的退路「点遮罩关闭」也断了（详见 pages.css 里
            `.sidebar-backdrop` 那条 —— 它被独立外壳的隐藏规则一起藏了）。
         ⇒ **两条关掉的路全断**，用户只能靠"点一个菜单项"把抽屉带走，
            如果只是想关掉菜单、不想切页，就真的**无路可走 = 卡死**。
       **修法**：抽屉与遮罩都从 `--drawer-top` 起（有标签栏的板块把它设成
       `--tabs-bar-height`）⇒ 标签栏连同它里面的汉堡按钮**永远露出一条、永远可点**，
       「再点一次关闭」和「点遮罩关闭」两条路同时恢复。
       ⚠️ 为什么做成变量而不是直接写 `top: var(--tabs-bar-height)`：
          门户 / 系统配置两个板块**没有标签栏**（它们不恢复 `.page-tabs-bar`），
          写死会让它们的抽屉顶部空出一条 56px 的缝。
          变量默认 0 ⇒ 没标签栏的板块行为**与改前完全一致**（零回归）。 */
    top: var(--drawer-top);
    left: 0;
    bottom: 0;
    width: min(280px, 82vw);
    border-radius: 0 var(--radius-xl) var(--radius-xl) 0;
    transform: translateX(-100%);
    z-index: var(--z-modal);
    transition: transform var(--motion-base) var(--motion-ease-standard);
  }
  .app-shell.sidebar-open .sidebar {
    transform: translateX(0);
  }
  .app-main {
    margin-left: 0;
    padding-right: 0;
    /* 移动端不分「桌面 + 悬浮面板」——内容区全屏，把悬浮相关的四条复位。
       （源顺序在后、特异性相同，所以能覆盖桌面端的规则） */
    margin-top: 0;
    margin-right: 0;
    margin-bottom: 0;
    background: transparent;
    border-radius: 0;
  }
  .sidebar {
    /* 抽屉式：贴左边缘、只圆右侧两角（上面的 .sidebar 块已设，这里补齐悬浮相关的复位） */
    margin: 0;
  }
  .page-stack {
    background: var(--color-bg);
    border-radius: 0;
  }
  .topbar {
    top: 0;
    margin-top: 0;
    border-radius: 0;
    padding: 0 var(--space-4);
  }
  .page {
    padding: var(--space-4);
    /* ⭐⭐⭐ 2026-09-29 顶部让位（乐哥：「二级页面你没优化啊」）
       ══════════════════════════════════════════════════════════════════════
       顶栏 `.page-tabs-bar` 是 `position: fixed` 高 **56**（实测），而 .page 的
       padding-top 只有 16 ⇒ 页面**第一个元素**被顶栏压住 40px。
       实测（390×844，scrollY=0）：二级页首个块 top **16**、顶栏底边 **56**
         · 达人详情 `.detail-shell` 被压 40（hero 里的昵称/头像上半截被糊掉）
         · 结算明细 / 商品详情 `.detail-head` 被压 24、`.page-title` 被压 11
         · 新增货品 / 提交报销 `.detail-head` 被压 40、标题被压 25
       —— 截图上肉眼可见：大标题「结算明细」只有下半截露在顶栏边缘外。
       ⇒ padding-top 16 → **66**（= 顶栏 56 + 10 呼吸）。

       ⚠️⚠️ 66 不是随手取的，它由**有 tab 的一级页必须零变化**倒推：
          那类页头自带 `margin-top: var(--space-2)`(8px)，66 + 8 = **74**，
          恰好等于它的 sticky 吸附值
          （`.page-header--has-tabs` 的 `top: calc(--tabs-bar-height + --frame-gap + 4px)`，
           手机端实测 74）。此时 `updateStuck` 的判据 delta = 74 − 74 = 0 ⇒
          仍判"贴顶"、玻璃态照旧 ⇒ **那一档页面逐像素不变**。
          写成别的值：小于 66 则页头会被顶栏啃掉几像素；大于 66 则页头
          离开吸附位、页面顶部多出一段空白。
       ⚠️ 为什么不做成"给首个元素加 margin-top"：本项目首个块的类名有
          `.detail-head` / `.detail-hero` / `.detail-shell` / `.sf-shell` /
          `.page.page--table` / `.card` 六种以上，逐个加必漏；
          而 padding 挂在 `.page` 一层，**全站一次覆盖**。 */
    padding-top: 66px;
  }
  /* ⛔ 明细页是**双层 .page** 结构（`.page-stack > .page` › `.page.page--table`，
     见 app.js 的 KIND.tier / WIDTH_TIER 说明）⇒ 上面那条会命中**两层**，
     顶距变成 66 + 66 = 132（实测结算明细页高高出 100px）。
     这里把**内层**复位回原值：一条 `.page .page`(0,2,0) 稳压 `.page`(0,1,0)。
     ⚠️ 别改成 `padding-top: 0` —— 内层自身的 padding 是明细页标题行的留白来源。 */
  .page .page {
    padding-top: var(--space-4);
  }
  .page-header {
    flex-direction: column;
    align-items: stretch;
  }
  .page-actions {
    flex-wrap: wrap;
  }
  /* ⚠️ 单个动作按钮的页头**保持整行宽**（移动端易点，且只有一个按钮不浪费空间）。
     实测：直播货盘「新增货盘」330x44、日历视图「新增排期」330x44 —— 高度 44~60px，正常。
     ⛔ 本节下方「有 tab 的页头」那条会把它覆盖掉，**只影响 .page-header--has-tabs**。 */
  .page-actions .btn--lg {
    width: 100%;
  }

  /* ⭐⭐⭐ 2026-09-29 有 tab 的页头：手机上不再「一个按钮占一整行」
     ══════════════════════════════════════════════════════════════════════
     乐哥原话：「做下手机端 UI 的适配，尤其是那个 tab 栏，现在在手机端会分成两行」。

     ⛔ 根因（实测，不是推测）——两条规则的**叠加**：
       ① tab 容器 `.table-tabs` 基础样式是 `flex-wrap: wrap`（注释写"极窄屏时折行"）
          且手机端**没有**覆盖它 ⇒ 390 视口下 5 个 tab 项折成 2 行；
       ② `.page-actions .btn--lg { width: 100% }`（本节上方那条）在手机端
          让**每个动作按钮独占一整行** ⇒ 3~4 个按钮就是 3~4 行。
     实测页头高度（390×844，视口仅 844px 高）：
       达人列表 274 / 费用明细 292 / 订单明细 292 / 库存管理 236 / 业绩总表 218
       —— 页头一家伙吃掉视口 1/3。

     ✅ 修法（乐哥拍板「方案 A」）：
       · tab 容器改 `nowrap` —— 复用它**本来就有的** `overflow-x: auto`
         （见 table.css 的 `.table-tabs--in-header`，两条都在，只是被 wrap 挡住没生效）
         ⇒ 一行横滑，不再折行；
       · 按钮取消 `width: 100%`、压到 36px 高横排 ⇒ 一行放下（放不下则横滑兜底）。
     实测效果：页头 274/292/292/236/218 → **全部 114px**（tab 一行 + 按钮一行）。

     ⚠️ 为什么**只**改 `--has-tabs`，不改全局 `.page-actions`：
        无 tab 的页头只有一个动作按钮（直播货盘 / 日历视图），满宽反而更好点、
        也不浪费空间；改了反而把它们从"一个整行按钮"变成"一个小按钮"。
     ⚠️ 为什么 `nowrap` 是安全的（`.table-tabs` 基础样式特意用 `overflow: visible`，
        注释说 `overflow-x:auto` 曾导致纵向滚动条）：
        那条坑来自**下划线指示器** `bottom:-1px`（内容 41 > 容器 40 ⇒ 纵向溢出 1px）；
        而 `--in-header` 是**胶囊形态**、下划线已 `display:none`，
        且它自身已有 `overflow-y: hidden` ⇒ 不会出现纵向滚动条。
     ⚠️⚠️ **必须显式 `width:100%` + `align-self:stretch`**，不能指望父级拉伸：
        父级 `.page-header--has-tabs` 有 `align-items: center`（**在 `table.css:525`**），
        而 table.css 的加载顺序在 layout.css **之后** ⇒ 它把 layout.css 里本块的
        `.page-header { align-items: stretch }` 盖掉了（同为 (0,1,0)、源顺序后者胜）。
        后果（实测）：tab 托盘按内容宽撑到 **457px 并居中**，左边缘跑到 **x=-34**
        （视口 390）⇒ tab 行溢出卡片、首尾被切掉。
        本选择器是 (0,2,0) > table.css 的 (0,1,0) ⇒ 显式声明能稳稳压住。
        ⚠️ 这是本项目的**复发形态**（同 `.sf-shell--modules` 那次）：改这类分组件的
           对齐/宽度前，先 `grep` 一遍**所有 CSS 文件**里同名选择器 —— 光看本文件会漏。 */
  .page-header--has-tabs .table-tabs--in-header,
  .page-header--has-tabs .page-actions {
    width: 100%;
    max-width: 100%;
    align-self: stretch;
  }
  .page-header--has-tabs .table-tabs--in-header {
    flex-wrap: nowrap;                        /* ⭐ 一行，不折 */
    -webkit-overflow-scrolling: touch;        /* 复用基础样式的 overflow-x: auto */
  }
  .page-header--has-tabs .page-actions {
    flex-wrap: nowrap;
    overflow-x: auto;                         /* 按钮多于一屏时横滑兜底，不换行 */
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
    gap: var(--space-2);                      /* 12 → 8：横排更紧凑 */
  }
  /* ⚠️ 横滑条要藏起来（Firefox 用上面的 `scrollbar-width`，WebKit 用这条）——
     与 tab 托盘（table.css 基础样式里已有 `scrollbar-width:none` + 同款 ::-webkit-）保持一致。 */
  .page-header--has-tabs .page-actions::-webkit-scrollbar {
    display: none;
  }
  .page-header--has-tabs .page-actions .btn,
  .page-header--has-tabs .page-actions .btn--lg {
    width: auto;                              /* ⛔ 取消上一条的 width:100% */
    flex: none;                               /* 不被 flex 压扁 */
    height: 36px;                             /* 44 → 36：紧凑档，横排放得下 */
    padding: 0 var(--space-3);                /* 20 → 12 */
    /* ⚠️ 用令牌，手机端算出来正好 13px —— design-tokens.css 的手机端块把
       `--font-size-sm` 从 14 降到 13，与 tab 胶囊的视觉重量一致（预览实测值）。 */
    font-size: var(--font-size-sm);
    white-space: nowrap;
  }

  /* ⭐⭐⭐ 2026-09-29 二级页 tab「一行不折」（上一轮漏网的那一半）
     ══════════════════════════════════════════════════════════════════════
     ⛔ 乐哥上一轮原话：「尤其是那个 tab 栏，现在在手机端会分成两行」——
        上轮只修了**一级页**（上方 `.page-header--has-tabs .table-tabs--in-header`
        → nowrap + 复用 overflow-x），二级页的 tab 内层小框漏了。
        本轮乐哥复述：「**二级页面你没优化啊**」。

     ⛔ 根因：二级页的胶囊装在 `.tabs__tray`（内层小框，与一级页
        `.table-tabs--in-header` 是**同位物**，由 app.js 的 wrapTabTrays 产出），
        而它的基础样式是 `flex-wrap: wrap`（components.css，注释写"窄屏换行"）。
        实测 390 视口折行情况：
          · 新增签约公司 **3 行**（136px，4 个 tab）
          · 达人详情 / 排期详情 / 新增达人 / 编辑排期 / 公司详情 **2 行**（80~100px）
        视口才 844px ⇒ tab 栏最高吃掉 1/6 屏。

     ✅ 修法：nowrap + 复用 `overflow-x: auto` 横滑（与一级页同一做法、同一观感）。

     ⚠️⚠️ **必须同时放开两层父容器**，否则横滑不生效、甚至撑破卡片：
        托盘是 `flex: none`（设计上"收缩到内容宽"），而它的父
        `.tabs` 是 `width: fit-content`、`.sf-tabs__track` 是 `display: inline-flex`
        —— 两者都是"宽度由内容决定"。托盘一旦 nowrap，就把父**一起撑破**
        ⇒ 二级页整页横向溢出（`.sf-shell` 虽有 overflow:hidden 兜住，但内容被裁）。
        给父一个**受限宽度**（`width: 100%`）后，托盘的 `flex: 1 1 auto; min-width: 0`
        才能收缩 ⇒ 触发横滑。
        ⚠️ 实测：放开父宽度后各层宽度与改前**完全一致**
           （.tabs 330 / .sf-tabs__track 298 / .tabs__tray 248~280）
           ⇒ 视觉上只有"折行消失"这一个变化，没有布局漂移。
        ⚠️ `.tabs` 只改宽度、**不改 wrap**：它里面还有 `.detail-head__act`（编辑/
           佣金配置按钮，宽 84~202），nowrap 会把按钮和托盘挤到同一行互相抢宽度。
           保持 wrap ⇒ 托盘一行 + 按钮一行（与一级页"tab 一行、按钮一行"同构）。

     ⚠️ `overflow-y: hidden` **必须显式写**：`overflow-x: auto` 会把 overflow-y
        也变成 auto ⇒ 托盘高 44（4+34+4+2）与内容高 42 只差 2px，可能长出竖滚动条
        （本项目 2026-09-14 已在 `.table-tabs` 踩过同一个坑，见 `.tabs` 的注释）。
     ⚠️ 为什么这次敢开横滑（当年 `.tabs` 注释说"不用 overflow-x: auto"）：
        那次怕的是**下划线指示器** `bottom:-1px` 纵向溢出 1px；而托盘现在全是
        胶囊（`.tab` / `.sf-tab`，`box-shadow: none`）、`.tabs__ink` 已
        `display: none` ⇒ 没有会溢出的装饰。

     ⛔⛔⛔ **选择器必须带 `.page` 前缀（= 两条都写全）** —— 这是本项目最容易
        复发的一类坑，本次差点又栽：
          · `.tabs__tray` 的基础样式在 **components.css (0,1,0)**；
          · `.tabs { width: fit-content }` 在 **pages-influencer.css:543 (0,1,0)**；
          · 而 index.html 的顺序是 `layout.css`(23) → `components.css`(24) → …
            → `pages-influencer.css`(32) ⇒ **本文件排在两者之前**，
            同特异性一律**后者胜** ⇒ 单写 `.tabs__tray` 会被 components.css 的
            `flex: none; flex-wrap: wrap` **原样盖回去，改动静默失效**。
          ⇒ 故写成 `.page .tabs__tray`(0,2,0) / `.page .tabs`(0,2,0) 稳压。
          ✅ `.page` 是页面根容器，所有 tab 都在它里面（详情页 / 表单页 / 卡内
             三种形态都是后代）⇒ 后代选择器全部匹配。
          ⚠️ 这与一级页那条同款教训（上方 1584 行注释：`.page-header--has-tabs
             .table-tabs--in-header` 是 (0,2,0) > table.css 的 (0,1,0)）。 */
  .page .tabs,
  .page .sf-tabs__track {
    width: 100%;
    max-width: 100%;
    min-width: 0;
  }
  .page .tabs__tray {
    flex: 1 1 auto;                           /* 收缩到父宽（原 flex:none 会撑破父） */
    min-width: 0;                             /* ⛔ 允许收缩（flex 项的收缩下限） */
    flex-wrap: nowrap;                        /* ⭐ 二级页托盘一行，不折（与一级页同做法） */
    overflow-x: auto;                         /* 项多于一屏时横滑兜底 */
    overflow-y: hidden;                       /* ⛔ 显式：防止 x:auto 连带把 y 变 auto */
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }
  .page .tabs__tray::-webkit-scrollbar {
    display: none;
  }
  .page .tabs__tray > * {
    flex: none;                               /* 胶囊不被压缩（横滑而非挤扁） */
  }

  /* ⭐⭐⭐ 2026-09-29 顶栏（.page-tabs-bar）手机端溢出 —— 用户头像被推出屏幕
     ══════════════════════════════════════════════════════════════════════
     ⛔ 实测（390 视口）：`.page-tabs-bar__tools`（logo 右侧那组）宽 **236**，
        从 l=270 一直排到 **r=506** ⇒ `.user-chip`（「张乐 超级管理员」）
        整体落在 390~506，**完全在屏幕外**。
        后果：**手机上无法进入个人中心 / 无法退出登录**。
        （截图 `/tmp/mob/topbar-达人列表.png`：顶栏只有 logo + 汉堡 + 搜索 + 铃铛。）

     ⛔ 根因：`.page-tabs-bar__left` 在手机端仍占 `width: calc(--sidebar-width + ...)`
        ≈ 246px（它是为**桌面**「让 logo 与侧栏同宽」而设的），加上 tools 的 236px
        ⇒ 390 视口装不下；而三者都是 `flex: 0 0 auto`（不许收缩）⇒ 只能溢出；
        `.app-main` 是 `overflow: clip` ⇒ 溢出部分被裁、**不可滑动**。

     ✅ 修法三件（缺一不可）：
       ① `__left` 不再按侧栏宽度对齐（手机上侧栏是抽屉，无"对齐"可言），交给 flex 分配；
       ② **隐藏品牌名文字**（保留 logo）—— 390 视口下可用宽度实测只有 ~108px
          （390 − logo34 − 汉堡44 − 搜索44 − 铃铛44 − 头像116 − 间距），
          而品牌名「星盟达人后台」要 107px，正好把余量吃光。
          ⚠️ 不隐藏会怎样（实测踩过）：`__left` 被压到 75px，但品牌名不收缩、
             溢出到 147px ⇒ **压在页面标签「达人列表」上、两层文字重叠**。
          移动端只留 logo 是通行做法（星形图标本身已足够标识品牌）。
       ③ `__tools` 允许横滑兜底（窄屏如 iPhone SE 320 仍够得着）。
     ⚠️ 为什么不去缩小 logo/汉堡：它们已是 44×44 触摸目标（Apple HIG 下限），
        再压会违反可达性。
     ⚠️ 与 page-tabs.test.js 的关系：那条门禁锁的是**桌面**顶栏的
        top/left/right/圆角/底边线（见该文件 G 系列），本条只改移动端块内，
        不影响桌面断言。 */
  /* ⭐⭐⭐ 2026-09-29 乐哥拍板（方案A）：手机端**整格隐藏「页面标签」**
     ──────────────────────────────────────────────────────────────────────────
     原话：「顶部去掉标签栏吧，要不然标签栏本来就显示不了多少位置，还会挤压别的组件」。
     ⛔ 实测（390 视口 / 4 个标签）：标签格只占 140px，却要装 4 个约 120px 宽的标签
        ⇒ **只显示得下 2 个**，其余靠横滑（要滑才知道还有别的页打开着）；
        而这 140px 是从「菜单开关 + 搜索 + 铃铛 + 头像」身上硬挤出来的
        （见下方 ④ 的历史记录：最紧的时候页面标签压在汉堡按钮上，只差 9px）。
        iPhone SE（320 宽）上更挤。⇒ **手机端不再显示标签格**。
     ⚠️ 只隐藏 `#page-tabs-tabs`，**不动** `__left`（菜单开关）与 `__tools`
        （搜索 / 通知 / 头像）—— 这三件都是手机端的**唯一入口**，去掉等于少一个功能：
          · 菜单开关 = 手机上唯一能唤出侧栏的入口
          · 搜索     = 全站搜索的唯一入口
          · 头像     = 唯一能进个人中心 / 退出登录的入口
     ⚠️ 多标签能力不丢：桌面照旧；手机端切页走侧栏菜单或页内返回。
        标签的**保活语义**（切回不重渲染）在后端逻辑里，与本条无关，完全不受影响 ——
        只是没有可视的标签条可点。
     ⚠️ 桌面不受影响：媒体查询只覆盖 ≤768px（本块内）。 */
  #page-tabs-tabs {
    display: none;
  }
  /* ⚠️ 与上一条配套：标签格隐藏后 `__left` 不必再当"吃掉剩余宽度"的弹性项
     （原来 flex:1 的作用是把 `__tools` 顶到右边；现在改由 `__tools` 自己 margin-left:auto 推）。 */
  .page-tabs-bar__left {
    width: auto;
    flex: 0 0 auto;
    min-width: 0;
  }
  .page-tabs-bar__left .brand-name {
    display: none;                            /* 见上方 ②：手机只留 logo */
  }
  /* ⭐ 2026-10-06：手机端**面包屑也隐藏** —— 与"品牌名只留 logo"同一条理由：
     390 视口下 `__left` 的可用宽度只有约 100px，而面包屑「货品与佣金 › 货品列表」
     是 nowrap、要 130px+ ⇒ 会把 `__tools`（搜索/铃铛/头像）**推出屏幕**
     （实测：工具组被挤到 l=472，屏宽只有 390 ⇒ 用户看不到通知与头像）。
     ⚠️ 手机端"我在哪"由**抽屉里高亮的菜单项**回答（打开抽屉一眼看到当前项），
        不需要占顶栏——这与"手机端本来就不显示多标签"是同一套取舍。 */
  .page-tabs-bar .bc-crumb {
    display: none;
  }
  /* ⚠️⚠️ 2026-10-06 手机端**必须把顶栏的四边复位** ——
     桌面那条规则是 `left: var(--sidebar-width); right: var(--frame-gap)`
     （"顶栏只在内容卡内"）。手机上侧栏是抽屉、`.app-main` 也是全屏（margin 归零），
     顶栏若还留 left=240 ⇒ 整条顶栏从屏幕 240px 处才开始、宽度溢出屏幕右侧，
     **实测工具组（搜索/铃铛/头像）被推到 l=324 / r=497（屏宽 390 ⇒ 看不见）**，
     而这三件是手机上"搜索 / 通知 / 个人中心"的**唯一入口**。
     ⚠️ `@media` 不改变特异性，所以必须在媒体查询里**显式**覆盖（与
        pages.css 里 `.is-standalone.is-finance .app-main` 那条同一个坑）。 */
  .page-tabs-bar {
    left: 0;
    right: 0;
    border-radius: 0;
    padding: 0 var(--space-4);
  }
  .page-tabs-bar__tools {
    flex: 0 0 auto;
    overflow-x: auto;
    scrollbar-width: none;
    /* ⭐ 推到最右（与桌面"搜索/通知/头像靠右"的观感一致）。
       ⚠️⚠️ 桌面那条规则上方写着「`__tabs` 是 flex:1 时不许有第二个 auto」——
          手机端**恰恰相反**：`__tabs` 已 display:none、`__left` 也不再是弹性项
          ⇒ 此刻**必须有且只有这一个 auto** 才能推右（否则整组贴左、右侧留一片空）。
          改这里之前先确认那两个前提仍然成立。 */
    margin-left: auto;
  }
  .page-tabs-bar__tools::-webkit-scrollbar {
    display: none;
  }
  /* ④ 用户 chip 只留头像（隐藏姓名/角色两行文字）。
     ⚠️ 历史（2026-09-29 已被方案A取代，保留是为了看"为什么曾经必须这么做"）：
        当时乐哥说过"顶栏标签要保留"，所以腾宽度的来源只能是别处。实测各件需求
        （390 视口，扣左右 padding 后可用 ~358）：
          logo 34 + 汉堡 44 + 页面标签 85 + 搜索 44 + 铃铛 44 + 完整 chip 116 = **367 > 358**
        ⇒ 差 9px，表现为**页面标签压在汉堡按钮上**（实测 tab 45~130 vs toggle 70~90）。
        把 chip 压到只留头像（≈46px）后总需求 **297px**，余量 61px，各件不再挤压。
     ⭐ 现行（方案A）：**标签格已整格隐藏**（见上方），宽度不再是问题 ——
        但**这条仍然保留**：手机端只显示头像本来就是通行做法（姓名/角色两行在 390 宽下
        纯属占位），而且它也保证了在 iPhone SE（320）等更窄的屏上不会重新开始挤压。
     ⚠️ 功能不丢：头像本身仍是同一个可点入口（点击进个人中心），只是不显示文字。
     ⚠️ 不要改成 `display:none` 把它整个藏掉 —— 那会让手机上**没有个人中心入口**
        （正是 2026-09-29 修过的那个 bug）。 */
  .page-tabs-bar .user-chip .user-meta {
    display: none;
  }
  .input,
  .select {
    min-height: 44px;
  }
  .icon-btn {
    width: 44px;
    height: 44px;
  }
  /* ⭐ 顶部标签栏内的图标按钮：移动端**回到 44px**（触摸目标）。
     ────────────────────────────────────────────────────
     ⚠️⚠️ 这条是**必须的**，不是可选的修饰。原因：
       桌面那条 `.page-tabs-bar .icon-btn { 36px }`（特异性 0,2,0）**在手机上也照样生效**
       （media 查询不改变特异性），会把上面这条 `.icon-btn{44}`（0,1,0）盖掉。
       不补本条，手机上的菜单开关 / 刷新 / 关闭 / 搜索 / 通知 全会变成 36px。
     ⭐ 为什么不能只看桌面：菜单开关是**手机上唯一能唤出侧栏的入口**
       （顶栏已撤，见本文件 C4 那条门禁）。36px 命中区偏小：
       Apple HIG 建议 ≥ 44pt、Material 建议 ≥ 48dp。
       （实测过：本轮改动前手机上它是 40px、改后若不补本条会掉到 36px —— 比改前还小。）
     ⭐ 两个场景各自回到既有基准（不是新造一套尺寸）：桌面 36 / 移动端 44。 */
  .page-tabs-bar .icon-btn {
    width: 44px;
    height: 44px;
  }
  /* 栏高在移动端单独加到 56（桌面 52）——
     因为里面装的是 44px 的触摸目标，需要上下各 6px 的留白。
     ⚠️ 必须联动 `--header-height`（分析页目录条的吸顶高度读它），否则手机上目录条会错位。
     ⚠️ 不能把本条写进上面那个公共区域：它只管移动端，而 `--tabs-bar-height` 是全局令牌。 */
  :root {
    --tabs-bar-height: 56px;
    --header-height: 56px;
  }
}

/* 侧栏抽屉遮罩（仅移动端显示） */
.sidebar-backdrop {
  display: none;
}
@media (max-width: 768px) {
  .sidebar-backdrop {
    display: block;
    position: fixed;
    /* ⚠️ 与抽屉同一起点（见上面 `.sidebar` 的 `--drawer-top` 说明）：
       遮罩若从 0 起，会**盖住标签栏**（遮罩 1199 > 标签栏 1100）⇒
       标签栏里的汉堡按钮照样点不到，"再点一次关闭"还是断。
       两者必须同起点，才能留出那条永远可点的标签栏。 */
    inset: var(--drawer-top) 0 0 0;
    background: color-mix(in srgb, var(--color-gray-900) 40%, transparent);
    z-index: calc(var(--z-modal) - 1);
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--motion-base) var(--motion-ease-standard);
  }
  .app-shell.sidebar-open .sidebar-backdrop {
    opacity: 1;
    pointer-events: auto;
  }
}

/* ==========================================================================
   侧栏：搜索入口 + 底部「我的」（2026-09-19 结构重排）
   乐哥：「搜索框放在菜单栏里面（位置你给建议）」「右上角我的 → 挪菜单栏最下面」
   ========================================================================== */

/* ==========================================================================
   标签栏右侧：搜索入口（2026-09-19 乐哥：「搜索框要不继续放到右上角，就是通知左边」）
   ─────────────────────────────────────────────────────────────────────────────
   从侧栏搬过来。三件事决定了它的样子：

   ① 宽度 150px（不是历史那种 280px 的完整输入框）——
      这是"标签区空间"与"框的体量"的取舍。实测标签区余量（4 个标签需 484px）：
        视口 1512：280px 宽时余 319 / 150px 宽时余 449
        视口 1280：280px 宽时余  87 / 150px 宽时余 217
        视口 1180：280px 宽时**缺 13px（标签开始横向滚动）** / 150px 宽时余 117
      ⇒ 280px 会在 13 寸笔记本上把标签挤到滚动；150px 在 1280 以上都稳。
      （150 这个数是照"搜索 + 图标 + / 提示"实际需要的宽度定的，不是随手取的。）

   ② 它是一个**入口**、不是输入框（点击弹 640px 居中弹层）——
      原因见 index.html 的注释：问 AI 的长回答需要 640px，下拉面板装不下。

   ③ 位置在**通知左边**、且属于"全局工具"那一组（刷新/关闭是"标签的操作"，
      搜索与通知是"全站的能力"）⇒ 与通知同组、共用一个左侧分隔线。 */
.gs-entry {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 150px;
  /* 高 40（2026-09-19 晚 34 → 40）—— 与标签（40）和顶栏内图标按钮（40）同一体量。
     原来 34 比邻居矮 2~4px，一行里看着"没对齐、没长齐"。 */
  height: 40px;
  padding: 0 var(--space-3);
  flex-shrink: 0;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-bg);
  color: var(--color-muted);
  font-size: var(--font-size-sm);
  font-family: inherit;
  cursor: pointer;
  transition: border-color var(--motion-fast) var(--motion-ease-standard),
    background var(--motion-fast) var(--motion-ease-standard);
}
.gs-entry:hover { border-color: var(--color-accent-text); background: var(--color-surface); }
.gs-entry__icon { display: flex; flex-shrink: 0; opacity: .75; }
.gs-entry__text { flex: 1; text-align: left; }
/* 快捷键提示（`/`）—— 与侧栏那版同款小键帽，告诉新人这功能有快捷键 */
.gs-entry__kbd {
  flex-shrink: 0;
  font-size: var(--font-size-xs);
  line-height: 16px;
  padding: 0 5px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  color: var(--color-meta);
}
/* 这一组（搜索 + 通知）与左边「标签操作」（刷新/关闭）用一条竖线断开，语义不同 */
.gs-entry-wrap {
  display: flex;
  align-items: center;
  margin-left: 6px;
  padding-left: 10px;
  border-left: 1px solid var(--color-border-soft);
}
/* 通知：同组内与搜索留一点气口（分隔线已由 .gs-entry-wrap 提供，这里不再画第二条） */
.page-tabs-bar__tools .notify-wrap {
  margin-left: 6px;
  padding-left: 0;
  border-left: none;
}
/* 窄屏（≤1100px）：搜索入口收成图标，把宽度还给标签。
   实测 1024px 视口下 150px 入口会让标签区缺 39px；收成图标后够用。
   ⚠️ 只隐藏文字与键帽、**不隐藏整个按钮** —— 搜索在窄屏上更该有入口（侧栏入口已撤）。 */
@media (max-width: 1100px) {
  .gs-entry { width: 36px; padding: 0; justify-content: center; }
  .gs-entry__text, .gs-entry__kbd { display: none; }
}
/* 手机端（≤768px）：工具区本来就只有图标，保持图标形态即可；
   侧栏入口已撤，手机上搜索必须还能用 ⇒ 不隐藏。 */

/* 底部「我的」——基础盒模型在 base.css 的 .sidebar__footer（padding/border-top/flex-shrink），
   这里只写**布局变更**部分，避免同一个类在两处重复声明同一批属性。 */

/* ⚠️ 箭头（chevronDown）必须推到最右（2026-09-19 乐哥：「那个箭头太靠左」）。
   原因：`.user-chip` 是 flex，箭头是第 3 个子元素，默认紧跟在名字后面；
   在原顶栏里它靠得近不明显，搬到 240px 宽的侧栏底部、且 chip 被拉成 100% 宽之后，
   箭头停在中间、右边留一大片空 —— 看起来像没对齐。
   ⇒ `margin-left:auto` 把它顶到右边缘，与下方菜单项右缘对齐。
   ⚠️ 只作用于侧栏底部这一个 chip，不动顶栏/其它位置的 .user-chip。 */
.sidebar__footer .user-chip > .nav-icon {
  margin-left: auto;
  flex-shrink: 0;
}
/* 名字区可收缩：名字长时省略，不把箭头挤出容器 */
.sidebar__footer .user-chip > .user-meta {
  min-width: 0;
  overflow: hidden;
}
.sidebar__footer .user-chip .user-name,
.sidebar__footer .user-chip .user-role {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.sidebar__footer .user-chip { width: 100%; }
/* ⚠️ 下拉**向上**弹（在底部）：原顶栏里是 `top: calc(100% + 8px)`，
   搬到底部后那样会弹到屏幕外。改为向上、且左对齐铺满侧栏。 */
.sidebar__footer .profile-menu {
  top: auto;
  bottom: calc(100% + 8px);
  left: 0;
  right: auto;
  width: 100%;
  min-width: 208px;
}

/* ==========================================================================
   全站搜索 · 居中弹层
   --------------------------------------------------------------------------
   ⭐⭐ 2026-09-26 加**入场/退场动画**（乐哥实报：「点击右上角搜索按钮后，弹出了对话框，
   对话框弹出没动画」）—— 全站其他弹层（抽屉 / 弹窗 / toast / 行内菜单）都有
   fade-in / modal-in，只有这里一直是"瞬时显隐"（hidden 切 display，display 不可过渡）。
   ✅ 复用的就是 overlays.css 那套 keyframes 与令牌（语言完全一致，不新造动画）：
        · 遮罩 .gs-overlay → fade-in / fade-out（与 .drawer-overlay 同款）
        · 对话框 .gs-dialog → modal-in / modal-out（与 .modal 同款，弹入带回弹）
   ⚠️ 退场由 JS 加 `[data-closing]` 触发、**播完才 hidden**（见 app.js 的 gsCloseOverlay）——
      hidden 是 display:none，先设就看不到退场。退场倍率 0.62 与全站一致。
   ⚠️ 入场**不需要 JS**：元素从 display:none 恢复显示时，CSS animation 会重新开始（规范行为）。
   ========================================================================== */
.gs-overlay {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  background: color-mix(in srgb, var(--color-gray-900) 38%, transparent);
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 12vh var(--space-4) var(--space-4);
  animation: fade-in var(--motion-slow) var(--motion-ease-sheet) both;
}
.gs-overlay[hidden] { display: none; }
/* 退场：与 .drawer-overlay 同款（时长同倍率、曲线同 --motion-ease-exit） */
.gs-overlay[data-closing] {
  animation: fade-out calc(var(--motion-slow) * 0.62) var(--motion-ease-exit) both;
}
.gs-dialog {
  width: min(640px, 100%);
  max-height: 70vh;
  display: flex;
  flex-direction: column;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
  animation: modal-in var(--motion-slow) var(--motion-ease-sheet) both;
}
/* 退场：与 .modal 同款；⚠️ pointer-events:none —— 退场期间对话框不可再点（防误触） */
.gs-dialog[data-closing] {
  animation: modal-out calc(var(--motion-slow) * 0.62) var(--motion-ease-exit) both;
  pointer-events: none;
}
.gs-head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: 0 var(--space-4);
  height: 52px;
  flex-shrink: 0;
  border-bottom: 1px solid var(--color-border-soft);
}
.gs-head__icon { display: flex; flex-shrink: 0; color: var(--color-muted); }
.gs-head__input {
  flex: 1;
  min-width: 0;
  border: none;
  outline: none;
  background: transparent;
  font-size: var(--font-size-base);
  color: var(--color-fg);
}
.gs-head__input::placeholder { color: var(--color-placeholder); }
.gs-head__close {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  flex-shrink: 0;
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--color-muted);
  cursor: pointer;
}
.gs-head__close:hover { background: var(--color-bg); color: var(--color-fg); }
/* 弹层里的结果面板：不再绝对定位（它是弹层的直接内容），铺满并自己滚 */
.gs-dialog .global-search__panel {
  position: static;
  min-width: 0;
  width: 100%;
  max-width: none;
  max-height: none;
  flex: 1 1 auto;
  overflow-y: auto;
  border: none;
  border-radius: 0;
  box-shadow: none;
}
/* 打开时锁住背景滚动，避免弹层后面还能滚 */
body.gs-open { overflow: hidden; }


/* ══════════════════════════════════════════════════════════════════════════
   ⭐⭐ 2026-09-25 Gashora 骨架落地：顶栏内部三段布局
   ──────────────────────────────────────────────────────────────────────────
   参考图（乐哥指定、逐字描述）：
     「最上面一行左右贯穿 —— 左上角 logo，中间有一部分留白，接着是搜索框，
       右边是通知，最后是头像框（头像框周围做了胶囊状的边框）」
   落位（DOM 由 app.js 的 relocateChrome 迁移，样式在本段）：
     #tabs-left        [菜单按钮] [logo]          ← 左上（菜单按钮是折叠/移动端入口，保留）
     #page-tabs-tabs   **隐藏**（乐哥：「顶部标签我觉得可以不要了，直接去掉」）
     #page-tabs-tools  [搜索] [通知] [头像胶囊]     ← 右上（margin-left:auto 推到最右）
   ══════════════════════════════════════════════════════════════════════════ */


/* 顶栏品牌区（由 app.js 从侧栏 #sidebar-brand 迁入）。
   ⚠️ 尺寸覆盖侧栏那套（.brand-logo 36 / .brand-name__zh 18）—— 顶栏里要更大：
      参考图左上角的 logo 是顶栏的视觉主体（乐哥：「左上角 logo 好像可以大点」→ 46px）。 */
.topbar-brand {
  display: flex;
  align-items: center;
  gap: 12px;
  /* ⭐⭐ 2026-09-26 补（乐哥实报「左上角点击 logo 没法跳回首页了」的**视觉那一半**）：
     本元素由 `relocateChrome()` 从 `#sidebar-brand` 复制而来，而 `.sidebar__brand` 上
     原本就写着 `cursor: pointer` —— **该属性没被复制过来**（innerHTML 只复制内容），
     实测这里一直是 `cursor: auto` ⇒ 就算点击已经修好，鼠标移上去也不变手型、
     用户仍会认为"这里不能点"。
     ⚠️ 修 bug 要修**能感知的那一半**：功能通了但毫无可点暗示 = 用户眼里还是坏的。
     ⚠️ 不要在这里另写焦点环：`activateBrand()` 会给本元素设 `role="button"`，
        而 base.css 的全局规则 `:where(button,[role=button],[tabindex]):focus-visible`
        已经给了 `2px solid --color-accent` + offset 2px（与侧栏那份同一套）。
        重复写一条只会多一处"改了全局这里不生效"的隐患。
     ⚠️ 尤其不要写 `outline: var(--focus-ring)` —— 那个令牌是 **box-shadow 语法**
        （`0 0 0 3px rgba(...)`），当 outline 用会被整条丢弃、焦点环直接消失。 */
  cursor: pointer;
  /* hover 反馈：与 `.sidebar__brand:hover` **同一个令牌**（--color-surface-2 = #FAFBFC），
     两处语言一致 —— 同一个"回首页"的动作，不该因为搬到了顶栏就换一套手感。
     ⚠️ 圆角 + 负外边距：顶栏品牌区两侧没有侧栏那样的通栏留白，
        直接画方块底色会紧贴文字；用 8px 圆角 + 4px 内扩让它看起来像个"药丸"。
     过渡时长用 --motion-fast（hover 类统一档位）。 */
  padding: 4px 8px;
  margin: -4px -8px;
  border-radius: var(--radius-md);
  transition: background-color var(--motion-fast) var(--motion-ease-standard);
}
.topbar-brand:hover {
  background: var(--color-surface-2);
}

.topbar-brand .brand-logo,
.topbar-brand .brand-logo svg {
  width: 34px;    /* A6：46 → 34（栏 96→72 的配套；原型实测 34 与 18px 名称、40px 控件成档）*/
  height: 34px;
  display: block;
}
.topbar-brand .brand-name__zh {
  font-size: 18px;   /* A6：24 → 18（同上配套）*/
  font-weight: 700;
  color: var(--color-fg);
  letter-spacing: -0.01em;
}
/* 英文名在顶栏不显示（空间宝贵，中文名已足够识别） */
.topbar-brand .brand-name__en { display: none; }


/* 顶栏里的搜索入口：A6 改为 **40×40 图标盒**（乐哥：「搜索改成图标吧，不要输入框了，
   这样标签就有更多位置」—— 让出 ~200px 给标签区，1440 宽实测 8 个标签全放下）。
   点击仍弹全局搜索弹层（#gs-dialog），交互不变；「/」快捷键也仍在，只是不再显示提示。
   ⚠️ 形态与铃铛/头像同槽：1px 描边 var(--color-border)（=#E1E8F2）+ 透明底（乐哥：「里面不要填充颜色」）+ 圆角 10。
   ⚠️ 文字与 kbd 的隐藏**限定在顶栏作用域**——侧栏等其他处的 .gs-entry 不受影响。 */
.page-tabs-bar__tools .gs-entry {
  width: 40px;
  min-width: 40px;
  height: 40px;
  /* ⭐ 2026-10-06：10px → 令牌。原值 10 与 `--radius-md` **恰好同值**，
     但写死会让人以为"这里可以单独调" —— 右上角三个控件必须同圆角（本轮刚统一）。 */
  border-radius: var(--radius-md);
  background: transparent;
  border: 1px solid var(--color-border);
  padding: 0;
  justify-content: center;
}
.page-tabs-bar__tools .gs-entry__text,
.page-tabs-bar__tools .gs-entry__kbd { display: none; }
.page-tabs-bar__tools .gs-entry svg { width: 18px; height: 18px; }
/* ⭐⭐ 2026-09-26 修（乐哥实报「搜索图标颜色和通知的不一致」）：
   顶栏这三个盒子（搜索 / 通知 / 头像）是**并列的主控件**，图标必须同一档颜色。
   🐞 差在哪（生产实测，getComputedStyle 取值）：
     · 通知图标 → color rgb(63,71,83) = `--color-fg-2`，**无透明度**
     · 搜索图标 → color rgb(84,101,121) = `--color-muted`，**外加 `.gs-entry__icon` 的 opacity .75**
       ⇒ 实际渲染 ≈ rgb(127,137,150)，比通知**浅两档**。
   ⭐ 归因：`.gs-entry` 的 muted 与 `.gs-entry__icon` 的 .75 都是"搜索还是个输入框"时代的
     产物（输入框里的图标是装饰，弱化合理）；A6 把它改成**与铃铛并列的图标盒**后，
     这层弱化就变成了"两个同款盒子、图标深浅不一"。
   ⚠️ 只在本作用域覆盖（`.gs-entry` 在**侧栏**里仍是带文字的入口，那里保持原样更合适）。 */
.page-tabs-bar__tools .gs-entry { color: var(--color-fg-2); }
.page-tabs-bar__tools .gs-entry__icon { opacity: 1; }
.page-tabs-bar__tools .gs-entry:hover { background: var(--color-topbar-chip-hover); }
.page-tabs-bar__tools .gs-entry-wrap {
  border: 0;
  padding: 0;
}
/* 通知铃铛：A6 与搜索同款 40×40 图标盒（原 48 圆形无底；svg 20 → 18 同步） */
.page-tabs-bar__tools #notify-wrap .icon-btn,
.page-tabs-bar__tools #notify-bell {
  width: 40px;
  height: 40px;
  border-radius: var(--radius-md);
  background: transparent;
  border: 1px solid var(--color-border);
}
.page-tabs-bar__tools #notify-wrap .icon-btn:hover { background: var(--color-topbar-chip-hover); }
.page-tabs-bar__tools #notify-wrap .icon-btn svg { width: 18px; height: 18px; }

/* ── 顶栏里的「我的」：**胶囊（头像 + 名字）**
   ⭐ 2026-09-26 乐哥：「右上角个人中心，在头像右边再加个名字吧，做成胶囊形状」。
   ▸ 形态演进：A5 文字版（头像+名字+角色+箭头，58px 高）→ A6 **纯圆头像**（40px，全文字隐藏）
     → 本版 **胶囊**（40px 高、头像 30px + 名字单行）。
   ▸ 只显示**名字**（`.user-role` 仍隐藏）：乐哥说的是"加个名字"；
     角色（超级管理员）是另一层信息，挤进 40px 胶囊会让文字缩到 11px、反而不清爽。
   ▸ 箭头仍隐藏（A6 的决定，乐哥当时没要求恢复）：菜单照常弹（点击/回车都行）。
   ▸ 宽度自适应：`width: auto`，由「左内边距 5 + 头像 30 + 间距 8 + 名字宽 + 右内边距 12」决定；
     名字长度不同（2~4 字）胶囊宽度随之变化，不会像固定宽那样出现"名字贴边"。
   ⚠️ 这套样式**只作用于顶栏内**（.page-tabs-bar__tools 作用域）——
     侧栏底部那个「我的」是"头像+名字+角色+箭头"的完整版（宽度 100%），不受影响。
   ⚠️ 头像 32 → 30px：40px 胶囊里 32px 头像上下只剩 4px，视觉"顶格"；
     30px 时上下各 5px、左右留白协调（门禁 H8 的尺寸区间已同步放宽为 28~32）。 */
.page-tabs-bar__tools .profile-wrap { position: relative; }
.page-tabs-bar__tools .user-chip {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: auto;
  height: 40px;
  /* ⭐ 2026-09-26 晚：右内边距 12 → 16（乐哥「可以稍微宽一点点」）。
     加了角色行之后，右侧若还是 12px 会显得"文字顶到胶囊边"（角色名 5 个字比名字宽）。
     ⚠️ 只加右内边距、不动高度与圆角 —— 胶囊仍与旁边 40px 的搜索/通知按钮同高。 */
  padding: 0 16px 0 5px;
  /* ⚠️⚠️ 圆角**保持 `--radius-pill`（胶囊）**，别改：
     2026-09-26 乐哥明确要过「做成胶囊形状」，且由 tests/schedule-day-ui.test.js ① 静态锁着。
     ⚠️ 2026-10-06 乐哥问「右上角的这个样式是不是要调一下」时，我实测发现同一行里
        确实有**三种圆角/尺寸语言**（搜索 40 方角 / 通知 36 无边框 / 本胶囊全圆），
        但"要不要把本胶囊也改成方角 10px"属**乐哥的取舍**，不是能替他定的 ——
        已出三档对比图（`scripts/frontend-debug/mock-topright-radius.js` 的 T1/T2/T3），
        他点了再改；⛔ 不要因为"看着不齐"就单方面把胶囊改方。
        客观缺陷（通知按钮 36×36 **无边框**，与 A6 定稿的"统一 40×40 有框无底"不符）
        已在 `.page-tabs-bar__tools .icon-btn` 修掉 —— 那处不涉及他的取舍。 */
  border-radius: var(--radius-pill);
  border: 1px solid var(--color-border);
  background: transparent;
  cursor: pointer;
  box-sizing: border-box;
}
.page-tabs-bar__tools .user-chip:hover { background: var(--color-topbar-chip-hover); }
.page-tabs-bar__tools .user-avatar,
.page-tabs-bar__tools .user-avatar * {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  font-size: 14px;
}
/* ⭐⭐ 2026-09-26 修（乐哥实报「右上角头像，没有居中展示」）。
   ──────────────────────────────────────────────────────────────────────────
   🐞 根因：base.css 的 `.user-avatar` 带 `padding-top: 1px`（那条是给**文字首字母**
     做的基线补偿："张"字在圆里按几何居中会显偏上）。但它对本元素是 `box-sizing: border-box`，
     于是**内容盒高只剩 31px**；而下面那条 `*` 规则把 `img` 强制成 `height: 32px`
      ⇒ 图片比内容盒高 1px、flex 居中后**整体下溢 0.5px**。
      生产实测（getComputedStyle + getBoundingClientRect）：
        图片相对外层圆框 上间距 **4.5px** / 下间距 **3.5px**（差 1px，即中心偏下 0.5px）。
      8 倍放大截图能直接看到上缘那条环比下缘宽。
   ⭐ 修：本作用域内把 padding 归零 ⇒ 内容盒回到 32px、图片正好填满、上下间距回到对称的 4/4。
   ⚠️ 只覆盖顶栏（不改 base）：侧栏底部那个「我的」仍是"头像+名字"的文字版 chip，
      首字母的 1px 补偿在那边是对的 —— 这正是本项目"改公共规则前先看谁在复用"的又一次实践。
   ⚠️ 顶栏里若某用户**没有头像**（显示首字母），少这 1px 补偿的影响远小于"图片明显偏下"；
      两者不可兼得时按"多数场景 + 用户实报"取图片正确（乐哥实报的就是图片态）。 */
.page-tabs-bar__tools .user-avatar { padding: 0; }
/* 名字 + 角色 = **上下两行**（乐哥 2026-09-26 晚：「右上角个人头像那个，可以稍微宽一点点，
   然后名字位置偏上，下面是角色名（字号偏小）」）。
   ⚠️ `.user-meta` 本来就是 `flex-direction: column`（两行结构早就有），
      改前只是把 `.user-role` 设成 `display:none` 只留一行 —— 本次是**放开**它，
      并把行高/间距收紧，让两行在 40px 胶囊里排得下。 */
.page-tabs-bar__tools .user-meta {
  display: inline-flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  gap: 1px;              /* 两行贴近一点 —— 40px 高里塞两行，间距大了会撑爆 */
  line-height: 1.15;
}
.page-tabs-bar__tools .user-name {
  /* 13 → 14px（乐哥 2026-09-26 深夜：「人名大一点点」）。
     配角色 11px ⇒ 两级差 **3px**（改前是 13/12，只差 1px，主次拉不开）。 */
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
  color: var(--color-fg);
  line-height: 1.15;
  white-space: nowrap;              /* 名字不许换行（换行会把胶囊撑高、与其它 40px 控件不齐） */
  max-width: 7em;                   /* 超长名字上限：防极端值把顶栏右侧挤爆 */
  overflow: hidden;
  text-overflow: ellipsis;
}
/* ⭐ 角色名：更小一档 + 弱化色（乐哥 2026-09-26 深夜：「角色文字再小点」）。
   ⚠️ 用**裸 11px** 而不是 `--font-size-xs`（桌面 12px / 手机 11px）——
      令牌那一档太大，与 14px 的名字只差 2px，主次不够分明。
      11px 是本项目既有的"最小可读档"（cost-chart / finance / workbench 等 8+ 处同值），
      不再往下降（10px 的中文可读性会明显变差）。 */
.page-tabs-bar__tools .user-role {
  display: block;
  font-size: 11px;
  font-weight: var(--font-weight-regular);
  color: var(--color-muted);
  line-height: 1.15;
  white-space: nowrap;
  max-width: 7em;
  overflow: hidden;
  text-overflow: ellipsis;
}
.page-tabs-bar__tools .user-chip .nav-icon { display: none; }  /* 箭头仍隐藏（菜单照弹）*/
/* 「我的」下拉：顶栏里改为**向下弹出**（原来在侧栏底部是向上弹） */
.page-tabs-bar__tools .profile-menu {
  top: calc(100% + 10px);
  bottom: auto;
  right: 0;
  left: auto;
}

/* ⭐⭐ 2026-09-26 A6 配套：顶栏**搜索盒 / 通知盒**在移动端必须回到 44px 触摸目标。
   ──────────────────────────────────────────────────────────────────────────────
   🐞 实测踩到（乐哥报徽章遮挡时顺带查出的**回归**）：
      视口 390 下，通知按钮 40×40、搜索入口 40×40 —— 而同栏的菜单开关是 44×44。
      根因：A6 这两条桌面规则的特异性比移动端那条高，**media 查询不改变特异性**：
        · `.page-tabs-bar__tools #notify-wrap .icon-btn` = (1,2,0)   ← 含 id，赢
        · `.page-tabs-bar .icon-btn`（移动端 44px 那条） = (0,2,0)
      ⇒ 手机上它们停在桌面的 40px（低于 Apple HIG ≥44pt / Material ≥48dp）。
   ⚠️⚠️ 本块**必须放在 A6 那两条规则之后**（同特异性时后写的赢）。
      第一版把它加在了文件上方那个 `@media (max-width: 768px)` 块里（第 ~1400 行），
      结果**不生效**（A6 规则在 1700+ 行，靠后 ⇒ A6 赢）—— 实测复现过。
      这也正是 H6 门禁那条"两条都要显式存在"纪律的延伸：**位置也是契约的一部分**。
   ⚠️ 改 A6 那几个控件的桌面尺寸/选择器时，本块要一起复核。
   ⚠️ 移动端栏高 56px（见上方 `:root` 覆盖）⇒ 44px 控件上下各留 6px；
      徽章的 `top:-5px` 外溢 5px 仍距栏边 1px，不越界（`--tabs-bar-height` 移动端覆盖处已验算）。 */
@media (max-width: 768px) {
  .page-tabs-bar__tools #notify-wrap .icon-btn,
  .page-tabs-bar__tools #notify-bell,
  .page-tabs-bar__tools .gs-entry {
    width: 44px;
    min-width: 44px;
    height: 44px;
  }
}

/* ══════════════════════════════════════════════════════════════════════════════
   ⭐⭐ 2026-10-17 导出中心（右上角工具排的中间那个按钮）
   ──────────────────────────────────────────────────────────────────────────────
   乐哥原话：「数据导出，我想坐在系统右上角就是通知和搜索中间，不单独放菜单栏了，
             点击导出按钮会有动画，然后再系统里点了导出后，有ui交互和文字提醒导出文件在哪下载」

   ▸ 这一排的现行顺序 = [品牌] [刷新] [关闭全部] ｜ **[搜索] [导出] [通知]**
     （`｜` 处是一条竖线，语义 = "标签的操作" / "全站的能力"）
   ▸ 为什么**去掉通知前那条竖线**：原来只有搜索 + 通知两个，通知前那条线让它们
     读起来像两个组；插入导出之后会变成三条线、四个孤立按钮，反而更碎。
     现在三个按钮之间只用 6px 间距，整排一眼是"一个能力组"。
     ⚠️ 改的是 `.page-tabs-bar__tools .notify-wrap`（作用域覆盖，与 2026-10-06
        那轮「右上角统一 A6 图标盒」同一个手法），⛔ 没动 `.notify-wrap` 的全局定义
        （侧栏/移动端别处还在用它）。
   ▸ 尺寸与描边**不重复声明**：`.page-tabs-bar__tools .icon-btn` 已经是 40×40 +
        1px 描边 + 透明底 + 圆角 10，导出按钮直接继承 —— 单独写一套必然与它漂移
        （2026-10-06 那轮就是"三种语言混排"才统一的）。
   ══════════════════════════════════════════════════════════════════════════════ */
.export-wrap {
  position: relative;
  margin-left: 6px;
  padding-left: 0;
  border-left: none;
}
.page-tabs-bar__tools .notify-wrap {
  margin-left: 6px;
  padding-left: 0;
  border-left: none;
}

/* 图标盒里的图标：单独一层包住，动画只作用在这层（⛔ 不要直接动 svg，
   父级 .icon-btn 的 hover/active 过渡也带 transform，两层一起动会互相打架） */
.export-btn { position: relative; }
.export-btn__icon { display: inline-flex; align-items: center; justify-content: center; }

/* ---- 点击动画：箭头"落进托盘" ----------------------------------------------
   ⚠️ 时长 560ms / iOS smooth 曲线 —— 与侧栏折叠（`--nav-fold-*`）同一套节奏，
      整站"手的动作"都是一种手感，不要另起一套（本项目最容易漂移的就是动效时长）。
   ⚠️ 位移只有 6px：图标盒本身 19px 图标，位移再大就会撞到边框，
      看起来像"图标跳出来了"而不是"箭头落下去"。 */
@keyframes exportDrop {
  0%   { transform: translateY(-6px); opacity: .25; }
  45%  { transform: translateY(1.5px); opacity: 1; }
  72%  { transform: translateY(-2px); }
  100% { transform: translateY(0); }
}
.export-btn.is-dropping .export-btn__icon {
  animation: exportDrop 560ms cubic-bezier(0.32, 0.72, 0, 1);
}

/* ---- 有新导出：按钮外扩一圈光环 --------------------------------------------
   ⚠️ 用 `::after` 画环、`inset:0` 跟着按钮盒子走 —— 不写死宽高，
      这样 40px（桌面）/ 44px（手机）两档都自动对。
   ⚠️ `pointer-events:none` 必须有：不然那圈光会挡住按钮自己的点击。 */
@keyframes exportRing {
  0%   { opacity: .5; transform: scale(.72); }
  100% { opacity: 0;  transform: scale(1.6); }
}
.export-btn.is-new::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  border: 2px solid var(--color-accent-text);
  animation: exportRing 900ms cubic-bezier(0.32, 0.72, 0, 1) 2;
  pointer-events: none;
}

/* ---- 面板（复用通知面板的骨架类 .notify-panel，这里只写差异） ---- */
.export-panel { width: 360px; }
.export-panel .notify-panel__body {
  max-height: 392px;
  overflow-y: auto;
  padding: 0;
}
.export-item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: 10px var(--space-4);
  border-bottom: 1px solid var(--color-border-soft);
  text-decoration: none;
  color: inherit;
  transition: background-color var(--motion-fast) var(--motion-ease-standard);
}
.export-item:hover { background: var(--color-surface-2); }
.export-item__ico { display: inline-flex; flex-shrink: 0; color: var(--color-muted); }
.export-item__main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.export-item__name {
  font-size: var(--font-size-sm);
  color: var(--color-fg);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.export-item__meta {
  font-size: var(--font-size-xs);
  color: var(--color-meta);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.export-item__dl { flex-shrink: 0; font-size: var(--font-size-xs); color: var(--color-accent-text); }
.export-item.is-failed { cursor: default; }
.export-item.is-failed .export-item__ico,
.export-item.is-failed .export-item__dl { color: var(--color-danger); }

.export-empty { padding: var(--space-6) var(--space-4); text-align: center; }
.export-empty__title { font-size: var(--font-size-sm); color: var(--color-fg); }
.export-empty__desc {
  margin-top: var(--space-1);
  font-size: var(--font-size-xs);
  color: var(--color-meta);
  line-height: 1.6;
}
.export-more {
  display: block;
  width: calc(100% - var(--space-4) * 2);
  margin: var(--space-2) var(--space-4) var(--space-3);
  padding: 8px 0;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--color-accent-text);
  font-size: var(--font-size-xs);
  font-family: inherit;
  cursor: pointer;
  transition: background-color var(--motion-fast) var(--motion-ease-standard);
}
.export-more:hover { background: var(--color-surface-2); }

/* ---- 导出完成卡片（乐哥要的"点了导出后有 ui 交互 + 文字提醒在哪下载"） ----
   ▸ 为什么**不用 toast**：toast 2.6 秒自动消失、且里面放不了按钮 ——
     而这里必须让人**当场就能点下载**（否则还得自己找右上角）。
   ▸ 位置：右上角下方 64px（标签栏高 52 + 12 气口），right 20；
     与通知面板同一角，视线不用移动。
   ▸ 8 秒自动收（见 app.js 的 showCard），带淡出上浮的退场。 */
@keyframes exportCardIn {
  0%   { opacity: 0; transform: translateY(-8px) scale(.98); }
  100% { opacity: 1; transform: none; }
}
.export-done {
  position: fixed;
  top: 64px;
  right: 20px;
  width: 320px;
  z-index: var(--z-toast);
  padding: var(--space-3);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-raised);
  animation: exportCardIn 340ms cubic-bezier(0.32, 0.72, 0, 1);
}
.export-done.is-out {
  opacity: 0;
  transform: translateY(-6px);
  transition: opacity var(--motion-base) var(--motion-ease-standard),
    transform var(--motion-base) var(--motion-ease-standard);
}
.export-done__head { display: flex; align-items: center; gap: var(--space-2); }
.export-done__ico {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  /* ⚠️ 只用**已定义**的令牌：`--color-success*` 在本项目令牌表里不存在，
     写 `var(--x, 兜底)` 也会被全站门禁 ② 拦（兜底值会静默掩盖拼错的令牌名）。 */
  background: var(--color-surface-2);
  color: var(--color-accent-text);
  flex-shrink: 0;
}
.export-done__title { flex: 1; font-size: var(--font-size-sm); font-weight: var(--font-weight-semibold); color: var(--color-fg); }
.export-done__x {
  display: inline-flex;
  border: none;
  background: none;
  padding: 2px;
  color: var(--color-meta);
  cursor: pointer;
  border-radius: var(--radius-sm);
}
.export-done__x:hover { background: var(--color-surface-2); color: var(--color-fg); }
.export-done__file {
  margin-top: var(--space-2);
  font-size: var(--font-size-xs);
  color: var(--color-fg-2);
  word-break: break-all;
  line-height: 1.5;
}
.export-done__tip {
  margin-top: 4px;
  font-size: var(--font-size-xs);
  color: var(--color-meta);
  line-height: 1.6;
}
.export-done__hint { display: inline-flex; vertical-align: -2px; margin: 0 2px; color: var(--color-accent-text); }
.export-done__acts { display: flex; gap: var(--space-2); margin-top: var(--space-3); }

/* ⚠️ 2026-10-17 这里曾有一个自造组件 `.import-type-lock`（导入页「本页导入」的只读块）。
   已删除 —— 全站只读字段的唯一形态是 `.input--readonly`（见 DESIGN-SPEC §6 控件五类），
   自造一个"看起来像输入框但不是控件"的块，正是规范里"只对齐既有基线、不新造视觉语言"要禁的。 */

/* ══════════════════════════════════════════════════════════════════════════════
   ⭐⭐ 2026-10-17 导入页按 DESIGN-SPEC 重排时补的两条样式
   ──────────────────────────────────────────────────────────────────────────────
   ⚠️ 为什么写在 layout.css 而**不是** components.css：
     这两条本来属于组件层（`.upload-drop` 在 components.css），但当时工作区里
     有另一批未提交的组件层改动正在同一个文件上作业 ⇒ 放进去会让两份改动互相污染、
     提交时无法只带走自己那一份。等那批落地后可搬回 components.css（无功能差异）。
   ══════════════════════════════════════════════════════════════════════════════ */

/* 上传区的说明行（「点击选择文件，或拖拽到此处」）—— 从 import-data.js 的 inline
   style 收进样式层：同一句提示在别的上传位（酒店账单/粉丝体验装）也要用，
   ⛔ 不要再在页面里写 `margin-top:8px;color:…`（散落的 inline 是这类页面"不像一套"的来源）。 */
.upload-drop__tip {
  margin-top: var(--space-2);
  color: var(--color-fg-2);
}

/* 导入预览表**短值不折行**（日期被折成「2026-10-」/「01」两行，读不出是哪天）。
   ▸ DESIGN-SPEC 的列宽纪律：单号/金额/日期这类短值必须 `nowrap`，长中文交给横向滚动。
     预览表的列是**文件里来的**（动态），没法逐列判断长度 ⇒ 统一不折行 + 靠 `.table-wrap`
     横向滚动兜住（超宽时滚，不压字）。
   ⚠️ 用 `#imp-preview` 限定：⛔ 不要加到全站 `.table td`（业务表里有长中文列，nowrap 会把列撑爆）。 */
#imp-preview .table th,
#imp-preview .table td {
  white-space: nowrap;
}

/* ── 导出「正在生成」态（2026-10-17 第二轮：后台生成）────────────────────────
   乐哥：「不应该是点击导出后给个提醒，告诉我们在哪下载，然后后台加载吗」
   ⇒ 卡片多一个 pending 态：转圈 + 「可以继续做别的」，生成完原地换成完成卡片。

   ▸ 转圈用 CSS 画（12px 圆环 + 上边染 accent），不用图标：
     图标旋转要靠 class 加动画，而这里是**运行时创建**的节点，多一个类名就多一处状态。
   ⚠️ 转速必须 `.8s`：`tests/motion-system` ⑬ 锁着"循环旋转全站统一 0.8s"，
      写成 1s 会被判红（不同转速会被读成两个系统）。
   ⚠️ 用 `border-top-color` 而不是 `border-color: transparent` + 单独一条：
      后者在浅色底上整圈都会"发灰"，转起来像缺一块。 */
@keyframes exportSpin {
  to { transform: rotate(360deg); }
}
.export-spin {
  display: inline-block;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  border: 2px solid var(--color-border);
  border-top-color: var(--color-accent-text);
  animation: exportSpin .8s linear infinite;
}
.export-done.is-pending .export-done__ico {
  background: var(--color-surface-2);
}
/* 失败态：图标换红；标题不染色（红色标题在大面积白卡上过于刺眼，靠图标+文案足够） */
.export-done.is-failed .export-done__ico {
  background: var(--color-surface-2);
  color: var(--color-danger);
}

/* 面板里的「生成中」行：不可点（还不是文件），文字退到次要色 */
.export-item.is-pending { cursor: default; }
.export-item.is-pending .export-item__dl { color: var(--color-meta); }

/* ══════════════════════════════════════════════════════════════════════════════
   ⭐⭐⭐ 2026-10-17 全站 UI 统一：**控件高度只剩两档**（34 紧凑 / 44 标准）
   ＋页头「与 tab 托盘同高」的 42 —— 把最后一处第三档收掉
   ──────────────────────────────────────────────────────────────────────────────
   乐哥：「全局检查下系统的 ui，包括按钮宽高样式，模块的样式等等，全部统一」

   ▸ 实测（1680 视口 / dpr1，逐页读 rect）发现的**唯一一处第三档**：
       `.ef-ext__merge`（门户 › 系统配置 › 报销审批流 的"合并"触发器）= **28px**
       同页其他控件 34/44 ⇒ 它比同行控件矮 6px。
   ▸ 28 的来源是"卡内小控件"的历史值（`padding: 0 6px; font-size: xs`），
     但它旁边就是 `.select`（34）⇒ 两件东西并排、高度差一档。
   ▸ 改法：归到**紧凑档** 34。⚠️ 只动高度，字号仍是 12（它是"标签式"下拉，
     字放大反而会撑破 148px 的定宽）。
   ⚠️⛔ 选择器带 `#page-root`：`.ef-ext__merge` 的基础规则在 `pages.css`（本文件
     **之后**加载），同特异性会被它反超 ⇒ 用 id 前限定把优先级抬到 (1,1,0)。
     不要为此改 `pages.css` —— 那个文件当前有别人未提交的改动，不碰。
   ══════════════════════════════════════════════════════════════════════════════ */
#page-root .ef-ext__merge {
  height: var(--control-height-sm);
  min-height: var(--control-height-sm);
}

/* ══ 金额单位切换按钮（2026-10-22）════════════════════════════════════════
   乐哥：「月度利润明细这些也都改下，或者实在不行留一个单位转换的按钮」
   ▸ 位置：顶栏工具组，在导出中心**左侧**（通知必须保持最右）。
   ▸ 文字按钮（「元」/「万」）：图标表达不了这个语义，一个字最直白。 */
.unit-toggle .unit-toggle__txt {
  font-size: 12px;
  font-weight: var(--font-weight-semibold);
  line-height: 1;
  letter-spacing: 0;
}


/* ==== /css/components.css ==== */
/* ==========================================================================
   达播工作台 — components.css
   按钮 / 输入 / 徽章 / 卡片
   ========================================================================== */

/* ---- 写操作按钮的「权限门」（2026-09-24）-------------------------------
   由 `App.applyPermGate()`（public/js/app.js）给无权限的元素打上
   `data-perm-hidden="1"`，这里负责真正藏起来。

   ⛔⛔ 为什么必须用 CSS 规则 + `!important`，而不是在 JS 里写内联 `display:none`：
      页面代码里**大量**存在自己控制显隐的写法（典型：导入页的
      `root.querySelector('#imp-go').style.display = ''` 在预览出数据后把按钮显示出来）。
      内联样式会被这些代码**覆盖** ⇒ 权限门看起来"有时生效、有时不生效"，
      而且无法从权限门这一侧排查（改的是同一个 style 属性）。
      CSS 规则带 `!important` 的优先级高于内联样式 ⇒ 页面怎么改都盖不住。

   ⚠️ 用属性选择器而不是 `[hidden]`：`hidden` 会被任何 `display:flex/block` 的
      类规则覆盖（本项目按钮大量带 flex 类），同样会静默失效。 */
[data-perm-hidden="1"] {
  display: none !important;
}

/* ---- 按钮 ---- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  /* ⭐ 2026-09-26 两档制：36 → **44（标准档）**
     ⚠️ 原来 36 是"第三档"—— 既不是紧凑（34）也不是标准（44），
        导致它跟旁边的 select(44) 同排时矮 8px（报销/结算工具栏实测）。
     ⚠️ 顶栏与页面头部的按钮不受影响：它们用 `.btn--lg`（也是 44）或各自作用域规则。
     ⚠️ 放进 `.toolbar` 的按钮会被全局兜底压到 34px（紧凑档，见 table.css）。 */
  height: var(--control-height);
  padding: 0 var(--space-4);
  border-radius: var(--radius-md);
  border: 1px solid transparent;
  /* ⭐ 2026-09-23 补 `background` / `color` / `border-color` —— **原来这三项一个都没定义**，
     后果是：不带颜色变体的 `.btn`（如 `class="btn btn--sm"`）掉进**浏览器原生按钮样式**。
     实测计算值（无头 Chrome 读 getComputedStyle）：
       background = `rgb(239,239,239)`（= Chrome 的 `buttonface`，不是任何设计令牌）
       color      = `rgb(0,0,0)`（原生黑，不是 `--color-fg` 的 #1C1E21）
     ⇒ 表现是「一块原生灰底黑字」，既不在设计系统里，也和旁边真正的按钮不像一套
       （乐哥看到「指定某人的审批人 / 从飞书同步部门」两个方块会问"颜色要不要调"，
        根因就在这：它们压根不是被 CSS 画的）。
     ⚠️ 只补**中性默认**（白底 + 常规边框 + 正文色）；既有变体（primary / secondary /
        ghost / danger / scope / icon）各自 `background` 覆盖，行为一字未变。 */
  background: var(--color-surface);
  color: var(--color-fg);
  border-color: var(--color-border);
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-medium);
  line-height: 1;
  white-space: nowrap;
  transition: background-color var(--motion-fast) var(--motion-ease-standard),
    color var(--motion-fast) var(--motion-ease-standard),
    border-color var(--motion-fast) var(--motion-ease-standard),
    /* ⭐ 2026-09-29 **Apple 式响应式按压**（乐哥：「按钮也要动效…要自然」）：
       transform 走 **base(155ms) + sheet 曲线**（无过冲）—— 这是**松开**时的"平顺收回"。
       **按下**由下面 `:active` 里的 `transition-duration: var(--motion-fast)` 压到 110ms
       （手指落下必须立刻有反馈，这是"跟手"的关键）。
       ⚠️ 原来按下/松开同一条 110ms：按下去快、**弹回来也一样快** ⇒ 读作"抖一下"而不是"按了一下"。
       ⚠️ 不用 `--motion-ease-pop`（过冲）：按钮是小面积元素，带回弹会像"廉价玩具"；
          回弹归 `.switch__thumb`。 */
    transform var(--motion-base) var(--motion-ease-sheet),
    box-shadow var(--motion-fast) var(--motion-ease-standard);
}

.btn:hover:not(:disabled) {
  background: var(--color-surface-2);
}

.btn:active:not(:disabled) {
  /* ⚠️ 原来这里是 `background: var(--color-surface-2)` —— **与 .btn:hover 完全相同**，
     等于按下没有任何视觉变化（hover/pressed 不可区分，违反 ui-ux-pro-max
     「state-clarity：hover/pressed/disabled 三态必须可区分」+「press-feedback」）。
     改为深一档（gray-100）+ 极轻缩放：缩放用 transform，不触发重排（transform-performance）。 */
  background: var(--color-gray-100);
  transform: scale(var(--motion-press));
  /* ⭐ 2026-09-29：按下时**所有属性压到 110ms**（立刻跟手）——
     松手后自动回到基础规则那条（transform 155ms + sheet 曲线，平顺收回）。
     ⚠️ 只写 `transition-duration` 一个值即可覆盖全部属性（值少于属性数时 CSS 会循环套用）；
        不写 `transition-property` 去逐个点名 —— 那与基础声明**顺序强耦合**，改顺序就静默失效。 */
  transition-duration: var(--motion-fast);
}

/* ⭐ 2026-09-26 动效：主按钮「悬停微浮 + 按下回弹」（乐哥：「轻盈灵动，不要傻快」）
   ──────────────────────────────────────────────────────────────────────────
   为什么只给 `.btn--primary` 加位移：它是**页面级 CTA**（新增 / 导入 / 保存），
   一屏通常只有 1~2 个 ⇒ 值得给一个"可被点击"的强暗示。
   次级按钮（ghost / secondary）一屏几十个，全都上下浮动会显得整页在抖。
   ⚠️ 上浮只 1px：到 3px 就成了"按钮在跳"，是廉价感。
   ⚠️ 投影用**同色系**（主色的低透明蓝）而不是中性黑 —— 中性黑投影落在浅蓝按钮下方
      会发灰发脏（大面积阴影实测过这个问题）。
   ⚠️ `:active` 必须把 translateY 归零再叠加小缩放：
      不归零的话"按下"会继承 hover 的 -1px 再缩放，位移与缩放叠加后位置偏上。 */
.btn--primary:hover:not(:disabled) {
  transform: translateY(-1px);
  box-shadow: 0 3px 8px rgba(0, 100, 224, 0.18);
}
.btn--primary:active:not(:disabled) {
  transform: translateY(0) scale(var(--motion-press));
  box-shadow: 0 1px 2px rgba(0, 100, 224, 0.16);
}

.btn:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}

.btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.btn--lg {
  height: var(--control-height);   /* 标准档（与 .btn 同值，保留类名兼容既有页面） */
  padding: 0 var(--space-5);
}

.btn--sm {
  /* ⭐ 2026-09-26 两档制：30 → **34（紧凑档）**
     ⚠️ 它主要用在**表格行内**（如业绩总表的「维护」）。改成 34 后与同排的
        `.icon-btn`（也是 34）齐平 —— 改前差 6px（30 vs 36），肉眼可见的错位。
     ⚠️ 配套：`.icon-btn` 基础值同时 36 → 34（见 layout.css）。 */
  height: var(--control-height-sm);
  padding: 0 var(--space-3);
  font-size: var(--font-size-sm);
}

.btn--primary {
  background: var(--button-primary-bg);
  /* ⭐ 2026-09-25 UI 美化第二刀（组件层）：主 CTA 接入品牌「蓝→青」渐变。
     为什么改这里：`.btn--primary` 全站 185 处引用，是全站出现频率最高的彩色元素；
     改为渐变后每屏的"品牌色存在感"立刻提升，且**面积很小**（符合"彩色只出现在小面积元素上"）。
     为什么用 background-image 而不是 background：`background` 简写会把 border-color 一起重置，
     而下面那行注释说明了本类必须显式保住 border-color: transparent（否则主按钮会挂一圈灰边）。
     ⇒ 用 `background-image` 只叠渐变层，底色 `--button-primary-bg` 仍在（渐变不支持的场景自动回落纯色）。
     ⚠️ 三态必须配套（hover / active 见下方），否则某态会回落成纯色造成"闪变"。 */
  background-image: var(--button-primary-image);
  color: var(--button-primary-fg);
  /* ⚠️ 必须显式声明：`.btn` 现在默认 `border-color: var(--color-border)`（见上），
     不写这一句主按钮会**多出一圈浅灰描边**（实测 border = rgb(229,232,236)）——
     实心色按钮周围挂一圈灰边看着很脏。danger 同理。 */
  border-color: transparent;
}

.btn--primary:hover:not(:disabled) {
  background: var(--button-primary-hover);
  background-image: var(--button-primary-image-hover);
}

.btn--primary:active:not(:disabled) {
  background: var(--button-primary-active);
  background-image: var(--button-primary-image-active);
}

.btn--secondary {
  background: var(--button-secondary-bg);
  color: var(--button-secondary-fg);
  border-color: var(--button-secondary-border);
}

.btn--secondary:hover:not(:disabled) {
  border-color: var(--color-accent-text);
  background: var(--color-primary-50);
}

/* ⭐⭐ 2026-10-09 新增：**主色的描边档**（白底 + 主色描边 + 主色字）
   --------------------------------------------------------------------------
   用途：与实心 `.btn--primary` 并列、但**弱一档**的动作 —— 典型是
   「审批弹窗」里的「修改并同意」：它是"通过"的一条变体路径（改完照样同意），
   用实心蓝会与「通过」抢主行动，用白底灰边又和普通次级按钮分不开。
   乐哥原话：「修改那个你找个颜色」。

   ⚠️ 为什么补这个变体而不是在页面上硬写颜色：
     全站原来只有"实心主色"与"白底灰边"两档，中间是空的 ——
     于是需要"有色但不主推"的地方只能各写各的（或干脆不用色，就是这次的 bug）。
     补公共变体是一次性把这一类收口（与 `.btn--danger` 同一层次）。
   ⚠️ 颜色**只用令牌**（门禁禁裸 hex）：描边与文字都用 `--color-accent-text`
     （它在中性底上是可读性达标的那个档），hover 底用 `--color-primary-50`。 */
.btn--accent {
  background: var(--color-surface);
  color: var(--color-accent-text);
  border-color: var(--color-accent-text);
}

.btn--accent:hover:not(:disabled) {
  background: var(--color-primary-50);
}

.btn--ghost {
  background: var(--button-ghost-bg);
  color: var(--button-ghost-fg);
  border-color: var(--color-border);
}

.btn--ghost:hover:not(:disabled) {
  background: var(--color-surface-2);
  border-color: var(--color-gray-400);
}

/* ==========================================================================
   费用大类的语义色按钮（2026-09-18 乐哥拍板）
   --------------------------------------------------------------------------
   用途：批量操作栏的三个归属按钮（达人溯源直播 / 商务BD / 其他费用），
   让「按钮颜色」与「列表标签颜色」是同一套 —— 用户在勾选维护时，
   看到的色相与他在列表里认出的大类一致，不需要重新学一套。

   ⚠️ 色值不在 CSS 里写死，由 JS 通过 --scope-fg / --scope-bg 注入，
      来源唯一（dict.js 的 Dict.expenseScopeStyle）——
      色义规则写在那边，这里只负责"怎么用"。
      两处各写一份色值就会出现"按钮是绿、标签是蓝"的静默不一致。
   ⚠️ 底色用浅底（--scope-bg）：整排彩色按钮若用实心色会过分抢眼，
      抢掉真正该被注意的金额与异常状态；实心色只留给"选中态"（见 .seg__btn--scope）。
   ========================================================================== */
.btn--scope {
  color: var(--scope-fg, var(--color-fg-2));
  background: var(--scope-bg, transparent);
  border-color: var(--scope-bg, var(--color-border));
}

.btn--scope:hover:not(:disabled) {
  background: var(--scope-bg, var(--color-surface-2));
  border-color: var(--scope-fg, var(--color-gray-400));
}

/* 分段选项的语义色选中态：选中的那一项用**它自己的归属色**做实心底 + 白字。
   用途：行内「费用归属」弹窗的选项（达人溯源直播/商务BD/其他费用…）——
   用户点开一条记录时，一眼看出"当前这笔算什么"。 */
.seg__btn--scope.is-active {
  background: var(--scope-fg, var(--color-accent));
  color: #FFFFFF;
}

.btn--danger {
  background: var(--button-danger-bg);
  color: var(--button-danger-fg);
  border-color: transparent;   /* 同 .btn--primary：实心色按钮不要灰描边 */
}

.btn--danger:hover:not(:disabled) {
  /* 必须显式保持红色背景：否则会被 .btn:hover 的 background:surface-2 覆盖，白字在浅灰底上看不清 */
  background: var(--button-danger-bg);
  filter: brightness(0.9);
}

.btn--icon {
  width: 36px;
  padding: 0;
}

/* ---- 输入 / 选择 ---- */
.input,
.select,
.textarea {
  height: var(--control-height);   /* 标准档 44px（两档制，见 design-tokens 的说明） */
  /* ⭐ 2026-09-29 乐哥全站审察：控件内左右留白 12 → 16（呼吸感）。
     ⚠️ .select / .input[list] 的 padding-right 由下方各自规则覆盖为 32px（给下拉箭头留位），
        故本次实际只加宽**输入框**的文字左右留白与下拉框的左侧留白。 */
  padding: 0 var(--space-4);
  border: 1px solid var(--input-border);
  border-radius: var(--radius-md);
  background: var(--input-bg);
  color: var(--color-fg);
  font-size: var(--font-size-base);
  transition: border-color var(--motion-fast) var(--motion-ease-standard),
    box-shadow var(--motion-fast) var(--motion-ease-standard);
}

.textarea {
  height: auto;
  padding: var(--space-2) var(--space-4);
  resize: vertical;
  min-height: 96px;
}

/* ⭐ 2026-09-29 复选框 / 单选框：补「按下实体反馈」（乐哥：「所有的…按钮等等都要动效」）
   ──────────────────────────────────────────────────────────────────────────────
   现状：全站 **66 处**在用原生 `input[type=checkbox]`，**零动效**
        （仅排期页 1 处写了 `accent-color`）。
   ⚠️ 为什么**不做**"自绘 + 打勾描线动画"：那需要 `appearance: none` 重画控件，
      而这些复选框散布在 66 处、各页尺寸/边距不一（如 `.perm-item` 带 `margin-top: 2px`）
      ⇒ 重画会牵动大量布局，风险远大于收益。
   ✅ 采用 Apple 做法的**最小侵入版**：**按下缩小 → 松开平顺收回**。
      实体感来自位移反馈，不来自改外观；`transform` 不触发重排 ⇒ 零布局风险。
   ⚠️ `accent-color` 一并**全局兜底**（原先只有排期页有）—— 否则未覆盖处的选中色
      会退回浏览器默认蓝（与本项目主色 #0064E0 不同源，隔着几页就能看出两种蓝）。 */
/* ⭐⭐ 2026-10-11 乐哥：「**选中的那个框，全局都改下，按照我们风格来**」
   ──────────────────────────────────────────────────────────────────────────
   改前：全站 66 处复选框是**浏览器默认外观** —— macOS/Chrome 的系统蓝方框，
        与主色 `#0064E0` **不同源**（隔几页就能看出两种蓝），圆角/描边也跟全站控件
        （4px 圆角 + `--color-border` 描边）不是一套。这就是"不搭"的来源。
   改后：`appearance:none` **自绘** —— 方框 + `--radius-sm` 圆角 + `--color-border` 描边；
        选中填 `--color-accent` + 白色勾。
   ⚠️⚠️ **尺寸取 14px**（真机实测原生 **13×13**，只差 1px）——
      这些框散布在 66 处、各页布局不同，尺寸一动就牵动对齐（本项目纪律：控件尺寸不许乱动）。
   ⚠️ **不设 `margin`**：原生 checkbox 自带 margin（约 3px），覆写成 0 会让 66 处整体位移。
   ⚠️ 只自绘 **checkbox**；`radio` 保持原生 + `accent-color` 兜底（自绘圆点要画内外两圈，
      收益小、影响面同样大，暂不动）。
   ⚠️ 保留 2026-09-29 的「按下缩放」实体反馈（Apple 做法）。 */
input[type="checkbox"] {
  appearance: none;
  -webkit-appearance: none;
  width: 14px;
  height: 14px;
  flex: none;
  border: 1.5px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  position: relative;
  cursor: pointer;
  transition: background var(--motion-fast) var(--motion-ease-standard),
              border-color var(--motion-fast) var(--motion-ease-standard),
              transform var(--motion-base) var(--motion-ease-sheet);
}
input[type="checkbox"]:hover:not(:disabled) { border-color: var(--color-accent); }
input[type="checkbox"]:checked { background: var(--color-accent); border-color: var(--color-accent); }
input[type="checkbox"]:checked::after {
  content: '';
  position: absolute;
  left: 3.5px;
  top: 0.5px;
  width: 4px;
  height: 8px;
  border: solid var(--color-accent-on);
  border-width: 0 1.6px 1.6px 0;
  transform: rotate(45deg);
}
input[type="checkbox"]:disabled { opacity: .5; cursor: not-allowed; }
input[type="checkbox"]:active:not(:disabled) { transform: scale(0.88); transition-duration: var(--motion-fast); }

input[type="radio"] {
  accent-color: var(--color-accent);
  transition: transform var(--motion-base) var(--motion-ease-sheet);
}
input[type="radio"]:active:not(:disabled) {
  transform: scale(0.88);
  transition-duration: var(--motion-fast);
}

/* 下拉选择框：去掉默认箭头，用自定义向下箭头，离右边框留白避免贴边 */
.select {
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12' fill='none'%3E%3Cpath d='M3 4.5L6 7.5L9 4.5' stroke='%238595A4' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
  background-size: 12px 12px;
  padding-right: 32px;
}

/* 带下拉提示的输入框（datalist）：去掉浏览器原生黑色箭头，用自定义向下箭头 */
.input[list] {
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12' fill='none'%3E%3Cpath d='M3 4.5L6 7.5L9 4.5' stroke='%238595A4' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
  background-size: 12px 12px;
  padding-right: 32px;
}
/* 移除 WebKit 对 datalist 输入框的原生下拉指示器（黑色实心箭头，hover/focus 时出现；必须 !important 覆盖浏览器内置样式） */
.input[list]::-webkit-calendar-picker-indicator {
  display: none !important;
  opacity: 0 !important;
}

/* 数字输入框：去掉上下增减箭头（spinner） */
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
input[type="number"] {
  -moz-appearance: textfield;
  appearance: textfield;
}

.input::placeholder,
.textarea::placeholder {
  color: var(--color-placeholder);   /* #B9C1CC，设计稿占位符色 */
}

.input:focus,
.select:focus,
.textarea:focus {
  outline: none;
  border-color: var(--input-focus-border);
  box-shadow: var(--focus-ring);
}

.input:disabled,
.select:disabled {
  background: var(--input-disabled-bg);
  color: var(--input-disabled-text);
  cursor: not-allowed;
}

/* ⭐ 2026-10-11：**只读字段**（目前只有项目表单的「项目名称」自动预览用它）。
   视觉与上面 `.input:disabled` **同一套令牌** ⇒ 一眼就知道"这不是给填的"。
   缘起（乐哥）：「这个既然是自动生成的，那就默认灰色显示，这样大家就知道填不了」。
   ⛔ 不要改用 `.input[readonly]` 属性选择器：全站还有别处用 `readonly`
      （如表格内联编辑的格子），那样会**连带改到它们**（属性选择器外溢，本项目踩过）。
   ⚠️ 与 `:disabled` 的唯一差别是**光标**：它仍可选中/复制（对账时要核对名字），
      所以用 `default` 而不是 `not-allowed` —— 不是"禁止交互"。 */
.input--readonly {
  background: var(--input-disabled-bg);
  color: var(--input-disabled-text);
  cursor: default;
}

/* 可清空下拉（sel-clear）：包一层 wrapper，右侧 ✕ 位于箭头左侧 */
.sel-clear-wrap {
  position: relative;
  display: inline-block;
  width: 100%;
}
.sel-clear-wrap select.sel-clear {
  width: 100%;
  padding-right: 52px; /* 给右侧 ✕ + 原生箭头让位 */
}
.sel-clear-x {
  position: absolute;
  right: 30px; /* 原生箭头在 right 12px 处，✕ 放它左侧 */
  top: 50%;
  transform: translateY(-50%);
  width: 18px;
  height: 18px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: none;
  background: transparent;
  cursor: pointer;
  color: var(--color-fg-2);
  border-radius: 50%;
  line-height: 1;
}
.sel-clear-x:hover {
  color: var(--color-fg);
  background: var(--color-surface-2);
}

/* 字段级错误态：红边框 + 就近提示 */
.input.is-error,
.select.is-error,
.textarea.is-error {
  border-color: var(--color-danger);
}
.input.is-error:focus,
.select.is-error:focus,
.textarea.is-error:focus {
  border-color: var(--color-danger);
  box-shadow: 0 0 0 3px var(--color-danger-bg);
}
.field-error {
  margin-top: var(--space-1);
  font-size: var(--font-size-xs);
  color: var(--color-danger);
}
/* 飞书选人组件等复合控件的错误态 */
.is-error .fp__box {
  border-color: var(--color-danger);
}

.input--search {
  position: relative;
  display: inline-flex;
  align-items: center;
}

/* 放大镜图标：绝对定位到输入框内部左侧（页面里图标是裸 svg 或 .search-icon 包裹，两种都兼容） */
.input--search .search-icon,
.input--search > svg {
  position: absolute;
  left: var(--space-3);
  z-index: 1;
  color: var(--color-meta);
  pointer-events: none;
  display: inline-flex;
}

.input--search .input,
.input--search .select {
  padding-left: var(--space-8);
}

/* 日期范围筛选：日历图标 + 两个日期选择器 + 「至」分隔，与输入框同高同风格 */
.range-filter {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--color-meta);
}
.range-filter > svg {
  flex-shrink: 0;
  color: var(--color-meta);
}
.range-filter__sep {
  color: var(--color-meta);
  font-size: var(--font-size-sm);
  flex-shrink: 0;
}

/* 密码框：右侧眼睛切换显示/隐藏 */
.input--password {
  position: relative;
  display: flex;
  align-items: center;
}
.input--password .input {
  width: 100%;
  padding-right: var(--space-10);
}
.pwd-toggle {
  position: absolute;
  right: var(--space-1);
  border: none;
  background: none;
  cursor: pointer;
  color: var(--color-muted);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: var(--control-height-sm);   /* 两档制：32 → 34（紧凑档） */
  border-radius: var(--radius-sm);
  transition: color var(--motion-fast) var(--motion-ease-standard),
    background-color var(--motion-fast) var(--motion-ease-standard);
}
.pwd-toggle:hover {
  color: var(--color-fg);
  background: var(--color-surface-2);
}
.pwd-toggle:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}

.field-label {
  display: block;
  /* 标签文字右内缩 6px（⭐ 2026-10-09 乐哥：「文字向右移 6px，其他不动」）——
     用 padding-left 只推文字，控件/列宽/右缘全不动。令牌见 design-tokens.css。
     ⚠️ 别改成 margin-left（那会把控件一起推走，还会挤压列宽）。 */
  padding-left: var(--field-label-indent);
  margin-bottom: var(--space-2);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  color: var(--color-fg-2);
}

.field-hint {
  margin-top: var(--space-1);
  font-size: var(--font-size-xs);
  color: var(--color-muted);
}

/* ⭐⭐⭐ 小模块标记 `.sub-mark`（2026-10-09 乐哥定为**系统级设计规范**，见 DESIGN-SPEC §7.11）
   ──────────────────────────────────────────────────────────────────────────
   乐哥原话（2026-10-09）：「刚刚做的这个，可以做为系统级的设计规范，就是**大模块标题前标数字，
              小模块就是这个竖条**」
   ⇒ ⭐⭐ **2026-10-17 规范修订**（乐哥：「**大模块也用竖条**」）：
     · **大模块**（一次提交 / 一个流程里的**有序分区**）→ **也用竖条**
       （实现：`.module-card__title::before` 默认带线，见下方 `.card__title--bar` 那组选择器）
     · **小模块**（同一分区里的"**每一笔 / 每一项**"）→ 竖条（本类）
     ⚠️ 即：**大小模块一律竖条，不再给大模块标数字**。`.form-sec__no` 若还有残留用法，
        属于旧规范，新页不要再加。
   规格：**3px × 14px · inline-block · 圆角 2px**，挂在标题文字前面
   （⛔ 不是"卡左侧的长色带"—— 那一版乐哥明确否掉了：「是…文字前面，小小一个竖的」）。
   ▸ **颜色**：取 `--sub-mark-color`。本类给了**默认值**（主色），所以新页面直接
     `<span class="sub-mark">未签类型</span>` 就能用；
     需要"每条一个颜色"的场合，在**容器**上覆盖 `--sub-mark-color` 即可
     （报销单就是按 `idx % 5` 挂 `eb-dt--cN`、在那 5 档里覆盖它 —— 见 pages.css）。
   ⚠️ 规则只写在**这一处**：旧类 `.eb-dt .dt__kind::before` 已**并入本组多选择器**（别名），
     pages.css 里不再重复定义外观（与 `.form-sec` 处理旧分区头的做法一致）。
   ⛔ 变量不写兜底值（全站禁 `var(--令牌, 兜底)`）。 */
.sub-mark {
  --sub-mark-color: var(--color-primary-500);
}
.sub-mark::before,
.eb-dt .dt__kind::before {
  content: ''; display: inline-block;
  width: 3px; height: 14px; margin-right: var(--space-2);
  background: var(--sub-mark-color);
  border-radius: 2px; vertical-align: -2px;
}

.field-hint.is-error {
  color: var(--color-danger);
}

/* 字段宽度工具类（按业务语义，替代内联 grid 列比例）：数字窄 / 日期金额中 / 下拉宽 */
.f-w-xs { width: 96px; }
.f-w-sm { width: 140px; }
.f-w-md { width: 220px; }

/* ---- 徽章（深字 + 浅底成对） ---- */
.badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  /* ⭐ 2026-09-25 UI 美化第三刀（表格/标签精简）：
     height 24→22、字重 500→400、圆角 `--radius-pill`(全圆 9999px) → `--radius-seg`(6px)。
     为什么（实测依据）：一屏数据表实测 **113 个胶囊**（达人管理 93 个），
     全圆胶囊+中等字重+实心底三者叠加，让每个标签都成为一块"鼓起来的彩色积木"，
     整屏读起来碎、吵（乐哥原话「感觉还是不够清新简约，包括表格，表格里的标签这些」）。
     ⇒ 改小圆角（精确感替代糖果感）+ 降字重 + 缩短高度，三处一起把"视觉重量"降下来，
        但**保留底色**——底色是"这是状态"的信号，完全去掉会让它和「—」占位符混在一起分不清。
     ⚠️ 只改基础形状与重量，**不动任何语义色**（未授权仍为橙、未入驻仍为灰，业务语义不变）。 */
  height: 22px;
  /* 中文字面在行盒内偏上：顶部留 1px、底部 0，视觉上居中 */
  padding: 1px var(--space-2) 0;
  border-radius: var(--radius-seg);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-regular);
  line-height: 1;
  white-space: nowrap;
}

.badge--success { color: var(--badge-success-fg); background: var(--badge-success-bg); }
.badge--warning { color: var(--badge-warning-fg); background: var(--badge-warning-bg); }
.badge--danger  { color: var(--badge-danger-fg);  background: var(--badge-danger-bg); }
.badge--info    { color: var(--badge-info-fg);    background: var(--badge-info-bg); }
.badge--neutral { color: var(--color-fg-2);       background: var(--color-surface-2); }

/* ---- 「审批中」列表标识（2026-09-22 新增，见 ui.js 的 pendingApprovalBadge）----
   ⚠️ 与 `.badge` 同族但**更小更轻**：它常与"已锁定"这类状态胶囊并排出现在**同一格**里，
      用 `.badge` 的 24px 高会把那一格撑得很挤。故单独一档（20px）。
   ⚠️ 配色**复用 warning 令牌**（不新造颜色）—— 与"已锁定"同色系，因为它们都是
      "这行有需要留意的状态"；区分靠图标（clock vs lock）与文案。
   ⚠️ 可点击（进审批详情）⇒ 保留 cursor:pointer 与 hover 反馈，与"纯展示"的 `.badge` 不同。 */
.pending-approval {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  height: 20px;
  margin-left: 6px;
  padding: 0 var(--space-2);
  border-radius: var(--radius-pill);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-medium);
  line-height: 1;
  white-space: nowrap;
  /* ⚠️ 用 `--badge-warning-*` 这一对（`--color-warning-fg` 在本项目**不存在**——
     写成不存在的变量会静默失效、文字变黑，这类"看不见的错"最常见） */
  background: var(--badge-warning-bg);
  color: var(--badge-warning-fg);
  cursor: pointer;
}
.pending-approval:hover {
  background: var(--color-warning);
  color: #fff;
}
.pending-approval__n {
  font-size: var(--font-size-xs);
  opacity: 0.8;
}

.badge--outline {
  background: transparent;
  border: 1px solid var(--color-border);
  color: var(--color-fg-2);
}

/* ---- 彩色标签（达人类型 / 平台等，颜色由 JS 通过 --tag-fg/bg 内联注入，无边框对齐 badge 风格） ---- */
.tag {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  /* ⭐ 2026-09-25 UI 美化第三刀：height 22→20、字重 500→400、圆角全圆→6px。
     与 `.badge` 同一套轻量化逻辑（见 .badge 的注释），保持两类标签形状语言一致。 */
  height: 20px;
  /* 同 .badge：顶部留白补偿中文字面偏上，视觉居中 */
  padding: 1px 6px 0;
  border-radius: var(--radius-seg);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-regular);
  line-height: 1;
  white-space: nowrap;
  color: var(--tag-fg, var(--color-fg-2));
  background: var(--tag-bg, var(--color-surface-2));
}
.tag--sm {
  height: 18px;
  padding: 1px 6px 0;
  font-size: var(--font-size-xs);
}

/* ---- 佣金来源标签「公司佣金 / 达人佣金」（2026-10-13 乐哥 · 二版：单独成列 + 贴合系统风格） ----
   乐哥二版原话：「公司通用佣金、达人单独佣金，这俩是**单独放一列**，不是放在名字那，
                 然后**留一下颜色**，**得贴合系统风格**」
   ────────────────────────────────────────────────────────────────────────────────
   ▸ 一版（同日更早）是**实心饱和底 + 白字**（`--color-tone-*-fg` 当底 + 白字）。
     那个形态当时的唯一理由是：它挂在**达人列**里、要在一堆浅底小标签中跳出来。
     现在它**独立成列**（不再与平台/机构/身份标签挤在同一格）⇒ 那个理由消失，
     而"实心大色块"与全站标签语言（`.tag` / `.badge` 一律浅底深字）不一致
     ⇒ 这正是乐哥说的"不贴合系统风格"。
   ▸ 二版做法：**形状整个交给 `.tag .tag--sm`**（高 18 / 内距 `1px 6px 0` / 圆角 `--radius-seg` /
     字号 xs / 字重 regular —— 与平台、机构、身份标签**逐项同规格**），
     本处**只覆盖配色**。⇒ 以后 `.tag` 怎么调，这里自动跟随，不会留第二套形状。
   ▸ 配色仍**保留颜色区分**（乐哥："留一下颜色"），且只走令牌、禁裸 hex：
     公司 = 紫 `--color-tone-purple-{bg,fg}`、达人 = 青 `--color-tone-teal-{bg,fg}`
     —— 这两对本来就是"浅底 + 深字"的配对（见 design-tokens 的说明）。
     ⛔ 一版把 `-fg`（语义是"**浅底上的深字**"）当**底色**用、再压白字 = 混用了两套令牌。
     ⚠️ 两色与既有四类标签的关系：平台（视频号绿/抖音蓝）、机构（中性灰）、达人身份（琥珀/灰）
        —— 因为已**独立成列**、不同格，紫/青不会与它们在同一格内打混。
   ⚠️ 特异性必须是 **`.tag.comm-src--*`（0,2,0）**，压过 `.tag` 基类（0,1,0）；
     写成裸 `.comm-src--*` 与 `.tag` **同特异性**、靠源顺序决胜，换个文件就翻车。 */
.tag.comm-src--company { color: var(--color-tone-purple-fg); background: var(--color-tone-purple-bg); }
.tag.comm-src--own { color: var(--color-tone-teal-fg); background: var(--color-tone-teal-bg); }
/* ⭐ 2026-10-13 第三档「按本场佣金」= 橙（乐哥从 4 个候选里拍板选了这个文案）——
   三色分工：公司=紫 / 达人=青 / **本场=橙**。
   ⚠️ 为什么不用蓝或绿：蓝被抖音平台标签占、绿被视频号平台标签占，
      而这一列与「平台」列同屏出现，撞色会让人以为标签说的是平台。
   ⚠️ 橙在排期页**没有**其它用途（`--color-tone-orange` 目前只被项目板块的
      `.pms-k-product` 与 KPI 卡用）⇒ 不冲突。 */
.tag.comm-src--schedule { color: var(--color-tone-orange-fg); background: var(--color-tone-orange-bg); }

/* ---- 卡片（白底 + 1px 浅描边 + 极轻投影，对齐 Meta 设计语言） ----
   ⭐ 2026-09-25 UI 美化第二刀：`box-shadow` 由 `none` 改为 `--shadow-sm`。
   为什么：v2 把页面底压到近白（#F8FAFC）后，卡片与底的明度差只剩 2%，
   纯靠 1px 边框区分会显得"平贴在纸上"、「框」的存在感弱（乐哥要调「那些框」）。
   极轻投影（0 1px 2px + 0 1px 3px，最深仅 6% 透明度）让卡片**微微浮起**，
   在不破坏"近白简洁"的前提下补回层次。
   ⚠️ 用 `--shadow-sm` 而非 `--shadow-md/lg`：后两者扩散大，在近白底上会形成"脏边"而非纵深
      （design-tokens 里已注明这条）。 */
.card {
  /* ⭐ 2026-09-25 Gashora 骨架落地：卡片从「白底 + 阴影浮起」改为**靠浅边框划界**。
     原因：卡片现在住在**白色大卡**里面，与底盘同色系——阴影在"白上白"的场景几乎不可见
     （还容易显脏），改用 1px 浅边框（#EBECEF）才能清晰划出每张模块卡的边界。
     ⭐⭐ 2026-10-06 改回**边框 + 阴影并存**（乐哥：「底色用之前那个接近白色的也行，
        但是**得有悬浮效果**，**其他模块，也加个悬浮**」）：
        · 底盘现在接近白（#F6F6F6）、内容区还是一张白卡 ⇒ 卡片**必须靠阴影**才浮得起来；
        · 但**边框保留**：它承担"边界清晰"（阴影只负责"浮起"，不负责划线），
          且 `.kpi-card--estimate` 那类用 border-color 表达状态的卡片不能失去边框。
        阴影配方见 `--shadow-card`（刻意很淡：底盘近白，重了会显脏）。
     ⚠️ 弹窗/下拉/抽屉这类**浮层**不在此列，它们仍用更重的阴影表达层级（见 overlays.css）。 */
  background: var(--color-surface);
  border: 1px solid var(--color-frame-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
}

/* ==========================================================================
   卡片级间距统一（2026-09-25 UI 美化第四刀「呼吸感」）
   --------------------------------------------------------------------------
   标准：**相邻卡片 20px**（`--space-5`），与 `.module-card + .module-card` 同一标准。
   ⭐ 2026-09-28：`--space-8`(32) → `--space-5`(20) —— 与 09-27 的「模块卡间距 32→20」对齐
   （那条改了 `.module-card + .module-card`，却漏了这条 `.card` 内联兜底 ⇒ 同为"卡片"却差 12px，
   乐哥：内部排版乱）。
   为什么要有这条：全站约 27 张卡片的间距是**写在 JS 内联 style 里**的
   （实测 `var(--space-6)` 24px ×25、`var(--space-4)` 16px ×2，其中一处就是看板
    「快捷入口」那张卡 —— 16px 让它与下方图表卡几乎粘在一起，是首屏最明显的一处"紧"）。
   内联样式优先级最高 ⇒ 必须 `!important` 才能覆盖。
   ⭐ 这是项目**既有做法**，不是新引入的 hack —— 见本文件顶部对
      「为什么必须用 CSS 规则 + !important，而不是在 JS 里写内联」的说明。
   ⚠️ 只匹配**卡片类**（`.card` / `.chart-card`）且只匹配这两个具体值：
      · 不碰 `.cell-sub`(39 处) / `.toolbar` / `.kpi-row` / `.field-label` 等非卡片元素；
      · 不动表单页 `var(--space-3)`(12px) 那 3 处 —— 那是刻意的紧凑表单分组，不是遗留。
   ⚠️ 内联写法有两种格式（实测 150 处无空格 + 1 处有空格），故两条属性选择器都写。
   ⚠️ 若以后新增卡片，直接在 CSS 里排版式（别再用内联 margin-bottom），
      这样天然命中标准、不需要这条兜底。
   ========================================================================== */
.card[style*="margin-bottom:var(--space-6)"],
.card[style*="margin-bottom:var(--space-4)"],
.card[style*="margin-bottom: var(--space-6)"],
.card[style*="margin-bottom: var(--space-4)"],
.chart-card[style*="margin-bottom:var(--space-6)"],
.chart-card[style*="margin-bottom:var(--space-4)"],
.chart-card[style*="margin-bottom: var(--space-6)"],
.chart-card[style*="margin-bottom: var(--space-4)"] {
  /* ⭐ 2026-09-28：20(--space-5) → **16(--space-4)**（乐哥：「这些卡片整体的上下间距
     是不是太大了，要不要小一点点」）—— 全站卡片间距统一口径从 20 收一档到 16，
     与卡片内边距（24）形成「内 24 / 间 16」—— 外层小于内层，卡片更抱团、页面更紧凑。 */
  margin-bottom: var(--space-4) !important;
}

/* ============================================================
   模块卡（全站通用）—— 信息模块的统一容器
   用法：
     <div class="module-card">
       <div class="module-card__head">…</div>                          有状态开关 → 标题栏状态底色
       <div class="module-card__head module-card__head--plain">…</div>  无开关   → 默认色 + 分隔线
       <div class="module-card__title">模块名</div>
     </div>
   规律（重要）：标题栏**有底色就不加线，没底色才用线补分界**；
   状态色只出现在标题栏与状态开关上，不外溢到卡片边框（禁止彩色描边 / 彩色外发光）。
   **间距标准：相邻模块卡之间固定 24px（由卡片自身用 + 选择器保证，不依赖外层容器配置）**。
   ============================================================ */
.module-card {
  /* ⭐ 2026-09-25 Gashora 骨架落地：同 .card——改浅边框划界（理由见 .card 注释）。
     ⭐ 2026-10-06：同 .card 一起加回**阴影**（乐哥：「其他模块，也加个悬浮」）。 */
  background: var(--color-surface);
  border: 1px solid var(--color-frame-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
  padding: var(--space-6);   /* ⭐ 2026-09-27：20(--space-5) → 24(--space-6)。
     乐哥：「文字跟模块的边距也要调整。像现在这个就很挤，之前不觉得挤，
     是因为它中间还有一层底」—— 去掉内容区白大卡后，卡片直接落灰底，
     原 20px 内边距在"卡片 vs 灰底 9 阶色差"的强边界下显得**相对更挤**。
     实测 padding 20→24：表格页**横滚无新增**（达人列表 1px→1px，看板/编辑无横滚）。
     ⚠️ 不再加回 28+：表格可用宽度会再缩，1440 视口下宽表会新增横滚。 */
}
/* 模块间距（标准）：相邻模块卡之间 20px（⭐ 2026-09-27 由 32 改为 20）。
   乐哥：「模块与模块之间间距…感觉不对」—— 对照 PMS 参考图与大厂（GitHub/Linear），
   卡片间距统一为 20px：与卡片内边距（24）形成「内 24 / 间 20」的紧凑节奏，
   比原「内 24 / 间 32」更聚拢、更像一个整体页面而非散落的卡。
   放在卡片自身而不是容器 gap 上 —— 任何容器里都自动生效。 */
.module-card + .module-card {
  /* ⭐ 2026-09-28：20(--space-5) → **16(--space-4)**（乐哥：「上下间距太大，小一点点」）*/
  margin-top: var(--space-4);
}
/* 表格类模块卡（通栏变体）：卡内是「工具栏 + 表格」，两者**自带内边距**，
   所以卡片自身不留内边距 —— 观感与全站表格页一致，也**不挤压表格的可用宽度**
   （订单明细/商品汇总这类表的列宽是按容器宽度抠过的，多 32px 内缩就会让长商品名折行）。
   用法：<div class="module-card module-card--flush">…</div> 后直接放 .table-toolbar / .table-wrap。
   注意：卡内**不要**再用 .card（那会套两层白底）。 */
.module-card--flush {
  padding: 0;
  overflow: hidden; /* 让内部表格/工具栏跟随卡片圆角裁切 */
}
/* flush 卡没有内边距 → 标题栏不需要负 margin 抵消；顶部圆角回到卡片圆角本身 */
.module-card--flush > .module-card__head {
  margin: 0;
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
}
/* 无内容体变体：卡内只有标题栏、没有正文（如排期页「大概排期」开关关闭时）。
   必须**同时**清掉标题栏的 margin-bottom 与卡片 padding-bottom —— 两者相加是 28px 空隙，
   留着的后果是卡片下沿多出一块空白、与下方内容视觉粘连（2026-09-15 乐哥反馈「排期展示有问题」）。
   顺带去掉标题栏的下分隔线：没有内容就没有分界的必要，留着会紧贴卡片底边像多画了一条线。 */
.module-card--bodyless {
  padding-bottom: 0;
}
.module-card--bodyless > .module-card__head {
  margin-bottom: 0;
  border-bottom: none;
}
/* 去卡片化变体（2026-09-15）：用于「一行分组标题 + 一行内容」这类**轻量区块**。
   乐哥原话：「大概排期就不要那个底了，直接和下面的板块一根线隔开就行，然后上下间距大点」。
   改前：整块是浮影卡片（白底 + 阴影 + 圆角 + 16px 内边距），与下方板块是「两个盒子」的关系；
   改后：去掉底（背景 / 阴影 / 圆角 / 内边距），退化为「标题行 + 内容」，
   用**一条分隔线**与下方断开 —— 与详情页 `.detail-block` 的视觉语言一致。
   做成通用变体而不是就地 hack：这类"轻量分组"在表单页会反复出现。

   ⚠️ 分隔线放在**整块的下沿**（border-bottom），**不是**标题行下面：
      语义是"这一组到此结束"；放标题下方会被读成"标题栏的装饰线"，与分组无关。 */
.module-card--bare {
  background: none;
  box-shadow: none;
  border-radius: 0;
  padding: 0 0 var(--space-6); /* 分隔线**上方**的留白（下方的留白由后继元素自己的 margin-top 给） */
  /* ⛔⛔ 2026-09-28 修一个**渲染成空框**的 bug（乐哥截图报「uibug」）——
     本变体的意图是"去掉整个盒子、只留**一条底部分隔线**"（见上方说明），
     但原实现**只覆盖了 `border-bottom`**，基类 `.module-card` 的
     `border: 1px solid var(--color-frame-border)` 其余三边仍在 ⇒
     实际渲染成「上/左/右细框 + 底部分隔线」的**透明空盒子**。
     实测（`#/schedule/138/edit` 的「大概排期」，开关关闭的 bodyless 态）：
       高度只剩标题行 36px，却画出完整四边框 ⇒ 看上去就是一个**多出来的空框**
       （乐哥：「上面那个…uibug」+「不要外面的框框了，多余」）。
     ✅ 显式清掉四边、再单独给底部一条线（顺序不能反：`border: 0` 必须在前面）。
     ⚠️ 影响面：全站仅 3 处用 `--bare`（schedule-form 大概排期 / t-tickets 2 处），
        都是"轻量分组"语义，本就该只有一条底线 ⇒ 修正是全局一致的方向。 */
  border: 0;
  border-bottom: 1px solid var(--color-border);
}
/* 内边距归零后，标题栏不需要负 margin 抵消；底色与它自带的分隔线也一并去掉（线已挪到整块下沿）。
   ⚠️ 但**上内边距要保留** `--space-3`（2026-09-15 收尾实测发现）：
      普通卡的标题靠 `.module-card__head` 自身的 `padding-top: --space-3` 把文字压到
      「卡片顶 + 12px」；`--plain` 版的原实现把 padding 一并清零 →
      同一页里两个 tab 的标题文字顶差 3px、区块起点差 8px，切 tab 时标题会"跳"。
      去卡片化要去的是**底**（背景/阴影/圆角）和**线的位置**，不是标题行的呼吸。
      左右仍为 0：bare 块没有底色盒，标题与它自己的内容必须共享同一条左基线。 */
.module-card--bare > .module-card__head {
  margin: 0 0 var(--space-4);
  padding: var(--space-3) 0 0;
  background: none;
  border-radius: 0;
  border-bottom: 0;
  min-height: 0;
}
/* 无内容体（开关关闭）时高度只剩标题行 —— 底部留白要收窄，否则线上方吊着一段空白。
   与 --bodyless 同一个诉求（"没有内容就别留内容的位置"），故两条变体合并处理。
   ⚠️ 必须用**双类**选择器：单类 `.module-card--bare` 与 `.module-card--bodyless` 特异性相同(0,1,0)，
      谁写在后面谁赢 —— 靠源码顺序"碰巧生效"是最脆的那种写法，显式提高特异性才稳。 */
.module-card--bodyless.module-card--bare {
  padding-bottom: var(--space-4);
}
.module-card--bodyless.module-card--bare > .module-card__head {
  margin-bottom: 0;
}
/* 模块卡内的二级区块（同一趴里的子分组，如佣金配置卡内的「排期单独维护」）：
   与上方内容用一条浅分隔线断开，标题用 .detail-section__title */
.module-card__sub {
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px solid var(--color-border-soft);
}
/* 标题栏：通栏（负 margin 抵消卡片内边距，四角跟随卡片圆角） */
.module-card__head {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-3);
  margin: calc(var(--space-4) * -1) calc(var(--space-4) * -1) var(--space-3);
  padding: var(--space-3) var(--space-4);
  /* ⭐ 2026-09-25 Gashora 骨架落地：模块卡标题栏的**浅色底块**改透明。
     原来"标题栏铺一层浅灰底"与"卡片标题下画一条线"是同一目的（分隔标题与正文）；
     参考图两者都不用 ⇒ 去掉底色块，标题靠字重/字号与正文区分。 */
  background: transparent;
  border-radius: calc(var(--radius-lg) - 1px) calc(var(--radius-lg) - 1px) 0 0;
}
/* 开启态：标题栏底色/字色切到"黄"。
   两种写法都要认 —— 有的页面把 is-on 加在标题栏自身（schedule-form.js），
   有的加在外层卡片上（influencer-detail.js / schedule-detail.js）。
   只认前者的话，后两处的"单独维护"会落到默认的关闭态绿底，语义正好相反（2026-09-10 排查发现）。 */
.module-card__head.is-on,
.module-card.is-on > .module-card__head {
  background: var(--color-head-on-bg);
}
/* 标题行内的开关：靠右，并清掉开关自带的上下外边距 */
.module-card__head .switch {
  margin-left: auto;
  margin-top: 0;
  margin-bottom: 0;
}
.module-card__title {
  /* ⭐ 2026-09-28 全站提档：14(sm) → 16(lg) —— 卡片标题是卡片内的**一级信息**，
     与正文(14)同号会淹没；按令牌既定语义（lg = 卡片标题）与达人两页的 .ia-page 先行实践统一。 */
  /* ⭐⭐ 2026-10-17 全站 UI 统一（乐哥：「卡片标题字重 600」）：
     `medium(500)` → **`semibold(600)`**，与全站另一种卡片标题 `.card__title` 对齐。
     ▸ 病根：同为"卡片标题"，`.card__title`（详情页/明细页卡）是 **600**、本类是 **500**
       ⇒ 同一个层级两种粗细，列表页的卡头比明细页明显"轻"（实测 65 页、21 处 vs 5 处）。
     ▸ 取 600 而不是把 `.card__title` 降到 500：600 是现网多数派，
       且 16px 的标题用 500 在白底上偏软（乐哥原话就是"卡片标题字重 600"）。
     ⚠️ 层级仍靠**字号**表达（卡片标题 16 / 正文 14 / 注释 12），字重只是"加重一档"。 */
  font-size: var(--font-size-lg);
  font-weight: var(--font-weight-semibold);
  color: var(--color-head-off-fg);
}
.module-card__head.is-on .module-card__title,
.module-card.is-on > .module-card__head .module-card__title {
  color: var(--color-head-on-fg);
}
/* 无开关 / 无状态的模块：标题栏默认色（不着色），用一条分隔线与内容区分 */
.module-card__head--plain {
  background: var(--color-surface);
  border-bottom: 1px solid var(--color-border);
}
.module-card__head--plain .module-card__title {
  color: var(--color-fg);
}

.card__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-6);   /* ⭐ 2026-09-28：左右 20(--space-5) → 24(--space-6)，
     与 `.card__body`(24) 对齐 —— 原 20 让卡片标题比正文左缩 4px（乐哥：内部排版乱）。 */
  /* ⭐ 2026-09-25 Gashora 骨架落地：去掉标题与正文之间的分隔线（乐哥：「他那边有些标题跟下面的
     正文中间是没有线分隔的，咱们这边有些是有线分隔的，把这个线也去掉」）。
     参考图的卡片标题直接压在卡上、下面不画线——靠**间距**分组而非线条（更少线条、更干净）。 */
  border-bottom: 0;
}

/* ⭐⭐⭐ 2026-10-16 深夜 乐哥：「我们的**所有系统**，这个二级页面的**标题图标和标题没对齐，
   间距也很奇怪**」——实测根因（线上真机，⛔ 不是看截图）：
     `.card__title` 是 `display: block`、`gap: normal` ⇒ 图标与文字靠 **inline 基线**各自漂：
       · **图标右缘到文字左缘 = 0px**（紧贴，没有间距）← "间距也很奇怪"就是这条
       · 图标中心比文字中心高 1px（各页还各传不同尺寸：明细页 16、其它地方 13/18 都有）
   ⇒ 改 **flex + 垂直居中 + 统一间距**：
       · `align-items: center` 让图标中心与文字中心**对齐**（不再靠基线猜）
       · `gap: var(--space-2)`（8px）给图标↔文字一个固定间距（也顺带管住标题旁的 ⓘ）
   ⚠️ 图标**尺寸由调用方传**（全站多数是 16），本条只负责"怎么排"，⛔ 别在这里写死尺寸。
   ⚠️ `flex-wrap: nowrap`：标题本来就短，不允许折行（折了会把卡头撑高）。
   ⛔ 别改回 `display: block` —— 一改回去，间距立刻变回 0、对齐又靠基线漂。 */
/* ⭐⭐⭐ 2026-10-16 深夜 乐哥选 B：「要 B **都**改成彩色竖短线」
   ──────────────────────────────────────────────────────────────────────────────
   范围：全站**所有"标题前置图标"**共 **12 处**，分 4 个标题类 ——
     `.card__title` 5 · `.module-card__title` 5 · `.cal-approx__title` 1 · `.pc-sub-panel__title` 1。
   ⛔⛔ **为什么必须用修饰类 `--bar`、不能给这两个类全局加 `::before`**（本轮实测）：
     · `.card__title` 全站 **73 处**、`.module-card__title` **50 处**，其中**只有各 5 处**带图标；
     · 更要命的是 `.module-card__title` 被**混用**了 —— 它里面还挂着一批**表单字段标签**
       （实测样本：「事由」「费用明细」「佣金税点时间段」「基础信息」）⇒
       全局加线会把这些字段标签也做成"带色条的标题"，那是错的。
     ⇒ 修饰类精确命中"**原来是带图标**的那些标题"，其余 111 处一概不动（状态与改动前等价）。
   ▸ `.cal-approx__title` / `.pc-sub-panel__title` 各自只有 **1 处**实例、且本来就带图标
     ⇒ 直接写类选择器，不需要修饰类（将来同类新增也自动带线，符合"标题都带线"）。
   ▸ 规格：**3px 宽 × `1em` 高**（随标题字号自适应 —— 14px 与 16px 的标题不必各写一条）
     × 圆角 2px × 主色 `--color-primary-500`（#0064E0）。
     `flex: none` 保证被挤压时不被压扁。
   ▸ 与上一轮的 `.card__title` flex 改动配合：容器已是 flex + `gap: 8px` ⇒ 竖线与文字自动隔开 8px。
   ⛔ 别换成裸色值 / 别用 `--color-primary`（**这个令牌不存在**，只有 `-50`~`-900`；
      本轮做备选图时踩过，竖线压根没渲染）。 */
/* ⭐⭐⭐ 2026-10-17 **规范修订：大模块标题也用竖条**（乐哥：「大模块也用竖条」）
   ──────────────────────────────────────────────────────────────────────────
   ▸ 原规范（2026-10-09，`.sub-mark` 那段注释）：「大模块标题前标**数字**，小模块就是这个竖条」
   ▸ 新规范（本行起生效）：**大模块与小模块一律用竖条**，不再给大模块标数字。
     ⇒ 所以竖条从"变体类 `--bar` 才生效"**提升为 `.module-card__title` 的默认装饰**。
   ⚠️ 之前显式写了 `module-card__title--bar` 的地方（`schedule-detail.js` 5 处）**不用改**：
     两个选择器命中**同一个伪元素**，CSS 同属性覆盖 ⇒ 只会画**一条线**，不会双线。
   ⚠️ 用 `inline-block + vertical-align` 而不是给标题加 `flex` ——
     `.module-card__title` 全站 **66 处**，改 `display` 会牵连一批布局；只加伪元素最安全。
   ⛔ 别换成裸色值 / 别用 `--color-primary`（这个令牌不存在，只有 `-50`~`-900`）。 */
.card__title--bar::before,
.card__title::before,
.module-card__title::before,
.module-card__title--bar::before,
.cal-approx__title::before,
.pc-sub-panel__title::before {
  content: '';
  flex: none;
  display: inline-block;
  width: 3px;
  /* ⚠️ 用**固定 14px**（规范规格 3×14），不用 `1em`：
     在 flex 容器里 `1em` 会被按行高算成 24px（实测），竖条比字还长，显笨。 */
  height: 14px;
  /* ⚠️ `align-self` 防拉伸：flex 容器里 item 默认 `stretch`，会把 14px 撑成行高。 */
  align-self: center;
  margin-right: var(--space-2);
  border-radius: 2px;
  background: var(--color-primary-500);
  vertical-align: -2px;
}
/* `.module-card__title` 原样式不含 flex ⇒ 带线的那几处要补上，竖线才会与文字居中对齐并隔开 */
.module-card__title--bar {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}
/* ⚠️⚠️ 2026-10-17 乐哥：「刚刚加的**标题竖条和文字没对齐**」
   病根：`.module-card__title` 是 **block**（行高 24px），而竖条是 `inline-block` 16px +
   `vertical-align: -2px` ⇒ 竖条**整体偏上**（实测竖条顶部高出文字顶、底部不到文字底）。
   ✅ 修：卡片头里的标题改成 **`flex` + `align-items: center`** ⇒ 竖条作为 flex item
     与文字**严格垂直居中**，不再靠 `vertical-align` 猜像素。
   ⚠️ 作用域收在 **`.module-card__head` 内的标题**：`.module-card__title` 全站 66 处，
     只动"标准卡片标题"这一种；别处（如单独用的 title）保持原样，避免牵连布局。
   ⚠️ `gap` 与 `::before` 的 `margin-right` 会叠加 ⇒ 竖条与文字的间距是从前 8 变成 8+8=16。
     这里把 `margin-right` 归零，只用 `gap`（改间距只改一处）。 */
.module-card__head > .module-card__title { display: flex; align-items: center; }
.module-card__head > .module-card__title::before { margin-right: 0; }
.module-card__head > .module-card__title { gap: var(--space-2); }

.card__title {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: nowrap;
  font-size: var(--font-size-lg);
  font-weight: var(--font-weight-semibold);
  color: var(--color-fg);
}

/* ⭐ 2026-10-15 乐哥：「标题栏按照标准做淡蓝色」（提成总览首用）——
   卡头染**标准浅蓝底**（`--color-accent-bg`），与白卡体形成"标题栏"的层次。
   ⚠️ 上圆角必须与 `.card` 同值（`--radius-lg`），否则淡蓝块会顶破卡片圆角。
   ⛔ 不写裸色值 —— 淡蓝一律走令牌（换令牌全站跟随）。 */
/* ⭐⭐⭐ 2026-10-16 晚 改色：实色淡蓝 → **托盘同款（半透明玻璃蓝 + 毛玻璃）**
   ──────────────────────────────────────────────────────────────────────────────
   乐哥当晚两条反馈是同一条标准：
     · 列表页「上面工具栏的颜色也不对」→ 那一版我把托盘皮清成了**透明**，标准是**玻璃蓝**；
     · 结算明细页「里面也是同样的问题，颜色不对」→ 这一版卡头是**实色** `--color-accent-bg`
       = rgb(229,238,254)，与列表页托盘的 `rgba(227,238,251,.32)` 不是同一种蓝。
   ⇒ 全站的"头部色"统一取**托盘那套**（`--liquid-tray-bg` + `--glass-blur`）——
     同一套令牌（与 tab 吸顶托盘、列表页工具栏同源，不是新造色）。
   ⚠️ 影响面（改动前实测）：带 `--tint` 的卡头全站只有 **4 处**
     （华大对账 2 · 结算明细 2 的同一处实现 · 提成总览 1），其余 **68 处** `.card__header` 本来就是白底。
   ⚠️ 半透明色在这块是"叠在白卡上"⇒ 合成后比实色浅一档，正是乐哥要的那种"更淡"。
   ⛔ 别写裸色值 —— 一律走令牌（换令牌全站跟随）。 */
.card__header--tint {
  background: var(--liquid-tray-bg);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
}

/* 卡头**右侧的限定词**（`<span class="card__meta">`）—— 2026-10-16 加。
   ⭐ 起因：结算明细页两块（本期佣金构成 / 业绩构成）的限定词原来铺在页面右侧
     （`.st-comp__meta` / `.st-drill-head__meta`，各写一套），现在随卡头一起统一。
   ▸ 角色：**"读数据时必须看到的限定词"**（这条数据是哪个月/哪个渠道、
     这批单到期没有）—— 不是"读一次就够的长解释"，所以**留在卡面上**、不进 `?`。
   ▸ 色/号取 `--color-muted` / `sm`：
     · 不用 `--color-meta`（那是辅助/徽章档 12px）—— 这两个句子偏长（最长约 40 字），
       12px 读着费劲；`sm 14` 与表格正文同档，扫读更顺。
     · 不用主文字色：它是"背景条件"，不该与标题抢第一眼。
   ⚠️ 与列表页卡头右侧那个「共 42 张单 · 32 位达人」（`.table-toolbar__total`，xs/meta）
      **档位不同是有意的**：那是**计数**（越轻越好），这里是**限定词**（要读得清）。
   ⛔ 不写裸色值/裸字号 —— 一律走令牌。 */
.card__meta {
  font-size: var(--font-size-sm);
  color: var(--color-muted);
}



/* 卡头标题旁的「?」（点开看口径说明）——
   图标走全站同款 `helpCircle`（与页面标题旁的说明按钮同图标），
   视觉/过渡**照抄 `.th-note`**（同色 --color-muted → hover --color-accent-text、
   同 `--motion-fast` + `--motion-ease-standard`）：⛔ 不新造颜色、不新造时长。 */
.card__help {
  display: inline-flex;
  align-items: center;
  vertical-align: middle;
  margin-left: var(--space-1);
  padding: 0;
  border: 0;
  background: none;
  color: var(--color-muted);
  cursor: pointer;
  transition: color var(--motion-fast) var(--motion-ease-standard);
}
.card__help:hover { color: var(--color-accent-text); }
.card__help svg { flex: none; }

.card__body {
  padding: var(--space-6);   /* ⭐ 2026-09-27：与 .module-card 同步 20 → 24
     （去白大卡后卡片直接落灰底，"文字跟模块的边距"需要加大，见 .module-card 注释） */
}

/* 卡片内嵌的「内容分组 Tab」/ 工具栏 / KPI（整页内容包一个白卡片时，各区左右留白对齐）
   ⚠️⚠️ 2026-09-26 托盘化：缩进必须用 **margin** 而不是 padding。
      改前是 `padding: 0 20px`；若沿用 padding，它会因特异性 (0,2,0) > `.tabs` 的 (0,1,0)
      **覆盖掉 `.tabs` 自身的托盘内边距 `padding: 4px`** ⇒ 白块紧贴灰托盘边缘（没有呼吸），
      实测 .tabs 高度从应有的 40px 变成 32px（真机量到，肉眼还没那么明显）。
      ⇒ 改 `margin: 16px 20px`：左右 20 与卡片内工具栏左缘对齐（原意图不变），
        上下 16 给托盘与卡片顶部 / 下方内容留呼吸（原来是 0，靠旧方案 44px 行高自己撑）。 */
/* 卡片内嵌的「内容分组 Tab」的呼吸（上下 16 / 左右 20）。
   ⚠️ 本条对"**自身没有内边距**的卡片"是正确的（如达人详情 `.sf-shell`、排期表单）——
      16px 就是托盘与卡片边的间距。
   ⛔ 但"**自带 padding 的卡片**"会与它**叠加**：`.pf-card` 自身 `padding: 20px`，
      叠加后上方 = 20 + 16 = **36px**，而左右只有 20px ⇒ **托盘偏下、上下不对称**。
      实测（业绩总表，乐哥实报「二级 tab 样式不对」）：
        托盘 top=261 / 卡片内容顶=245 ⇒ 上方 36px；托盘 bottom=301 / 工具栏 top=321 ⇒ 下方 20px。
   ✅ 处理办法：自带内边距的卡片**自己把 margin-top 归零**（见 pages.css 的 `.pf-card > .tabs`），
      让它只吃自己的 padding —— 这样"上 = 左右 = 20px"，整齐。
   ⚠️ 别在这里写 `:not(.pf-card)` 之类的排除法：将来再出现一个"自带 padding 的卡片"，
      排除法不会自动生效、又会踩同一个坑。**谁有内边距谁负责归零**才是收敛的规则。 */
.card > .tabs {
  /* ⭐ 2026-09-28 下外边距 16 → **0**（乐哥：「tab 栏和下面内容模块间距太大了，全局要一致」）——
     卡内 tab 与内容的间距改由**紧跟的 card__body 的 padding-top(16)** 唯一承担：
     改前「tab margin 16 + card__body padding 24 = 40px」是两处叠加的产物（全站最大的一处空隙）。
     ⇒ 配合下方 `.card > .tabs + .card__body` 的 padding-top 收档，间距 = 16，与全站一致。
     ⚠️ 原 `border-bottom: 0`（下划线基线时代的"去基线"）已删——现在基类是**完整 1px 边框**，
        留着它会让容器缺底边、胶囊上下项距不对称（实测 7px vs 6px，探针抓到）。 */
  margin: var(--space-4) var(--space-5) 0;
  /* ⭐⭐ 2026-09-28 **卡内 tab 也吸顶**（乐哥：「然后上面 tab 没悬浮」——指编辑排期页）——
     该页（`#/schedule/:id/edit`）的 tab 是 `.card > .tabs` 结构（唯一一处），
     而上一轮「表单页吸顶」只覆盖了 `.sf-tabs` / `.sf-tabs__track` 两种外壳
     ⇒ 这页的 tab **完全没吸顶**（实测：滚动 500px 后 top 从 322 变 275，跟着页面滚走）。
     ▸ sticky 挂在本层（`.tabs` 自己）：父级是 `.card`（实测高 1968px）⇒ 吸附余量充足。
     ▸ `width` **不能用 100%**（这是 2026-09-28 我自己埋的 bug，当日修）：
       本块已有 `margin: 16px 20px 0`（左右各 20），再叠 `width: 100%` ⇒
       实际宽 = 父内容宽 1278 + 只有左 margin 生效的错觉，实测**右缘戳出卡片 19px**
       （tabs r=1738.5 / card r=1719.5），白条跨过卡片右边框 —— 截图肉眼可见。
       ⚠️ 基类 `.tabs` 是 `width: fit-content`（408px）—— 吸顶时若只有内容宽，
          会变成"一条窄条浮着"，所以要覆盖；但覆盖值必须是 **`auto`**
          （块级 flex 容器 = 填满含 margin 的可用宽 ⇒ 1278−40=1238，左右各留 20 对称），
          而**不是** 100%（100% 不含 margin，必然溢出）。
       实测修后：tabs 460.5→1698.5（1238 宽），卡片 439.5→1719.5，两侧各留 20 ✓
     ⚠️ `.card` 的 `overflow` 必须是 visible（实测 ✓）—— hidden/auto 会让 sticky 失效。 */
  width: auto;
  position: sticky;
  /* ⭐⭐⭐ 2026-10-07「边框锁死 · 框内滚动」：`+ 4px` **删掉**，只留 `--tabs-bar-height`。
     ▸ 为什么：吸附参照从**视口**变成了 **`.app-main`（那台"显示屏"）**，
       而顶栏是**框内**的 sticky（`top: 0`、高 `--tabs-bar-height`）
       ⇒ 顶栏下沿 = **框顶 + tabs-bar-height**。
     ▸ 那 4px 是"顶栏吸视口 top:0 时，下面还要留一道 frame-gap"的补偿 ——
       框内滚动下顶栏本来就贴着框顶，多留 4px 会让卡内 tab **悬在顶栏下方 4px**
       （实测：滚 600 后它停在 72，而顶栏下沿是 68 ⇒ 差 4px 的缝）。
     ⚠️ 四处必须同值：layout.css（.page-header--has-tabs）/ 本处（.card > .tabs）
        / pages-influencer.css 两处（.sf-shell .tabs 与 .sf-tabs）；
        门禁 `detail-sticky-tabs ③` 四处一起锁。
     ⚠️ 别改回 `+ 4px`：那会让本页的卡内 tab 与顶栏之间出现一道不该有的缝。 */
  top: var(--tabs-bar-height);
  z-index: 60;
  transition: box-shadow var(--motion-base) var(--motion-ease-standard),
              background-color var(--motion-base) var(--motion-ease-standard);
}
/* 卡内 tab 的「贴顶变玻璃」——与二级页 / 表单页同一套 `.is-stuck` 机制
   （`app.js` 的 updateStuck 扫描列表里已含 `.tabs`）。
   ⚠️ 用 `.tabs.is-stuck`（不限定 `.card >`）：别的 `.tabs` 没有 sticky ⇒ 永远不会挂上本类 ⇒ 安全。 */
.tabs.is-stuck {
  background: var(--liquid-bg);
  backdrop-filter: var(--liquid-blur);
  -webkit-backdrop-filter: var(--liquid-blur);
  border-color: var(--liquid-edge);
  box-shadow: var(--liquid-shadow);
}
/* 卡内 tab 紧跟内容区时：padding-top 收一档（24 → 16），配合上一条 margin 归零
   ⇒ tab 底 → 内容首行 = 16px（= 全站卡片间距）。
   ⚠️ 只命中「tabs 的相邻兄弟 card__body」，卡内其它位置的 card__body 不受影响。 */
.card > .tabs + .card__body {
  padding-top: var(--space-4);
}
.card > .toolbar {
  padding: var(--space-4) var(--space-5) var(--space-3);
  margin: 0;
}
.card > .kpi-row {
  padding: var(--space-3) var(--space-5);
  margin-bottom: 0;
}

/* ---- 开关 switch ---- */
.switch {
  position: relative;
  display: inline-flex;
  align-items: center;
  flex-direction: row-reverse; /* 文字在左，开关轨道在右 */
  cursor: pointer;
  user-select: none;
}
.switch input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
  margin: 0;
}
.switch__track {
  position: relative;
  width: 40px;
  height: 22px;
  background: var(--color-border);
  border-radius: var(--radius-pill);
  flex-shrink: 0;
  transition: background-color var(--motion-fast) var(--motion-ease-standard);
}
.switch__thumb {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 18px;
  height: 18px;
  background: #fff;
  border-radius: 50%;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
  /* ⭐ 2026-09-26：滑块用 **弹入曲线**（落位时轻微过冲 ~6% 再咬合）。
     开关是"小物件蹦一格"，过冲是它该有的手感；用通用位移曲线会显得"滑过去就停了"。
     ⚠️ 轨道变色仍用普通 ease —— 它是"状态色"不是"运动"，跟着弹会花。 */
  transition: transform var(--motion-base) var(--motion-ease-pop);
}
.switch input:checked + .switch__track {
  background: var(--switch-active-bg);
}
.switch input:checked + .switch__track .switch__thumb {
  transform: translateX(18px);
}
.switch input:focus-visible + .switch__track {
  box-shadow: var(--focus-ring);
}
.switch input:disabled + .switch__track {
  opacity: 0.5;
  cursor: not-allowed;
}
.switch__label {
  margin-right: var(--space-2);
  font-size: var(--font-size-sm);
  color: var(--color-fg);
}
.switch--sm .switch__track {
  width: 34px;
  height: 19px;
}
.switch--sm .switch__thumb {
  width: 15px;
  height: 15px;
  top: 2px;
  left: 2px;
}
.switch--sm input:checked + .switch__track .switch__thumb {
  transform: translateX(15px);
}

/* ---- 分段控件 / 筛选条（Apple 灰托 + 选中项实心蓝圆角块，对齐乐哥截图：T+35口径 样式）----
   ⭐⭐⭐ 2026-10-08 底色 **白 → `--tab-tray-bg`（Apple 灰 #F1F3F5）**（乐哥：「你还没做完啊，
   比如达人详情、字典配置、用户管理，其他页面也查下」）
   ────────────────────────────────────────────────────────────────────────────────
   ▸ 为什么是"没做完"：10-07 13:40 乐哥拍板"tab 底色用 apple 那种"（灰托），
     但那次只改了 **`.tabs`** 一套（`--tab-tray-bg` 的令牌注释）；`.seg` 这一套
     （分段控件/筛选条——全站 14+ 处在用）**还停在白底** ⇒
     同一个系统里「达人详情 tab = 灰托」「用户管理 tab = 白托」「字典 tab = 无托」
     三种形态并存（乐哥三张截图圈的就是这三种）。
   ▸ 灰底的理由（与 `.pill-filter` / `.table-tabs--in-header` 处同源）：
       · 白底场景下白托盘"化掉"，看不出这是个选项组；
       · #F1F3F5 与白卡拉开 Δ≈10 ⇒「白卡 → 浅灰托盘 → 实心蓝胶囊」三级可读。
     ⚠️ 与 `.tabs` / `.sf-tabs` 是不同组件、不同 DOM，但**共用同一份"浅灰托盘"意图**
        ⇒ 指向同一令牌，将来要调只改一处（与 `.pill-filter` 处的注释同一纪律）。
     ⛔ 别写回 var(--color-surface)——那正是本轮修掉的"半套白托"。 */
.seg {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);                    /* 选项间 4px 间隙（不紧贴） */
  padding: 3px;                            /* ⭐ 2026-10-08 **4 → 3**（44 → 42）：
                                              全站托盘已统一到「3 + 34 + 3 + 2 = 42」这一档
                                              （`.tabs__tray` / `.table-tabs` / 页头 tab 行同值）；
                                              基类停在 4 ⇒ 少数**裸用 `.seg`** 的页面
                                              （/performance/influencers、/performance/huada-settlement）
                                              比别处厚 2px（全站巡检 audit-tab-consistency 抓到）。
                                              容器内边距 = 选中块内缩，3px 与全站同呼吸感。 */
  background: var(--tab-tray-bg);          /* Apple 灰托盘（2026-10-08 统一） */
  border: 1px solid var(--color-border);   /* #E5E8EC */
  border-radius: var(--radius-md);          /* 8px 圆角矩形 */
}
.seg__btn {
  /* ⭐⭐ 2026-10-08 补显式 `height: 34px`（乐哥：「你还没做完啊…其他页面也查下」）
     ────────────────────────────────────────────────────────────────────────────────
     🐞 原状：无显式高度，靠 `padding: 6px 16px` 撑出 **32** ——
       而 `.table-tabs--in-header .seg__btn` 是 **34**（table.css 显式写死）。
       ⇒ 同一套控件在"页头 tab"里 34、在"工具栏/卡内"里 32，**同排和搜索框（34）差 2px**
         （实测用户管理页：seg 32 vs 相邻 `.input--search` 34，肉眼可见不齐平；
          全站普查：34 是绝对主流 23 处、32 有 8 处）。
     ✅ 统一 34（与搜索框/下拉/页头胶囊同一档）—— 视觉齐平，且与 in-header 那条**同值同源**。
        ⚠️ 保留 padding 的横向留白（16），只补纵向高度。 */
  height: 34px;
  padding: 6px 16px;
  border: none;
  border-radius: var(--radius-seg);         /* 6px：选中块自身圆角 */
  background: transparent;
  color: var(--color-fg-2);                 /* 未选中深字 #3F4753 */
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  cursor: pointer;
  white-space: nowrap;
  transition: color var(--motion-fast) var(--motion-ease-standard),
    background var(--motion-fast) var(--motion-ease-standard);
}
.seg__btn:hover {
  color: var(--color-accent-text);
}
.seg__btn.is-active {
  background: var(--color-accent);     /* 选中主色实底 #0064E0（内缩圆角块） */
  /* ⭐ 2026-09-25 UI 美化第二刀：选中态接入品牌渐变，与 `.btn--primary` 同一套语言。
     为什么改这里：分段控件是**全站筛选区最常见的控件**，它一屏可能有 2~4 组。
     原来是纯色实底，与主按钮并排时"两种蓝"（同一品牌色但一个有渐变一个没有）会显得不统一；
     接上同一渐变后，全站所有"实心主色块"（主按钮 / 分段选中 / 胶囊选中）视觉语言一致。
     ⚠️ 用 background-image 叠层（保住上面那行 background 作回落），与 .btn--primary 同款写法。 */
  background-image: var(--gradient-brand);
  color: var(--color-accent-on);       /* 白字 */
  /* ⭐ 2026-10-08 选中态浮起（乐哥动效第②条「选中要有悬浮突出效果」，见令牌注释） */
  box-shadow: var(--shadow-pill-active);
}

/* 分段控件内的分组标签 + 下拉 + 分隔线 */
.seg__label {
  display: inline-flex;
  align-items: center;
  padding: 0 var(--space-2) 0 var(--space-3);
  font-size: var(--font-size-sm);
  color: var(--color-muted);
  font-weight: var(--font-weight-medium);
  white-space: nowrap;
}
.seg__divider {
  width: 1px;
  align-self: stretch;
  margin: 4px var(--space-1);
  background: var(--color-border);
}
.seg-select {
  padding: 6px 16px;
  border: none;
  border-radius: var(--radius-seg);
  background: transparent;
  color: var(--color-fg-2);
  font-size: var(--font-size-sm);
  font-family: inherit;
  cursor: pointer;
  white-space: nowrap;
  outline: none;
  transition: color var(--motion-fast) var(--motion-ease-standard),
    background var(--motion-fast) var(--motion-ease-standard);
}
.seg-select:hover {
  color: var(--color-accent-text);
}
.seg-select.is-active {
  background: var(--color-accent);
  color: var(--color-accent-on);
}

/* ---- 筛选条别名（.pill-filter，与 .seg 同规格：白容器 + 选中项内缩蓝色圆角块）---- */
/* ⭐⭐⭐ 2026-10-07 13:50 **必须压过 `.table-tabs`**（真机实测踩到，见下）
   ────────────────────────────────────────────────────────────────────────────────
   ⚠️⚠️ DOM 上这两个 class 是**同时挂在一个元素上**的：
      `<div class="pill-filter table-tabs table-tabs--in-header">`
      而 `table.css` 的 `.table-tabs`（0,1,0）写了 `background: transparent`
      且**加载更晚** ⇒ 同特异性下**覆盖**掉本组件的底色
      ⇒ 我第一次只改 `.pill-filter` 时，真机量出来**仍是 rgb(255,255,255)**。
   ⇒ 基础类仍写自己的底色（不依赖下面那条），下面再补一条同等特异性的覆盖
      —— 这是"同元素双 class"的通用解法：**谁后加载谁赢，所以要在后加载的文件里赢它**。 */
.pill-filter {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  /* ⭐⭐⭐ 2026-10-07 14:40 **4 → 3**（乐哥：「右边新增达人和左边tab 的高度也不一致」）
     —— DOM 上它是 `<div class="pill-filter table-tabs table-tabs--in-header">`，
        **本行的 padding 是最终生效值**（table.css 那两条都会被
        `.pill-filter` (0,1,0) 与 `.table-tabs--in-header` (0,1,0) 之间的
        源顺序决定，必须三处同值才不会分叉）。
     ▸ 3×2 + 胶囊 34 + 边框 2 = 托盘 42；留 4 的话托盘 = 44，
        比右侧按钮(34) 高 10px —— 那正是本次要修的症状。
     ⛔ 别改回 4。 */
  padding: 3px;
  /* ⭐⭐⭐ 2026-10-07 13:45 托盘底色 → **Apple 浅灰**（乐哥：「这个悬浮的tab不对，
     首先 tab 的底色，**之前不是说用apple那种吗**」）
     ────────────────────────────────────────────────────────────────────────────────
     ▸ 实测病根（真机 1920×950 排期列表）：本组件底色是 `var(--color-surface)`(#FFF)，
       而内容页现在**也是纯白** `#FFF` ⇒ 同色相接 ⇒ **整个托盘"化掉"**，
       截图上只剩几个字和一个蓝胶囊，看不出这是个选项组。
     ▸ Apple 的分层：白内容卡 → **浅灰托盘** → 实心品牌蓝胶囊，三级才读得出来。
       ⇒ 托盘用 `#F1F3F5`（与 `--color-surface-2` 同族、但更中性一档，白底上 Δ≈10 可见）。
     ⚠️ 为什么不用 `--tab-tray-bg` 令牌：**这是另一套组件**（页头筛选器，与 `.tabs`
        / `.sf-tabs` 无关）。上午改令牌时只覆盖了 `.tabs` 那一族、这套没被带上
        ⇒ **同名不同物**，是本轮"改了却没生效"的第二个原因。 */
  /* ⛔ 走令牌不写裸 hex：全站禁裸 hex（门禁 no-raw-hex），且令牌一处改全站生效。
     ⚠️ 这套（页头筛选器）与 `.tabs` / `.sf-tabs` 是**不同组件、不同 DOM**，
        但**共用同一个"浅灰托盘"的视觉意图** ⇒ 指向同一令牌，将来要调只改一处。 */
  background: var(--tab-tray-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  /* ⭐ 2026-10-07 14:40：本托盘内距 4 → **3**，与 `.table-tabs--in-header`（table.css）
     桌面媒体块那条**同值** —— 两个托盘组件曾各写各的内距（4 vs 4 但胶囊高度不同），
     导致"左边 tab 高、右边按钮矮"。3×2 + 胶囊 34 + 边框 2 = 42。 */
}

/* ⭐⭐⭐ 与 `.table-tabs` 组合时的全部修正（Apple 浅灰托盘 + 收缩到内容宽）
   ────────────────────────────────────────────────────────────────────────────────
   ⚠️⚠️ 为什么必须写在这条双 class 上（而不是 `.pill-filter` 基类）：
     DOM 上它是 `<div class="pill-filter table-tabs table-tabs--in-header">`，
     而 `table.css` 的 `.table-tabs`（0,1,0、同特异性、**加载更晚**）写了
     `background: transparent` ⇒ 会**覆盖**掉基类 ⇒ 只改基类，真机量出来仍是白。
     这就是"同元素双 class"的通用解法：**在后加载的文件里、用同等特异性赢它**。
   ▸ 实测病根与修法（1920×950）：
     · 改前：托盘 `var(--color-surface)`#FFF，与白色内容卡同色 ⇒ 整个"化掉"；
     · 改后：`#F1F3F5`，与白卡拉开 Δ≈10 ⇒ 「白卡 → 浅灰托盘 → 实心蓝胶囊」三级可读。
   ⚠️ 只写 background / border-radius / width 三项：
     **不写 padding 与 border** —— 那两项要沿用 `.table-tabs` 的
     （左右 20px 对齐表格左边界 + 下划线基线），覆盖它们会把胶囊型 tab 变成别的形态。
   ⚠️ `width: fit-content` 是**必须**的：`.table-tabs` 是 flex 容器，
     灰底会铺满整行变成"分区横条"（实测一直铺到"新增排期"按钮），
     而 Apple 那种是**收缩到内容宽**、右侧留白。 */
.pill-filter.table-tabs {
  background: var(--tab-tray-bg);
  border-radius: var(--radius-md);
  width: fit-content;
  flex: 0 0 auto;
}

/* ⭐⭐⭐ 2026-10-08 `.seg` 版的同款修正（乐哥：「字典配置…你还没做完啊」）
   ────────────────────────────────────────────────────────────────────────────────
   🐞 字典配置页的分类 tab 是 `<div class="seg">` 被 fix 函数打上 `table-tabs` 类
      （实测 `class="seg table-tabs"`，**没有** `--in-header`——它在 `.card` 里，
       `fixBareSeg` 有 `closest('.card')` 守卫会跳过它）。
      ⇒ 它吃到的是 `.table-tabs` 的**基础样式**（transparent 底 + 下划线基线 +
        `padding: 0 20px` 散排）—— 而它的 16 个分类按钮是 `.seg__btn`
      ⇒ 结果既没有托盘、又不是下划线形态，还因无高度约束撑到 **44**（普查里唯一一个）。
   ▶ 修法：与 `.pill-filter.table-tabs` **同一套**（双 class 赢基础样式）——
     灰托 + 圆角 + 收缩到内容宽；`padding: 4px` 与 `.seg` 基类同值（选中块内缩）。
     ⚠️ 本规则加了 `:not(.table-tabs--in-header)`：页头那版由 table.css 的
        `--in-header` 规则负责（padding 3px / 各自上下文），两者半径相同、内距不同。
   ⚠️ 普查确认"卡内 `.seg.table-tabs`（非 in-header）"**全站仅字典 1 处** ⇒ 作用域安全。 */
.seg.table-tabs:not(.table-tabs--in-header) {
  background: var(--tab-tray-bg);
  border: 1px solid var(--color-border);      /* 完整边框（取代基础样式的"仅下划线"） */
  border-radius: var(--radius-md);
  /* ⭐⭐⭐ 2026-10-09：**4 → 3**（乐哥：「财务中心系统ui，要按照设计规范做，
     当前的这个不对，包括tab…」）
     ────────────────────────────────────────────────────────────────────────────
     病根：`.seg` 基类 10-08 已把内距统一到 **3**（"3 + 34 + 3 + 2 = 42"那一档），
     而本规则**写死 4px** ⇒ 它是全站托盘里最后一个 4px 的漏网。
     ▸ 实测（1920×950 真机，修前）：达播台托盘 `padding 3px` / 高 **36px**（截图 dpr3 下）；
        财务托盘 `padding 4px` / 高 **38px** ⇒ **同一个东西两个厚度**。
     ⛔ 别改回 4：`.seg` 基类那条注释已把"3 + 34 + 3 + 2 = 42"写成全站契约。 */
  padding: 3px;
  gap: var(--space-1);
  width: fit-content;
  flex: 0 0 auto;
}
/* ⭐⭐⭐ 2026-10-09：**在页面级工具栏里必须独占一行，但托盘本身仍收缩到内容宽**
   ────────────────────────────────────────────────────────────────────────────
   🐞 症状（真机 1920×950 实测，财务「报销单总览」）：**tab 与筛选控件挤在同一行** ——
      `.seg.table-tabs` rect.y=233、搜索框 rect.y=242 ⇒ 同一行。
      页面读起来是"左边几个 tab、右边一排筛选"的一长条，**层次完全塌掉**。
   根因：`table.css` 的 `.toolbar--has-page-tabs > .table-tabs { flex-basis: 100% }`
   是 (0,2,0)，而本文件上面那条 `.seg.table-tabs:not(.table-tabs--in-header)`
   是 **(0,3,0)** 且写了 `flex: 0 0 auto`（简写 ⇒ 含 `flex-basis: auto`）
   ⇒ **高特异性赢**，`.pill-filter` 版能独占行、`.seg` 版不能。
   ⚠️ 这正是"改一套等于没改"的经典现场：**同一族组件两套类名**（`.pill-filter`
      与 `.seg`），基类都修到 3px 了，`.seg` 这条覆盖规则忘了跟着改内距与独占行。

   ✅ 修法：下面这条**同文件、更靠后、特异性 (0,4,0) > (0,3,0)** ⇒ 稳定赢。
   ⭐⭐ **关键是「占满整行」与「灰底铺满」是两件事** ——
      `flex-basis: 100%` 只让它**独占一行**（布局），
      而 `width: fit-content` 决定**灰底画到哪里**（视觉）。
      ⚠️⚠️ 第一版只写了 `flex: 0 0 100%`、没在同一块里重申 `width: fit-content`
      ⇒ 灰底从内容宽 325px **铺到 1339px**，整行成"分区横条"
      —— 正是 10-07「`width: fit-content` 是**必须**的，灰底会铺满整行
      把搜索框与按钮都罩住」那个坑，**换个地方重犯一次**。
      ⇒ 所以下面这条**两个属性必须成对写**，且都要与 `.pill-filter.table-tabs`
      （`width: fit-content` + `flex: 0 0 auto`）同族同值。 */
.toolbar--has-page-tabs > .seg.table-tabs:not(.table-tabs--in-header) {
  flex: 0 0 100%;          /* 占满整行 ⇒ 独占一行（布局层） */
  width: fit-content;      /* 灰底只画到内容宽（视觉层）—— 两个必须成对 */
}
/* ⚠️⚠️ 托盘里的按钮必须**重设回"胶囊形态"** —— 否则会吃到 `.table-tabs .seg__btn`
   （table.css:498，散排形态：高 44 / 字号 16 / padding 0 2px，特异性 (0,2,0) 高于
   `.seg__btn` 基类）⇒ 实测"托盘加了、按钮还是 44 高挤在托盘里"。
   本选择器 (0,3,0) 压过它。值与 `.table-tabs--in-header` 的胶囊**同值同源**：
   34 高 / 0 16 内距 / 圆角 --radius-seg。 */
.seg.table-tabs:not(.table-tabs--in-header) .seg__btn {
  height: 34px;
  padding: 0 16px;
  border-radius: var(--radius-seg);
  font-size: var(--font-size-sm);
  /* ⛔ color 不写在这条里了（乐哥 2026-10-08：「tab 里文字看不清」）——
     本选择器 (0,4,0) 是"重设按钮形态"用的，把 `color: --color-fg-2` 一起写在里面
     会**压过** `.table-tabs .seg__btn.is-active`（0,3,0）的选中白字 ⇒
     选中胶囊变成"深灰字贴蓝色渐变"（字典配置页截图实锤）。
     ✅ 颜色下沉到下一行的 `:not(.is-active)` 规则：只给未选中，选中态让白字赢。 */
}
.seg.table-tabs:not(.table-tabs--in-header) .seg__btn:not(.is-active) {
  color: var(--color-fg-2);
}

.pill-filter__item {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* ⭐⭐ 2026-10-08 与 `.seg__btn` 同批：显式 34（普查里它也有 32 的裸实例） */
  height: 34px;
  /* 原 6px 16px：改上 6.5 下 5.5，配合 flex 居中补偿中文字面偏上 */
  padding: 6.8px 16px 5.2px;
  border: none;
  border-radius: var(--radius-seg);
  background: transparent;
  color: var(--color-fg-2);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  line-height: 1.4;
  white-space: nowrap;
  cursor: pointer;
  transition: color var(--motion-fast) var(--motion-ease-standard),
    background var(--motion-fast) var(--motion-ease-standard);
}

.pill-filter__item:hover {
  color: var(--color-accent-text);
}

.pill-filter__item.is-active {
  background: var(--color-accent);          /* 纯色作**回落**，与 .btn--primary 同款 */
  /* ⭐ 2026-09-25 UI 美化第二刀：胶囊选中态同步接品牌渐变（与主按钮 / 分段控件一致）。
     ⭐ 2026-10-08：`.table-tabs` 里的胶囊/分段也已统一为同一渐变
        （table.css 两条：`.table-tabs …` 与 `.table-tabs--in-header …`）——
        全站"实心主色块"（主按钮 / 分段选中 / 胶囊选中）至此**完全同源**。
     ⚠️ 旧注已失效（保留一行防后人被老说法带偏）：曾提"table.css 那条写 transparent
        会盖掉本条渐变"—— 10-07 起那条改为实心底、10-08 起同为品牌渐变，覆盖关系已消失。 */
  background-image: var(--gradient-brand);
  color: var(--color-accent-on);
  /* ⭐ 2026-10-08 选中态浮起（乐哥动效第②条，与 `.seg__btn.is-active` 同批） */
  box-shadow: var(--shadow-pill-active);
}

/* ⭐⭐⭐ 2026-10-08 19:2x **TAB 上的计数徽标**（唯一实现：`UI.setTabLabel`）
   ──────────────────────────────────────────────────────────────────────────────
   🐞 乐哥：「这每个类目后统计的数字格式改下，当前这种看着很乱，看下怎么展示好」——
      旧写法是 `label + ' ' + n`（纯文本 + 一个半角空格）⇒ 数字与标签**同字号同色**、
      间距只有一个空格，一排 tab 并排时数字糊在词尾（读起来像"待合作67"一个词）。
   ✅ 徽标：与标签间 6px、坐进自己的浅底小胶囊、字号降一档 ⇒ 名字归名字、数量归数量；
      一排并排时数字也自成一列（`tabular-nums` 保证位数不同时也等宽）。
   ⚠️ 底色为什么用 `--color-gray-200` 而**不是** `--color-surface-2`：
      宿主托盘是 `--tab-tray-bg`(#F1F3F5)，而 surface-2 是 #F1F5F9 —— **只差 1~4 阶**
      ⇒ 徽标在托盘上会"化掉"（这正是 10-07「托盘看不见」那类病的同一机理）。
      gray-200(#E8E8E8) 比托盘深 9 阶，在白卡上也同样可见。
   ⚠️ 圆角走 `--radius-seg`(6px)、**不用全圆**：2026-09-25「标签去糖果感」那一刀
      把全站标签从 9999 收到 6；这是同一个视觉家族，别加回全圆。
   ⚠️ 高度用 `line-height` 撑、**不写死 height**：宿主胶囊在 34 / 44 两档都出现过。
   ⚠️ 它是"徽标"不是控件 ⇒ 不适用"控件只有 44/34 两档"那条（`.badge` 22 是同一类例外）。 */
.tab-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-left: 6px;
  padding: 0 6px;
  min-width: 19px;
  border-radius: var(--radius-seg);
  background: var(--color-gray-200);
  color: var(--color-fg-2);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-medium);
  font-variant-numeric: tabular-nums;
  line-height: 18px;
}
/* 选中态（蓝块上）：半透明白底 + 白字 —— 与"选中胶囊"同一套颜色语义 */
.is-active .tab-count {
  background: rgba(255, 255, 255, 0.28);
  color: var(--color-accent-on);
}

/* 胶囊上的人数角标（2026-09-15，提成总览「类目」筛选用）——
   让「点助播能筛出几个人」在点之前就知道。
   ⚠️ 用 <em> 而不是 <span>：<em> 语义上是"强调"，这里恰好是"次要补充信息"，
      用 span 也行但项目里 <em> 已被用于这类数字角标（与 .co-role-n 呼应）。
   ⚠️ 继承当前文字色阶（未选中=次要色、选中=白字），不要写死颜色，
      否则选中态（蓝底）上会出现灰字看不清。 */
.pill-filter__item .co-role-n {
  margin-left: 5px;
  font-size: var(--font-size-xs);
  font-style: normal;
  opacity: .78;
}

/* 0 人的类目：置灰但**不禁用**（禁用会让"这个类目不存在"和"今天没数据"看起来一样） */
.pill-filter__item.is-empty {
  opacity: .45;
}
.pill-filter__item.is-empty.is-active {
  opacity: 1;   /* 已选中的不置灰：用户明确选了它，得看清楚自己选了什么 */
}

/* ---- 货品缩略图 ---- */
.product-thumb {
  width: 40px;
  height: 40px;
  border-radius: var(--radius-sm);
  object-fit: cover;
  border: 1px solid var(--color-border-soft);
}
.product-thumb--empty {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--color-surface-2);
  color: var(--color-muted);
}

/* ---- 图片上传控件 ---- */
.img-upload {
  position: relative;
  width: 160px;
  height: 160px;
  border: 2px dashed var(--color-border);
  border-radius: var(--radius-md);
  cursor: pointer;
  overflow: hidden;
  transition: border-color var(--motion-fast) var(--motion-ease-standard);
}
.img-upload:hover {
  border-color: var(--color-accent-text);
}
/* ⭐ 2026-10-11 **横向拖拽区**变体（发票 / 单据类附件；实现见 ui.js `imageUpload` 的 `variant: 'drop'`）
   ─────────────────────────────────────────────────────────────────────────────
   乐哥截图反馈：「这个登记追回弹窗里面布局，ui 调整下」。
   病根：默认的 160×160 方形是给**头像 / 商品图缩略**用的 —— 发票是 PDF、文件名长，
     ① 「点击或拖入文件 + 支持 JPG/PNG/PDF/OFD」两行字挤在 160px 里换行；
     ② 它只占表单格的左半边（380 宽里只有 160）⇒ 右边一大片空、右缘也不齐平
        （违反 DESIGN-SPEC §7.4「控件右缘必须齐平」）。
   ⇒ 横向铺满容器宽、高度压到 96（拖拽区不需要 160 那么高），图标与文案**横排**。
   ⚠️ 与 `.img-upload` **同特异性 (0,1,0)** ⇒ 靠**源顺序**决胜，必须写在本组里 `.img-upload` 之后，
      别往上挪（挪上去就静默失效，本项目在特异性上栽过多次）。
   ⚠️ 外层类仍叫 `.img-upload` ⇒ 拖拽 / 预览 / 移除 / OCR 那几段 JS 一行都不用改。 */
.img-upload--drop {
  width: 100%;
  max-width: 100%;
  height: 96px;
}
.img-upload--drop .img-upload__empty,
.img-upload--drop .img-upload__docfile {
  flex-direction: row;          /* 方形版是竖排；横条里要横排 */
  gap: var(--space-2);
  text-align: left;
}
.img-upload--drop .img-upload__docfile {
  justify-content: center;
  padding: 0 var(--space-5);    /* 给右上角的「移除」按钮让位 */
}
/* ⭐ 2026-10-11 拖入高亮（乐哥：「系统全局上传都支持拖入上传」）。
   ⚠️ 类名与配色**必须**沿用全站既有语言：`.is-dragover` + `--color-accent` 边 /
   `--color-accent-bg` 底 —— 与 `.upload-drop.is-dragover`（本文件 1835 行）、
   `.eb-files.is-dragover`、`#tt-shot.is-dragover` 逐字同款。
   ⛔ 别写 `is-drag`、别用别的颜色：同一件事两套名字/两套配色，
      用户在两个页面看到的"能放这里"信号会不一样（pages.css 6173 行记过这条）。 */
.img-upload.is-dragover {
  border-color: var(--color-accent);
  background: var(--color-accent-bg);
}
/* 非图片（PDF / OFD）的预览形态：没有缩略图可画，画"文件卡片"。
   ⚠️ 一律**不用** `<img>` —— 把 PDF 塞进 img 会渲染成裂图，而且不报错、最难发现。 */
.img-upload__docfile {
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: var(--space-3);
  color: var(--color-muted);
  font-size: var(--font-size-xs);
  text-align: center;
  word-break: break-all;
}
.img-upload__empty {
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  color: var(--color-muted);
  font-size: var(--font-size-sm);
}
.img-upload__preview {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.img-upload__remove {
  position: absolute;
  top: 6px;
  right: 6px;
  width: 24px;
  height: 24px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: var(--radius-sm);
  background: rgba(0, 0, 0, 0.5);
  color: #fff;
  cursor: pointer;
}
.img-upload__remove:hover {
  background: rgba(0, 0, 0, 0.7);
}

/* ---- ⭐⭐ 上传 / 识别的等待指示（2026-10-12）────────────────────────────────
   ────────────────────────────────────────────────────────────────────────────
   乐哥原话：「发票上传，识别的时候发票一多就会卡一下，这个也得加等待的交互」

   ▸ 背景：发票上传后要**等服务端识别**（飞书 IDP，单张数秒）。报销明细一笔可以传
     多张票，而上传是**逐个串行**的（见 expense-batch-form.js 的 uploadFiles 说明：
     并发会同时解多张大图、返回顺序还不定）⇒ 攒 5 张就是十几秒的等待。
     此前这两处**只把容器变淡/写一行死文字**，没有任何"在动"的东西 ⇒ 体感就是卡死。
   ▸ 本类是**两处共用的一份实现**（⛔ 别各写一条）：
       ① 报销明细的发票格 `.eb-files`（多张 · 带进度 n/N）
       ② 单文件上传件 `.img-upload`（华大服务票 / 达人结算 / 票据等，见 ui.js 的 doSend）
   ⚠️ 参数与全站既有四处 spin 对齐：**0.8s linear**
      （DESIGN-SPEC §9.6：「一直转」的循环动画**不引 `--motion-*`**，全站统一 0.8s）。
      ⛔ 别写成 `--motion-fast` 之类 —— 那不是同一类东西（过渡 vs 循环）。
   ⚠️ 无障碍不用单独写：`design-tokens.css` 的全局 `prefers-reduced-motion` 会把它
      降到 0.01ms（转一圈即停），不会真的转。 */
.up-wait {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--font-size-xs);
  /* ⚠️ 用**主色**而不是 `--color-muted`：报销单那格在上传中会整格 `opacity: .6`
     （父级透明度会级联到本元素）⇒ 灰色文字会被冲淡到几乎看不清，
     而"正在识别 2/30…"恰恰是用户唯一能确认"它还在动"的依据（2026-10-12 真机截图确认过）。 */
  color: var(--color-accent-text);
  white-space: nowrap;
}
.up-spin {
  width: 14px;
  height: 14px;
  flex: 0 0 auto;
  border: 2px solid var(--color-border);
  border-top-color: var(--color-accent);
  border-radius: 50%;
  animation: up-spin .8s linear infinite;
}
@keyframes up-spin { to { transform: rotate(360deg); } }

/* ---- ⭐⭐ 头像场景修饰类（2026-10-06「头像三者一致」）
   ────────────────────────────────────────────────────────────────────────
   ▸ 问题：全站头像有三处 —— 顶栏 `.user-avatar`(32)、下拉菜单头 `.profile-avatar`(44)、
     设置弹窗「修改头像」的 `.img-upload`(160)。前两者本来就同构（`--radius-pill` +
     `--color-primary-100` 底 + `--color-accent-text` 字 + semibold），
     **第三处是唯一异类**：10px 方角 + 2px 虚线框 + 灰色上传图标 ⇒ 三样语言全不同。
     乐哥要的是"头像三者一致" ⇒ 把③收进①②这一族。
   ⛔⛔ **必须用修饰类，不能直接改 `.img-upload` 本身** —— 它是**通用组件**，
     还被商品图(product-form `#f-image-box`)、套装图(bundle-form `#bf-image-box`)、发票图
     (influencer-settlement) 用着；直接改会把**商品图上传框也变成圆的**。
     （规范铁律 6：改一个位置值前先 grep 查依赖。）
   ▸ 保留虚线圆：上传区的"可点击上传"语义靠它表达，去掉会失去 affordance。
     空态 = 圆形 + 虚线边 + 居中上传图标；**有图态 = 实心圆 + 首字母/图片**（与①②同一语言）。 */
.img-upload--avatar {
  width: 160px;
  height: 160px;
  border-radius: var(--radius-pill);   /* ⭐ 与①②同一档（全圆），不再是 10px 方角 */
  border: 2px dashed var(--color-border);
  /* 上传中态（JS 写入的 inline style 容器）也要是圆，否则闪一下方角 */
  overflow: hidden;
}
/* 有图时：实心圆、去掉虚线边 ⇒ 与①②的"头像已就位"观感一致。
   （`.img-upload__preview` 是铺满的 <img>，object-fit 已裁成方形，这里只把容器转圆） */
.img-upload--avatar:has(.img-upload__preview) {
  border-style: solid;
  border-color: transparent;
}
/* 空态：虚线圆 + **首字母降级**（复用①②的底色与字色），而不是灰色上传图标。
   浅蓝底 + 主色字 = 与顶栏/下拉菜单头完全同一套语言；
   虚线边保留 = "这里可以点上传"的 affordance 不丢。 */
.img-upload--avatar .img-upload__empty {
  border-radius: var(--radius-pill);
  color: var(--color-accent-text);
  background: var(--color-primary-100);
}
/* 上传中态：也是浅蓝圆（JS 写的是 inline color:muted，这里只补底与圆） */
.img-upload--avatar {
  background: var(--color-primary-100);
}

/* ---- 飞书通讯录选人组件 ---- */
.fp {
  position: relative;
}
.fp__box {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  /* ⭐ 2026-09-29 乐哥全站审察：原 min-height 40px ⇒ 与同行 44px 输入框/下拉不齐
     （实测「接待人/助播/商务助理」40，「地点」44）。归位到标准档令牌。 */
  min-height: var(--control-height);
  padding: var(--space-1) var(--space-4);
  border: 1px solid var(--input-border);
  border-radius: var(--radius-md);
  background: var(--input-bg);
  cursor: text;
}
.fp__box:focus-within {
  border-color: var(--input-focus-border);
  box-shadow: var(--focus-ring);
}
.fp__field {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex: 1;
  min-width: 120px;
}
.fp__value {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--font-size-base);
}
.fp__placeholder {
  color: var(--color-muted);
}
.fp__search {
  flex: 1;
  min-width: 80px;
  border: none;
  outline: none;
  font-size: var(--font-size-base);
  background: transparent;
}
.fp__chips {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}
/* ── 可搜索多选（`UI.searchMultiPick` · 第 75 批）的下拉面板与候选项 ────────────
   ⚠️ `schedulePicker` 那份 dropdown 样式是**内联**写的（历史遗留）；这里落成类名，
      两个组件共用同一套外观 —— 改一次两处都变（本项目对"同一件事两处维护"零容忍）。
   ⚠️ 面板必须能滚动（候选多时）且**不能撑破弹窗** ⇒ max-height + overflow-y。
   ⚠️ 候选项的点击用的是 `mousedown`（不是 click）—— 见 ui.js 里为什么。 */
.sp__dropdown {
  margin-top: var(--space-2);
  max-height: 240px;
  overflow-y: auto;
  border: 1px solid var(--input-border);
  border-radius: var(--radius-md);
  background: var(--input-bg);
}
.sp__item {
  padding: var(--space-2) var(--space-4);
  font-size: var(--font-size-sm);
  cursor: pointer;
}
.sp__item:hover { background: var(--color-surface-2); }
/* 「＋ 新建「<搜的词>」」那一行（`searchMultiPick` 的 `onCreate`，第 76 批）
   ⚠️ 与普通候选项区分开：上面一条分隔线 + 主色文字 ——
      它不是"已有的一条数据"，点下去会**开一个新建表单**，长得一样会让人误以为在选现成的。 */
.sp__item--new {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  color: var(--color-accent-text);
  border-top: 1px solid var(--color-border-soft);
}

.sp__empty {
  padding: var(--space-3);
  color: var(--color-muted);
  font-size: var(--font-size-sm);
  text-align: center;
}

.fp-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 8px 2px 2px;
  border-radius: var(--radius-pill);
  background: var(--color-surface-2);
  font-size: var(--font-size-sm);
}
.fp-chip__x {
  border: none;
  background: none;
  cursor: pointer;
  color: var(--color-muted);
  display: inline-flex;
  padding: 0;
  /* ⭐ 2026-10-13 乐哥报「这个商务我发现删不了」。
     ───────────────────────────────────────────────────────────────────────
     实测结论：**删除链路是好的**（真机端到端跑通：删 → 保存 → 重开，库里确实少了；
     `schedules.bd_owner` 真被改写）⇒ 他遇到的不是"删不掉"而是"**点不到**"。
     真因：本按钮 `padding: 0` + 图标 12px ⇒ **热区只有 12×12px**，
     而全站控件最小档是 34px（见设计规范"控件尺寸两档制"）—— 鼠标极难点准。
     ✅ 用伪元素把热区扩到 **24×24**：`inset` 是负值 ⇒ **视觉与布局一点不变**
        （⛔ 别改用 padding 撑：那会把 .fp-chip 顶高、整排 chip 变形）。 */
  position: relative;
}
.fp-chip__x::after {
  content: '';
  position: absolute;
  inset: -6px;          /* 12 + 6×2 = 24×24 热区 */
}
.fp-chip__x:hover {
  color: var(--color-danger);
}
.fp-avatar {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  overflow: hidden;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.fp-avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.fp-avatar--empty {
  background: var(--color-primary-100);
  color: var(--color-accent-text);
  font-size: var(--font-size-xs);
}
.fp__dropdown {
  position: absolute;
  top: calc(100% + 8px);
  left: 0;
  right: 0;
  width: 100%; /* 显式约束，不让内容撑大溢出到旁边列 */
  max-height: 240px;
  overflow-y: auto;
  overflow-x: hidden; /* 裁剪水平溢出，防止 fp-item 长名字撑开 */
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-raised);
  z-index: var(--z-dropdown);
  /* ⭐ 2026-09-29 补入场动效（原为**零动效**：直接 `display:block` 冒出来）。
     勘察发现：同族的日期面板 `.dtp-panel` 有淡入，唯独选人下拉没有 ——
     并排放在同一个表单里会读出"两个系统"。
     ⚠️ 本元素靠 `style="display:none"` 切换显隐 ⇒ 从 none 恢复可见时
        CSS animation **会自动重放**（规范行为），故无需改 JS。
     ⚠️ 用 `--motion-base`(155ms) 而不是大浮层的 `--motion-slow`：
        紧贴输入框的小面板，等 300ms 才展开会显得拖（Apple 的下拉也是小浮层节奏）。
     ⛔ fill-mode 必须是 `backwards` 而不是 `both` —— 本元素**内部还承载选项列表**
        （`.fp-item`），`both` 会在动画结束后残留 `transform` ⇒ 给容器留下层叠上下文
        ⇒ 内部元素被外层卡片盖住（乐哥 2026-09-26 实报过同类事故，全站 6 页受影响；
        由 tests/motion-system.test.js ⑪ 当场拦住）。`backwards` 播完即移除动画声明，
        元素回到自身样式（opacity:1 / transform:none），无残留。 */
  animation: panel-in var(--motion-base) var(--motion-ease-sheet) backwards;
}
.fp-item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  cursor: pointer;
  font-size: var(--font-size-sm);
}
/* 下拉项圆角：首项顶部、末项底部跟随弹窗圆角，避免直角突兀 */
.fp__dropdown .fp-item:first-child,
.fp__dropdown .fp-empty:first-child {
  border-top-left-radius: calc(var(--radius-md) - 1px);
  border-top-right-radius: calc(var(--radius-md) - 1px);
}
.fp__dropdown .fp-item:last-child,
.fp__dropdown .fp-empty:last-child {
  border-bottom-left-radius: calc(var(--radius-md) - 1px);
  border-bottom-right-radius: calc(var(--radius-md) - 1px);
}
.fp-item:hover {
  background: var(--color-surface-2);
}
.fp-item__name {
  color: var(--color-fg);
}
.fp-empty {
  padding: var(--space-4);
  text-align: center;
  color: var(--color-muted);
  font-size: var(--font-size-sm);
}
/* 选人组件修复：输入框占满剩余空间，点击任意位置可聚焦 */
.fp__box .fp__value {
  flex-shrink: 0;
  max-width: 100%;
}
.fp__search {
  flex: 1;
  min-width: 80px;
  cursor: text;
}
/* 历史「常用」标签 */
.fp-item__tag {
  margin-left: auto;
  padding: 0 6px;
  font-size: var(--font-size-xs);
  line-height: 16px;
  border-radius: var(--radius-pill);
  background: var(--color-surface-2);
  color: var(--color-muted);
}
/* 下拉可滚动（移动端顺滑） */
.fp__dropdown {
  -webkit-overflow-scrolling: touch;
}
/* 外部人员（不在飞书通讯录，如兼职商务） */
.fp-item--external {
  border-top: 1px dashed var(--color-border-soft);
  margin-top: 4px;
  padding-top: 8px;
}
.fp-item__tag--ext {
  background: var(--color-warning-bg);
  color: var(--color-warning);
}

/* ---- 列表操作列按钮：一行展示 + 配色 ---- */
.table .toolbar {
  flex-wrap: nowrap;
}
/* 图标按钮配色：查看灰 / 编辑蓝 / 删除红 */
.icon-btn[data-view] { color: var(--color-muted); }
.icon-btn[data-view]:hover { color: var(--color-fg); background: var(--color-surface-2); }
.icon-btn[data-edit] { color: var(--color-accent-text); }
.icon-btn[data-edit]:hover { color: var(--color-accent-hover); background: var(--color-primary-100); }
.icon-btn[data-del] { color: var(--color-danger); }
.icon-btn[data-del]:hover { color: var(--color-danger); background: var(--color-danger-bg); }
/* ⭐ 2026-10-11：合同表「查看条款」按钮（`data-view`）。与 `data-edit` 同款
   "平时灰、hover 变主色"，但两者语义不同（看 vs 改），删除仍保持红色 ——
   一列里三个按钮，颜色就是它们的语言。 */
.icon-btn[data-view] { color: var(--color-muted); }
.icon-btn[data-view]:hover { color: var(--color-accent-text); background: var(--color-primary-100); }

/* ⭐ 2026-09-25 UI 美化第三刀：「编辑」图标在**表格内**改为**中性灰**（hover 才显品牌蓝）。
   为什么（实测依据）：一屏数据表 20 行 × 3 个图标 = **60 个图标**，
   其中 20 蓝 + 20 红全彩 —— 一屏 40 个高饱和小图标，让"操作列"成了表里最花的一块，
   数据反而退居其次（乐哥：「感觉还是不够清新简约…还有文字」）。
   改后一行是「灰 · 灰 · 红」：两个只读类操作（查看/编辑）退为中性，
   只有**删除**保留红色警示（这是真风险操作，颜色要留）。hover 时蓝色回来，可点性不受影响。
   ⚠️ 特异性 `.table .icon-btn[data-edit]`(0,3,0) 高于基础规则 `.icon-btn[data-edit]`(0,2,0)，
      故顺序无关，但写在后面便于阅读。
   ⚠️ 只改**表格内** —— 表格外（详情页、卡片、表单）的编辑按钮数量少、且常是主入口，保持蓝色。
   ⚠️ 不改任何图标语义 / 类名 / JS：`data-edit` 属性照旧，只调颜色。 */
.table .icon-btn[data-edit] { color: var(--color-muted); }
.table .icon-btn[data-edit]:hover { color: var(--color-accent-text); background: var(--color-primary-100); }
/* 表单页小删除按钮（trash 图标）统一红色 */
.icon-btn.n-del,
.icon-btn.sess-del,
.icon-btn.acc-del,
.icon-btn.bf-del,
.icon-btn.pid-del,
.icon-btn.rule-del,
.icon-btn.sk-del,
.icon-btn.item-del,
.icon-btn.pf-prod-del {
  color: var(--color-danger);
}
.icon-btn.n-del:hover,
.icon-btn.sess-del:hover,
.icon-btn.acc-del:hover,
.icon-btn.bf-del:hover,
.icon-btn.pid-del:hover,
.icon-btn.rule-del:hover,
.icon-btn.sk-del:hover,
.icon-btn.item-del:hover,
.icon-btn.pf-prod-del:hover {
  color: var(--color-danger);
  background: var(--color-danger-bg);
}
/* 文字按钮配色（用户管理等）：改角色/重置密码蓝 / 停用启用灰 / 删除红 */
.btn--ghost[data-role], .btn--ghost[data-pwd] { color: var(--color-accent-text); }
.btn--ghost[data-role]:hover, .btn--ghost[data-pwd]:hover { color: var(--color-accent-hover); background: var(--color-primary-100); }
.btn--ghost[data-status] { color: var(--color-muted); }
.btn--ghost[data-status]:hover { color: var(--color-fg); background: var(--color-surface-2); }
.btn--ghost[data-del] { color: var(--color-danger); }
.btn--ghost[data-del]:hover { color: var(--color-danger); background: var(--color-danger-bg); }

/* ---- 必填项星号 ---- */
.field-label .req { color: var(--color-danger); }

/* ---- 移动端：搜索框占满、控件触控目标 ≥40px、表格紧凑 ---- */
@media (max-width: 768px) {
  .input--search {
    flex: 1 1 auto;
    min-width: 0;
    max-width: 100%;
  }
  .input--search .input {
    width: auto !important;
    flex: 1 1 auto;
    min-width: 0;
  }
  /* 触控目标（Apple HIG ≥44px 可点区域）：输入/选择/日期触发 44，按钮/分段 40 */
  .input,
  .select {
    min-height: 44px;
  }
  .dtp-trigger,
  .fp__box {
    min-height: 44px;
  }
  .seg {
    min-height: 40px;
  }
  .btn {
    min-height: 40px;
  }
  .btn--lg {
    min-height: 44px;
  }
  /* 表格更紧凑 */
  .table tbody td {
    padding: 8px 12px;
  }
}

/* ===== 数据导入页 ===== */
.upload-drop {
  border: 2px dashed var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--space-6) var(--space-4);
  text-align: center;
  cursor: pointer;
  color: var(--color-muted);
  transition: border-color var(--motion-fast) var(--motion-ease-standard), background var(--motion-fast) var(--motion-ease-standard);
  margin-top: var(--space-2);
}
.upload-drop:hover { border-color: var(--color-accent); background: var(--color-surface-2); }
.upload-drop.is-dragover { border-color: var(--color-accent); background: var(--color-accent-bg); }
.upload-drop svg { color: var(--color-muted); }

.sheet-option {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-md);
  margin-bottom: var(--space-2);
  cursor: pointer;
  transition: border-color var(--motion-fast) var(--motion-ease-standard), background var(--motion-fast) var(--motion-ease-standard);
}
.sheet-option:hover { border-color: var(--color-accent); }
.sheet-option.is-active { border-color: var(--color-accent); background: var(--color-accent-bg); }

/* 导入预览：原始数据可编辑单元格 */
.pv-cell { cursor: text; }
.pv-cell.is-edited { background: var(--color-warning-bg); }

/* 导入预览·「导入后效果」视图（落地结果，只读） */
.ld-ok { color: var(--color-success-text); font-size: var(--font-size-sm); line-height: 1.4; }
.ld-bad { color: var(--color-danger); font-size: var(--font-size-sm); line-height: 1.4; }
.ld-ok + .ld-ok, .ld-bad + .ld-ok, .ld-ok + .ld-bad, .ld-bad + .ld-bad { margin-top: 2px; }
/* 行内 SVG 图标与文字基线对齐（2026-09-14 新增）
   起因：`.ld-ok/.ld-bad` 的状态标记、排期表「其他佣金备注」标记原先用 ✓/✗/✱ **文字符号**当图标，
        违反 P0 规则（禁止用文字符号/emoji 代替图标），已改用 icons.js 的 SVG。
        SVG 默认按基线对齐会略微偏上，故统一下移一点，视觉上与文字居中。 */
.ld-ok > svg, .ld-bad > svg, .icon-inline > svg { vertical-align: -0.15em; }

/* ══════════════════════════════════════════════════════════════════════════
   ⭐⭐⭐ 2026-09-28 全站 tab 条「内层小框」（乐哥拍板：统一成一级页那个形态）
   ──────────────────────────────────────────────────────────────────────────
   全站 tab 条统一为**三层**（以一级页为基准）：
     .page-header--has-tabs / .tabs / .sf-tabs__track   ← 白卡（外层）
       .table-tabs--in-header / .tabs__tray             ← **内层小框**（本规则）
         胶囊（.pill-filter__item / .tab / .sf-tab）    ← 高 34
   ▸ 为什么要有中间这层：它把「一组 tab」圈成一个控件（与右侧操作按钮区分开），
     也是"看起来很整"的来源 —— 一级页一直这么做，其余页面缺这层。
   ▸ 本层由 JS 包装产出（见 app.js 的 wrapTabTrays，在 relayoutActive 里跑、幂等），
     **不改 9 个页面的渲染代码**。
   ⚠️ 与本表下方 `.table-tabs--in-header`（一级页的原生内层框）**同规格** ——
      两条规则必须同值（高 44 = 4+34+4+2、圆角 10、边框 1px --color-border、项距 2px），
      门禁 tests/tab-unify.test.js 会锁这个一致性（防两处漂移）。 */
.tabs__tray {
  display: inline-flex;
  align-items: center;
  gap: 2px;                                     /* 项距 2px：胶囊是"紧邻的块"，不是散排文字 */
  flex: none;                                   /* 收缩到内容宽（不拉伸，同 .table-tabs--in-header） */
  padding: 4px;                                 /* 4 + 34 + 4 + 2(边框) = 44，与右侧按钮齐平 */
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);              /* 10px */
  /* ⭐⭐⭐ 2026-10-08 底色 **白 → `--tab-tray-bg`（Apple 灰）**（乐哥「你还没做完啊」批次）
     ────────────────────────────────────────────────────────────────────────────────
     ▸ 10-07 13:55 那次"托盘改灰"只推到了 `pages-influencer.css` 的两条 scoped 规则
       （达人详情 / 达人表单，注释里写明"本条是真凶"）——**本基类没跟着改**
       ⇒ 其他用到 `.tabs__tray` 的页面仍是白托（与灰托的达人页又不一致）。
     ▸ 改基类后：所有 `.tabs__tray` 消费方**一次性对齐**（scoped 那两条同值，无冲突）。
     ⛔ 别写回 var(--color-surface)：同 `.seg` / `.table-tabs--in-header` 处的说明。 */
  background: var(--tab-tray-bg);
  max-width: 100%;
  flex-wrap: wrap;                              /* 窄屏换行，不出横向滚动条 */
  /* 贴顶变玻璃时的平滑过渡（乐哥要的是"和其他内容叠加才有"，突变会很生硬） */
  transition: background-color var(--motion-base) var(--motion-ease-standard),
              border-color var(--motion-base) var(--motion-ease-standard);
}

/* ⭐ 2026-10-08 桌面端托盘内距 4 → **3**（托盘高 44 → 42）——
   与 `.table-tabs--in-header`（桌面 pad 3 = 42）**同规格**；
   配合 `.sf-shell .tabs` 骨架回白（见 pages-influencer.css 同批注释），
   详情页 tab 行回到标准 **64** 高（10×2 + 42 + 2，与 `.page-header--has-tabs` 白卡逐像素一致）
   —— 乐哥：「这个 tab 栏的高度和标准不一样」。
   ⚠️ 手机端保持 4（触摸场景既有值，窄屏零回归）。
   ⛔ 别把这行升成基础值：那会连手机端一起改（本文件顶部多处同类先例）。 */
@media (min-width: 769px) {
  .tabs__tray { padding: 3px; }
}

/* 一级 Tab 卡（跨页共用）：业绩总表 ↔ 订单明细 顶部是同一组 tab，切页后 tab 仍在，不会「困在」某一页 */
.tabs-card {
  background: var(--color-surface);
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-lg);
  padding: var(--space-4) var(--space-5) 0;
  margin-bottom: var(--space-4);
}
.tabs-card > .tabs { margin-bottom: 0; }

/* ============ 月份选择组件 UI.MonthPicker ============ */
/* ══ 品牌头像 + 品牌选择器（2026-10-13）══════════════════════════════════════
   乐哥：「品牌名维护还有前端展示，我们做成头像+名称那种吧，头像我上传，名称我维护，
         展示方式展示的时候，也都放一起，这样好点」「包括全局筛选也是」。
   ▸ `.bavatar` 是切换器 / 各页筛选器 / 列表品牌列 / 详情品牌行**共用的头像容器**。
     未上传头像的品牌由 JS 画成「品牌色底 + 名称首字」（"加第 N 个品牌 = 插一行数据"
     这条要求新品牌天然没有头像，⛔ 不能因为没图就显示裂图或整块不渲染）。
   ▸ `.bpick` 是**自绘**下拉：原生 `<option>` 放不了图片，而乐哥要「头像 + 名称」一起显示。
     交互与 `.mp`（月份选择器）同款：点开 / 点外关闭 / Esc 关闭 / 同时只开一个。
   ⚠️ 令牌只用**确实存在**的那些（本项目踩过 `--color-border-strong` 不存在、
      写 var() 兜底静默落空的问题）—— `--color-surface-alt` 不存在，用 `--color-surface-2`。 */
.bavatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  border-radius: var(--radius-sm);
  background: var(--color-surface-2);
  color: #fff;
  font-weight: var(--font-weight-semibold);
  line-height: 1;
  overflow: hidden;
  vertical-align: -0.15em;
  /* ⛔ 不用 object-fit:cover：方形 logo 被裁掉角比"留白一点"难看得多 */
  object-fit: contain;
}
/* 「全部品牌」不是某个品牌 ⇒ 中性图标 + 中性底色（⛔ 不套品牌色，否则会被误读成某个品牌） */
.bavatar--all { color: var(--color-muted); background: var(--color-surface-2); }

.bpick { position: relative; display: inline-block; }
/* 表单里用（`UI.brandSelect(el, { block: true })`）：撑满容器宽。
   筛选栏里保持 inline-block 自适应；⛔ 别全局改成 block —— 那会把筛选栏的控件拉满一行。 */
.bpick--block { display: block; width: 100%; }
.bpick--block .bpick__trigger { width: 100%; max-width: none; }
.bpick__trigger {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: var(--control-height);      /* 与同排的 .input / .select 齐平（标准档） */
  max-width: 176px;                   /* 品牌名超长时省略，别把筛选栏撑开 */
  padding: 0 var(--space-2);
  border: 1px solid var(--input-border);
  border-radius: var(--radius-md);
  background: var(--input-bg);
  color: var(--color-fg);
  font-size: var(--font-size-base);
  font-family: inherit;
  cursor: pointer;
  transition: border-color var(--motion-fast) var(--motion-ease-standard);
}
.bpick__trigger:hover { border-color: var(--input-focus-border); }
.bpick__trigger:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 1px; }
/* 选中具体品牌 ⇒ 与"未筛选"的素色态区分（与月份选择器同一个考虑：
   "正在按品牌筛"这件事必须在视觉上说出来，否则用户会把"看不到别的品牌的数据"当成 bug） */
.bpick__trigger.is-on { border-color: var(--color-accent); }
.bpick__label { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bpick__caret { display: inline-flex; flex: 0 0 auto; color: var(--color-muted); }
.bpick__panel {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  z-index: var(--z-dropdown);
  min-width: 200px;
  max-height: 320px;
  overflow-y: auto;
  padding: var(--space-1);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-raised);
}
.bpick__opt {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 7px var(--space-2);
  border: 0;
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--color-fg);
  font-size: var(--font-size-base);
  font-family: inherit;
  text-align: left;
  cursor: pointer;
}
.bpick__opt:hover { background: var(--color-surface-2); }
.bpick__opt.is-active { font-weight: var(--font-weight-semibold); }
.bpick__name { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bpick__check { display: inline-flex; flex: 0 0 auto; color: var(--color-accent); }
.bpick__empty { padding: 10px var(--space-2); color: var(--color-muted); font-size: var(--font-size-sm); }

.mp { position: relative; display: inline-block; }
/* 表单里用（`UI.MonthPicker({ block: true })`）：撑满容器宽。
   筛选栏里保持 inline-block 自适应；⛔ 别全局改成 block —— 那会把筛选栏的月份控件拉满一行。 */
.mp--block { display: block; width: 100%; }
.mp--block .mp__trigger { width: 100%; text-align: left; }
.mp__trigger {
  height: var(--control-height);   /* 与同排的 .input / .select 齐平（标准档） */
  padding: 0 32px 0 var(--space-3);
  border: 1px solid var(--input-border);
  border-radius: var(--radius-md);
  background: var(--input-bg);
  color: var(--color-fg);
  font-size: var(--font-size-base);
  cursor: pointer;
  white-space: nowrap;
  transition: border-color var(--motion-fast) var(--motion-ease-standard);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12' fill='none'%3E%3Cpath d='M3 4.5L6 7.5L9 4.5' stroke='%238595A4' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
  background-size: 12px 12px;
}
.mp__trigger:hover { border-color: var(--input-focus-border); }

/* 筛选生效中（已选中具体某月）：与「未筛选」的素色态明显区分 —— 主色描边 + 前置筛选图标 + 可点的 ✕。
   设计意图：月份触发器与它旁边那些「全部平台 / 全部状态」下拉长得一样时，用户看不出自己正被月份筛着，
   会误以为看到的是全量数据（搜不到别的月份 → 当成 bug）。所以「正在筛」这件事必须在视觉上说出来。 */
.mp__trigger.is-on {
  border-color: var(--color-accent);
  color: var(--color-accent);
  font-weight: var(--font-weight-semibold);
  background-image: none;              /* 去掉下拉箭头：改由前置筛选图标表达「这是筛选条件」 */
  padding-right: var(--space-3);
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.mp__trigger.is-on:hover { border-color: var(--color-accent); }
.mp__trigger .mp__label { white-space: nowrap; }
.mp__trigger.is-on > svg { flex-shrink: 0; }
.mp__clear {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-left: 1px;
  opacity: .5;
  transition: opacity var(--motion-fast) var(--motion-ease-standard);
}
.mp__trigger.is-on:hover .mp__clear { opacity: 1; }

.mp__panel {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  z-index: var(--z-dropdown);
  min-width: 232px;
  padding: var(--space-3);
  background: var(--color-surface);
  border: 1px solid var(--color-border-soft));
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-raised);
}
.mp__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--space-2);
}
.mp__nav {
  width: 28px;
  height: 28px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--input-border);
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--color-fg-2);
  font-size: var(--font-size-lg);
  line-height: 1;
  cursor: pointer;
}
.mp__nav:hover:not(:disabled) { border-color: var(--color-accent); color: var(--color-accent-text); }
.mp__nav:disabled { opacity: .35; cursor: not-allowed; }
.mp__year { font-size: var(--font-size-base); font-weight: 600; color: var(--color-fg); }

.mp__grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}
.mp__month {
  padding: 7px 0;
  border: 1px solid var(--input-border);
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--color-fg);
  font-size: var(--font-size-sm);
  cursor: pointer;
  transition: border-color var(--motion-fast) var(--motion-ease-standard), background var(--motion-fast) var(--motion-ease-standard);
}
.mp__month:hover:not(:disabled) { border-color: var(--color-accent); }
.mp__month.is-active {
  border-color: var(--color-accent);
  background: var(--color-accent);
  color: var(--color-accent-on);
  font-weight: 600;
}
.mp__month.is-disabled,
.mp__month:disabled {
  border-color: var(--input-border);
  background: transparent;
  color: var(--input-disabled-text);
  opacity: .5;
  cursor: not-allowed;
}
/* 当月（今天所在的月）：主色描边 + 主色字。
   要点：必须跟「已选中」区分开 —— 已选中是主色实心，当月是空心描边，两者同月出现时以实心为准。
   当月若没有数据（禁用灰格）也保留描边，让人知道"这是当前月，只是还没数据"。 */
.mp__month.is-current:not(.is-active) {
  border-color: var(--color-accent);
  color: var(--color-accent);
  font-weight: var(--font-weight-semibold);
}
.mp__month.is-current:not(.is-active):disabled {
  color: var(--input-disabled-text);
  opacity: .55;
}

.mp__foot {
  display: flex;
  gap: var(--space-2);
  padding-top: var(--space-2);
  border-top: 1px solid var(--color-border-soft));
}
/* 底部两个快捷按钮：给底色提亮，并分出主次 ——
   「全部月份」是"清除月份筛选"的出口（主），给主色淡底；「回到当月」是快捷键（次），给中性淡底。 */
.mp__today,
.mp__all {
  flex: 1;
  padding: 8px 0;
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  cursor: pointer;
  transition: border-color var(--motion-fast) var(--motion-ease-standard), background var(--motion-fast) var(--motion-ease-standard), color var(--motion-fast) var(--motion-ease-standard);
}
.mp__today {
  background: var(--color-surface-2);
  border-color: var(--color-border);
  color: var(--color-fg);
}
.mp__today:hover:not(:disabled) { border-color: var(--color-accent); color: var(--color-accent-text); }
.mp__today:disabled { color: var(--input-disabled-text); opacity: .5; cursor: not-allowed; }
.mp__all {
  /* ⭐ 2026-10-09 常态改回与「回到当月」**同一套**（灰底 + 描边 + 常规字色）。
     ──────────────────────────────────────────────────────────────────────────
     改前它是 `background: var(--color-accent-bg)`（浅蓝实心 + 主色字 + 半粗），
     两条都不对（全局检查时实测抓到）：
       · **同一个 `.mp__foot` 里两个按钮两套语言**：「回到当月」是 `surface-2` 灰底，
         「全部月份」是浅蓝 —— 看着像其中一个"更重要/被选中"；
       · 更要紧：这个面板里**实心蓝 = 已选中的月份**（`.mp__month.is-active`），
         所以浅蓝底读起来就是"半个选中"（乐哥 2026-10-09 定的铁律：
         **状态色不许当常态底色**，浅蓝只留给 hover / 拖拽 / 选中）。
     ⚠️ 只去底色，**功能不变**：它仍是"一键筛全部月份"的快捷按钮（点了照旧生效）。 */
  background: var(--color-surface-2);
  border-color: var(--color-border);
  color: var(--color-fg);
}
.mp__all:hover { border-color: var(--color-accent); color: var(--color-accent-text); }

/* 筛选栏「重置」按钮：高度必须对齐同一行里的输入框 / 下拉 / 月份选择器（都是 44px）。
   用小号按钮（30px）会比邻居矮一截、看着又小又弱 —— 2026-09-10 乐哥反馈"太小了、看不清"。 */
.filter-reset {
  height: var(--control-height);
  padding: 0 var(--space-4);
}

/* 兼职助播结算：费用块（开关 + 标题 + 字段） */
.pta-fee {
  margin-top: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-md);
}
.pta-fee__head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}
.pta-fee__title {
  font-weight: var(--font-weight-semibold);
  color: var(--color-fg);
}
.pta-fee__body {
  margin-top: var(--space-3);
}

/* 列表多选批量操作栏 */
.batch-bar {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  background: var(--color-surface-2);
  border-radius: var(--radius-md);
  margin-bottom: var(--space-3);
}
.batch-bar__count {
  font-size: var(--font-size-sm);
  color: var(--color-fg);
  font-weight: var(--font-weight-medium);
}

/* 提成「人为校准」标记 */
.calib-tag {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 6px;
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-medium);
  color: var(--color-warning);
  background: var(--color-warning-bg);
  border-radius: var(--radius-pill);
  vertical-align: middle;
}

/* 场次提成：展开子行（与主表同列结构，一行一个直播场次 / 支援人力明细） */
.table tr.row-sub > td {
  background: var(--color-surface-2);
  padding-top: var(--space-2);
  padding-bottom: var(--space-2);
}
.table tbody tr.row-sub:hover > td {
  background: var(--table-row-hover);
}
/* 场次提成：岗位分组分隔线（商务 | 助播 | 商务助理 | 支援人力成本） */
.table th.group-sep,
.table td.group-sep {
  border-left: 1px solid var(--color-border);
  padding-left: var(--space-4);
}
/* 人员维度：展开明细里的内嵌子表 */
.table td .table--sub {
  margin: 0;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  overflow: hidden;
}
.table--sub th,
.table--sub td {
  padding: var(--space-2) var(--space-3);
  font-size: var(--font-size-xs);
}
.link-btn {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 0;
  border: none;
  background: none;
  color: var(--color-accent-text);
  cursor: pointer;
  font-weight: var(--font-weight-medium);
}
.link-btn:hover {
  color: var(--color-primary-500);
  text-decoration: underline;
}
/* 支援人力「不计成本」：整行淡化 */
.table tr.is-excluded td {
  opacity: 0.55;
}

/* ==========================================================================
   口径提示气泡（tooltip）—— 2026-09-12 新增（配 UI.tip / UI.bindTips）
   · 单例元素挂 body，**position:fixed** —— 必须 fixed：
     表格外层 .table-wrap{overflow-x:auto} 会裁掉 absolute 定位的气泡
   · 默认在触发器上方居中，上方空间不足时由 JS 翻到下方
   ========================================================================== */
.ui-tip {
  position: fixed;
  z-index: var(--z-toast);
  max-width: 320px;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  background: var(--color-fg);
  color: var(--color-surface);
  box-shadow: var(--shadow-md);
  font-size: var(--font-size-sm);
  line-height: 1.5;
  word-break: break-word;
  /* ⭐ 2026-09-25：支持 `\n` 换行 —— 口径气泡常有"算法定义 + 数字算式"两段
     （如「金额口径：退款额 ÷ 实付总额」/「¥1,548,186.41 ÷ ¥5,189,547.80」），
     挤成一行既长又难读。pre-line 让 JS 里写的 \n 生效、同时保留自动折行。 */
  white-space: pre-line;
  pointer-events: none;      /* 气泡本身不拦鼠标：移开就消失，不闪烁 */
}

/* ==========================================================================
   备注文本展示（乐哥 2026-09-13）
   「所有涉及备注的地方，文字表达、排版都优化下，一句话一行会好点，
     展示实在不行就用弹窗。然后展示重点文字得标出来的。」
   --------------------------------------------------------------------------
   为什么要做成组件：改动前全站 7 处备注列是同一个复制粘贴写法
     （`ellipsis + max-width:96px` + `title` 悬停），另有约 10 处裸输出。
   而真实数据（店铺订单备注 550 条）：中位 12 字、最长 98 字、8% 超 40 字，
   55% 用空格 / 42% 用冒号 / 24% 用逗号把好几件事挤在一行 —— 挤成一行等于看不见。
   配合 js/remark.js：拆句（一句一行）+ 重点高亮 + 超长折叠 / 弹窗。
   -------------------------------------------------------------------------- */

/* ---- 文本块 ---- */
.rmk { line-height: 1.6; }
.rmk-line { display: block; }
/* 多句之间给一点行距，读起来像清单而不是一段话 */
.rmk--block > .rmk-line + .rmk-line { margin-top: 6px; }
.rmk--block { font-size: var(--font-size-sm); color: var(--color-fg); }

/* 表格单元格里的备注：小一号、次要色（是辅助信息，不抢主字段的注意力） */
.rmk--cell { font-size: var(--font-size-xs); color: var(--color-fg-2); }
.rmk--cell > .rmk-line + .rmk-line { margin-top: 2px; }
/* ⚠️ 表格里的每句**必须严格占 1 行**（乐哥 2026-09-13 排查「明显高行」时发现的组件缺陷）：
   JS 的 `maxChars` 是**按字符数**限长的，但中文在窄列里会自动折行 ——
   实测：达人列表备注列 100px，一行只放得下 ~7 个中文字，而 maxChars 设的是 36
   → 一句被折成 5 行，"maxLines:2" 实际渲染出 10 行，行高从 67px 涨到 125px
     （全站 3 张表的"明显高行"都是这一个原因：达人列表 / 成本·滴滴 / 成本·报销）。
   这里用 nowrap 把「1 个 .rmk-line == 1 行」这个前提固定下来；
   放不下的部分由 `text-overflow: ellipsis` 收尾，完整内容仍可 hover（title）或点「还有 N 条」看弹窗。
   ⚠️ 只作用于 `.rmk--cell`：弹窗里的 `.rmk--block` **要保持可折行**（那里空间大，应显示完整句子）。 */
.rmk--cell > .rmk-line {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.rmk-empty, .rmk--empty { color: var(--color-muted); }

/* 「还有 N 条 / 展开全文」：视觉上是个轻量文字按钮，不占地方但点得到 */
.rmk-more {
  margin-top: 3px; padding: 0;
  border: 0; background: none; cursor: pointer;
  font-size: var(--font-size-xs); font-family: inherit;
  color: var(--color-accent-text); text-decoration: none;
}
.rmk-more:hover { text-decoration: underline; }
.rmk-more:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 1px; border-radius: 2px; }
.rmk-more-dot { color: var(--color-accent-text); }

/* ---- 重点高亮 ----
   ⚠️ `<mark>` 浏览器默认是「黄底黑字」，必须显式覆盖，否则与莫代尔色系冲突。
   只标三类「能拿去做事的」信息（详见 js/remark.js 的注释）：
     id    单号 → 要拿去查单，等宽 + 浅底 + 描边（像一枚可复制的标签）
     money 金额 → 要对账，沿用项目「价格=红」的既有语义（.price 同色）
     act   动作 → 退货/补发/拒绝这类结论词，警示色加粗，一眼看到"这单要不要处理"
   ---- */
.rmk-hl {
  background: none; color: inherit;      /* 先抹掉 <mark> 的默认黄底 */
  padding: 0 2px; border-radius: var(--radius-sm);
  font-weight: var(--font-weight-medium);
}
/* 单号：等宽字体，方便与订单号逐位核对 */
.rmk-hl--id {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.94em;                    /* 等宽字略宽，缩一点保持整行长度 */
  background: var(--color-surface-2);
  border: 1px solid var(--color-border-soft);
  color: var(--color-fg);
  letter-spacing: -0.01em;
}
/* 金额：与项目「价格红」一致 */
.rmk-hl--money { color: var(--color-price); font-weight: var(--font-weight-semibold); }
/* 动作词：警示色（不是错误，是"需要留意"） */
.rmk-hl--act { color: var(--color-warning-text); font-weight: var(--font-weight-semibold); }

/* ---- 备注弹窗：每句一行、行距舒展 ---- */
.rmk-modal__meta {
  margin-bottom: var(--space-3); padding-bottom: var(--space-2);
  border-bottom: 1px solid var(--color-border-soft);
  font-size: var(--font-size-xs); color: var(--color-meta);
}
.rmk-modal .rmk--block { font-size: var(--font-size-base); }
.rmk-modal .rmk--block > .rmk-line + .rmk-line { margin-top: var(--space-3); }

/* ==========================================================================
   长文本单行省略（乐哥 2026-09-13「统一收敛行内省略号写法」）
   --------------------------------------------------------------------------
   背景：全站 15 处把 `overflow:hidden;text-overflow:ellipsis;white-space:nowrap`
   写成**行内 style**（商品名 / SKU / 订单号 / 地址 / 用户名），散落 8 个文件、重复 15 遍。
   目标统一成一处定义，行为不变（仍单行省略 + title 悬停看全）。

   ⚠️ 为什么**没有**套用 `.rmk`（备注组件）—— 这是本条的判断点：
     这 15 处全是**长文本字段**（单个长字符串），**没有一处是备注**（多句自然语言）。
     `.rmk` 会做「重点高亮」（单号/金额/动作词），套到商品名上会把
     「益畅(30袋)*3」「YY010107-000002」这类**规格数字误标成金额/单号** —— 反而更乱。
     所以这里抽的是「单行省略」工具类，与 `.rmk`（拆句+高亮）职责不同、各用其所。

   `.ell` 同时给 `min-width:0`：flex 子项若不设它，内容会把父级撑宽、ellipsis 不生效
   （项目里 5 处 flex 场景原本靠外层 `min-width:0;flex:1` 凑合，现在内层自己带上更稳）。
   ========================================================================== */
.ell {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}
/* 直接省略一个 span/div 时它需要是块级才吃得住宽度 */
span.ell, div.ell { display: block; }
/* `td.ell`：在单元格上直接省略（不套内层 div 的场景）——限制在自身宽度内，不撑破表格 */
td.ell { max-width: 100%; }


/* ==========================================================================
   交互反馈补齐（按下 / 键盘可达）—— 2026-09-13，依据 ui-ux-pro-max 规范
   ---------------------------------------------------------------------------
   审计实测发现的缺口（全站范围，非个例）：
     ① `.icon-btn` **完全没有 :active 规则**（全站 83 个图标按钮）
        → 按下零反馈：点完不知道有没有生效，在表格里连点容易误操作。
     ② `.btn:hover` 与 `.btn:active` 背景同色（已在上方修掉）。
     ③ **15 处可点击表格行**只有 cursor:pointer，没有任何按下反馈。
   规范依据：
     · 优先级 2「Touch & Interaction」— press-feedback / state-clarity
     · 优先级 7「Animation」— 只用 transform/opacity（不动 width/height 以免重排）、
       时长落在 150–300ms（沿用项目 --motion-fast=150ms）、缩放幅度取细微的 0.94–1.05
   ⚠️ 按下底色用 `--color-gray-100`（#EEEEEE）——比 hover 的 surface-2(#FAFBFC) 深一档，
      莫代尔色系里没有 surface-3，直接用中性色阶，不新造 token。
   ⚠️ 行按下态必须落在 `td` 上：`tr` 的背景会被 `td` 覆盖，只写 tr 看不到效果。
   ========================================================================== */
.icon-btn:active:not(:disabled) {
  background: var(--color-gray-100);
  transform: scale(0.94);
}

/* 可点击行的按下反馈。
   ⚠️ 按下色必须**与 hover 同色系加深**：行 hover 是 `--color-primary-50`(#EDF4FE)，
      按下用 `--color-primary-100`(#DBE8FE)。**不能**用灰色 —— 悬停是淡蓝、按下变灰
      会产生色相跳变，看着像"状态丢了"（违反 state-clarity 的同色系递进原则）。
      这是本次实现时自己先写错、复核 token 后修正的一处。
   选择器同时列「已知 class」+「行内 cursor:pointer」兜底 ——
   后者是因为本项目 15 处可点击行**统一用行内 style 标记可点**（已核实），
   用属性兜底可覆盖未来新增的同惯例行；已知 class 列出便于 grep 定位。 */
.table tbody tr.c-row:active,
.table tbody tr.hs-row:active,
.table tbody tr.ip-row:active,
.table tbody tr.s-row:active,
.table tbody tr.so-row:active,
.table tbody tr.person-row:active,
.table tbody tr.group-row:active,
.table tbody tr.fe-group:active,
.table tbody tr[data-row]:active,
.table tbody tr[data-key]:active,
.table tbody tr[data-pkey]:active,
.table tbody tr[data-inf]:active,
.table tbody tr[data-id]:active,
.table tbody tr[data-day]:active,
.table tbody tr[style*="cursor:pointer"]:active {
  background: var(--color-primary-100);
}

/* 减弱动效：按下态的缩放属于"动效"，偏好减少动效时去掉（保留底色变化作反馈）。
   全局那段把 transition-duration 归零只能让过渡"瞬间完成"，**不会**取消 transform 本身，
   故这里显式重置。 */
@media (prefers-reduced-motion: reduce) {
  .btn:active:not(:disabled),
  .icon-btn:active:not(:disabled) {
    transform: none;
  }
}

/* ---- 占位隐藏（2026-09-18，乐哥「切 tab 工具组会跳」）----
   与 `display:none` 的区别：**保留布局空间**。
   工具组/页头按钮都是右对齐的，用 display:none 隐藏其中一项会让其余控件补位、
   整组横向跳动（实测 订单明细 105px）。visibility:hidden 位置一个像素不动，
   且天然不可点击/不可聚焦。用法：ui.js 的 UI.setKeepSpace(el, visible)。 */
.keep-space-hidden {
  visibility: hidden;
}

/* ---- 「新增」按钮定宽（2026-09-18，乐哥「切 tab 页头按钮会跳」）----
   兼职助播页两个 tab 下这个按钮的文案不同：
     「助播人员名单」tab → 新增兼职助播（实测宽 150px）
     「兼职主播直播场次」tab → 新增结算（实测宽 122px）
   页头按钮组是**右对齐**的 → 按钮变宽会把左边缘向左推 28px（实测）。
   直接给它最小宽度 = 较宽的那个，两种状态下宽度完全一致，位置一动不动。 */
#pta-add {
  min-width: 150px;
  justify-content: center;
}

/* 页面模块按需加载中的过渡态（2026-09-29）
   ⚠️ 只在"冷加载"（未命中 immutable 缓存）时短暂出现，见 app.js 的 loadPageModule()：
      延迟 120ms 才显示，所以命中缓存时用户完全看不到它。
   ⚠️ 不用骨架屏：页面模块加载完会立刻整块替换，骨架屏反而会闪一下。 */
.page-loading {
  padding: 56px 24px;
  text-align: center;
  color: var(--color-fg-3);
  font-size: var(--font-size-sm);
}

/* ⭐ 2026-10-10：合同「上传 PDF 自动识别」块（**共享组件**）。
   使用方：① 项目侧「合同管理」新增/编辑（project.js）；
          ② 签约公司年框合同（influencer-company-form.js）。
   ⚠️ 用**浅底 + 虚线边**把它和下面的正式字段区分开 ——
      它是一个**动作**、不是表单字段；长成字段的样子会让人以为也要填。
   ⚠️ 2026-10-10 第 11 批从 pages-project.css 迁来（原来叫 `.pms-extract`，只有项目侧一个使用方）。
      两个使用方共用的东西必须放共享层，否则第二个使用方会顺手复制一份样式 ⇒ 两边漂移。 */
.ct-extract {
  margin-bottom: var(--space-4);
  padding: var(--space-3) var(--space-4);
  border: 1px dashed var(--color-border-soft);
  border-radius: 8px;
  background: var(--color-surface-2);
}
.ct-extract__hd {
  display: flex; align-items: center; gap: 6px;
  font-size: 13px; font-weight: 500; color: var(--color-fg);
  margin-bottom: var(--space-2);
}
/* 标题右侧的形态说明（"二选一：…"）：弱化一档，别跟标题抢 */
.ct-extract__hd-tip { font-size: 12.5px; font-weight: 400; color: var(--color-fg-3); }
/* ⭐ 2026-10-11 乐哥：「上传和链接维护**应该放一起**，反正二选一」
   ⇒ 上传区与链接框同一行、中间一个「或」；窄屏（≤768）竖排堆叠。 */
.ct-extract__pick { display: flex; align-items: flex-start; gap: var(--space-3); flex-wrap: wrap; }
/* ⭐ 2026-10-11 上传区改为复用全站标准件 `.upload-drop`（见 contract-extract-ui.js 的注释）。
   ⚠️ 高度按**紧凑档**收：`.upload-drop` 基础版 `padding: 24px`（数据导入页那种大块）在表单里太高
      ⇒ 这里覆写成 12/16，实高 ≈ 44（= `--control-height`），与同一行/同一张表单里的输入框同一档。
   ⚠️ 同特异性（0,1,0）与 `.upload-drop` 撞 ⇒ 靠**源顺序**决胜，本条必须留在 `.upload-drop` 之后。 */
.ct-extract__drop {
  /* ⚠️ 纵向 8（不是 12）：`.upload-drop` 带 2px 虚线边框 ⇒ 12+12+20(图标)+4(边框)=48，
     而同一行的 `.input` 是 44 ⇒ **又造出"第三档"**（实测上传框 48 / 链接框 44，一眼不齐）。
     8 ⇒ 8+8+20+4=40 < 44 ⇒ 由 `min-height: var(--control-height)` 兜到 **恰好 44**，与输入框同档。 */
  padding: var(--space-2) var(--space-4); margin-top: 0;
  flex: 1 1 300px; min-width: 0; min-height: var(--control-height);
  display: flex; align-items: center; justify-content: center;
}
.ct-extract__drop-in { display: flex; align-items: center; justify-content: center; gap: var(--space-2); }
.ct-extract__drop-tip { font-size: 13px; color: var(--color-fg-2); }
.ct-extract__or { flex: none; font-size: 12.5px; color: var(--color-fg-3); }
/* ⭐ 2026-10-11 第三批：链接可**多条** ⇒ 从单个 `.ct-extract__link` 升级成"一列"
   （每行一条链接 + 底部「+ 再加一条」）。多份合同时会往下长，所以 pick 的 align 改成 flex-start。 */
.ct-extract__links { flex: 1 1 260px; min-width: 0; display: flex; flex-direction: column; gap: var(--space-2); }
.ct-extract__link { flex: none; width: 100%; min-width: 0; }
/* ⚠️ 2026-10-11 修：原来这一行**紧贴**上传框（实测间距 0px，按钮上沿顶在虚线上）——
   乐哥：「当前识别按钮和上面留的间距不对」。⇒ 与表单网格行距同档（`--space-3`）。 */
.ct-extract__row { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap;
  margin-top: var(--space-3); }
/* ⭐ 第 77 批：`linkOnly` 模式那一行只有「识别并整理」按钮（没有文件名 span）
   ⇒ 靠右放（默认是 flex-start，孤零零一个按钮贴左边会显得像排版坏了）。 */
.ct-extract__row--linkonly { justify-content: flex-end; }
.ct-extract__filename { flex: 1 1 auto; min-width: 0; font-size: 12.5px; color: var(--color-fg-3);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ct-extract__msg { margin-top: var(--space-2); font-size: 12.5px; line-height: 1.6; color: var(--color-fg-3); }
.ct-extract__msg--ok { color: var(--color-fg-2); }
.ct-extract__msg--err { color: var(--color-danger); }
/* ⭐⭐ 2026-10-11 识别进行中的显眼提示（乐哥：「不然大家会以为没执行…提醒要显眼」）
   ▸ 观感对齐「导入订单」的解析中：浅蓝底 + 转圈 + 走动着的秒表 + 一条来回跑的进度条。
   ▸ 循环动画用**固定时长**（规范：`.spin` 0.8s linear / 循环类不用过冲曲线）。 */
.ct-extract__busy {
  display: flex; flex-direction: column; gap: var(--space-3);
  margin-top: var(--space-3); padding: var(--space-3) var(--space-4);
  background: var(--color-accent-bg); border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-md);
}
/* ⚠️⚠️ 上面设了 `display: flex` ⇒ 只靠 `hidden` 属性**藏不住**（它只是 UA 的
   `[hidden] { display: none }`，特异性比这条低）⇒ 元素会**永远显示**、没有报错。
   本节必须显式兜底（门禁 `hidden-attr-guard ①` 专锁这一类"空条子"）。 */
.ct-extract__busy[hidden] { display: none; }

/* ── 多步骤步骤条（2026-10-11 乐哥：「这个识别能做多步骤展示不，这样更直观」）────────
   横向三步：圆点（未开始=序号 / 进行中=转圈 / 完成=绿勾）+ 连线 + 标题/说明。
   ⛔ 步骤推进是按典型节奏**推演**的（接口一次性返回）；不会提前显示"完成"。 */
.ct-steps { display: flex; gap: var(--space-2); }
.ct-step { flex: 1 1 0; min-width: 0; }
.ct-step__hd { display: flex; align-items: center; gap: 6px; }
.ct-step__dot {
  flex: none; width: 20px; height: 20px; box-sizing: border-box;
  border-radius: var(--radius-pill); display: inline-flex; align-items: center; justify-content: center;
  font-size: 11px; font-weight: var(--font-weight-semibold);
  background: var(--color-surface); color: var(--color-fg-3); border: 1px solid var(--color-border);
  transition: background var(--motion-fast) var(--motion-ease-standard), color var(--motion-fast) var(--motion-ease-standard);
}
.ct-step__line { flex: 1 1 auto; height: 2px; border-radius: var(--radius-pill); background: var(--color-border); }
.ct-step:last-child .ct-step__line { display: none; }
.ct-step__txt { margin-top: 6px; }
.ct-step__txt > b { display: block; font-size: 12.5px; font-weight: var(--font-weight-semibold); color: var(--color-fg-3); }
.ct-step__txt > span { display: block; margin-top: 1px; font-size: 11.5px; color: var(--color-fg-3); }
/* 进行中：圆点转圈、标题加深 */
.ct-step.is-doing .ct-step__dot { background: var(--color-accent); border-color: var(--color-accent); }
.ct-step.is-doing .ct-step__txt > b { color: var(--color-fg); }
.ct-step__spin {
  width: 10px; height: 10px; box-sizing: border-box; display: block;
  border-radius: var(--radius-pill); border: 2px solid rgba(255, 255, 255, 0.5);
  border-top-color: #fff; animation: ct-spin 0.8s linear infinite;
}
/* 已完成：绿勾 + 绿连线 */
.ct-step.is-done .ct-step__dot { background: var(--color-success-bg); border-color: transparent; color: var(--color-success-text); }
.ct-step.is-done .ct-step__line { background: var(--color-success); }
.ct-step.is-done .ct-step__txt > b { color: var(--color-fg-2); }

.ct-extract__busy-foot { display: flex; flex-direction: column; gap: var(--space-2); }
.ct-extract__spin {
  flex: none; width: 15px; height: 15px; box-sizing: border-box;
  border-radius: var(--radius-pill);
  border: 2px solid var(--color-border); border-top-color: var(--color-accent);
  animation: ct-spin 0.8s linear infinite;
}
/* 按钮里的转圈：白圈压在主色按钮上 */
.ct-extract__spin--btn { width: 12px; height: 12px; border-color: rgba(255, 255, 255, 0.45);
  border-top-color: #fff; display: inline-block; vertical-align: -1px; margin-right: 6px; }
.ct-extract__busy-txt { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.ct-extract__busy-t { font-size: 13px; font-weight: 600; color: var(--color-fg); }
.ct-extract__busy-sub { font-size: 12.5px; color: var(--color-fg-2); }
/* 无限进度条：一条浅槽 + 一段来回跑的主色（`transform` 不动布局） */
.ct-extract__busy-bar {
  flex: 1 0 100%; height: 3px; border-radius: var(--radius-pill);
  background: var(--color-border-soft); overflow: hidden; position: relative; display: block;
}
.ct-extract__busy-bar > i {
  position: absolute; top: 0; bottom: 0; width: 38%;
  border-radius: var(--radius-pill); background: var(--color-accent); display: block;
  animation: ct-busy-slide 1.15s var(--motion-ease-standard) infinite;
}
@keyframes ct-spin { to { transform: rotate(360deg); } }
@keyframes ct-busy-slide { 0% { transform: translateX(-100%); } 100% { transform: translateX(263%); } }
/* 识别中：选件区与按钮行锁住（连点没意义，也让人看懂"现在该等"） */
.ct-extract.is-busy .ct-extract__pick,
.ct-extract.is-busy .ct-extract__row { opacity: 0.5; pointer-events: none; }
/* 结果区骨架屏：全站 `.skeleton`（shimmer 1.4s）+ 这里只负责行距 */
.ct-extract__skel { display: flex; flex-direction: column; gap: var(--space-3); padding: var(--space-1) 0; }

/* 窄屏：上传与链接**竖排**（同一行挤两个控件在手机上二者都不可用） */
@media (max-width: 768px) {
  .ct-extract__pick { flex-direction: column; align-items: stretch; }
  .ct-extract__or { text-align: center; }
  .ct-extract__drop, .ct-extract__link { flex: 1 1 auto; width: 100%; }
}

/* ⭐ 2026-10-11 第 12 批：识别结果预览 + 条款详情（三个使用方共用，见 contract-extract-ui.js） */
.ct-extract__hint { margin-top: var(--space-2); font-size: 12.5px; line-height: 1.65; color: var(--color-fg-3); }
.ct-extract__result { margin-top: var(--space-3); border-top: 1px solid var(--color-border-soft); padding-top: var(--space-3); }

.ct-res__hd { display: flex; align-items: center; gap: var(--space-2); margin-bottom: var(--space-2); }
.ct-res__tag { font-size: 11px; padding: 1px 8px; border-radius: var(--radius-pill); }
.ct-res__tag--ai { background: var(--color-success-bg); color: var(--color-success-text); }
.ct-res__tag--rule { background: var(--color-warning-bg); color: var(--color-warning-text); }
.ct-res__meta { font-size: 11.5px; color: var(--color-fg-3); }
/* 各段真实耗时（对应进行中那三步）——推到最右，不与"页数/扫描件"挤在一起 */
.ct-res__timing { margin-left: auto; font-size: 11.5px; color: var(--color-fg-3); }
/* 多份一起识别：先说清"读了哪几份"（补协议最容易漏，得让人一眼确认） */
.ct-res__src { margin-bottom: var(--space-2); padding: var(--space-2) var(--space-3);
  background: var(--color-accent-bg); border-radius: var(--radius-md);
  font-size: 12.5px; line-height: 1.6; color: var(--color-fg-2); }
.ct-res__sum { font-size: 13px; line-height: 1.7; color: var(--color-fg); background: var(--color-surface); border-radius: var(--radius-md); padding: var(--space-2) var(--space-3); margin-bottom: var(--space-3); }

.ct-res__block { border-radius: var(--radius-md); padding: var(--space-2) var(--space-3); margin-bottom: var(--space-3); font-size: 12.5px; }
.ct-res__block ul { margin: 0; padding-left: 16px; }
.ct-res__block li { margin: 3px 0; line-height: 1.6; }
.ct-res__block-hd { display: flex; align-items: center; gap: 5px; font-weight: 500; margin-bottom: 4px; }
.ct-res__block--dev { background: var(--color-warning-bg); color: var(--color-warning-text); }
.ct-res__block--warn { background: var(--color-info-bg); color: var(--color-info); }

.ct-res__grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: var(--space-2); margin-bottom: var(--space-3); }
.ct-res__item { background: var(--color-surface); border: 1px solid var(--color-border-soft); border-radius: var(--radius-md); padding: var(--space-2) var(--space-3); min-width: 0; }
.ct-res__item--full { grid-column: 1 / -1; }
.ct-res__label { font-size: 11.5px; color: var(--color-fg-3); margin-bottom: 3px; }
.ct-res__val { font-size: 13px; line-height: 1.65; color: var(--color-fg); word-break: break-word; }
.ct-res__ev { margin-top: 5px; font-size: 11.5px; line-height: 1.5; color: var(--color-fg-3); border-left: 2px solid var(--color-border-soft); padding-left: 8px; }
.ct-res__more { font-size: 12px; color: var(--color-fg-3); margin-bottom: var(--space-3); }
.ct-res__actions { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; }
.ct-res__actions-hint { font-size: 12px; color: var(--color-fg-3); }

/* ── 合同条款富文本（`ContractExtractUI.remarkHtml` 产出）────────────────────
   ⭐ 2026-10-11（乐哥：「得用富文本，加粗的加粗，加字号的加字号，加分割线的加
      分割线，改颜色的改颜色」）：改前是全篇 13px 一个灰 —— 十几条一样重。
   ⛔ 颜色只用令牌，且**只有三个色相各司其职**：暖色=风险（罚钱/解约）、
      主色=钱与结算、中性=其余。再多加一色就变成"满屏色块 = 没有重点"。 */
.ct-md__blk + .ct-md__blk { margin-top: var(--space-3); padding-top: var(--space-3);
  border-top: 1px solid var(--color-border-soft); }
.ct-md__h { font-size: var(--font-size-sm); font-weight: var(--font-weight-semibold);
  color: var(--color-fg); margin-bottom: var(--space-2); }
.ct-md__p { margin: 0; font-size: var(--font-size-sm); line-height: 1.8; color: var(--color-fg-2); }
.ct-md__p + .ct-md__ul { margin-top: var(--space-1); }
.ct-md__ul { margin: 0; padding: 0; list-style: none; }
.ct-md__ul > li { position: relative; padding-left: var(--space-4);
  font-size: var(--font-size-sm); line-height: 1.8; color: var(--color-fg-2); }
.ct-md__ul > li + li { margin-top: 3px; }
.ct-md__ul > li::before { content: ''; position: absolute; left: 5px; top: 10px;
  width: 4px; height: 4px; border-radius: var(--radius-pill); background: var(--color-gray-400); }
/* 关键数值 / `**加粗**`：加粗 + 近黑 ⇒ 在灰正文里一眼跳出来 */
.ct-md__n, .ct-md__b { font-weight: var(--font-weight-semibold); color: var(--color-fg); }
/* 风险块的数值转红（只有"罚钱/赔钱"的数才配红） */
.ct-md__n--risk { color: var(--color-danger); }

/* 摘要 = 引文块（大一号字 + 浅蓝底 + 左蓝条）——"这份合同是什么"一眼可读 */
.ct-md__blk--sum { background: var(--color-accent-bg); border-left: 3px solid var(--color-accent);
  border-radius: var(--radius-md); padding: var(--space-3) var(--space-4); }
.ct-md__blk--sum .ct-md__h { font-size: var(--font-size-xs); font-weight: var(--font-weight-medium);
  color: var(--color-accent-text); letter-spacing: .5px; margin-bottom: var(--space-1); }
.ct-md__blk--sum .ct-md__p { font-size: var(--font-size-lg); font-weight: var(--font-weight-medium);
  line-height: 1.7; color: var(--color-fg); }

/* 风险块（违约/赔偿/解除/终止）：暖色标题 + 警示圆点。
   ⛔ 不给整块底色 —— 实测有三块连排，全上底色反而糊成一片。 */
.ct-md__blk--risk .ct-md__h { color: var(--color-warning-text); }
.ct-md__blk--risk .ct-md__ul > li::before { background: var(--color-warning); }

/* 钱与结算（佣金/结算/保底/发票/税/价保/投流）：主色标题 —— 乐哥点名的核心信息 */
.ct-md__blk--money .ct-md__h { color: var(--color-accent-text); }
.ct-md__blk--money .ct-md__ul > li::before { background: var(--color-accent); }

/* 【说明】（模型没读到、由规则补上、需重点核对）→ 提示条 */
.ct-md__blk--note { background: var(--color-info-bg); border-radius: var(--radius-md);
  padding: var(--space-2) var(--space-3); }
.ct-md__blk--note .ct-md__h, .ct-md__blk--note .ct-md__p { color: var(--color-info); }

/* ⚠️ 与标准不一致（新版已不再产出，存量数据里还有） */
.ct-md__blk--warn { background: var(--color-warning-bg); border-radius: var(--radius-md);
  padding: var(--space-2) var(--space-3); }
.ct-md__blk--warn .ct-md__h, .ct-md__blk--warn .ct-md__p { color: var(--color-warning-text); }

/* 其他条款：次要信息，弱化（不抢眼，但仍可读） */
.ct-md__blk--minor .ct-md__h { color: var(--color-fg-3); font-weight: var(--font-weight-medium); }
.ct-md__blk--minor .ct-md__p { color: var(--color-fg-3); }
.ct-remark__empty { font-size: var(--font-size-sm); color: var(--color-fg-3); }

/* ── 合同条款详情抽屉 ── */
.ct-detail__grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: var(--space-2); margin-bottom: var(--space-4); }
.ct-detail__item { background: var(--color-surface-2); border-radius: var(--radius-md); padding: var(--space-2) var(--space-3); }
.ct-detail__label { font-size: 11.5px; color: var(--color-fg-3); margin-bottom: 3px; }
.ct-detail__val { font-size: 13px; line-height: 1.6; color: var(--color-fg); word-break: break-word; }
.ct-detail__section { margin-bottom: var(--space-4); }
.ct-detail__section-hd { font-size: 13px; font-weight: 500; color: var(--color-fg-2); margin-bottom: var(--space-3);
  padding-bottom: 6px; border-bottom: 1px solid var(--color-border-soft); }
.ct-detail__link { font-size: 13px; }

/* ══ 地点下拉浮层（combobox，2026-10-22）═════════════════════════════════════
   日历视图「编辑环节」的地点字段。原来用 <input list=datalist>，但本文件上方把
   `::-webkit-calendar-picker-indicator` 设成 display:none（那才是能唤起候选列表的原生按钮），
   右侧 ⌄ 只是背景图 ⇒ 点箭头没反应（乐哥报的 bug）。
   ⇒ 自带按钮 + 浮层，跨浏览器行为确定；仍保留输入框手输。 */
.combo-wrap { position: relative; }
.combo-wrap .input { padding-right: 30px; }
.combo-btn {
  position: absolute; right: 1px; top: 1px; bottom: 1px; width: 28px;
  display: flex; align-items: center; justify-content: center;
  border: 0; background: transparent; cursor: pointer;
  color: var(--color-muted); border-radius: 0 6px 6px 0;
}
.combo-btn:hover { color: var(--color-fg-2); background: var(--color-accent-bg); }
.combo-list {
  position: absolute; left: 0; right: 0; top: calc(100% + 4px); z-index: 60;
  max-height: 216px; overflow: auto;
  background: var(--color-content-bg);
  border: 1px solid var(--color-border);
  border-radius: 8px;
  box-shadow: 0 8px 24px rgba(16, 24, 40, .12);
  padding: 4px;
}
.combo-item {
  padding: 7px 10px; border-radius: 6px; cursor: pointer;
  font-size: var(--font-size-sm); color: var(--color-fg);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.combo-item:hover { background: var(--color-accent-bg); }
.combo-item.is-on { background: var(--color-accent-bg); color: var(--color-accent); font-weight: 600; }
.combo-empty { padding: 10px; font-size: var(--font-size-xs); color: var(--color-muted); }


/* ==== /css/table.css ==== */
/* ==========================================================================
   达播工作台 — table.css
   表格 / 单元格 / 骨架屏 / 空态·错误态 / 分页
   ========================================================================== */

/* ---- 表格 ---- */
.table-wrap {
  overflow-x: auto;
}

.table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--font-size-sm);
}

/* ---- 表格内空行 / 列表容器内空态（2026-09-29 立）----
   ⭐ 为什么必须立这条：本项目有两种"没数据"的写法，**都长期没有样式**——
      ① 表格内空行：`<tbody>' + (rows || '<tr><td colspan="6" class="…">暂无记录</td></tr>')`
      ② 容器内空态：`wrap.innerHTML = '<div class="…">加载中…</div>'`
   实测（2026-09-29，人事行政板块）：它们写的是 `.tbl-empty`，而这个类名在
   **全部 15 个 CSS 文件里 0 处定义** ⇒ 渲染成"裸文字、无内边距、顶在表格左上角"，
   与有数据时的表格观感完全脱节（乐哥：「表格不对」）。

   ⚠️ 与 `.state` 的分工（别混用）：
      · `.state`（本文件下方）= **整页/整卡级别**的空态 —— 48px 图标块 + 大标题
        + 描述 + CTA 按钮，用于"这个列表整个是空的，请去做点什么"。
      · `.table-empty`（本条）= **表格里的一行 / 容器里的一行字** —— 轻量、纯文字、
        不抢戏，用于"表格正在加载""刚好没有匹配记录"这类**过程性**提示。
      把 `.state` 塞进 `<td>` 会让表格里长出一个 48px 图标块，行高爆炸。

   ⚠️ 两条选择器都要写：
      · `.table-empty` 单独一条 —— 用于 `<div class="table-empty">`（容器内空态）；
      · `.table td.table-empty` —— 用于 `<td colspan="N" class="table-empty">`。
        必须带 `.table` 前缀：单元格的基础内边距是 `.table tbody td`（特异性 0,1,2），
        光写 `.table-empty`（0,1,0）**压不住它**，padding 会被覆盖掉（实测过）。 */
.table-empty {
  padding: var(--space-8) var(--space-4);
  text-align: center;
  color: var(--color-muted);
  font-size: var(--font-size-sm);
  line-height: var(--font-line-height-body);
}
.table td.table-empty {
  padding: var(--space-8) var(--space-4);
  text-align: center;
  color: var(--color-muted);
}

/* 首 / 末列左右留白对齐卡片内的工具栏（2026-09-14 乐哥反馈「列表文字紧贴边框」）
   ⚠️ 数值依据（真实渲染实测）：`.table-toolbar` 水平 padding = --space-5(20px)，
      而 `.table` 单元格 = --space-3(12px) → **表格首列文字比工具栏标题左移 8px**，
      视觉上就像贴住卡片左边框（实测：工具栏文字距卡片左 21px、表格首列文字仅 13px）。
      这里把首/末列补齐到同一 20px，让「工具栏标题 ↔ 表格首列」「表格末列 ↔ 工具栏右侧」对齐。

   ⚠️ 2026-09-14 二次修正 —— 排除条件从 `.table--sub` 换成**真实结构判据**：
      上一版写 `.table:not(.table--sub)`，本意是排除"嵌在父表单元格里的子表"，
      但 `table--sub` 在本项目里**语义已漂移** —— 它既用在真嵌套子表上，也用在
      **面板里的顶层表**上（结算明细的「本期佣金构成」「业绩构成」两张就是）。
      结果这两张顶层表被误排除、首末列仍是 12px，而同层级的提成明细是 20px
      → 乐哥看到的「达人结算二级页和提成总览不一样」，主因就是这 8px。
      （实测：全站 18 张顶层表里只有结算明细那 2 张漏网；真嵌套子表实际 **0 张**。）
      改用结构判断：**在 `td` 里的**才是子表（跟随父表列宽，不能加内边距），
      其余顶层表一律补 20px。`:where()` 不参与优先级计算，本条权重维持不变。
   · 只改 padding 不改 width：`table-layout: fixed` 下宽度含 padding，不会撑破列宽预算。 */
.table:not(:where(td .table)) > thead > tr > th:first-child,
.table:not(:where(td .table)) > tbody > tr > td:first-child {
  padding-left: var(--space-5);
}
.table:not(:where(td .table)) > thead > tr > th:last-child,
.table:not(:where(td .table)) > tbody > tr > td:last-child {
  padding-right: var(--space-5);
}

.table thead th {
  background: var(--table-header-bg);
  color: var(--table-header-fg);
  font-weight: var(--font-weight-medium);
  font-size: var(--font-size-xs);
  letter-spacing: 0.01em;
  /* 全列居中（乐哥 2026-09-14 定稿，方案B）：原为左对齐。
     配套改动见本文件「全列居中」区块（.is-num 同步改居中 + flex 子容器补偿）。 */
  text-align: center;
  height: 44px;
  padding: 0 var(--space-3);
  /* ⭐ 2026-09-25 Gashora 骨架落地：表头分隔线 --color-border(#E1E8F2，**带蓝调**) → #EFEFEF
     （中性浅灰，参考图实测值）。蓝调是与整体中性灰白不协调的来源（乐哥：「表格的线不对」）。 */
  border-bottom: 1px solid var(--table-header-line);
  white-space: nowrap;
}

.table thead th.is-num {
  /* ⭐ 2026-10-15 乐哥拍板（结算明细表格优化，方案 B）：**表头也靠右** ——
     整列一条竖轴（账表式），表头贴住数字、与正文末位同轴。
     历史：09-15「数字靠右，其他所有的位置都不变」时表头**保持居中**；本轮他看了
     真实 DOM 对比图（deliverables/表格优化方案/）后明确选 B 翻掉这一条。
     ⚠️ 回退：把 right 改回 center 即可，只此一处。 */
  text-align: right;
}

/* 表头排序（库存列表等）：所有可排列表头常驻箭头指示，hover/激活高亮 */
.table thead th.sortable {
  cursor: pointer;
  user-select: none;
}
.table thead th.sortable:hover {
  color: var(--color-accent-text);
}
.table thead th.sortable:hover .th-sort-ic {
  color: var(--color-accent-text);
}
.table thead th.is-sorted {
  color: var(--color-accent-text);
}
.table thead th .th-sort-ic {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  vertical-align: middle;
  margin-left: 4px;
  color: var(--color-muted);
  line-height: 0;
}
.table thead th .th-sort-ic svg {
  display: block;
}
.table thead th .th-sort-ic svg + svg {
  margin-top: -4px;
}
.table thead th .th-sort-ic--on {
  color: var(--color-accent-text);
}

.table tbody td {
  padding: var(--space-3);
  border-bottom: 1px solid var(--table-divider);
  color: var(--color-fg);
  vertical-align: middle;
}

.table tbody tr:last-child td {
  border-bottom: none;
}

/* ⛔⛔ 2026-10-18 去掉了这一行的渐入过渡（原来是 `var(--motion-fast)` = 110ms）——
   乐哥实报：「选中的那个蓝色条子慢半拍，和其他行的显示不一致不同频」。
   根因：行 hover 是**跟随鼠标**的即时反馈，110ms 的渐入让人眼看到的是"蓝条追着光标走"，
   鼠标越往下扫越明显（整行 19 列一起变，重绘本来就慢半拍，再叠加渐变就更明显）。
   ⇒ 行选中/hover **必须瞬时**（`transition: none` 而非 0s：语义上就是"不参与过渡"）。
   ⚠️ 全站表格共用这条 ⇒ 所有表格一起变，反馈节奏一致（这是好事，别只改一页）。
   ⛔ 别再给 `tr` 加 background 过渡；「活泼」那档动效请用在按钮/卡片/浮层上。 */
.table tbody tr {
  transition: none;
}

.table tbody tr:hover {
  background: var(--table-row-hover);
}

/* ==========================================================================
   隔行底色（斑马纹）· **已按乐哥要求去掉**（2026-09-25）
   --------------------------------------------------------------------------
   ▸ 历史：2026-09-15 乐哥要求加（「这些表格为了看的清楚，错行用很浅的背景色做区分」），
     当时定为**全站默认生效**（48 张表统一，避免"8 张有、40 张没有"的读法不一致）。
   ▸ 2026-09-25 乐哥改主意：「表格斑马纹，我觉得是不是可以不要」+「我的报销页也有斑马纹」
     ⇒ 本次**全线去掉**，不止这一处（全站共 4 处实现，都已处理）：
        ① 本文件       `.table tbody tr:where(:nth-child(even))`   ← 全站默认（主战场）
        ② `pages.css`  `.table--zebra tbody tr:nth-child(even)`   ← 售后分析的历史写法
        ③ `pages.css`  `.pmd-table tbody tr.pmd-row.is-alt`       ← 利润月明细（自己按主行序号加类）
        ④ `pricing-compare.css` `.pc-table tbody tr:nth-child(even)` ← 机制比价（自己实现）

   ▸ 去掉斑马纹后，行与行的区分靠什么（**不能什么都不靠**，否则横排数字会串行）：
       · **行分隔线**：`.table tbody td` 自带的 `border-bottom`（本文件上方定义）——
         淡到几乎看不见但足以引导视线，这是当前的主要手段
       · **hover 高亮**：`.table tbody tr:hover`（下方定义），鼠标所在行整行变色 ⇒
         读长行数字时"当前行是哪一行"始终有反馈
       · **特殊行底色**：合计行 / 主指标行仍有自己的底色
         （`.an-pnl-total`、`.so-subtotal`、`.af-audit__main`、`.af-bd__total` 等，各自独立定义，
          **不依赖本段规则**，所以本次删除不会让它们掉色）
     ⚠️ 如果乐哥以后反馈"看数字串行"，优先**加深行分隔线**（改 `--color-border-soft`），
        而不是加回斑马纹 —— 分隔线更"简约"，且不会在展开子表/隐藏行上错位。

   ⚠️ 以下原注释保留，说明**当初为什么用 `:where()`**（万一以后要恢复斑马纹，直接抄这段）：
       「必须把斑马纹的特异性压到最低，否则会盖掉页面自己的行样式。
         用 `:where()` 把伪类包裹 —— `:where()` 内选择器特异性记 0：
           `.table tbody tr:where(:nth-child(even))` → (0,1,2)
         而各页面/子表的行规则普遍是 (0,2,2) 及以上，自动胜出，例如：
           `.table tbody tr:hover` (0,2,2)、`.an-pnl-table tbody tr.an-pnl-total` (0,2,2)、
           `.table--sub tbody tr.so-subtotal` (0,2,2)、`.af-audit__main` / `.af-bd__total` (0,2,2)。
         ⇒ 不需要维护"特殊行清单"、也不依赖 CSS 加载顺序。」
   ⚠️ 展开子表（`.table--sub`，全站 16 张）当初就**不加**斑马纹：它嵌在父表展开行里、
      通常只有 2~4 行，再加纹会与父表行底/展开区底叠成"三层底色"。
      现在全站都没有斑马纹了，这条差异自动消失。
   ========================================================================== */
/* ⛔ 2026-09-25 起这两条规则已删除（保留位置与说明，方便回溯）：
     .table tbody tr:where(:nth-child(even)) { background: var(--table-row-alt); }
     .table--sub tbody tr:where(:nth-child(even)) { background: transparent; }
   恢复方式：取消上面两行注释即可（`--table-row-alt` 令牌仍在 design-tokens.css 里，未删除）。 */

.table .is-num {
  font-feature-settings: "tnum", "lnum";
  font-variant-numeric: tabular-nums;
  font-weight: var(--font-weight-medium);
}
/* ══ 数值列靠右（乐哥 2026-09-15 定稿：数字靠右，其他位置一律不变）══════════════
   原话：「列表的文字，看下要不要数据，就是数字靠右，其他所有的位置都不变，和之前一样」

   背景：2026-09-14 曾把数字从右改**居中**（与表头共一条中线）。改回右对齐的实测理由：
     金额列居中后，同列里「¥6.9万 / ¥4,816.00 / ¥125.00」位数参差、看不出列边界；
     靠右后末位对齐，对账时逐位可比 —— 那是本系统最高频的用法。

   ⚠️⚠️ 下面这串选择器**不能简化**（这是本次最容易白改的一处）：
     本文件上方「全列居中」区块有一条
        `.table:not(:where(td .table)) > tbody > tr > td { text-align: center; }`
     特异性 (0,1,3)；而原来的 `.table .is-num` 只有 (0,2,0)，**压不过它**。
     ⇒ 只把 `center` 改成 `right` 会**静默不生效**（页面看起来"改了没反应"）。
     故显式写到 `tbody td` 层级拿 (0,2,2)，稳赢 (0,1,3)。
     （`.table--sub` 子表一并覆盖：它嵌在 td 里，会**继承**父 td 的居中，
       不显式声明就跟着居中 —— 全站 16 张子表。）

   ⚠️ 表头对齐：09-15「其他所有的位置都不变」时保持居中；**2026-10-15 乐哥拍板（方案 B）
      已改为跟着靠右**（整列一条竖轴）—— 见上方 `thead th.is-num` 块的注释与对比图。
      若想退回"表头居中"，把那块改回 center 即可，只此一处。 */
.table tbody td.is-num,
.table tfoot td.is-num,
.table--sub tbody td.is-num,
.table--sub tfoot td.is-num {
  text-align: right;
}

/* ══ 表格列对齐规范（乐哥 2026-09-14 定稿）═════════════════════════════════════
   两条规则，一句话可背：
     ① **长文本列靠左** —— 货品名、达人名、套餐名、备注这种"长短不一的一句话"
     ② **其他全部居中** —— 表头 + 数字列 + 短文本列（状态/类型/编号/时间/人员/操作…）
   为什么这样分（可验证的理由，不是审美）：
     · 长文本靠左：长短不一的文本若居中，每行起点参差、扫读更难；左对齐有固定起始线。
     · 其余居中：这些列内容短且长度接近，居中后两侧留白均等；表头也居中 →
       表头与正文共用同一条中线，整列成一条竖轴 —— 比"贴左"和"贴右"都整齐。

   ▸ 实现分工（**默认居中 + 例外类**，这样新增列不用做任何事就是对的）：
     · 默认居中写在 `th` / `td` 层（下面 .table tbody td / tfoot td / thead th）
     · "长文本列"由 JS 在渲染后打 `.col-align-left`（判定逻辑见 app.js 的 `fixColAlign()`）
       为什么用 JS 判定而不是 CSS：CSS 无法知道某列内容是"一句话"还是"一个词"，
       只能靠**实测内容**判定；用 JS 判定还能自动覆盖将来新增的页面，不用逐页配。
   ▸ 与上一版（2026-09-14 上午「文字左 + 数字右」）的差异：
     数字从**右**改**居中**、表头从**左**改**居中**、短文本列从**左**改**居中**；
     仅"长文本列"维持靠左。
   ▸ 2026-09-15 微调（乐哥定稿）：**数字改回靠右**，其余（表头、文本列）一律不动。
     即最终形态 = 长文本左 / 其余居中 / **数值列靠右**。
   ⚠️ 回退方式：删掉 `.col-align-left` 的规则即可回到"全居中"；
      再把 `.is-num`/`.num` 那两块改回 `center` 即回到 9-14 版本。 */
.table:not(:where(td .table)) > thead > tr > th,
.table:not(:where(td .table)) > tbody > tr > td,
.table:not(:where(td .table)) > tfoot > tr > td {
  text-align: center;
}
/* 长文本列（JS 判定后打类）→ 靠左。子元素（.cell-main/.cell-sub 等）自动继承。 */
.table .col-align-left,
.table--sub .col-align-left {
  text-align: left;
}

/* ② 数字靠右：`.num` 是同义的另一种写法（base.css 里定义的等宽数字类），
   表格内必须与 `.is-num` **同规则**，否则会出现"同一列有的格靠右、有的格居中"
   （实测提成总览有 2 处只写了 `.num`）。
   特异性要求与 `.is-num` 完全相同，理由见上方那块的注释。 */
.table tbody td.num,
.table tfoot td.num,
.table--sub tbody td.num,
.table--sub tfoot td.num {
  text-align: right;
}

/* 盈亏正负颜色：盈利=红，亏损=绿（中式股市惯例，涨红跌绿） */
/* ⚠️ 盈亏正负色（**全站约定：盈利=红、亏损=绿**，国内红涨绿跌习惯）
   特异性必须 ≥ 0,2,1 —— 因为上面有 `.table tbody td { color: var(--color-fg) }`（0,2,1）。
   2026-09-13 修正：原来写成单类 `.num-pos`（0,1,0）**被它压过**，
   导致**全站表格里的盈亏正负色从来没生效过**（KPI 卡里的生效，表格里的不生效 —— 两处表现不一致）。
   故加上 `.table tbody td` 前缀，并同时覆盖 `.table--sub`。 */
.table tbody td.num-pos,
.table--sub tbody td.num-pos,
.num-pos { color: var(--color-danger); }
.table tbody td.num-neg,
.table--sub tbody td.num-neg,
.num-neg { color: var(--color-success); }

/* 金额列：**红色加粗**（乐哥 2026-10-10：「金额什么的我记得之前说过，红色加粗显示」——
   当时说的是「报销单详情」那一屏，但这是"结论性金额"的通用语言，故与 `.num-pos` 并列放这里，
   将来别的表要用直接加类，不要再各页写一份）。
   ⚠️⚠️ 特异性必须与上面 `.num-pos` 那条**完全同样**（≥ 0,2,1）——
      否则会被 `.table tbody td { color: var(--color-fg) }`（0,2,1）压过，
      变成"规则在、但颜色不变"的**死规则**：`.num-pos` 就因此在全站表格里失效过整整一轮
      （见它上方那段说明）。本批第一版也原样踩了一次（量到明细列还是 rgb(28,30,33)）。 */
.table tbody td.num-price,
.table tfoot td.num-price,
.table--sub tbody td.num-price,
.table--sub tfoot td.num-price {
  color: var(--color-price);
  font-weight: var(--font-weight-semibold);
}

.cell-sub {
  display: block;
  margin-top: 2px;
  font-size: var(--font-size-xs);
  color: var(--color-muted);
}

/* ⭐⭐ 2026-10-10 「单元格里两个值 → 上下两行」（乐哥：「费用类型按**两行**展示，
   行程说明还有发生日期也是，**全局都这样改**」）
   ──────────────────────────────────────────────────────────────────────────
   ▸ 由各页面的 `cellLines([a, b])` 产出；**只有一个值时它不产这个类**
     （直接给裸值）⇒ 单值的外观与改造前逐字一致。
   ▸ `line-height: 1.35`：两行叠起来不能把表格行撑成原来的两倍高。
   ▸ `white-space: nowrap`：单个值内部不再折行（"2026-10-06" 被折成两截最难看）。 */
.cell-line { display: block; line-height: 1.35; white-space: nowrap; }

/* ⭐⭐ 2026-10-15 两个**新**类（都只做加法，⛔ 不动 `.cell-sub` / `.cell-line` 的既有行为） */
/* `.cell-hint` = **行内**的灰色小标签（要跟在日期/地点**同一行**前面）。
   ⛔⛔ 别拿 `.cell-sub` 干这事 —— 它是 `display:block` 的「格内第二行」，当行内用会在标签后
      强制换行：`<span class="cell-sub">起</span> 2026-10-12` 会渲染成「起」/「2026-10-12」
      两行，把整格撑成 4 行（2026-10-15 实测踩到，截图在 deliverables/item-cells-table.png）。 */
.cell-hint { font-size: var(--font-size-xs); color: var(--color-muted); }
/* `.cell-line--wrap` = 允许**这一行内部**折行（覆盖 `.cell-line` 的 `nowrap`）。
   用途：明细的「行程」行 —— 出发地/目的地可能很长（`乌鲁木齐… → 深圳…`），
   而 `.cell-line` 的 `nowrap` 是给"2026-10-06 不能被折成两截"定的 ⇒ 两者都要，
   所以用**双类覆盖**给这一行单独放开，⛔ 不去改 `.cell-line` 本身（全站很多格在用）。 */
.cell-line.cell-line--wrap { white-space: normal; }

/* ⚠️ 兜底：`td` 上误用 `.cell-sub`（2026-09-15 乐哥报「说明和分摊没有横线和斑马纹」，第 4 次踩到）
   ----------------------------------------------------------------------------
   `.cell-sub` 是「**格内第二行小字**」用的类，带 `display:block`。挂到 **td 自身**时，
   该格会**脱离表格单元格渲染** —— 浏览器为它包一层匿名单元格，后果：
     · 行高按内容算、不再撑满整行 → 横线（td 的 border-bottom）画在内容框底边，
       视觉上"断"在行的中间，看起来就是**没有横线**
     · 斑马纹设在 tr 上，而匿名框与 td 自身背景都透明 → 斑马纹在**这一列断开**
   项目此前已在 schedule-detail ×2 / fan-experience 踩过并逐个改成 `<span class="cell-sub">`；
   本次 profit-ledger 逐笔明细与 profit-settlement 校准弹窗又各出现一次，故加此兜底防复发。
   **正确写法**：`<td><span class="cell-sub">…</span></td>`（小字类只包内容，不挂单元格）。
   ⚠️ 只覆盖 display，font-size / color 仍沿用上方 `.cell-sub`（保留"小字"本意）。 */
.table td.cell-sub,
.table--sub td.cell-sub {
  display: table-cell;
}

/* 单元格内多信息 */
.cell-main {
  font-weight: var(--font-weight-medium);
  color: var(--color-fg);
}

/* 表格内头像 + 文本 */
.cell-person {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-width: 0;
}

/* 人员列：「名字 + 岗位」同一行（2026-09-14 乐哥：「岗位和名字一行，不要分行」）
   ----------------------------------------------------------------------------
   改前：`<div>名字</div><div>岗位徽章</div>` 上下两行 → 行高被撑起来、扫读时要分两段看。
   改后：`.cell-person__line` 一个 flex 行内放名字与岗位徽章，超宽时整行换行（不会把名字截断）。
   ⚠️ 必须把 `.cm-pitem__roles` 的 `margin-top: 3px` 归零 —— 那个边距是给左栏人员列表
      （上下堆叠布局）用的，在表格行内会让徽章比文字低 3px、看着没对齐。 */
.cell-person__body {
  flex: 1;
  min-width: 0;
}
.cell-person__line {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2);
}
.cell-person__line .cm-pitem__roles {
  margin-top: 0;
}

/* 费用列表人员列：名字完整展示、不换行、不截断（区别于达人列表的省略号） */
.cell-person--full .cell-person__name {
  white-space: nowrap;
  overflow: visible;
  text-overflow: clip;
}

.cell-person__avatar {
  width: 28px;
  height: 28px;
  border-radius: var(--radius-pill);
  background: var(--color-primary-100);
  color: var(--color-accent-text);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-semibold);
  flex-shrink: 0;
  overflow: hidden;
}

.cell-person__avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.cell-person__meta {
  min-width: 0;
}

.cell-person__name {
  font-weight: var(--font-weight-medium);
  color: var(--color-fg);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* 达人姓名下的平台/机构标签行 */
.cell-person__tags {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-top: 3px;
}

/* 达人姓名下的所属商务小字 */
.cell-person__sub {
  margin-top: 2px;
  font-size: var(--font-size-xs);
  color: var(--color-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* 达人一行展示（无头像）：姓名 + 平台/机构/类型标签 + 所属商务，默认单行，空间不足自动换行（不裁切） */
.cell-person-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  min-width: 0;
}
.cell-person-row__name {
  font-weight: var(--font-weight-medium);
  color: var(--color-fg);
  white-space: nowrap;
}
.cell-person-row__bd {
  font-size: var(--font-size-xs);
  color: var(--color-muted);
  white-space: nowrap;
}

/* ⚠️ 带视图 tab 的卡片：顶部补回呼吸空间（2026-09-14 实测修正）
   ----------------------------------------------------------------------------
   合并成一张卡时，为消除"卡外空容器"的灰缝，卡片被设了 `margin-top: -16px`。
   tab 行搬进卡内顶部后会被这个负 margin 顶到筛选行下面（实测重叠 -16px）。
   这里用 **padding-top** 补回来并多留 12px 呼吸（16 抵消 + 12 = 28px）。
   ⚠️ 必须用 padding：合并后卡片 `border-top: 0`，子元素的 margin-top 会穿透到卡片外
      （margin collapse），改不成效果。 */
.toolbar--join-target.card--has-tabs { padding-top: 28px; }

/* ── 表格「视图 tab」：下划线式（2026-09-14 乐哥：「tab 选择和其他工具栏混在一起了」）
   ════════════════════════════════════════════════════════════════════════════
   背景：表格视图切换（全部 88 / 待合作 47 / 已结束 24…）原本用的是**分段按钮**样式
     （`.pill-filter` / `.seg`：白底带框、选中实底块）。
     把它和「搜索框 / 下拉筛选」并到一排后，乐哥指出问题：
       「tab 选择和其他工具栏**混在一起**了」—— 对，因为两者都是"带框的控件"，
       读者分不清哪个是"切换视图"、哪个是"筛选条件"。

   解法：把视图切换改成**下划线式 tab**，并让它**独立成一行、紧贴表格**。
     · 视觉上与"带框下拉"天然区分（一排文字 + 底部指示条 vs 带边框的下拉）
     · 位置紧贴表格 = 语义上属"表格的一部分"（它切换的就是表格显示什么）
     · **完全复用**项目已有的 `.tabs` / `.tab` 语言（pages-influencer.css:321）：
       40px 高、24px 项间距、2px 主色指示条、未选中用 --color-muted
       → 不新造视觉语言，与订单明细页的「直播业绩/私域业绩/订单明细」同一套观感

   ⚠️ 覆盖的是 `.pill-filter__item` / `.seg__btn` 两个内部类（分段控件两套类名并存，
      本项目"同一件事两套名字"的老问题）—— 两个都写，否则有一半页面改不动。
   ⚠️ 必须把 .seg 外容器的白底/边框/内边距也清掉，否则下划线 tab 会被套在白色胶囊里。 */
.table-tabs {
  display: flex;
  align-items: center;
  flex-wrap: wrap;                  /* 极窄屏时工具组可折行，不挤压 TAB */
  gap: var(--space-6);              /* 与 .tabs 一致的 24px 项间距 */
  margin: 0;
  padding: 0 var(--space-5);        /* 与表格 / .table-toolbar 同左边界（20px） */
  border: 0;
  border-bottom: 1px solid var(--color-border-soft);   /* 与 .tabs 相同的基线 */
  border-radius: 0;
  background: transparent;
  /* ⭐⭐ 2026-10-08 14:1x **定位上下文**：这一族现在也有 `.tabs__ink`（滑动选中块）了，
     ink 是绝对定位 ⇒ 宿主必须定位，否则 ink 会相对外层祖先定位跑偏
     （`.sf-tabs__track` 2026-09-26 栽过一次：白块实测高 1757px、横贯整页）。 */
  position: relative;
  /* ink 的圆角与"选中胶囊"逐值同源（in-header 族胶囊是 --radius-md(10px)；
     ink 基类的 8px 会有 2px 差，在纯色块上肉眼看得出来）。 */
  /* ⚠️ 这条写在文件后段（`.tabs__ink` 基类之后加载顺序无关，靠选择器特异性取胜）。 */
  /* ⚠️ 必须是 overflow: visible，**不能**用 overflow-x: auto（2026-09-14 实测修正）
     原因：CSS 规范规定「当一个轴不是 visible 时，另一个轴的 visible 会计算为 auto」，
     所以 `overflow-x: auto` 会让 `overflow-y` 也变成 auto；
     而选中态的下划线用了 `bottom: -1px`（为了压住容器那 1px 底边框），
     内容高度 41px > 容器 40px → **纵向溢出 1px → 右侧出现一条纵向滚动条**
     （乐哥截图里红框圈出的那条竖杠）。
     现在行内用 flex-wrap 折行，不需要横向滚动，直接 visible 最干净。 */
  overflow: visible;
}

/* ── TAB 行内的「表格工具」（多选 / 共N条 / 每页）────────────────────────────
   乐哥 2026-09-14：「多选 / 共88位达人 / 20条每页 这个是不是也应该和
                     全部88 待合作47 … 同一行，然后相同的尺寸」
   → 对。它们和 TAB 同属"表格自己的事"（视图切换 + 批量操作 + 分页统计），
     都是表格头部工具条的一部分；而"搜索/下拉"才是页面级筛选，留在第 1 排。
   本段负责：靠右 + **尺寸统一**（这是乐哥特别强调的）。

   ⚠️ 为什么要统一尺寸：搬过来之前是三套尺寸混排 ——
      多选按钮 30px、共N条 18px（字号还小一号，12px）、每页下拉 28px，
      而 TAB 是 40px / 14px。三种高度并排会显得"东倒西歪"。
      现在：字号一律 14px（--font-size-sm），控件高度一律 30px。
   ⚠️ TAB 保持 40px 高不跟着改 30px：它的 40px 是行高 + 下划线基线（同 .tab），
      工具是"控件"，两者高度不必相同，靠 align-items:center 垂直居中即视觉齐平。 */
.table-tabs > .table-toolbar__right {
  margin-left: auto;                /* 推到最右，与 TAB 分列两端 */
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex: none;
}
.table-tabs > .table-toolbar__right .btn,
.table-tabs > .table-toolbar__right .select {
  /* 34px：与筛选行的控件（`.toolbar--list` 那组）**统一为同一个高度**。
     ⚠️ 原来是 30px，但那样一页里会出现三种高度：
        筛选行控件 34px、TAB 行工具 30px、TAB 项 40px —— 看着仍然"不齐"。
        统一规格：**有框控件一律 34px**、**TAB 项一律 40px**（它是文字+下划线、无框，属"标签"）。
        这样全站只有两个数字，好记也好维护。 */
  height: var(--control-height-sm);
  min-height: var(--control-height-sm);
  font-size: var(--font-size-sm);
}
.table-tabs > .table-toolbar__right .table-toolbar__total {
  font-size: var(--font-size-sm);   /* 12px → 14px，与同行 TAB 字号一致 */
  color: var(--color-fg-2);
  white-space: nowrap;
}
.table-tabs .pill-filter__item,
.table-tabs .seg__btn {
  position: relative;
  height: 44px;                     /* 与 .tab 同高（2026-09-18：40 → 44，配合 16px 字号） */
  padding: 0 2px;
  background: transparent;
  border: 0;
  border-radius: 0;
  color: var(--color-muted);        /* 未选中（同 .tab） */
  /* ⚠️ 2026-09-18 乐哥：「字号大一点点」→ 14 → 16px（--font-size-lg）。
     ⚠️ 必须改**这里**，不能只改 `.tab` —— 本规则选择器是 `.table-tabs .seg__btn`（0,2,0），
        特异性**高于** `.tab`（0,1,0），会把 `.tab` 的 font-size 整个盖掉。
        实测踩过：只改 `.tab` → 13 个页面探测出来全是 14px，一度以为 CSS 没生效。
        （这类"改了一处被另一处盖掉"的坑，本项目已栽多次，见技能
          dabotai-ui-consistency-fix「语义色被更具体选择器盖过」一节。） */
  font-size: var(--font-size-lg);
  font-weight: var(--font-weight-medium);
  white-space: nowrap;
  transition: color var(--motion-fast) var(--motion-ease-standard);
}
.table-tabs .pill-filter__item:hover,
.table-tabs .seg__btn:hover { color: var(--color-accent-text); }

/* ⭐⭐⭐ 2026-10-08 19:xx 「非页头那族」的 tab 行统一到**工具栏标准档**
   ─────────────────────────────────────────────────────────────────────────────
   🐞 乐哥 18:50：「我的报销的这个 tab 栏，提成总览里也是一样问题，也不对…彻查下」。
   全站巡检（`scripts/frontend-debug/audit-tab-rows.js`，8 页逐页量）结论：
   **只有那两页离群** —— 它们的 tab 行是 `.pill-filter.table-tabs`（**没有 `--in-header`**）
   ⇒ 吃到了胶囊基类的那一档：
        托盘：无边框 / 内距 2px / **间距 24px**
        胶囊：**44 高 / 16px 字 / 0 圆角 / 内距 2px**
   而同在工具栏里的 `.seg.table-tabs`（费用明细 / 人员成本）与页头那族（`--in-header`）都是：
        托盘：**1px 边框 + 10px 圆角**（与「新增达人」同源）/ 内距 4px / 间距 4px
        胶囊：**34 高 / 14px 字 / 6px 圆角 / 内距 16px**
   ⇒ 以**同位置的既有标准**（费用明细，`diag-tab-spec.js` 逐项量过）为准补齐 —— 照抄，不自造数值。

   ⛔ 为什么不直接改上面那条老规则（`.table-tabs .pill-filter__item` 的 44/2px/16px）：
      它**同时**喂着 `--in-header` —— 那族的托盘内距是 3px、还有首尾 9px 同心圆
      （由 `four-fixes-1005 ③a/③a2` 锁着）⇒ 改老规则会波及页头那族。
   ✅ 只补"非页头"这一条覆盖，作用面 = 工具栏 / 卡内的 tab 行。
   ⚠️ 老规则里 44 高 / `--font-size-lg` 是 2026-09-18 乐哥当时要的「字号大一点点」，
      **本次一字未动**（页头那族照旧）；从此"工具栏里的 tab 行"一律 34 档。
   ⛔ 别把这一条并进老规则：并进去就会把页头那族一起改掉。 */
.table-tabs:not(.table-tabs--in-header) {
  gap: 4px;
  padding: 4px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
}
.table-tabs:not(.table-tabs--in-header) .pill-filter__item,
.table-tabs:not(.table-tabs--in-header) .seg__btn {
  height: 34px;
  padding: 0 16px;
  border-radius: var(--radius-seg);
  font-size: var(--font-size-sm);
}
/* ⭐⭐⭐ 2026-10-07 选中态：**主色实心胶囊**（乐哥选A3 档「Apple 的形 + 我们的品牌色」）
   ────────────────────────────────────────────────────────────────────────────────
   ▸ 改前 = **透明底 + 主色文字 + 2px 下划线**（`::after`）
     ⇒ 白卡上"透明底 + 白字/主色字"很难看出选中——真正表明"我在这一页"的只有那条下划线。
   ▸ 改后 = **实心胶囊**（`--color-accent` 底 + 白字），与参照系一致：
     · 乐哥给的 Apple 参考图：托盘浅灰 + 选中项**深墨实心胶囊**（逐像素取色 rgb(29,29,32)）
       —— 它的可读性来自"**实心色块**"，不是下划线。
     · 我们取 A3（形制照 Apple、颜色用品牌蓝）⇒ 视觉语言一致，但保留品牌识别度。
   ▸ **圆角与底色必须与「新增达人」页那张tab 逐值同源**（乐哥 2026-10-07：
     「tab 的圆角（包括底色）得和新增达人那个一致，这样才协调」）——
     实测「新增达人」（`.sf-tabs__track > .tabs__tray > .sf-tab`）那一套是：
       · 托盘：白底 + 1px `var(--color-border)`(#E1E8F2) + **圆角 --radius-md(10px)** + padding 4px
       · 胶囊：**圆角 --radius-seg(6px)** + padding `0 16px` + 高 29（= 38 − 4×2）
     ⇒ 这两个值直接用令牌、不写死数字，将来改档两处会一起动。
   ⚠️ 胶囊高 29 + 托盘 padding 4×2 = 37，与本托盘现状（44）差 7px：
     见下面 `.table-tabs--in-header` 的 padding 归零补偿注释（配平由 four-fixes ③a 锁着）。 */
/* ink（选中滑动块）的圆角：**与选中胶囊同值 6px** —— 见上方 `.table-tabs` 里的说明。
   ⭐⭐⭐ 2026-10-08 19:2x **从 10px 改回 6px**（乐哥：「蓝色胶囊下一层有个白色的透明底」）
   ──────────────────────────────────────────────────────────────────────────────
   🐞 原注释写"in-header 那族胶囊就是 10px"—— **已过时**：实测该族胶囊
      computed `border-top-left-radius` = **6px**（`--radius-seg`，见 components.css
      `.seg__btn` / `.pill-filter__item` 基类）。于是 ink(10) 比胶囊(6) 更圆 ⇒
      **四个角上 ink 边缘往里缩、多露出 4px 内边距**（角部露得比边部多），
      叠加蓝色阴影后就成了乐哥放大图里那圈"跟着胶囊圆角走的浅色底"。
   ✅ 同值后 ink 与胶囊**逐像素贴合**，四角不再多露。
   ⚠️ 别再用 `--radius-md`：那是**托盘的**圆角（10px），不是胶囊的。 */
.table-tabs > .tabs__ink { border-radius: var(--radius-seg); }

/* ⭐⭐⭐ 2026-10-08 19:2x **ink 阴影收紧到适配托盘内边距**
   ──────────────────────────────────────────────────────────────────────────────
   全局 `--shadow-pill-active` 是 `0 2px 6px`（6px 模糊）—— 那是给**主按钮**用的：
   按钮周围是白卡，6px 光晕是"浮起"。但 ink 坐在**只有 3px 内边距的托盘**里，
   6px 模糊会**糊满整圈内边距**（实测蓝块外 -0.3～-2.6px 全被染成 (195..225,210..231,240..244)
   的浅蓝白）⇒ 观感就是"蓝块垫着一圈发白的底"。
   ✅ 收紧为 `0 1px 2px`：只占贴近蓝块的 1～2px，内边距恢复干净托盘灰；
      蓝块本身是实心品牌渐变，与灰托盘对比足够强，不靠阴影也能看清选中态。
   ⚠️ 这不是"与全局不一致"—— 是**按容器适配**：主按钮的容器是白卡、ink 的容器是 3px 内边距托盘，
      同一个模糊半径在窄内边距里必然溢出。 */
.table-tabs > .tabs__ink {
  box-shadow: 0 1px 2px rgba(0, 100, 224, 0.22);
}

/* ⭐⭐ 2026-10-08 14:1x **文字层**：有无 ink 都要（白字是"选中"的可读性底线）
   ──────────────────────────────────────────────────────────────────────────────
   🐞 踩过：第一版把整条规则都套进 `:not(:has(> .tabs__ink))` ⇒ ink 一出现，
      `color` 也一起失效 ⇒ 选中项文字退回 `--color-muted`(灰蓝) 压在蓝块上
      （实测 rgb(84,101,121) on 渐变蓝 —— 基本读不出来）。
   ⇒ 拆分：**文字（可读性）不让**，只让**填充**（下面的 `background*` / `border-radius` / `box-shadow`）。 */
.table-tabs .pill-filter__item.is-active,
.table-tabs .seg__btn.is-active {
  color: var(--color-accent-on);
  font-weight: var(--font-weight-semibold);
  /* ⭐⭐⭐ 2026-10-08 16:0x **文字翻白改硬切**（乐哥：「开始切换的时候，tab 那个要切换的
     按钮文字，能变白色吗，现在还有卡顿」）
     ──────────────────────────────────────────────────────────────────────────────
     ▸ 逐帧实测（`scripts/frontend-debug/diag-tab-whiten-frames.js`，rAF 每帧采样）：
         蓝块 **t+212ms** 到位，而文字 **t+244ms 才开始变白**、还要**渐变 100ms**
         到 **t+362ms** 才全白 ⇒ **空档 50ms + 拖尾 100ms**，观感就是"块到了、字没跟上"。
     ▸ 根因：二级页那两族（`.tab.is-active` / `.sf-tab.is-active`）10-08 15:5x 已经改成硬切，
         **一级页这条当时漏了** ⇒ color 走基类的 `--motion-fast` 渐入。
     ▸ 硬切后：蓝块落位那一帧文字**瞬间全白**；顺带"滑动期压深"也变成瞬时
        （原来新选中项的白→深也是 94ms 渐变，多一次可见的颜色漂移）。
     ⚠️ **本节方案 19:2x 已被下面那条取代**（"硬切"本身成了新的观感问题，见下）—— 保留作历史依据。 */
  /* ⭐⭐⭐ 2026-10-08 19:2x **翻白改成"延迟到蓝块到位 + 瞬时"**（乐哥：「现在切换的时候，
     还是**先闪黑子**然后再显示白字，很奇怪不自然」）
     ──────────────────────────────────────────────────────────────────────────────
     🐞 旧写法（`transition: none` + `.is-inking` 压深）为什么"闪黑" —— 逐帧实测
        （`scripts/frontend-debug/diag-tab-text-flash.js`，rAF 每帧 + 真实鼠标路径）：
          hover 中：文字 = **蓝** rgb(0,100,224)（hover 反馈色）
          点击后  ：被 `.is-inking` 压成 **深灰** rgb(63,71,83)  ← **"闪黑"就是这一跳**
          到位后  ：瞬时白
        ⇒ 蓝 → 黑 → 白，中间那次"从蓝突变到深灰"就是他说的不自然。
     ✅ 新写法：**目标色依然是白，但延迟一个滑动时长才生效，且 duration = 0s（瞬时）**：
        · 推迟的这 250ms 里，文字**保持原色**（鼠标悬停过 = 蓝，没悬停 = 未选中灰）
          ⇒ 没有"变黑"这一跳；
        · 250ms 到点 = **蓝块到位那一帧**，瞬时翻白（没有渐变拖尾）
          ⇒ "蓝块框住字 = 字变白"仍然严丝合缝。
     ⛔ 别再写回 `transition: none`：那会让颜色**立即**变白，白字压在浅灰托盘上 = 白卡一闪
        （乐哥 15:2x 报过「莫名有个白色的卡一闪而过」）。
     ⛔ 也别给 color 任何**非 0 的 duration**：15:4x 栽过 —— delay(180)+渐变(155) 把翻白推到
        ≈500ms，而蓝块 250ms 就到位 ⇒"蓝块压着字、字还是深色"（他当时报的就是那一帧）。
        正确组合只有一种：**delay = 滑动时长、duration = 0s**。 */
  transition: color 0s linear var(--motion-tab-slide);
}

/* ⭐⭐ 2026-10-08 14:1x **填充层：有 ink 时让给 ink**（`--in-header` 那条同理，见下方）
   ──────────────────────────────────────────────────────────────────────────────
   表格 tab 族补上 ink（滑动指示块）之后，若本层还自己画蓝底，就会出现**两个蓝块**：
   item 的蓝底**瞬间**跳到新项，ink 还在路上慢慢滑过去 ⇒ 观感"选中框分成了两个"。
   ⇒ 用 `:has(> .tabs__ink)` 判：**有 ink 就不画**（由 ink 负责填充 + 浮起），
      **没有 ink 才画**（JS 没跑 / 降级路径 —— 那时选中态仍然必须看得见，
      比"没有动效"严重得多）。与 `.tab.is-active` 的 `:not(:has(> .tabs__ink))` 兜底同一套写法。 */
.table-tabs:not(:has(> .tabs__ink)) .pill-filter__item.is-active,
.table-tabs:not(:has(> .tabs__ink)) .seg__btn.is-active {
  background: var(--color-accent);    /* 纯色作**回落**（渐变不支持时仍在），与 .btn--primary 同款 */
  border-radius: var(--radius-seg);
  /* ⭐⭐ 2026-10-08 填充接**品牌渐变**（乐哥：「选中的样子要做的和右边新增达人一样，是蓝色渐变」）
     ⇒ 与 `.btn--primary` 的 `--gradient-brand` **同一个令牌、同一份资产**。
     ⚠️ 写法必须"纯色 `background` + `background-image` 叠层"（与 `.btn--primary` 同款）：
        不要写成 `background: var(--gradient-brand)` 简写 —— 简写会把其它 background 子属性
        （将来可能加的叠加层）一起重置，且与主按钮的写法分叉。
     ⛔ 10-07 曾在此写 `background-image: none` 显式压掉渐变（当时理由"与卡内一致"），
        10-08 乐哥明确要渐变 ⇒ **已反转，别改回去**。
     ⚠️ 历史教训（10-07 19:55「和新增排期一致」那轮）：当时只比 `backgroundColor`，
        两边同为 `--color-accent` ⇒ 判了"一致"，而**真正的差异就在 `background-image`**
        （按钮有渐变、胶囊没有）⇒ 今后比"两个元素是不是同一个色"必须**连 background-image 一起比**。 */
  background-image: var(--gradient-brand);
  /* ⭐ 2026-10-08 选中态浮起（乐哥动效第②条，见 design-tokens 的 --shadow-pill-active） */
  box-shadow: var(--shadow-pill-active);
}
/* ⭐⭐⭐ 2026-10-08 19:2x **阴影也让给 ink**（乐哥：「就是在蓝色胶囊下一层，
   明显能看到有个白色的透明底在拖着」）
   ──────────────────────────────────────────────────────────────────────────────
   🐞 上一族只让了**填充**（background / border-radius），**漏了 box-shadow**：
      `components.css` 的基类 `.seg__btn.is-active` / `.pill-filter__item.is-active`
      自带一份 `box-shadow: var(--shadow-pill-active)`，而 ink 又是**同一份令牌** ⇒
      两层**完全重合**的蓝影叠在蓝块外那 4px 内边距上。
   ▸ 消融实测（`scripts/frontend-debug/ablate-tab-white.js`，逐像素，deviceScaleFactor=2）：
       基线 · 蓝块左外 -1px = **(188,212,239)**（被两层蓝影染色，观感"发白的一圈底"）
       去掉 item 的阴影后该点 = **(241,243,245)**（干净托盘灰）
       （-4px 恒为 (225,232,242) 托盘边框、-5px 起 (255,255,255) 页面白卡，与阴影无关）
   ⇒ 结论：那圈"白底"不是多余元素，是**本该干净的托盘内边距被重复阴影染脏**了。
   ✅ 有 ink 时 item 的阴影必须清掉 —— "浮起"由 ink 一份承担（与填充同一套让位逻辑）。
   ⛔ 别再让 item 画阴影：两份同位置阴影不会"更立体"，只会把内边距染成浅蓝白。
   ⚠️ 降级路径（没有 ink）仍由上面 `:not(:has(> .tabs__ink))` 那条负责画阴影，别动它。 */
.table-tabs:has(> .tabs__ink) .pill-filter__item.is-active,
.table-tabs:has(> .tabs__ink) .seg__btn.is-active {
  box-shadow: none;
}

/* 下划线**去掉**：现在选中靠"实心胶囊"表达，不再需要那条指示条。
   ⛔ 别加回来 —— 实心底 + 下划线会变成"两重选中提示"，且下划线在圆角托盘内会悬空。 */
.table-tabs .pill-filter__item.is-active::after,
.table-tabs .seg__btn.is-active::after {
  display: none;
}

/* ── 嵌在「页面级工具栏」里的 TAB 条（人员成本 / 兼职助播）────────────────────
   它们的 seg 直接放在 `.toolbar` 内（不在 .table-toolbar 里），由 JS 加了
   `.table-tabs` 复用下划线样式，并移到工具栏最前。
   本段让它**独占一行**，并去掉重复缩进。

   ⚠️ 行序：**TAB 在上、筛选在下**（2026-09-18 乐哥改版，参照微信小店）。
      这与 2026-09-14 那一版相反 —— 当时定的是"筛选在上、视图切换在下"，
      乐哥 2026-09-18 看到微信小店的形态后改主意：「那个 tab 栏，我看别的系统是
      放在工具栏上面的…我觉得这样更合理」。
      ⇒ 实现方式仍是 `flex-basis: 100%`（独占一行），但**行序由 DOM 顺序决定**：
        JS 改为 `insertBefore(seg, tb.firstChild)`（搬到最前），**不用 CSS order**
        —— order 会让键盘 Tab 焦点顺序与视觉顺序不一致（无障碍问题）。
   ⚠️ 必须清掉左右 padding：`.table-tabs` 自带 20px 左缩进，而父级工具栏**也有** 20px，
      不区分会变成 40px 双重缩进、与其它页面的 TAB 不对齐。 */
.toolbar--has-page-tabs > .table-tabs {
  flex-basis: 100%;
  padding: 0;
  /* TAB 行与下方筛选行的间距（改版后它在上面，需要与筛选拉开一点，读者才分得清两行）
     ⚠️ 与 `.toolbar` 的 `row-gap: var(--space-2)` 叠加 → 实际 8 + 8 = 16px。 */
  margin-bottom: var(--space-2);
}

/* ══════════════════════════════════════════════════════════════════════════
   状态 TAB「住进页面标题行」后的形态（2026-09-25 骨架级第 1 批）
   --------------------------------------------------------------------------
   排版目标（一行三块）：
     「达人管理   ｜ 全部83 待合作43 待开始4 …  ｜ [导入][导出][新增达人]」
        标题             TAB 组（占据中间弹性空间）         页面级操作按钮

   ⚠️ 触发条件用 `.page-header--has-tabs`（由**页面自己**在标记里写上，见
      `pages/influencer-list.js` 的说明）—— 全站 41 页有标题行、只有少数页有 TAB，
      标记法能把影响**严格限定**在这些页内。
   ⛔ 为什么不由框架统一加类/搬节点：实测会踩到 2026-09-24 那次线上故障的同一条路径
      （框架反复搬动 + 卡片异步重渲染 ⇒ 不收敛 ⇒ 业绩总表主线程卡死：
       改动前 2568ms 正常、改动后 6 秒起阻塞 40 秒不恢复）。
      ⇒ 结论：**这类"把节点从卡内搬到卡外"的动作，只能在页面渲染时一次性生成，
        不能让框架反复搬。** 详见 `docs/骨架级改动-立项方案-20260925.md`。

   ⚠️ 为什么 TAB 项从 44px 降到 32px：44px 是它当年"独占一行 + 带下划线基线"时的高度；
      进了标题行后标题行自身高 44px，TAB 项若也是 44px 会把标题行撑高、吃掉收益。
   ⚠️ 下划线从 `bottom:-1px` 改为 `bottom:0`：原来的 -1px 是为压住容器自身的 1px 底边框；
      标题行里没有那条基线了，且 -1px 会造成纵向溢出 ⇒ 在 `overflow-x:auto` 下
      右侧会冒出一条纵向滚动条（本项目 2026-09-14 已因此踩过坑）。 */
.page-header--has-tabs {
  align-items: center;                  /* 标题 / TAB / 按钮 三者垂直居中 */
  gap: var(--space-4);
  margin-bottom: var(--space-4);        /* 原 24 → 16：把省下的高度让给数据区 */
}
.page-header--has-tabs > div:first-child {
  flex-shrink: 0;                       /* 标题「达人管理」永不被压缩 */
}

.table-tabs--in-header {
  flex: 1 1 auto;                       /* 占据标题与按钮之间的弹性空间 */
  min-width: 0;                         /* 允许内部滚动而不撑破标题行 */
  /* ⭐⭐⭐ 2026-10-07（乐哥选 A3 档；「tab 的圆角**包括底色**得和新增达人那个一致」）
     ────────────────────────────────────────────────────────────────────────────
     基准 = 「新增达人」页那张 tab（`.sf-tabs__track > .tabs__tray`），真机实测：
       托盘 248×38  白底 + 1px --color-border(#E1E8F2) + 圆角 **10px** + **padding 4px（四向）**
       胶囊 29高     圆角 **6px** + padding 0 16px
     ⇒ 本行之前是「透明底· 无边框 · 无圆角 · padding 0」⇒ 两套完全不像。
     现在按基准**逐值同源**（用令牌，不写死数字）：
       · 圆角  --radius-md(10px)  与 `.tabs__tray` 同源
       · 边框色 --color-border(#E1E8F2) 与 `.tabs__tray` 同源
       · 底色  --color-surface(#FFFFFF)  与 `.tabs__tray` 同源
     ⚠️ padding 用**四值同值 4px**（不是 `4px` 单值，虽然等价，但显式写四值更直白，
        且门禁 `subtab-tray-form ⑥` 查的是"逐值同源"）。
     ⚠️ 配平：4×2 + 胶囊 34（`.table-tabs--in-header .pill-filter__item` 桌面高）
        = **42**，与卡片行高 44 差 2px —— 由下面那条 padding 归零补偿一起处理。
     ⛔ 别只改圆角不改底色/边框：乐哥要的是"**包括底色**"整套一致。 */
  padding: 3px;                       /* ⭐ 2026-10-07 14:40：4 → 3，与上面桌面媒体块那条**同值** */
  /* ⭐⭐⭐ 2026-10-08 底色 **白 → Apple 灰 `--tab-tray-bg`**（乐哥：「你还没做完啊，
     比如达人详情这个、字典配置、用户管理，其他页面也查下」+ 10-07 13:40 已拍板
     「tab 的底色，之前不是说用 apple 那种吗」）
     ────────────────────────────────────────────────────────────────────────────
     ▸ 为什么是"没做完"：10-07 那次"改用 Apple 灰"只落到了 **`.pill-filter` 版**
       （`components.css` 的 `.pill-filter.table-tabs`，注释白纸黑字写着"全站共用同一个
       浅灰托盘的视觉意图"）——而 **`.seg` 版（本规则）还停在白底** ⇒
       同为"页头 tab"，排期列表（pill-filter）是**灰**、报销单总览（seg）是**白**，
       两个组件各半套。本次把 seg 版对齐到同一令牌，两版终于一套语言。
     ▸ 灰底的理由（与 pill-filter 处同源）：内容页纯白 ⇒ 白托盘"化掉"看不出是选项组；
       #F1F3F5 与白卡拉开 Δ≈10 ⇒「白卡 → 浅灰托盘 → 实心蓝胶囊」三级可读。
     ⛔ 别写回 var(--color-surface)：那正是"两个组件不一致"的那半套。 */
  background: var(--tab-tray-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  border-bottom: 1px solid var(--color-border);   /* 抵消基础样式的下划线基线 —— 现在是"托盘下边框"了 */
  gap: var(--space-4);                  /* 项间距 24 → 16（横向空间比独占一行时紧） */
  overflow-x: auto;                     /* TAB 项过多时可横向滚动 */
  overflow-y: hidden;
  scrollbar-width: none;
}
.table-tabs--in-header::-webkit-scrollbar { display: none; }

/* ⭐ 2026-09-25 乐哥：「货品页不对，那个 tab 和工具栏放一起了」——
   被**搬到工具栏之前**的 TAB（`data-lifted` 标记，见 app.js 的 fixTableTabs）。
   它现在是内容区白大卡上的**独立一行**，下面依次是工具栏卡、表格卡（与达人管理页同构）。
   ⚠️ 与"卡内 TAB 行"（.table-tabs 的基础样式）的区别只在**外部间距**：
      基础样式自带 `padding: 0 20px`（与表格/工具栏内文字左对齐，实测对齐误差 0）
      与 `border-bottom` 下划线基线 —— 两条都保留（独立一行时基线让"这是一组 tab"更明确）。
   ⚠️ 只加下外边距（与下方工具栏卡留出呼吸），不加左右外边距（保持与卡内文字对齐）。 */
/* ⚠️ 只对"落在工具栏前"的兜底形态生效；进了标题行（--in-header）的不需要这个下边距
   —— 标题行自身有 margin-bottom（16px），再加会撑出双倍间距。 */
.table-tabs[data-lifted]:not(.table-tabs--in-header) {
  margin-bottom: var(--space-3);   /* 12px：与下方工具栏卡的间距（它是两张独立的卡/行） */
}
/* 首个元素时（页面内容顶部）不留额外上边距 —— 白大卡自身已有 padding-top。 */
.table-tabs[data-lifted]:first-child { margin-top: 0; }

/* ⭐ 2026-09-25（第二轮）乐哥：「货品那个 tab 上面留白也太多，和其他页面不统一」
   —— TAB 现在搬进**页面标题行**（`.page-header--has-tabs` + `.table-tabs--in-header`，
   与达人管理页同构）。上面那套 `.table-tabs--in-header` 的样式（去左右 padding、
   去下划线基线、flex:1 占中间位）**是现成的**（达人管理页在用），此处无需重复定义。
   ⚠️ 但"标题已隐藏"的页面（乐哥刚要求去掉页面标题）有个副作用要处理：
      `.page-header--has-tabs > div:first-child`（标题容器）`flex-shrink:0`
      但宽度为 0 ⇒ TAB 会被 `gap: 16px` 推出 16px 的左侧空档、与下方工具栏左缘错位。
      ⇒ 让**标题容器为空时**不参与 flex 排布（`:empty` 判据），TAB 就能贴到行首。
   ⭐⭐ 2026-09-28 修 Tab 左错位（乐哥：「现在修复」，13 页实测错位 134~390px）：
      ⛔ 只写 `:empty` 不够 —— 标题全局 `display:none` 后，容器里 `<h1>` **节点仍在**，
         `:empty` 判据不命中 ⇒ 容器继续占 flex 位 ⇒ space-between 把 TAB 当中间元素推到页头中部，
         与下方工具栏/表格左缘错位（实测：达人管理 134px、排期列表 299px、样品包 390px）。
      ✅ 补 `:has(.page-title)` 判据：容器内**含页面标题节点**（无论是否可见）⇒ 整容器隐藏。
      ⚠️ 两条判据都要留：`:empty` 兜底"真正空容器"、`:has(.page-title)` 覆盖"藏着标题的空容器"。
      ⚠️ 为什么不用 `justify-content: flex-start`：那会改动整个页头的对齐语义（按钮靠右要另写
         margin-left:auto），且与无 tab 页面的页头行为分化；隐藏空容器是**最小改动**。 */
.page-header--has-tabs > div:first-child:empty,
.page-header--has-tabs > div:first-child:has(.page-title) { display: none; }
/* ⚠️ 2026-10-05 曾在此加过一条例外（"容器里有 ? 按钮就别藏"）—— **已撤销**：
   实测那样会让第一个 div 重新参与 flex 布局 ⇒ 3 个 item 触发 space-between
   ⇒ **TAB 被推到页头中部**（费用明细 TAB 左缘 345 → 768、业绩总表 → 736），
   正是 2026-09-28 花大力气修掉的那个错位。
   ✅ 现行做法改在 JS 侧：带 TAB 的页面把「?」**放进 TAB 行**（`.table-tabs`）——
      既可见、又完全不扰动页头 flex（见 app.js 的 injectHelpButton）。 */

/* ⭐⭐ 2026-09-28 页头内的 TAB 改**胶囊形态**（乐哥拍板「方案C」+ 尺寸反馈）：
   ─────────────────────────────────────────────────────────────────────────────
   背景：TAB 原本是「下划线式」设计（2026-09-14，当时 TAB 紧贴表格）；2026-09-24 为省高度
   搬进页头后形态没跟着换 ⇒ 一排 16px 大字下划线 TAB 孤零零悬在页面顶部（乐哥：很奇怪）。
   本次与「页头包盒」配套改为胶囊（白容器 + 选中蓝块），与卡片内语言一致。
   ⚠️ 乐哥两条尺寸反馈（照做）：
     · 高度太矮 —— 胶囊总高提到 **44px**（容器 padding 4 + 项 34 + 边框 2），
       与页头右侧操作按钮（.btn--lg = 44px）齐平；
     · 宽度右边空非常多 —— 容器原是 flex 拉伸（909px 宽），改 **flex: none** 收缩到内容宽。
   ⚠️ 必须同时清掉 .table-tabs 的「下划线形态」遗留：透明底 / border-bottom 基线 /
     padding 0 20 / gap 24 / 项高 44 项距 2px，全部按胶囊规格重设。 */
.table-tabs--in-header {
  flex: none;                          /* 不拉伸：宽度随内容（乐哥：右边空太多） */
  gap: var(--space-1);                 /* 项间距 24 → 2（胶囊内挤着排） */
  /* ⭐⭐ 2026-10-06 纵向内边距 2 → **0**、胶囊 38 → **42**（乐哥第二次提同一件事：
     「输入框和月份筛选框，视觉上比左边 tab 栏要高，我估计是 tab 实心的原因」）
     ══════════════════════════════════════════════════════════════════════════
     ▸ **根因是"参照系不同"** —— 两个元素的外框都是 44px，但**各自量的是不同的线**：
         · `.input` / `.mp__trigger`：`box-sizing: border-box`、外框 44 ⇒
           **实心内容区 = 44 − 2×1(边框) = 42px**
         · 胶囊 `.pill-filter__item`：**没有边框**，它的 38 就是实心内容区
       ⇒ 实心块 38 vs 输入框实心区 42 ⇒ **差 4px**，正好是乐哥看到的"输入框更高"。
       上一轮（2026-10-05）只把 34 → 38，是把差从 10px 缩到 4px —— **没消除**（他这次又提）。
     ▸ 本次把胶囊做成 **42 = 输入框内容区**，两者实心块**精确等高**；托盘的 1px 边框
       与输入框的 1px 边框也在同一圈上 ⇒ **上下两条视觉边线都对齐**。
     ⚠️ 保持托盘的 44/页头卡的 64 不变 —— 这两条是既得约束，不能为对齐而牺牲：
        · 托盘 44 = 与右侧「新增排期」按钮（44）齐平（乐哥 2026-10-05 的要求）；
        · 页头卡 64 = 垂直内距 9×2 + 44 + 2（那个 9px 是乐哥 2026-09-28 亲自定的
          "矮 10%"，**不许动**，见 layout.css 该处注释）。
     ⚠️ **配平公式**：`托盘高 = padding(纵)×2 + 胶囊高 + 边框×2`
        当前：**0×2 + 42 + 1×2 = 44** ✓
        要 42 的胶囊，纵向内边距就必须让到 0（这是唯一解：42+2×2=46 会顶破 44）。
     ⚠️ 门禁 `tests/four-fixes-1005.test.js` 的 ③a 锁这组配平关系，改这里要同步改它。

     ⭐⭐ 2026-10-06 补：横向内边距也归零（乐哥：「如果选择最左或者最右，
        两边还有空余的地方没被填充」）
     ──────────────────────────────────────────────────────────────────────────
     ▸ 现象：选中最左/最右的 tab 时，蓝色块与托盘边框之间**露出一条白缝**。
       实测（5× 放大）：空隙 = 横向内距 **2px** + 托盘自身 1px 边框。
     ▸ ⚠️ 这 2px **不是这次改出来的**：改前 `padding: 2px`（四边）时左右也是 2px ——
       只是那时纵向也是 2px、上下左右"一圈白"看起来像对称留白；
       本次纵向让到 0 后，**上下贴边、左右不贴**，那条缝才显得突兀。
     ▸ 修法：横向 `2px → 0`（连同上一条的纵向 0 ⇒ `padding: 0`）。
     ▸ ⛔ **必须同时给首/末项做"同心圆"外侧圆角**（否则新的问题）：
         托盘圆角 **10px** 在外侧、胶囊圆角 6px 在内侧，两者若都从同一个角起，
         蓝块会**伸进托盘的圆角区**、超出边框弧线，角上出现蓝色"尖角"。
         按同心圆公式：**内侧圆角 = 外侧圆角 − 内距**。
         内距现为 0 ⇒ 理论上内侧也该是 10px；但蓝块还隔着托盘 1px 边框，
         用 **9px**（= 10 − 1 边框）实测与边框弧线**完全贴合**（12× 放大逐像素看过）。
         ⚠️ 只改**外侧**圆角（`border-top-left` + `border-bottom-left` 给首项，
           `border-top-right` + `border-bottom-right` 给末项），
           内侧保持 `--radius-seg` 6px —— 这样中间那颗还是四面 6px 的常规胶囊。
     ⚠️ 别用「保留 2px 内距 + 给首末项加负外边距」的写法：视觉效果相同，
       但会引入负 margin（影响 `gap` 计算与宽度累加），且下一轮维护看不懂为什么有负值。

     ⛔⛔ **本节的两条改动（42px 胶囊 / 内距归零 / 外侧圆角）全部只在桌面端生效**，
        见下方 `@media (min-width: 769px)` 块。基础值保持**手机端原样（38px / 2px）**，
        原因是实测（2026-10-06，与线上逐项对照）：
          · 手机端「费用明细 / 订单明细」的**页头会被顶高 4px**（120 → 124）——
            因为那两页的托盘本来就**折 2 行且被裁**（既有问题），胶囊变高只把页头撑高，
            而**吸顶卡是永久占屏**（本项目铁律）⇒ 手机端**零收益、纯涨高度**。
          · 其余页（达人列表/排期列表）手机端托盘高度不变(44)，但胶囊变高同样无收益。
        ⇒ 手机端保持线上原值 = **零回归**；桌面端才是乐哥反馈的场景（他截图都取 1920）。
        ⚠️ 与本项目既有惯例一致：上一轮的「筛选条整合」（layout.css 的
           `.page-header--filter-bar` 系列）同样是 `@media (min-width: 769px)` 才应用。 */
  /* ⭐⭐⭐ 2026-10-07 14:40 **2 → 3**（乐哥：「右边新增达人和左边tab 的高度也不一致」）
     —— **本条才是最终生效的那一条**（同文件另有两条同名规则，靠源顺序取胜）。
     ⚠️⚠️ 这个文件里 `.table-tabs--in-header` 的 `padding` 有**三条**：
        ① 行 ~620（基础值第一条）② 行 ~740（本条）③ 桌面媒体块内那条
        ⇒ 改任何一条都要问「它是不是最后生效的那条」，否则真机与断言会打架
        （本轮就踩过：改了 ①②，断言报「实际 2px」才发现漏了本条）。
     ▸ 3 的推导与桌面媒体块那条相同：3×2 + 胶囊 34 + 边框 2 = 托盘 42。
     ⛔ 别写回 2。 */
  padding: 3px;
  /* ⭐⭐ 2026-10-06 深夜（乐哥：「这个悬浮 tab，周围这个边框（白色的）是不是可以不要？」）
     ────────────────────────────────────────────────────────────────────────
     ▸ 改前：白底 + 1px 边框 + 圆角 10 的"托盘"，**套在**状态栏（已带边框+圆角+阴影）里
       ⇒ 三层框嵌套（状态栏 → 托盘 → 胶囊），读起来多了一层。
     ▸ 改后（桌面端）：托盘**透明、无边框、无圆角** ⇒ 层级只剩
       「状态栏（带框）」→「胶囊选中态」，最简。
     ⚠️ 连带三处必须同步改，否则出视觉 bug（都在桌面媒体块里）：
       ① 配平变了：无边框后 `0×2 + 42 = 42`（原 44）⇒ 页头卡 64 → **62**。
       ② 首/末项的**同心圆外侧 9px**（= 托盘圆角 10 − 边框 1）失去依据 ——
          没有边框就没有"边框内侧"，首/末项回到 `--radius-seg` 6px。
       ③ 手机端**完全不动**（38px / padding 2px / 有边框）：手机端页头窄，
          托盘没边框会读不出"这是一组 tab"。
     ⛔ 本条不与 `.page-header--has-tabs` 的边框冲突 —— 乐哥 2026-10-06 深夜
        同时拍板「状态栏与表格卡保持两张但样式对齐」，那说的是**外层**，这里说的是**内层**。 */
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  /* ⭐⭐⭐ 2026-10-08 白 → Apple 灰（同 624 行那条，乐哥「你还没做完啊」批次的统一；
     本条是"桌面端"的补充规则，与 624 那条**必须同值** —— 只改一条等于没改）。 */
  background: var(--tab-tray-bg);
}
.table-tabs--in-header .pill-filter__item,
.table-tabs--in-header .seg__btn {
  height: 34px;                        /* ⭐ 2026-10-07 14:40：38 → 34，与桌面媒体块那条**同值**。
                                           原来手机 38 / 桌面 44 是两个数，现在统一 34。 */
  padding: 0 16px;                     /* 1px → 16：胶囊项横向留白 */
  border-radius: var(--radius-seg);
  font-size: var(--font-size-sm);      /* 16 → 14：与页头控件同号 */
  flex: none;                          /* 不被 flex 压扁文字 */
  white-space: nowrap;
}

/* ══════════════════════════════════════════════════════════════════════════════
   ⭐⭐ 2026-10-06（桌面端）：胶囊 42px + 托盘内距归零 + 首/末项外侧圆角
   ──────────────────────────────────────────────────────────────────────────────
   ⛔⛔ **这三条只在 ≥769px 生效**，手机端保持线上原值（38px / padding 2px / 圆角 6px）。
      为什么分屏（实测，与线上逐项对照）：
        · 手机端「费用明细 / 订单明细」页头会被顶高 **4px（120 → 124）** ——
          那两页托盘本来就**折 2 行且被裁**（既有问题），胶囊变高只把页头撑高，
          而**吸顶卡永久占屏**（本项目铁律）⇒ 手机端**零收益、纯涨高度**。
        · 其余页手机端托盘高度不变(44)，胶囊变高同样无收益（手机端不做"与输入框等高"这件事，
          因为手机端输入框不在同一行）。
      ⇒ 手机端 = 线上原样（零回归）；桌面端才是乐哥反馈的场景（他截图取 1920）。
      ⚠️ 与本项目惯例一致：上一轮「筛选条整合」也是 `@media (min-width: 769px)` 才应用。
   ──────────────────────────────────────────────────────────────────────────────
   ① `padding: 0`（原 2px 四边）—— 乐哥「选中最左或最右，两边还有空余的地方没被填充」。
      让首/末颗贴住托盘的**边框内侧**（见上方 `.table-tabs--in-header` 的长注释）。
   ② `height: 42px`（原 38）—— 乐哥「输入框和月份筛选框，视觉上比左边 tab 栏要高」。
      42 = 输入框（border-box，外框 44）的**实心内容区** 44 − 2×1(边框)。
      配平：`托盘 44 = padding(纵)0×2 + 42 + 边框 1×2` ✓（托盘与页头卡都不变）。
   ③ 首/末项**外侧**圆角 9px = 托盘圆角 10px − 托盘边框 1px（**同心圆**）——
      否则蓝块会伸进托盘圆角区、在角上露出蓝色"尖角"。
      ⚠️ 只改外侧两个角；内侧保持 6px，中间那颗仍是常规胶囊。
      ⚠️ 选择器必须带 `>` 与 `:first-child`（**0,3,0**）—— 只写 `.pill-filter__item`（0,2,0）
         会被下方 `border-radius: var(--radius-seg)` 覆盖成 6px（本项目反复踩特异性坑）。
   ══════════════════════════════════════════════════════════════════════════════ */
@media (min-width: 769px) {
  /* ⭐⭐ 2026-10-06 深夜：托盘**去框**（乐哥「周围这个边框（就是白色的）是不是可以不要」）
     ────────────────────────────────────────────────────────────────────────
     ▸ 桌面端托盘 = `padding 0` + **无边框** + **无底色** + **无圆角**
       ⇒ 三层框嵌套（状态栏 → 托盘 → 胶囊）降为两层。
     ⚠️ 连带三处（缺任一都会出视觉 bug）：
       ① **胶囊 42 → 44**：原来 `0×2 + 42 + 边框1×2 = 44`，去掉边框后要补回这 2px
          ⇒ 托盘仍是 44，与右侧「新增达人」按钮（44）齐平（乐哥 10-05 的要求）。
          ⛔ 门禁 `four-fixes-1005.test.js` ③a/③a2 锁的就是这个配平，已同步改。
       ② **首/末项外侧圆角 9px → var(--radius-seg)**：9px = 托盘圆角 10 − 边框 1
          （同心圆公式）。**没有边框就没有"边框内侧"**，继续用 9px 会让首/末项
          比中间那些更方 ⇒ 三个胶囊圆角不一致。
       ③ 托盘 `background: transparent`：否则白底托盘在状态栏白底上仍是一块"白区"。
     ⚠️ 手机端（<769px）**完全不动**：窄屏上没边框读不出"这是一组 tab"。 */
  /* ⭐⭐⭐ 2026-10-07（乐哥选 A3 档 +「tab 的圆角**包括底色**得和新增达人那个一致」）
     ────────────────────────────────────────────────────────────────────────────
     改前（2026-10-06「托盘边框不要」那一版）：`padding:0 / border:none /
     border-radius:0 / background:transparent` ⇒ 托盘完全消失。
     改后：按「新增达人」页那张 tab（`.sf-tabs__track > .tabs__tray`）**逐值同源**——
       · 底色--color-surface(#FFFFFF)   同源
       · 边框 1px --color-border(#E1E8F2) 同源
       · 圆角 --radius-md(10px)          同源
       · 内距 4px（四向，与 `.tabs__tray` 的 padding:4px 一致）
     ⛔ 注意：这是**桌面媒体块**里的一条，与同文件基础值那条（`.table-tabs--in-header`
        在 `@media` 之外）**同名同优先级，靠文档顺序取胜** ⇒ 改这里才生效。
        （第一版改在基础值那条上，实测 `getComputedStyle` 仍是 transparent ⇒ 教训已写。）
     ⚠️ 配平随之变化：托盘高 = 4×2 + 胶囊 44 + 边框 2 = **54**
        （原 0×2 + 44 = 44）⇒ 页头卡会高出 10px，属预期（托盘重新有了边框与内距）。
        ⚠️ 胶囊高 44 是 10-06 为对齐「输入框实心区 42」定的，**本次不动**。 */
  .table-tabs--in-header {
    /* ⭐⭐⭐ 2026-10-07 14:40 **padding 4 → 3**（乐哥：「右边新增达人和左边tab 的高度也不一致」）
       ────────────────────────────────────────────────────────────────────────────────
       ▸ **为什么托盘内距必须跟着胶囊一起收**：胶囊 44 → 34 后，
          托盘若还留 4px 内距 ⇒ 高 = 4×2 + 34 + 2(边框) = **44**，
          而右侧按钮只有 34 ⇒ 托盘反而比按钮高 10px，"高度不一致"**换了个方向继续存在**。
       ▸ 取 3 的推导：3×2 + 34 + 2 = **42**；胶囊左右留白靠自身 `padding: 0 16px` 提供，
          托盘内距只负责"胶囊不贴托盘边框"。3 是能同时满足
          「① 托盘与按钮视觉等重」与「② 首/末胶囊不越出托盘圆角」的最小值。
          ⚠️ 改这个值必须同步核 `four-fixes-1005 ③a2`（同心圆：内侧圆角 = 外侧 − 内距）。
       ⛔ 别改回 4：那正是本次要修的「托盘比按钮高 10px」。 */
    padding: 3px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    /* ⭐⭐⭐ 2026-10-08 白 → Apple 灰（同 624 / 777 两条，三处**必须同值**——
       媒体块里的这条是实际生效的那条，只改上面会"看着改了、量出来还是白"）。 */
    background: var(--tab-tray-bg);
  }
  .table-tabs--in-header .pill-filter__item,
  .table-tabs--in-header .seg__btn {
    /* ⭐⭐⭐ 2026-10-07 14:40 **44 → 34**（乐哥：「右边新增达人和左边tab 的高度也不一致，
       很奇怪」）—— 与右侧按钮（`btn--sm` = 34）、搜索框（改为 34）**同档并排**。
       ▸ 病根不是"某处写错了"，而是**跨档并排**：胶囊走标准档 44，
          而同行右侧的「导入/导出/新增达人」是 `btn--sm`（紧凑档 34）⇒ 差 10px。
          真机实测（1920×950）：胶囊 44 / 按钮 34 / 搜索框 44。
       ▸ 为什么选"降到 34"而不是"按钮升到 44"（乐哥拍板）：
          这一行是**列表页工具条**（左：视图切换 + 筛选；右：批量操作），
          全站同类工具条控件（`.table-toolbar__right .btn` / `.select`）
          都是 `--control-height-sm` = 34 ⇒ 跟着走 34 才与邻近行一致；
          升到 44 会让这一行比下面的表格工具条高一截。
       ⚠️ 必须与本文件 759 那条（手机端 38px）、`.pill-filter` 托盘内距、
          `.page-header--filter-bar` 的搜索框高度**一起改**，否则又是一档新差异。 */
    height: 34px;
  }
  /* ⭐⭐⭐ 2026-10-07：托盘**恢复了 1px 边框 + 10px 圆角**（与「新增达人」页同源）
     ⇒ **同心圆公式重新成立** —— 2026-10-06「托盘边框不要」那版把这里退回 6px，
        依据是"没有边框就没有边框内侧"。现在边框回来了，按公式补回：
          内侧圆角 = 托盘圆角 − 内距 = 10px − 1px(边框) = **9px**
        ⛔ 别再退回 6px：那样选中蓝块（尤其选中最左/最右时）会**伸进托盘圆角区**、
           超出边框弧线，角上出现蓝色"尖角"（12× 放大逐像素验过）。
     ⚠️ 只改**外侧**两个角，内侧保持 --radius-seg(6px) —— 中间那颗仍是常规胶囊。
       与 `.table-tabs--in-header` 基础值那段的 9px 规则**同值**（门禁
       `four-fixes-1005 ③a2`锁的就是这个同心圆关系）。 */
  .table-tabs--in-header > .pill-filter__item:first-child,
  .table-tabs--in-header > .seg__btn:first-child {
    border-top-left-radius: 9px;
    border-bottom-left-radius: 9px;
  }
  .table-tabs--in-header > .pill-filter__item:last-child,
  .table-tabs--in-header > .seg__btn:last-child {
    border-top-right-radius: 9px;
    border-bottom-right-radius: 9px;
  }
}
/* ⭐⭐⭐ 2026-10-07 桌面端选中态：**主色胶囊**（乐哥选 A3 档「Apple 的形 + 我们的品牌色」）
   ⭐⭐⭐ 2026-10-08 填充升级为**品牌渐变**（乐哥：「选中的样子要做的和右边新增达人一样，
   是蓝色渐变」）—— 与 `.btn--primary` 同用 `--gradient-brand`，全站"实心主色块"至此同源。
   ────────────────────────────────────────────────────────────────────────────
   ⚠️⚠️ 这条**必须写在这里**（桌面块的**外面**），不能只写媒体块内：
   媒体块内那条 `.table-tabs--in-header .pill-filter__item.is-active`
   specificity 更高（0,3,0 > 0,2,0），只改媒体块内会被它压住。
   实测踩过：媒体块内写了 `background: var(--color-accent)` 但计算值仍是 transparent。
   ⇒ 这里把**两处一起对齐**：显式 `background`（纯色回落）+ `background-image`（品牌渐变）。
   ▸ 参照系：Apple 参考图里选中项是**实心色块**（深墨 rgb(29,29,32)），
     可读性来自色块本身，不是下划线 ⇒ 这才是我们要的。
   ⛔ 10-07 曾要求"纯色而非渐变"（当时理由：37px 高下渐变不可见）——
      10-08 乐哥明确要渐变，**已反转，别改回去**。 */
/* ⭐⭐ 2026-10-08 14:1x **页头 tab 行的 item 必须自己定位**
   ──────────────────────────────────────────────────────────────────────────────
   ink（选中滑动块）是宿主的**首个子元素 + 绝对定位**，绘制在 item **之下** ——
   前提是 item 自己也是定位元素（同层时按 DOM 顺序，后出现的盖在上面）。
   卡内那条（`.table-tabs .pill-filter__item`）本来就有 `position: relative`，
   **页头这条一直没有** ⇒ 若不给，静态 item 的文字会被蓝块整个盖住（实测会这样）。
   ⚠️ 只加定位，不改尺寸/内距（页头胶囊的 34/内距 由 `.pill-filter` 族规则给）。 */
.table-tabs--in-header .pill-filter__item,
.table-tabs--in-header .seg__btn { position: relative; }

/* ⚠️ 与卡内两条同理：**文字层不让、填充层让**（否则出现"两个蓝块"，或文字变灰读不出）。 */
.table-tabs--in-header .pill-filter__item.is-active,
.table-tabs--in-header .seg__btn.is-active {
  color: var(--color-accent-on);
  font-weight: var(--font-weight-medium);
  /* ⭐⭐⭐ 2026-10-08 19:2x 同上（卡内那条）：**延迟到蓝块到位 + 瞬时翻白**。
     完整机理与三处反面教训见 `.table-tabs .pill-filter__item.is-active` 那条长注释。 */
  transition: color 0s linear var(--motion-tab-slide);
}
.table-tabs--in-header:not(:has(> .tabs__ink)) .pill-filter__item.is-active,
.table-tabs--in-header:not(:has(> .tabs__ink)) .seg__btn.is-active {
  background: var(--color-accent);          /* 纯色作**回落**，与 .btn--primary 同款 */
  background-image: var(--gradient-brand);  /* ⭐ 与「新增达人」主按钮同一个渐变令牌 */
  /* ⚠️ `color` / `font-weight` 已上移到上面那条「文字层」规则
     （它们**不能**跟着这一条被 `:not(:has(> .tabs__ink))` 关掉 —— ink 一出现文字就变灰读不出）。 */
  /* ⭐⭐⭐ 2026-10-07 19:55 字重 600 → **500**、圆角 → 与右侧主按钮同为 10px
     （乐哥 19:42：「选中的那个颜色，也得和新增排期那个一致吧」）
     ────────────────────────────────────────────────────────────────────────────────
     ▸ 实测（1920）：底色**本来就是同一个令牌** `--color-accent` = #0064E0，两边一致；
        真正的残差只有两处：字重 **600 vs 500**、圆角 **6/9 vs 10**。
     ⚠️⚠️ 但这轮的"一致"是**假一致**（10-08 复盘才看清）：只比了 `backgroundColor`，
        **漏比 `background-image`** —— 主按钮有品牌渐变、胶囊是纯色，肉眼一眼看出差别
        （乐哥 10-08：「选中的样子要做的和右边新增达人一样，是蓝色渐变」）。
        ⇒ 教训：判"两个元素是不是同一个色"，必须 **backgroundColor + backgroundImage 一起比**。
     ▸ 为什么字重要降：`.btn` 用 `--font-weight-medium`(500)，胶囊用 600。
        同为主色的两个元素，字重却不同 ⇒ 并排看"选中项更重"，读起来不像一套。
     ⚠️ 10-07 当时只改**页头这一处**（`--in-header`）；10-08 起卡内 `.table-tabs`
        （本文件上一条规则）与页头**统一接同一渐变**，两处同源。
     ⚠️ 圆角从 `6px 9px 9px 6px`（左侧两角小，贴着托盘内圆角）改为等值 10px：
        托盘圆角 10、胶囊 10 ⇒ 视觉上"托盘套胶囊"的两层圆角不再互相打架。 */
  /* 字重已上移到「文字层」规则（与 color 一起：ink 有无都要） */
  /* ⚠️ 必须用令牌而不是 `10px` —— 门禁 tests/radius-tokens.test.js ③ 硬判
     「border-radius 一律写令牌/白名单值」。（第一版写了 10px 就被它拦下，
     这条门禁本来就是干这个的。）`--radius-md` = 10px，与 `.btn` 同值。 */
  border-radius: var(--radius-md);
  /* ⭐ 2026-10-08 选中态浮起（乐哥动效第②条）—— 与卡内族/胶囊族同一令牌 */
  box-shadow: var(--shadow-pill-active);
}
/* 胶囊形态不需要下划线指示条（下划线是「贴表格」形态的指示方式） */
.table-tabs--in-header .pill-filter__item.is-active::after,
.table-tabs--in-header .seg__btn.is-active::after {
  display: none;
}

/* ── 列表页筛选控件：全站统一高度（2026-09-14）────────────────────────────────
   由 app.js 的 fixPageTabs 给「含表格的页面」的工具栏打上 `.toolbar--list`。
   统一为 **34px**：搜索框 / 下拉 / 日期选择器 / 重置按钮 全部同高。

   ⚠️ 为什么需要「跨页统一」而不只是"同一行统一"（乐哥两次反馈逼出来的）：
     ① 第一次：同一行里三种高度（32/42/30px）→ 已修
     ② 第二次：「日期选择控件尺寸和其他的不对」→ 同一行里 44 vs 34 → 已修
     ③ 第三次（本门禁 `probe-toolbar-height.js` 抓出）：**跨页也不一致** ——
        订单业绩 / 业绩总表 的工具栏**本来就在卡内**、不参与合并，
        于是它们的筛选控件仍是 44px，而其它页已是 34px。
     只按"已合并的工具栏"写规则，就会漏掉这类页面。
     → 判据改成「这页有表格」即套用统一高度，跨页才真正一致。

   ⚠️ 覆盖 `.mp__trigger`（日期选择器）时必须用**后代选择器**：
      它的真实层级是 `span.mp-host > div.mp > button.mp__trigger`（是孙子不是儿子），
      写 `.toolbar--list > .mp__trigger` 会一条都匹配不上（实测踩过）。 */
.toolbar--list .select,
.toolbar--list .input,
.toolbar--list .input--search,
.toolbar--list .input--search > .input,
.toolbar--list .mp__trigger,
.toolbar--list .btn {
  height: var(--control-height-sm);
  min-height: var(--control-height-sm);
}

/* ══════════════════════════════════════════════════════════════════════════════
   ⭐⭐ 2026-09-26 两档制**全局兜底**（乐哥：「令牌化 + 全局兜底」）
   ──────────────────────────────────────────────────────────────────────────────
   ⛔ 改前的问题：紧凑档（34px）只写在 3 个**具体选择器**里
      （`.toolbar--list` / `.toolbar--plain` / `.table-tabs > .table-toolbar__right`）——
      ⇒ 任何**没加这些修饰类**的工具栏就回落标准档 44px。
      实测踩到：业绩总表搜索框 44px（它的容器是**普通 `.toolbar`**，没加 `--list`）、
      报销/结算工具栏的「刷新」36px 与旁边 select 44px 并排。
   ✅ 改法：**按容器兜底**，不按修饰类 —— `.toolbar` 内的一律 34px。
      这样"新页面把控件放进工具栏"就**自动**是对的，不依赖谁记得加修饰类。
   ⚠️ 刻意**不**覆盖的场景（各保持原样，理由都成立）：
      · `.page-actions`（页面头部主按钮）—— 不在 toolbar 内 ⇒ 自动不受影响 ✅
      · 弹窗/抽屉里的控件 —— 不在 toolbar 内 ⇒ 保持标准档 44px ✅
      · 分页区单独一条（在上面，因为它常常**不在** toolbar 内、而在卡片底部）
   ⚠️ `.btn--lg` 要排除：它是"页面头部级"的大按钮，被塞进工具栏时也该保持标准档
      （目前无此用法，但排除掉更稳 —— 免得将来有人放进去却静默变 34）。
   ══════════════════════════════════════════════════════════════════════════════ */
.toolbar .input,
.toolbar .select,
.toolbar .input--search > .input,
.toolbar .mp__trigger,
/* ⭐ 2026-09-27 补 `.dtp-trigger`（日期选择器触发器）：
   乐哥看了「达人分账附表」工具栏后指出控件不齐。实测 dtp-trigger 基础值 40px，
   而兜底规则里**没有它** ⇒ 放进工具栏仍是 40px（两档制外的第三档，与 34px 的
   输入框/按钮混排肉眼可见地不齐）。补上后工具栏内所有控件归紧凑档 34px。 */
.toolbar .dtp-trigger,
/* ⭐ 2026-09-29：`.fp__box`（飞书选人框）同步纳入兜底 ——
   它的基础值刚归位到标准档 44px；若将来被放进工具栏，这里把它兜到紧凑档 34px。 */
.toolbar .fp__box,
.toolbar .btn:not(.btn--lg) {
  height: var(--control-height-sm);
  min-height: var(--control-height-sm);
}
/* 工具栏内的图标按钮也归一档（否则 icon-btn 的 34 与别的混排仍可能不一致） */
.toolbar .icon-btn {
  width: var(--control-height-sm);
  height: var(--control-height-sm);
}
/* ⭐ 2026-09-29：工具栏内的控件**保持原左右留白 12px** ——
   标准档那次的"留白 12→16"（呼吸感）不该波及这里：工具栏控件本来就是紧凑档 34px、
   且宽度普遍很窄（实测「全部状态」下拉仅 102px），多加 4px 留白文字就没有余量了。
   ⇒ 按容器区分：表单区（标准档）要呼吸感，工具栏（紧凑档）要省空间。
   ⚠️ 只写 `padding-left`、**不动 padding-right** —— 右侧留白各控件有各自的精确值
      （`.select` 32px 给箭头、`.pagination__size` 26px 是自己调过的），
      写简写或统一值会把这些覆盖掉（实测踩到：分页选择器被改成 32px）。
   ⛔⛔ 必须用 `>` 限定「工具栏**直接子级**」（2026-09-29 实测踩到真回归）：
      本文件(table.css)在 components.css **之后**加载 ⇒ 同特异性(0,2,0)下本规则胜。
      不加 `>` 时，它会连带覆盖两类**自带精确左留白**的二级控件 ——
        · `.input--search > .input`（需 32px 给放大镜图标留位）⇒ 图标压住文字；
        · `.pagination__size`（自带 10px）。
      真机实测（改前 → 误伤后）：搜索框 32 → 12、分页 10 → 12，两者都中招。
      它们都在二级（`.input--search` / `.table-toolbar__right` 之内），`>` 天然避开。 */
.toolbar > .input,
.toolbar > .select {
  padding-left: var(--space-3);
}

/* ══════════════════════════════════════════════════════════════════════════════
   ⭐ 2026-09-26：操作列的图标按钮**不许被压缩**（乐哥：「操作列的按钮、文字等」）
   ──────────────────────────────────────────────────────────────────────────────
   ⛔ 改前的隐形问题：操作列若放 3 个图标按钮（货品/达人管理那种 日志+编辑+删除），
      列宽不够时 **flex 会把每个按钮从 34px 压到 26px** ——
      按钮自己的高度还是 34（= 一列里按钮宽度参差），且图标(16px)几乎贴住相邻按钮。
      实测：货品页 td 可用宽 83px、3 个按钮实需 102px ⇒ 每个被压到 26px。
   ✅ `flex: none` 让按钮**保持声明尺寸**，宁可让列宽参与布局（列宽由表格分配）。
      ⚠️ 若加宽后表格超容器，会出横向滚动条 —— 那说明**该表列宽本身需要调整**
         （属于"列宽统一"的范畴，见下一条注释），而不是靠压扁按钮来硬塞。
   ══════════════════════════════════════════════════════════════════════════════ */
.table td .icon-btn,
.table td .btn {
  flex: none;
}

/* ⭐ 2026-09-26：操作列的 td 收窄**左侧**内边距（给按钮腾空间）
   ──────────────────────────────────────────────────────────────────────────────
   背景：操作列常放 2~3 个按钮（34px 各），而列宽由表格分配 ——
        实测货品页 3 个按钮需 102px + 内边距(12+20)=134px，但列只有 115px
        ⇒ 加 `flex:none` 保住按钮尺寸后，表格**溢出 3px**（出横向滚动条）。
   ✅ 收窄**左**内边距（12 → 4，省 8px）：操作列是"右侧动作区"，
      左侧那点留白没有对齐意义；**右侧 20px 保留不动** ——
      它有明确约定（最右列留白对齐卡片内的工具栏，见本文件上方注释）。
   ⚠️ 判据用 `:has(> .toolbar)` —— 本项目操作列的按钮都包在 `.toolbar` 里
      （见各页渲染函数），比 `td:last-child` 更精确（末列不一定是操作列）。
   ══════════════════════════════════════════════════════════════════════════════ */
.table:not(:where(td .table)) > tbody > tr > td:has(> .toolbar) {
  padding-left: var(--space-1);
}

/* ══════════════════════════════════════════════════════════════════════════════
   ⭐⭐ 2026-09-26 操作列「按钮统一」（乐哥：「操作列的按钮，都看着统一下，展示方式也优化下」）
   ──────────────────────────────────────────────────────────────────────────────
   ⛔ 改前是**三种形态混用**（全站 14 张表 · 真机普查 `probe-td` 逐格量过）：

     | 形态 | 出现在 | 问题 |
     |---|---|---|
     | ① 纯图标（灰/红） | 9 张表（达人/货品/排期/费用…） | 本身没问题，但图标色**三档不齐**（见下） |
     | ② **蓝色实心文字 + 图标** | 库存「入库」/ 业绩「维护」/ 结算「确认」 | **与页面级主按钮撞权重** |
     | ③ 灰描边文字 + 图标 | 签约公司「查看」/ 用户管理「改角色・改部门」/ 日志详情 | 与②同是"业务动作"，长相却完全不同 |

   ⛔ 具体量到的三处「不齐」：
     · **图标颜色 5 种**：`data-log` 是 `--color-fg-2`(#3F4753，最深)、`data-edit` 是
       `--color-muted`、`data-confirm/finish/void/copy` 全是 `--color-fg-2`、
       `data-del` 红、`pc-del`（比价页删除）**却是深灰不是红**、`data-unlock` 是**黄色**
       （#F2A918 —— 全站唯一的黄色按钮，在一列灰/蓝里格外突兀，且白底对比度仅 ~2:1）
     · **图标尺寸**：佣金配置页 14px、比价页 15px、其余 16px
     · **动作顺序**：库存是「入库 → 日志」（业务在前）、其余是「日志 → 业务」；
       排期是「日志 → 编辑 → 删除 → 解锁」（业务动作排到了最末）

   ✅ 解法（**按语义分三档，一处 CSS 全覆盖全站 14 张表，不动任何页面的 JS 类名**）：
     · **业务文字按钮**（入库/维护/确认/查看/改角色/改部门/日志详情）
       → `--button-soft-bg` 软蓝底 + 蓝字（`.btn--primary` 与 `.btn--ghost` 统一成同一张脸）
       ⇒ 页面右上角的真 CTA 重新成为"全页最重的按钮"
     · **图标·辅助类**（变更日志/编辑/更多/成本类目）→ 中性灰，hover 变蓝
     · **图标·业务类**（确认锁定/解锁/成本校验/完成/作废/复制）→ 品牌蓝
       （⚠️ 只给"动作"上色，一列里**有色的就是能改变数据状态的**，扫一眼就能找到）
     · **图标·危险类**（删除）→ 红（含比价页漏网的 `.pc-del`）

   ⚠️⚠️ 为什么用「`.table td` 后代」做判据而不是给按钮加新类名：
     操作列在全站是 **13 个页面各写一份**渲染函数（本项目刻意不复用，见 AGENTS.md），
     逐个改类名 = 改 13 个文件 + 以后新页面必然漏。而**"在表格单元格里"是稳定判据** ——
     真机普查确认：全站 14 张表**只有末列（操作列）的 td 里有按钮**，非末列一处都没有。
   ⚠️ 特异性说明：`.table td .btn--ghost`(0,2,1) 高于 `.table .icon-btn[data-edit]`(0,3,0)？
     不是 —— 类/属性数前者是 2、后者是 3 ⇒ 后者更高。故下面"辅助类"那条必须写足
     `.table td .icon-btn[data-x]`(0,3,1) 才能盖住既有规则。已逐条对齐。
   ⚠️ 内联 `style="color:..."` 会盖过本段所有规则 —— 排期页的锁定/解锁按钮原来带内联色，
      已**同步从 JS 里删掉**（见 schedule-list.js 同批改动），此处才管得住。
   ══════════════════════════════════════════════════════════════════════════════ */

/* ⓪ 相邻的**文字按钮**之间必须补足间距（2026-09-26 乐哥：「按钮也粘一起了」）
   ──────────────────────────────────────────────────────────────────────────
   背景：全站 32 处操作列的容器都写着内联 `style="gap:2px"` —— 那是为**图标按钮**定的
        （16px 图标 + 2px 间距 = 紧凑的一排，像一组）。但操作列里还有**文字按钮**
        （用户管理的「改角色」「改部门」、报销后台的「详情」、达人结算的「登记回款」…），
        两段文字之间只隔 2px ⇒ **读起来是糊成一坨**，且两段文字的边界看不清。
   实测（/settings/user-management）：`.btn--ghost` ↔ `.btn--ghost` 的间距 = **2px**。
   ✅ 修法：只在"**前一个也是文字按钮**"时补 6px（合计 8px）。
      ⚠️ 用 `.btn + .btn` 而不是 `td > .toolbar > * + *`：
         后者会把图标按钮也撑开（图标排布就该紧凑），破坏上面那条"紧凑一排"的设计。
      ⚠️ 图标按钮的类名是 `.icon-btn`、**不含 `.btn`**，所以 `.btn + .btn` 精确命中
         "文字按钮紧跟文字按钮"，零副作用。
      ⚠️ 用 margin 而不是改裸的 gap：gap 写在 JS 的内联样式里（32 处），
         改 CSS 压不住内联；margin 则能正常叠加在 gap 之上。 */
.table td > .toolbar > .btn + .btn {
  margin-left: 6px;
}

/* ① 业务文字按钮：蓝实心 / 灰描边 → **软蓝**（统一的"行内次级动作"脸） */
.table td .btn--primary,
.table td .btn--ghost {
  background: var(--button-soft-bg);
  background-image: none;                /* 清掉主按钮的品牌渐变（它是"页面级 CTA"的语言） */
  color: var(--button-soft-fg);
  border-color: transparent;
  font-weight: var(--font-weight-semibold);
}
.table td .btn--primary:hover:not(:disabled),
.table td .btn--ghost:hover:not(:disabled) {
  background: var(--button-soft-bg-hover);
  color: var(--button-soft-fg);
}
/* ⚠️ 保活 `.btn--ghost[data-del]`（红字删除）—— 它在操作列里是危险动作，不能被软蓝盖掉。
   特异性：本条 (0,3,1) > 上面 (0,2,1) ⇒ 生效。 */
.table td .btn--ghost[data-del] {
  background: transparent;
  color: var(--color-danger);
}
.table td .btn--ghost[data-del]:hover:not(:disabled) {
  background: var(--color-danger-bg);
  color: var(--color-danger);
}
/* ② ⛔ 替票审核按钮的那两条属性选择器规则（`.table td .btn--ghost[data-tk-ok/-no]`）
   **已于 2026-10-10 删除** —— 审核按钮改成走全站唯一实现（`Pages.tTickets.verifyOpsHtml`），
   上色改用 `.ap-invbtn--pass / .ap-invbtn--fail` 两个类（见 `pages.css`），
   `data-tk-ok/no` 这两个属性**再也没有任何地方产出** ⇒ 留着就是死规则。
   ▸ 当年加它们是为了"达播侧那两个内联色将来统一过来"——**那个统一已经在今天完成了**，
     只是落点变成了类名而不是属性选择器（属性名还得两处对齐，类名天生只有一份）。
   ⛔ 别把这两条加回来：门禁 `tests/finance-tt-verify.test.js` ⑤ 会判红。 */

/* ② 图标·辅助类（变更日志 / 编辑 / 更多操作 / 成本类目）→ 中性灰 */
.table td .icon-btn[data-log],
.table td .icon-btn[data-edit],
.table td .icon-btn[data-edit-live],
.table td .icon-btn[data-ops],
.table td .icon-btn[data-allocate] {
  color: var(--color-muted);
}
.table td .icon-btn[data-log]:hover,
.table td .icon-btn[data-edit]:hover,
.table td .icon-btn[data-edit-live]:hover,
.table td .icon-btn[data-ops]:hover,
.table td .icon-btn[data-allocate]:hover {
  color: var(--color-accent-text);
  background: var(--color-primary-100);
}

/* ③ 图标·业务类（确认锁定 / 解锁 / 成本校验 / 完成 / 作废 / 复制）→ 品牌蓝
   （"有色的就是能改变数据状态的"，与辅助灰形成可扫读的区分） */
.table td .icon-btn[data-lock],
.table td .icon-btn[data-unlock],
.table td .icon-btn[data-confirm],
.table td .icon-btn[data-check],
.table td .icon-btn[data-finish],
.table td .icon-btn[data-void],
.table td .icon-btn[data-copy] {
  color: var(--color-accent-text);
}
.table td .icon-btn[data-lock]:hover,
.table td .icon-btn[data-unlock]:hover,
.table td .icon-btn[data-confirm]:hover,
.table td .icon-btn[data-check]:hover,
.table td .icon-btn[data-finish]:hover,
.table td .icon-btn[data-void]:hover,
.table td .icon-btn[data-copy]:hover {
  color: var(--color-accent-hover);
  background: var(--color-primary-100);
}

/* ④ 图标·危险类（删除）→ 红
   ⚠️ 含机制比价页的 `.pc-del` / `.pc-rec-del` —— 它们原先**漏配了语义色**，
      用的是默认深灰，与其它页真正的"删除红"不一致（同一个动作两种颜色）。
   ⚠️ `.table .icon-btn[data-del]` 的基础规则已存在（components.css），
      此处只补这两条漏网的类。 */
.table td .icon-btn.pc-del,
.table td .icon-btn.pc-rec-del {
  color: var(--color-danger);
}
.table td .icon-btn.pc-del:hover,
.table td .icon-btn.pc-rec-del:hover {
  color: var(--color-danger);
  background: var(--color-danger-bg);
}

/* ⑤ 图标尺寸统一 16px（原 14/15/16 三种混用）
   ⚠️ 用 `svg` 后代选择器把**渲染尺寸**钉住，不依赖各页面 `I.icon(x, n)` 传的 n ——
      否则"改了 CSS 却还有页面是 14px"（因为 svg 自带 width/height 属性）。
      这样即使某个页面漏改 JS 的尺寸参数，视觉也一致。 */
.table td .icon-btn > svg,
.table td .btn > svg {
  width: 16px;
  height: 16px;
}

/* ══ 二级明细页：内容区统一为「白底卡片」（2026-09-14 乐哥：「有的有底色，有的没底色，统一下」）
   ══════════════════════════════════════════════════════════════════════════════
   实测三张明细页**三种底色**（逐页采样）：
     · 结算明细     `.st-drill`  → **#F5F6F8**（页面灰底）
     · 提成明细     `.co-expand` → **#FAFBFC**（surface-2）
     · 华大对账明细 `.hs-drill`  → **透明**（露出的其实是页面灰底）
   三种底、三张皮 → 乐哥说"乱七八糟"完全准确。

   根因是**类名复用**：`.st-drill`（pages.css:2507）/`.co-expand`（pages.css:2486）/
     `.hs-drill`（pages.css:3149）本来是**列表页「行内展开面板」**的样式
     （注释都写着"用底色把展开明细与主表分层"），被明细页拿去当"明细区容器"用了
     → 于是各自继承了一身不同的"展开面板皮"。

   ⚠️ 注意 `.hs-drill` 那条：今天早些时候已为它做过一次"去灰底"
      （pages.css:3158 `.detail-drill .hs-drill { background: transparent }`），
      当时的注释写「transparent 露出白卡片」——**但那时并没有白卡片**，
      所以实际露出的是页面灰底，等于只做了一半（灰底没了、卡片也没补上）。
      本次把这一半补完。

   解法：在**明细页容器 `#dt-body`** 作用域内，把三个内容容器统一成白卡
     （与列表页 `.card` 同规格：白底 + 1px 边框 + 12px 圆角）。
     → 全站只剩「灰底页面 + 白卡内容」一种层次，与列表页同构。

   ⚠️ 为什么用 `#dt-body`（ID 选择器）而不是 `.detail-drill`：
      ① `.detail-drill` 只被结算/华大两页使用，**提成明细用的是 `#dt-commission`**，
         用它做作用域会漏掉提成明细；
         而 `#dt-body` 是 detail.js 的 `setBody()` 统一注入点，**三页都经过它**，是可靠判据。
      ② 已有的 `.detail-drill .hs-drill` 规则特异性是 (0,2,0)，
         普通类选择器（如 `.detail-drill > *:not(...)`）压不过它 —— 第一版就这么输了，
         实测 `.hs-drill` 一直保持透明。ID 选择器 (1,0,0) 稳定胜出。
   ⚠️ 必须清掉 `position: sticky`：`.st-drill` 在 ≥1024px 有 sticky
      （pages.css:2526，给展开面板横向滚动钉左用），明细页没有横向滚动容器。 */
#dt-body .st-drill,
#dt-body .hs-drill,
#dt-body .co-expand {
  background: var(--color-surface);
  border: 1px solid var(--color-border-soft);
  border-top: 1px solid var(--color-border-soft);   /* 覆盖展开面板那条 2px 上边界 */
  border-radius: var(--radius-lg);
  padding: var(--space-4) var(--space-5);
  position: static;
}
/* 白卡内的表格不再重复铺底（含已被旧规则设过灰底的 `.hs-drill .table--sub`） */
#dt-body .table-wrap,
#dt-body .hs-drill .table--sub {
  background: transparent;
}

/* ⭐⭐ 2026-10-16 乐哥（截图圈住「业绩构成」那张表）：「这块套了太多个框」
   ──────────────────────────────────────────────────────────────────────────
   上面的 `#dt-body .st-drill` 白卡（2026-09-14 定）与内容**自己**的 `.card`
   实测是**两层一模一样的白卡叠着**：
       card      四边宽 1px/1px/1px/1px  bg=#FFF  r=16px
       st-drill  四边宽 1px/1px/1px/1px  bg=#FFF  r=16px
   （2026-09-14 那会儿明细页内容是**平铺**的，那层白卡是对的；
     2026-10-15 乐哥拍板「按设计规范分模块」后，两块内容各自已经装进 `.card` ⇒ 重复了。）

   ⇒ 页面语境的 `.st-drill` 退回**纯容器**：透明、无框、无圆角、无内边距、不约束宽度。
      与同板块「提成明细」（`#dt-commission > .detail-stack`，本来就没有这层容器）同构 ——
      白卡只有一层，卡片直接受页面 padding 管辖（与页头同一条对齐线）。

   ⚠️ 选择器必须写 `#dt-body .st-drill.st-drill--page`（(1,2,0)）：
      上面那条 `#dt-body .st-drill`（(1,1,0)）特异性更低，这样才稳赢，不靠源码顺序。
   ⚠️ `width` / `max-width` 也要清：`.st-drill` 在 ≥1024px 有一条
      `width: calc(100vw − 侧栏 − 页面留白 …)`（原给**行内展开面板**横向滚动钉左用的），
      明细页没有横向滚动容器，留着会让内容比页面窄一截、与页头对不齐。
   ⛔ 行内展开区分支（非 page）**不带** `st-drill--page` ⇒ 展开面板那身皮原样保留。 */
#dt-body .st-drill.st-drill--page {
  background: transparent;
  border: 0;
  border-top: 0;
  border-radius: 0;
  padding: 0;
  width: auto;
  max-width: none;
  position: static;
}

/* ══ 二级明细页的表格：用「正文规格」而不是「子表规格」（2026-09-14）
   ══════════════════════════════════════════════════════════════════════════════
   乐哥：「达人结算里的结算明细，二级页面和其他地方的二级页面表格界面不一样，看下怎么处理」

   实测（12 页普查 · probe-table-density.js）：**全站正文基准 = 14px / 上下内边距 12px**，
     · 主列表页 6 页**全部一致**（达人列表/货品/库存/订单/排期/达人售后）
     · 结算明细 14px ✓ 12px ✓   提成明细 14px ✓ 12px ✓   ← 两者本就合规
     · **华大对账明细 12px ✗ 8px ✗**                       ← 唯一不合规的
   → 所以「不一样」的是**华大对账明细**，不是结算明细（此处先前的判断方向相反，已更正）。

   根因仍是**类名复用**：`.table--sub` 的规格（components.css:1308）本是给
     **「嵌套在主表里的子表」**用的 —— 8px 内边距 + 12px 字号（子表该比主表小一号）。
     华大对账明细把它拿来当**主表**（huada-settlement.js:484/492），于是整表偏小偏紧。

   解法：只在**二级明细页作用域**（`#dt-body`）内，把 `.table--sub` 提回正文规格：
     表格字号 12→14px、**数据行**上下内边距 8→12px。
     ⚠️ **表头完全不动**（字号仍 12px、上下内边距仍 8px）——
        全站表头标准就是「12px 字 + 44px 高」，这是一条独立的标准线。
        第一版顺手给 th 也加了 12px 上下内边距，实测把表头从 **44px 撑到 59px**
        → 被 `probe-table-standard.js` 的 ① 抓出（"表头 59px ≠ 44px"）。
     ⚠️ **副文本（.cell-sub 等）不动**（保持 12px）—— 它们是显式声明，属"主 14 + 副 12"的正常层级。
     ⚠️ 列表页展开区的 `.table--sub` **不受影响** —— 那里它就是子表，8px/12px 是对的。
        这就是限定 `#dt-body` 作用域的原因。 */
#dt-body .table--sub {
  font-size: var(--font-size-sm);
}
/* ⚠️ 必须显式把表头字号钉回 12px：上一条 (1,0,1) 的特异性高于
   `.table--sub th` (0,1,1)，不钉会被一起改成 14px、破坏表头标准。 */
#dt-body .table--sub > thead > tr > th {
  font-size: var(--font-size-xs);
}
/* 只提**数据行**的上下内边距（表头保持原 8px，见上） */
#dt-body .table--sub > tbody > tr > td {
  padding-top: var(--space-3);
  padding-bottom: var(--space-3);
  font-size: var(--font-size-sm);
}

/* ── 「轻量形态」：只有单一月份控件的页面（2026-09-14 乐哥选 B 方案）──────────────
   乐哥选 **B（按需）**：「有筛选就套标准工具栏；**只有"月份"这种单一控件的，
   就让它和白卡贴合、控件 34px，不再包一层工具栏**」。

   覆盖：华大对账 / 成本总览（只有月份选择器）、我的提成（月份 + 重置）。
   做法：**不铺白底、不描边** —— 就是内容上方一行轻量控件，
        避免"为了统一而多出一层空壳"（那样反而更乱）。
   ⚠️ 与 `.toolbar--join-card`（铺白底 + 上圆角、与下方卡片拼成一体）是**两种形态**，
      由 JS 按"筛选控件是否只有一个且为月份"自动判定，不手工指定。
   ⚠️ 必须自己带 34px 规则：我的提成页**没有表格**（不满足 `.toolbar--list` 的前置条件），
      不声明的话它的月份选择器仍是 44px（实测跨页不一致）。 */
.toolbar--plain {
  margin: 0 0 var(--space-3);      /* 与下方内容贴合（不是飘着的独立工具栏） */
  padding: 0;
  background: transparent;
  border: 0;
  border-radius: 0;
}
.toolbar--plain .select,
.toolbar--plain .input,
.toolbar--plain .mp__trigger,
.toolbar--plain > .input--search > .input,
.toolbar--plain .btn {
  height: var(--control-height-sm);
  min-height: var(--control-height-sm);
}

/* ── 售后页「口径说明行」里的筛选下拉也要 34px（2026-09-14）──────────────────────
   达人售后 / 商品售后 的月份与店铺下拉长在 `.an-flex-between`（口径说明行）里，
   用的是原生 select + `.input` 类，**不在任何 `.toolbar` 内**，
   所以 `.toolbar--list` 那条规则覆盖不到 → 实测它们一直是 44px。
   ⚠️ 只改高度、不动位置 —— 「筛选控件与口径说明混排」是另一个话题
      （要动 DOM 结构，风险更高），本轮的诉求是"标准统一"，先把尺寸对齐。 */
.an-flex-between select.input,
.an-flex-between .input {
  height: var(--control-height-sm);
  min-height: var(--control-height-sm);
}

/* ── 订单明细主表：把宽度让给「商品」列（2026-09-14 乐哥：「列表超出了页面的边界，需要左右拖动」）
   ─────────────────────────────────────────────────────────────────────────────
   问题：`table-layout: auto` 下所有列都参与"分配剩余宽度"，商品名列虽是弹性列却没优先权，
        实测「达人」列被压到 **48px**（"坚强的糖豆" 折成 3 行）、商品列只剩 89px（长名折 4~5 行），
        而整表仍溢出容器 25~209px（要横向拖）。
   修法（与 `.sdo-sumtbl` 同一套，见 pages.css）：
        **除「商品」列以外的所有列锁死单行**（`nowrap`）→ 它们的 min-content == max-content，
        浏览器压不动它们，只能把富余宽度全部让给唯一还能换行的商品列。
   ⚠️ 用 `:not(:nth-child(4))` 定位商品列 —— 列序：①勾选 ②订单号 ③达人 **④商品**
       ⑤收款 ⑥退款 ⑦净额 ⑧归属 ⑨备注 ⑩操作。
       ⚠️ 勾选列在多选模式下才出现，但它固定在**第一位**，商品列始终是第 4 个 → 定位稳定。
   ⚠️ 表头不用管：全站 `.table thead th` 本来就是 `nowrap`（本文件 27 行）。
   ⚠️ 新开作用域类、**不动** `.is-num` / `.num` 等公共类 —— 它们全站几十张表在用。 */
.so-detail-tbl tbody td:not(:nth-child(4)) {
  white-space: nowrap;
}
/* 商品列是唯一可换行列 → 限宽 + 裁剪，超长名折 2 行内 */
.so-detail-tbl tbody td:nth-child(4) {
  max-width: 320px;
  overflow: hidden;
}

/* 展开行内的子表（达人明细），更轻盈：字体小一号 */
.table--sub {
  font-size: var(--font-size-xs);
  table-layout: fixed;
}
/* ⚠️ 2026-09-14 乐哥拍板「所有列表统一标准」：子表表头**不再单独一套皮**。
   改前 `.table--sub thead th` 是 `transparent 底 + 28px 高 + muted 字`，
   而主表 `.table thead th` 是 `浅灰底 + 44px 高 + 12px 字` —— 两套皮并存，
   表现为「二级明细页（华大/结算）的表头比一级列表矮一截、还没有底色」，
   乐哥反馈「列头的高度和颜色，很多表格都不一致」说的就是它。
   （`table--sub` 在本项目既用在真嵌套子表、也用在二级页的顶层表上，语义已漂移 ——
    这是同一个坑的第三张脸，前两张分别是首末列留白与灰色底。）
   现改为**只保留 position: static**（子表在自己的容器内，不需要 sticky 表头），
   背景/高度/字色/字号全部继承主表 → 全站表头收敛为一种。 */
.table--sub thead th {
  position: static;
}
/* ── 列间竖线：**只在大类边界**（2026-09-14 乐哥二次修正 + 参考微信小店）
   ─────────────────────────────────────────────────────────────────────────
   乐哥原话：「也不用所有表格都加竖线，我觉得微信小店这种，大类加竖线的就很好」

   改前（v1，同一天早先版本）：`td + td` 给**每一列之间**都加线 —— 太密。
   微信小店实测（像素级分析三张参考图）：
     · 订单表只有 **2 条**竖线，位置是把「实收款/优惠信息」这个独立信息组夹在中间；
     · 商品表 / 带货者表是 **0 条**（那些表没有需要分隔的信息组）；
     · 线色 rgb(229,229,229)=#E5E5E5，1px，**贯穿表头 + 全部数据行**。
   → 结论：竖线是**大类分隔**，不是列分隔。

   现行规则（由 app.js 的 `UI.markColumnGroups()` 打标，CSS 只负责画）：
     只在「第一个数值列之前」和「最后一个数值列之后」各画一条，**每表最多 2 条**。
     数值列用既有的 `.is-num` 判定（该类的含义就是"数值列"，全站 13/14 张表覆盖一致）。
   好处：
     · 一张表内最多 2 条 → 不会变密；
     · 没有数值列的表（如达人列表）自动 0 条 → 与微信小店的商品表同理；
     · 复用 is-num，不新增维护点（新增列时只要正常标 is-num 就自动获得正确分组）。

   ⚠️ 为什么用 **box-shadow: inset** 而不用 `border-left`：
      项目已有若干**语义竖线** —— `st-col-fcast`（提成总览「应发星盟」分组线）、
      `st-drill-pendrow`（未满 35 天行左侧虚线）、`st-col-payable/fcasthead` 等。
      `border-left` 与它们是**同一个属性**，本类优先级更高会把语义线整条覆盖掉 ——
      线还在、但含义变了，极难排查。box-shadow 与 border 是两个独立属性，可叠加共存。
      另外 inset 阴影**不占布局宽度**：用 border 的话 12 列表会多 12px，
      而结算明细 10 列本就贴着容器下限（再窄 53px 就横滚）。 */
.table .col-sep,
.table--sub .col-sep {
  box-shadow: inset 1px 0 0 var(--color-border-soft);
}
/* 订单业绩天维度：主表固定布局 + colgroup 固定数字列宽，与展开子表精确对齐 */
.so-table--fixed {
  table-layout: fixed;
}
/* 展开子表所在的单元格去掉内边距，让子表左边界与主表对齐（上下间距由 .so-expand 提供） */
.table tbody td.so-expand-cell {
  padding: 0;
}
/* ⚠️ 2026-09-14 删除：此处原有 `.table--sub thead th { background: transparent;
   color: muted; height: 28px; ... }` —— 与 `.table thead th`（浅灰底/44px）并存的第二套皮，
   就是「二级页表头矮一截、没底色」的根源。已并入上方统一规则（只留 position: static）。 */
.table--sub tbody tr:hover {
  background: transparent;
}
.table--sub tbody td {
  padding-top: var(--space-2);
  padding-bottom: var(--space-2);
}
/* 子表数字不加粗（与主表数字列视觉一致） */
.table--sub .is-num {
  font-weight: normal;
}
/* 子表达人列自适应宽度（数字列用 colgroup 固定宽度与主表对齐，达人列吃剩余空间） */
.table--sub .sub-influencer {
  min-width: 0;
  overflow: hidden;
}
/* 天维度展开子表：未匹配达人整行标灰 */
.table--sub tbody tr.so-unmatched {
  background: var(--color-gray-50);
}
.table--sub tbody tr.so-unmatched:hover {
  background: var(--color-gray-50);
}
/* 天维度展开子表：底部当日小计行 */
.table--sub tbody tr.so-subtotal {
  border-top: 1px solid var(--table-divider);
  background: var(--color-surface);
  font-weight: var(--font-weight-medium);
}
.table--sub tbody tr.so-subtotal td {
  font-weight: var(--font-weight-medium);
  color: var(--color-fg);
}

/* ⭐ 2026-09-25：**非子表**的「交叉表合计行」独立定义。
   ⛔⛔ 这条是补一个**隐藏依赖**（删斑马纹时暴露出来的）：
      `.so-subtotal` 共 3 处使用 ——
        · `pages/aftersale.js`（售后分析交叉表）、`pages/analytics.js`（经营看板）
          → 这两处的表是普通 `.table`（售后分析还带 `.table--zebra`），**不在 `.table--sub` 内**
        · `pages/shop-orders-shared.js`（天维度展开子表）→ 在 `.table--sub` 内
      而 `.so-subtotal` 原本只有两个来源：① 上面那条 `.table--sub` 的（只管子表）；
      ② `pages.css` 里 `.table--zebra tbody tr.so-subtotal`（管非子表）。
      ⇒ 删掉斑马纹把 ② 一起删了，结果**非子表的合计行失去了唯一底色**（手工 A/B 实测：
        `rgb(250,251,252)` → 透明）。本条就是把 ② 的职责**独立化**，不再挂在斑马纹上。
   ⚠️ 特异性 (0,1,2) **低于**上面的 `.table--sub tbody tr.so-subtotal` (0,2,2)
      ⇒ 子表仍走 `--color-surface`，非子表走 `--color-surface-2`，两者互不干扰（这是有意的：
        子表嵌在展开行里、底色已由展开区提供，只需极淡的区分）。 */
.table tbody tr.so-subtotal {
  background: var(--color-surface-2);
}

/* ---- 骨架屏 ---- */
.skeleton {
  background: linear-gradient(
    90deg,
    var(--color-surface-2) 25%,
    var(--color-border-soft) 50%,
    var(--color-surface-2) 75%
  );
  background-size: 200% 100%;
  animation: skeleton-shimmer 1.4s ease infinite;
  border-radius: var(--radius-sm);
}

@keyframes skeleton-shimmer {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

.skeleton-table-row {
  display: flex;
  gap: var(--space-4);
  padding: var(--space-3);
  border-bottom: 1px solid var(--table-divider);
  align-items: center;
}

.skeleton-line {
  height: 14px;
  border-radius: var(--radius-sm);
}

/* 「保留表头的加载态」（2026-09-18，乐哥「切 tab 向右移动然后闪一下」）
   --------------------------------------------------------------------------
   切 tab 时不再把整张表换成灰条，而是保留表头、只把表体换成这些 `.is-loading-row` 行
   （实现见 ui.js 的 skeletonInto）。
   ⇒ 表头不消失、列宽不重算 → 切换瞬间页面结构与位置完全不变，看不到"闪"与错位。
   这些行不可交互（不是真实数据），所以去掉 hover 高亮与手型光标。 */
.table tbody tr.is-loading-row,
.table--sub tbody tr.is-loading-row {
  cursor: default;
}
.table tbody tr.is-loading-row:hover,
.table--sub tbody tr.is-loading-row:hover {
  background: transparent;
}
.table tbody tr.is-loading-row > td {
  padding-top: var(--space-3);
  padding-bottom: var(--space-3);
}
/* 灰线放在单元格内：垂直居中，横向留出一点边距（避免贴住列线） */
.table tbody tr.is-loading-row .skeleton-line {
  margin: 0 auto;
  max-width: 88%;
}

/* ---- 空态 / 错误态 ---- */
.state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: var(--space-12) var(--space-6);
  gap: var(--space-3);
}

.state__icon {
  width: 48px;
  height: 48px;
  border-radius: var(--radius-lg);
  background: var(--color-surface-2);
  color: var(--color-muted);
  display: flex;
  align-items: center;
  justify-content: center;
}

.state__title {
  font-size: var(--font-size-lg);
  font-weight: var(--font-weight-medium);
  color: var(--color-fg);
}

.state__desc {
  font-size: var(--font-size-sm);
  color: var(--color-muted);
  max-width: 42ch;
}

/* 空状态下"换个范围就有结果"的出口（如：当前月份没有，但全部月份有 N 条） */
.state__hint {
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px dashed var(--color-border);
  font-size: var(--font-size-sm);
  color: var(--color-fg-2);
}
.state__hint-link {
  color: var(--color-accent);
  font-weight: var(--font-weight-medium);
  cursor: pointer;
}
.state__hint-link:hover { text-decoration: underline; }

/* ---- 搜索范围提示：有月份筛选时，在搜索框下方说明"搜索被限制在哪个月" ----
   ⚠️ 2026-09-14 乐哥：「这些页面我选了月份后，工具栏和下面的内容栏隔开了」
   ────────────────────────────────────────────────────────────────────────
   根因（两层叠加，缺一不可）：
     ① 本类原先 `margin: -8px 0 **12px**` —— 下边距 12px 直接把"工具栏 → 内容"顶开；
     ② app.js 的 `markToolbarJoin` 用**几何判据**决定"工具栏是否与卡片拼成一体"：
        `gap = card.top - toolbar.bottom; if (gap > 24) 不合并`。
        而本提示行隐藏时高 0px（不在判据里），**显示后高 19px + 12px 下边距 = 31px**
        → 恰好越过 24px 阈值 → 工具栏与卡片**不再拼成一体**，视觉上断开一道缝。
   修法：**让提示行在视觉上归属工具栏**（成为它的"第二行"），而不是独立夹在中间：
     · 下边距 12px → 0（不再顶开内容）
     · 补上工具栏的白底 + 左右边框（上边不加，与工具栏相接；下边不加，与卡片相接）
       → 工具栏(白·上圆角) + 提示行(白·左右边框) + 卡片(白·下圆角) 三者连成一体，缝消失
     · 左右内边距对齐工具栏的 --space-5（20px），文字与上面控件左对齐
   ⚠️ 只在「紧跟已合并工具栏」时套这套皮（`.toolbar--join-card + .kw-scope`）——
      其余页面（工具栏没参与合并的）保持原来的轻量样式，避免无谓改动。 */
.kw-scope {
  margin: calc(var(--space-2) * -1) 0 var(--space-3);
  font-size: var(--font-size-xs);
  color: var(--color-fg-2);
  line-height: 1.6;
}
.kw-scope:empty { display: none; }
/* 提示行与工具栏相邻时，只清**提示行自己**的边距，让它贴住工具栏。
   ⛔⛔ 2026-09-26 修（乐哥实报「部分页面工具栏和列表粘一起了」）——
      原来写的是 `.toolbar + .kw-scope, .kw-scope + .toolbar { margin: 0; }`。
      后半个选择器 `.kw-scope + .toolbar` 命中的是**工具栏自己**，于是把 `.toolbar`
      的 `margin-bottom: var(--space-4)`（16px，"工具栏 ↔ 下方表格卡"的缝）**清成了 0**。
      而且它是 (0,2,0) 特异性 —— **压得过** `.toolbar--join-card`（(0,1,0)）的 margin-bottom，
      所以 CSS 加载顺序也救不回来。
      ⇒ 后果：凡是"提示行紧邻工具栏"的页面，工具栏与下方表格卡**完全贴死**（实测 gap=0px）；
        没有提示行的页面正常（gap=16px）—— 所以只有**部分**页面出问题。
        实测受影响：排期列表 / 费用明细 / 样品发放 / 订单明细（都是 gap=0）。
      ✅ 正确做法：**谁的边距该清，就写在谁身上**，绝不在"相邻选择器"里顺手清别人的。
         · 提示行在工具栏**上方**（`.kw-scope + .toolbar` 这种结构，见 ui.js 的 ensureTip）
           ⇒ 清提示行的 `margin-bottom`（用 :has 表达"我后面跟着工具栏"）
         · 提示行在工具栏**下方**（`.toolbar + .kw-scope`）
           ⇒ 清提示行的 `margin-top`（同时保留下边距，它还要与下方内容留缝）
      ⚠️ `.kw-scope:empty` 是 display:none 但**仍在 DOM 里**，所以 `+ .toolbar` 依然会命中 ——
         "空元素不占位所以无所谓"是错觉，这正是本 bug 能藏这么久的原因。 */
/* 两条都只清**提示行自己**的边距 —— 选择器命中谁，就只动谁。
   ⛔ 不要再写任何"命中 .toolbar"的相邻选择器（那正是本 bug 的形态）。 */
.toolbar + .kw-scope { margin-top: 0; }              /* 提示行在工具栏**下方**：清它的上边距 */
.kw-scope:has(+ .toolbar) { margin-bottom: 0; }      /* 提示行在工具栏**上方**：清它的下边距 */
.kw-scope {
  padding: 0 var(--space-5) var(--space-2);
}
.kw-scope b { color: var(--color-accent); font-weight: var(--font-weight-semibold); }
.kw-scope__sep { margin: 0 6px; color: var(--color-fg-3); }
.kw-scope__link { color: var(--color-accent); cursor: pointer; }
.kw-scope__link:hover { text-decoration: underline; }

/* ---- 表格卡片顶部筛选条（左：二级 tab / 右：总数 + 每页条数） ---- */
.table-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  flex-wrap: wrap;
  padding: var(--space-3) var(--space-5);
  border-bottom: 1px solid var(--color-border-soft);
}

.table-toolbar__right {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-left: auto;
}

.table-toolbar__total {
  font-size: var(--font-size-xs);
  color: var(--color-meta);
  white-space: nowrap;
  /* 定最小宽度 + 靠右（2026-09-18，乐哥「切 tab 工具组会跳」）
     ------------------------------------------------------------------
     它是工具组里最靠左的一项，而工具组**右对齐** → 本行文字一变长/变短，
     整组的左边缘就跟着移动。实测切 tab 时的位移：
       样品发放「共 40 条发放记录」→「共 32 位达人」：91→67px（组左移 **24px**）
       订单明细「共 6,375 条订单」→「共 0 条订单」  ：83→60px（**23px**）
       业绩总表 78→70px（8px） / 达人列表 73→67px（6px）
     给定最小宽度后，短文案不再让整组回缩；文字靠右贴住右侧下拉，观感不变。 */
  min-width: 100px;
  text-align: right;
}

/* ---- 分页（左：范围统计 / 右：页码分页器） ---- */
.pagination {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-5);
  border-top: 1px solid var(--color-border-soft);
  font-size: var(--font-size-sm);
  color: var(--color-muted);
}

.pagination__info {
  font-feature-settings: "tnum", "lnum";
  font-variant-numeric: tabular-nums;
  font-size: var(--font-size-xs);
  color: var(--color-meta);
}

.pagination__controls {
  display: flex;
  align-items: center;
  gap: var(--space-1);
}

.pagination__page {
  min-width: 28px;
  height: var(--control-height-sm);   /* 两档制：28 → 34（紧凑档，与同排控件齐平） */
  padding: 0 6px;
  border: none;
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--color-fg-2);
  font-size: var(--font-size-sm);
  font-feature-settings: "tnum", "lnum";
  font-variant-numeric: tabular-nums;
  cursor: pointer;
  transition: background var(--motion-fast) var(--motion-ease-standard),
    color var(--motion-fast) var(--motion-ease-standard);
}
.pagination__page:hover {
  background: var(--color-surface-2);
}
.pagination__page.is-current {
  background: var(--color-accent);       /* 当前页主色实底 */
  color: var(--color-accent-on);
  font-weight: var(--font-weight-medium);
}

.pagination__nav {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: var(--control-height-sm);   /* 两档制：28 → 34（紧凑档，与同排控件齐平） */
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  color: var(--color-fg-2);
  cursor: pointer;
  transition: color var(--motion-fast) var(--motion-ease-standard),
    border-color var(--motion-fast) var(--motion-ease-standard);
}
.pagination__nav:hover:not(:disabled) {
  color: var(--color-accent-text);
  border-color: var(--color-accent);
}
.pagination__nav:disabled {
  color: var(--color-disabled);
  cursor: not-allowed;
}

.pagination__ellipsis {
  min-width: 20px;
  text-align: center;
  color: var(--color-meta);
}

.pagination__size {
  width: auto;
  height: var(--control-height-sm);   /* 两档制：28 → 34（紧凑档，与同排控件齐平） */
  padding: 0 26px 0 10px;
  margin-right: var(--space-3);
  font-size: var(--font-size-sm);
  color: var(--color-fg-2);
  border-radius: var(--radius-md);
  background-position: right 8px center;
}

/* 页码跳转输入：与页码按钮同高、描边一致 */
.pagination__jump {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-left: var(--space-2);
  font-size: var(--font-size-sm);
  color: var(--color-muted);
  white-space: nowrap;
}

.pagination__input {
  width: 48px;
  height: var(--control-height-sm);   /* 两档制：28 → 34（紧凑档，与同排控件齐平） */
  padding: 0 6px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  color: var(--color-fg);
  font-size: var(--font-size-sm);
  font-family: inherit;
  text-align: center;
  outline: none;
  transition: border-color var(--motion-fast) var(--motion-ease-standard);
}
.pagination__input:focus {
  border-color: var(--color-accent);
}
.pagination__input::-webkit-outer-spin-button,
.pagination__input::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

/* ---- 移动端：表格触控滚动 + 分页简化 ---- */
@media (max-width: 768px) {
  .table-wrap {
    -webkit-overflow-scrolling: touch;   /* iOS 惯性滚动 */
    overscroll-behavior-x: contain;
  }
  .pagination {
    flex-wrap: wrap;
    gap: var(--space-2);
    padding: var(--space-3) var(--space-4);
  }
  .pagination__info {
    order: 2;                            /* 统计文案换到操作下方 */
    flex-basis: 100%;
    font-size: var(--font-size-xs);
    color: var(--color-meta);
  }
  .pagination__controls {
    flex-wrap: wrap;
    row-gap: var(--space-2);
  }
}

/* ==========================================================================
   可跳转单元格链接（.cell-link）—— 2026-09-13 补齐
   ---------------------------------------------------------------------------
   ⚠️ 原来**全站没有定义** .cell-link，而 base.css 里有 `a { color: inherit }`
      → 排期编号/场次编号这类可跳转内容显示为**普通深色文字**，
      用户完全看不出能点（乐哥 2026-09-13 反馈）。
   统一为主色蓝：一眼可辨"这里能跳"，hover 加下划线强化可点感。
   ========================================================================== */
.cell-link {
  color: var(--color-primary-500);
  text-decoration: none;
  cursor: pointer;
  transition: color var(--motion-fast) var(--motion-ease-standard);
}
.cell-link:hover {
  color: var(--color-primary-600);
  text-decoration: underline;
}
.cell-link:active { color: var(--color-primary-700); }

/* ==========================================================================
   密集表（列很多的宽表）：**减小水平内边距**，把宽度让给内容
   --------------------------------------------------------------------------
   背景（乐哥 2026-09-13「整体排查表格 ui 问题」）：全站巡检发现两张表超出容器、
   需要左右拖才能看到右侧列（「操作」「预估应结」被推出可视区）：
     · 人员成本  1633px > 容器 1374px（需横滚 259px）
     · 达人结算  1642px > 容器 1374px（需横滚 268px）
   这两张表都是 **16 列**，而列宽下限由「表头文字（nowrap 不可折）」与「内容宽度」共同决定
   （实测：达人结算各金额列内容最宽仅 81px，但表头「截止目前应结」就要 84px）。
   在不动信息、不缩字号、不折行的前提下，唯一可榨的空间就是**列内边距**：
   默认 `0 var(--space-3)` = 左右各 12px，16 列共占 384px；收到 4px 可省 256px
   （实测：5px 时「达人结算」自然宽度 1384px，仍比容器 1374px 多 10px；4px 后 1352px 达标）。

   ⚠️ 为什么新开一个类、而不是改 `.table thead th` 的默认 padding：
   那是全站 33 张表共用的基础规则（本项目铁律：改公共样式前先查复用范围）。
   这里只给「确实需要省宽度」的宽表加类，其余表保持原有舒适间距。
   ========================================================================== */
.table--dense thead th,
.table--dense tbody td {
  padding-left: 4px;
  padding-right: 4px;
}
/* 密集表里「操作」列的按钮组本来靠 gap 撑开，同步收一点，避免按钮贴边 */
.table--dense tbody td .toolbar { gap: 2px; }

/* ══ tfoot 单元格的 padding 必须**独立声明**（2026-09-14 乐哥：「下面求和这个，行高好窄」）
   ──────────────────────────────────────────────────────────────────────────────────
   根因：本项目表格单元格的 padding 只有一条规则 —— `.table tbody td { padding: 12px }`（本文件 93 行），
       它**只作用于 tbody**。而 `tfoot` 是**独立的行组**，thead/tbody 的规则一律覆盖不到它
       （pages.css:3167 的注释早已点明这一点）。
   实测后果（修复前）：
     · 普通表 tfoot  → padding **0px**、行高 **22px**（数据行 68~130px、表头 44px）→ 一条被压扁的窄条
     · `.table--sub` tfoot → padding 8px，而它的 tbody 是 12px → 同表内也不一致
   影响面：全站 **7 个页面**的 tfoot 合计行（华大对账 `.cm-foot-row`、提成总览、排期明细、
          订单明细、费用类、粉丝体验装…），都是这个毛病 —— 不是单个页面的问题。
   做法：在 tfoot 层**统一**补齐，规格与 tbody 完全对齐（12px 基础 + 首末列 20px）。
        这样无论合计行叫什么类名（`.is-sum` / `.cm-foot-row` / `.hs-drill__sum-row`）都自动生效，
        新增合计行也不会再漏。
   ⚠️ 排除密集表 `.table--dense`（它的 thead/tbody 左右只有 4px，单独给它一条规则），
      排除嵌套在单元格里的子表（`:where(td .table)`）。
   ⚠️ 不要用 `.table tbody td, .table tfoot td` 合并写法 —— 那会把 `border-bottom` /
      `vertical-align` 等 tbody 专有属性也带给 tfoot，影响合计行的既有视觉。只补 padding。 */
.table:not(.table--dense):not(:where(td .table)) > tfoot > tr > td {
  padding: var(--space-3);
}
.table:not(.table--dense):not(:where(td .table)) > tfoot > tr > td:first-child {
  padding-left: var(--space-5);
}
.table:not(.table--dense):not(:where(td .table)) > tfoot > tr > td:last-child {
  padding-right: var(--space-5);
}
/* 密集表：左右收窄到 4px，与它的 thead/tbody 一致（上下仍是 12px） */
.table--dense:not(:where(td .table)) > tfoot > tr > td {
  padding: var(--space-3) 4px;
}

/* ══ 表格「合计行」通用样式（2026-09-14 乐哥：「所有涉及费用的，表格上应该有一个
   地方能显示汇总，不然我们筛选出来都看不到总数」）══════════════════════════════════
   用法：`<tfoot><tr class="is-sum"><td>…</td></tr></tfoot>`
   口径 = **当前筛选条件下的全部匹配行**（不是当前页）——
     订单明细共 6,367 单而每页只显示 20 行，只算当前页的数字没有意义。
   ⚠️ 用 tfoot 而不是 tbody 尾行：tfoot 在语义上就是"表尾合计"，
      且不会被"全选/行展开"等交互误当作数据行处理。
   （内边距由上面那条 tfoot 统一规则提供，此处只管视觉区分） */
.table tfoot tr.is-sum > td,
.table tbody tr.is-sum > td {
  /* ⭐ 2026-10-12 乐哥：「这个汇总也不好看，不协调」。
     ▸ 改前：surface-2 灰底 —— 与全站「淡蓝玻璃」主题不搭，灰带在白色表格尾显得突兀；
     ▸ 改后：淡蓝柔底 + 主色系上边线 —— 与表格顶部工具栏的淡蓝玻璃托盘**首尾呼应**，
       合计行成为表格的"收官带"，不再像贴错的一块灰。 */
  background: var(--color-info-bg);
  font-weight: var(--font-weight-semibold);
  color: var(--color-fg);
  border-top: 1px solid var(--color-primary-100);
}
/* 合计行里的金额用等宽感更强的排版，方便与明细逐位核对；不换行避免金额被折行 */
.table tfoot tr.is-sum > td,
.table tbody tr.is-sum > td {
  white-space: nowrap;
}
.table tfoot tr.is-sum > td.num,
.table tbody tr.is-sum > td.num {
  font-variant-numeric: tabular-nums;
}
/* ⭐ 合计行里的盈亏格**必须保留红/绿**（2026-09-17 乐哥去掉全站金额的 `+` `-` 后暴露）
   ----------------------------------------------------------------------------
   上面的 `.table tfoot tr.is-sum > td { color: var(--color-fg) }` 特异性 (0,2,2)，
   **压过了** `.num-pos` / `.num-neg`（0,1,0）→ 合计行的盈亏一直是黑字。
   在"带 `+` 号"的年代这没问题（正负看符号），但符号去掉后：
   合计行的盈亏**既没符号也没颜色** → 看不出是赚还是亏（而它是全表最该看清的一格）。
   ⚠️ 这是本项目第 4 次被"高特异性规则盖掉语义色"咬到（pages.css 里已记过 3 次）——
      凡是用语义色表达的类，都要检查有没有被**页内更具体的选择器**压过。
   ⚠️ 只加 `td.num-pos` / `td.num-neg` 两条，**不动**其它合计行样式（底色/字重/nowrap 保持）。 */
.table tfoot tr.is-sum > td.num-pos,
.table tbody tr.is-sum > td.num-pos { color: var(--color-danger); }    /* 盈利 = 红 */
.table tfoot tr.is-sum > td.num-neg,
.table tbody tr.is-sum > td.num-neg { color: var(--color-success); }   /* 亏损 = 绿 */

/* 表格行「已单独校准」标记（2026-09-15）：暖底提示这是一行被人为改过的值。
   用途：项目团队成本「按月校准」弹窗 —— 填了金额的月份标出来，一眼看出哪几个月不是默认值。
   特异性 (0,2,2) 高于斑马纹的 (0,1,2)（后者用 :where() 压过），能稳定覆盖它。
   ⚠️ 底色只用最浅的暖色：这是"提示"不是"告警"，过重的底色会让人以为数据出错了。 */
.table tbody tr.is-custom > td {
  background: var(--color-head-on-bg);
}


/* ⭐ 2026-09-25 Gashora 骨架落地：**含表格的卡片裁切圆角**。
   背景：`.card` / `.module-card` 自身无内边距（padding:0，见 components.css），
   表头直接贴到卡片边缘 ⇒ 表头的**背景色会在卡片圆角弧线处溢出**（卡片 overflow:visible 不裁切），
   表现为"圆角被表头方角顶破"（乐哥报「表格左上角那个圆角线还没修好」）。
   修法：给含表格的卡片加 overflow:hidden，把表头背景裁进圆角内。
   ⚠️ 安全性依据（两处实测确认）：
      · 卡内下拉/选择器全是**原生 select** —— 原生下拉菜单由浏览器绘制，不受祖先 overflow 影响；
      · 卡内的浮层（若有）都是 `position: fixed` 且卡片链上无 transform/filter ⇒ 不受裁剪。
   ⚠️ 用 `:has()` 而非给所有 .card 加（会让不装表的卡片也无谓裁切，可能切到悬停浮出的提示）。 */
.card:has(.table),
.module-card:has(.table),
.table-card { overflow: hidden; }

/* ============================================================================
   ⭐ 表格行多选：**默认隐藏 checkbox，鼠标进入表格时才出现**（2026-10-11 乐哥）
   ----------------------------------------------------------------------------
   乐哥原话：「全局的多选，能不能做成多选框默认不显示，按钮也没有，我鼠标移到表格上，
   表格行内容前面就显示多选框，然后标题行表头左边也就有个全选框」。

   ▸ 用 `visibility` 而不是 `display:none`：**保留列宽**。
     用 display 切换会让整张表在鼠标进出时左右抖一下（列宽重算）——
     "鼠标一进表格整张表跳一下"比"多显示一列"难受得多。
   ▸ 触发点是 **`.table:hover`（鼠标在表格内，含表头）**，不是 `tr:hover`：
     乐哥要的是"移到表格上"就整列出现（方便一口气勾多行），
     表头的全选框也跟着出现 —— 若用 `tr:hover`，表头那一格永远不会亮、
     而"全选"恰恰是最常用的动作。
   ▸ 只作用于**首列**：全站的行选择框都在第一列（发票中心 / 排期列表 / 达人管理 /
     成本 / 利润结算 / 业绩提成 / 报销批次 / 用户管理 / 粉丝体验 / 两个导入预览表）。
     ⛔ 表格里**别的位置**的 checkbox 是**内容或开关**（设置页的勾选项、弹窗表单里的选择），
        那些**不该**被藏起来 —— 这也是本条限定 `:first-child` 的原因。

   ⚠️⚠️ 三条配套**必须同时有**，缺一条就会出真问题（都不是"风格问题"）：
     ① **已勾选的一直显示**（`:checked`）—— 否则勾完把鼠标移开，用户**看不见自己勾了哪些**，
        表头全选框的"半选态"也无从对照。这是最容易漏、后果最直接的一条。
     ② **键盘聚焦时显示**（`:focus-visible`）—— 否则纯键盘用户 Tab 到框上却看不见它。
     ③ **无 hover 的设备始终显示**（`@media (hover: none)`）—— 触屏/手机没有 hover，
        不兜底就**永远选不上**。

   ⭐⭐ 2026-10-11 第二批：原先只有"checkbox 常显"的页面享受得到，另外 5 个页面
      （达人管理 / 排期列表 / 成本 / 利润结算 / 业绩提成）的勾选列是
      **条件渲染**（`state.multiSelect ? '<td>…' : ''`）—— 不点「多选」按钮那一列
      根本不在 DOM 里。本批已把那 5 页统一：**列常驻 + 去掉「多选」按钮 + 批量条按"有勾选"显隐**，
      并明确「**点行仍进详情、勾选只走勾选框**」（乐哥 2026-10-11 拍板）。

   ⭐ 勾选热区**比框大**（乐哥：「勾选可选范围比框稍微大一点，这样好勾选」）——
      每个 checkbox 外面套一个 `.row-pick` 的 label：框 14×14、热区 **26×26**（四周各多 6px）。
      ⛔ **热区不能再大**（尤其不能让我整格都可点）：那一格还承担"点行进详情"，
         热区铺满整格 ⇒ 点行就变成勾选，正是乐哥明确否掉的。
      ⚠️ `margin: -6px` 是为了让 26 的 label 不把行撑高（26 − 14 = 12，上下各让 6）。
   ============================================================================ */
.table > thead > tr > th:first-child input[type="checkbox"],
.table > tbody > tr > td:first-child input[type="checkbox"] {
  visibility: hidden;
}
.table:hover > thead > tr > th:first-child input[type="checkbox"],
.table:hover > tbody > tr > td:first-child input[type="checkbox"],
.table > tbody > tr > td:first-child input[type="checkbox"]:checked,
.table > tbody > tr > td:first-child input[type="checkbox"]:focus-visible,
.table > thead > tr > th:first-child input[type="checkbox"]:focus-visible {
  visibility: visible;
}
@media (hover: none) {
  .table > thead > tr > th:first-child input[type="checkbox"],
  .table > tbody > tr > td:first-child input[type="checkbox"] {
    visibility: visible;
  }
}

/* 勾选热区（比框大一圈，好点）—— 各页统一写 `<label class="row-pick"><input …></label>` */
.table td.col-pick {
  padding-top: 0;
  padding-bottom: 0;
}
.table td.col-pick > .row-pick {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  /* ⚠️ 负外边距：26 的盒子若不回收，会把行高撑高 12px（每行都变胖）。
     26 − 14 = 12 ⇒ 上下各让 6、左右各让 7（让出的正是格子的内边距区），
     **列宽与行高都跟原来一致** —— 只是"能点到的地方"变大了一圈。 */
  margin: -6px -7px;
  vertical-align: middle;
  cursor: pointer;
  border-radius: var(--radius-sm);
  transition: background var(--motion-fast) var(--motion-ease-standard);
}
.table td.col-pick > .row-pick:hover {
  background: var(--color-surface-2);
}


/* ==== /css/cost-chart.css ==== */
/* ==========================================================================
   Star HUB丨星盟达人管理后台 — css/cost-chart.css
   「成本构成」环形图 + 可点列表（乐哥 2026-09-15）

   为什么单独开一个文件、且所有类都带 `cc-` 前缀：
     本项目全站几十张表都在用 `.table` / `.is-num` / `.cell-sub`。
     图表是**新增展示层**，若蹭用公共类去调样式，改一处会波及无关页面
     （项目已踩过：把 `.table--zebra` 加在页面上导致 8 张表与 40 张表读法不一致）。
     ⇒ 组件自带作用域：`.cc-scope` 之下的一切样式只服务本组件。

   配色（三组各自的同色系深浅）：
     成本三组用**同色系深浅**表示「同一大类的细分」，跨组才是不同色相 ——
     这样环形图上"蓝色一片＝达人相关、橙色一片＝人力、绿色一片＝项目公共"，
     不用读图例就能看出三级结构（原型阶段乐哥已看过这版配色）。
   ========================================================================== */

/* ⚠️⚠️ 配色变量**必须定义在 `:root`**，不能只挂在 `.cc-scope` 上（2026-09-15 实测踩到）。
   ----------------------------------------------------------------------------
   根因：JS 侧取色走 `UI.cssVar()`，而它的实现是
        `getComputedStyle(document.documentElement).getPropertyValue(name)`
   —— 只认 `:root`（html 元素）上的变量。变量若只定义在 `.cc-scope` 上，
   `cssVar` 读到空串 → `stroke=""` → **环形图整张画不出来**、色点与占比条也全空。
   症状极具迷惑性：DOM 里 `.cc-seg` 元素一个不少（11 个），
   所以"段数 = 11"这类断言照样全绿 —— **断言有效 ≠ 断言全绿**（本项目硬教训）。
   ⇒ 放在 `:root` 即可：CSS 变量本就继承，`.cc-scope` 内的元素照样能用。
   命名带 `cc-` 前缀，不会与全局令牌冲突。
   ⚠️ 图表连续色阶（t1-t6/h1-h4/p1-p4/r1-r2）是**刻意保留的裸 hex**
   —— 色阶要求相邻色肉眼可辨的连续渐变，design-tokens 没有连续色阶，
   已登记在 .render-check/audit-ui-standards.js 的 HEX_WHITELIST（账本式白名单）。
   ========================================================================== */
:root {
  /* 达人相关（蓝） */
  --cc-t1: #0064E0; --cc-t2: #2F82E8; --cc-t3: #5E9BF8;
  --cc-t4: #8CB8FF; --cc-t5: #B0CDFC; --cc-t6: #D3E3FD;
  /* 人力成本（橙） */
  --cc-h1: #E37318; --cc-h2: #F09B45; --cc-h3: #F8C078; --cc-h4: #FBD9BC;
  /* 项目公共（绿） */
  --cc-p1: #2BA471; --cc-p2: #57BE8C; --cc-p3: #8AD4AE; --cc-p4: #BFE8D3;
  /* 收入（蓝灰，与成本刻意区分开） */
  --cc-r1: #0064E0; --cc-r2: #9FB6CE;

  /* ===== 收支瀑布的「逐行同高」变量（乐哥 2026-09-17）=====
     ⚠️⚠️ 必须定义在 `:root`，但**当年记的原因不对**（2026-09-21 订正）：
        原注释写的是"因为它们写在 `.cw-scope` 上，所以那条规则整条没被解析"。
        **真因是一条注释被提前闭合**：本文件下方那段说明里写着
        `（--cc-t*` + 结束标记 + `--cc-h*` + 结束标记 + `--cc-p*）`，
        第一个结束标记就把整段注释**当场结束了** ⇒ 紧跟其后的 `.cw-scope { … }`
        被浏览器当成"野文本 + 规则"**整条丢弃**（实测：坏=257 条规则 / 修=258 条，
        少的正是 `.cw-scope`）。
        ⇒ 所以"写在上面的声明不生效"与"写在哪里"无关，**是上面那段注释坏了**。
        这与本文件顶部"配色令牌要放 `:root`"那条是**两回事**，别混。
        现已修掉那个结束标记；`.cw-scope` 本身是**空标记规则**（只有注释、无声明），
        所以这次修复**没有可见变化**，但它把"以后再往 `.cw-scope` 里写东西会静默失效"
        这颗地雷拆了。
        （证据脚本：`tests/css-comment-balance.test.js` —— 逐字符扫全站 CSS 的注释配平。）
        —— 变量放 `:root` 依然是对的（作用域更宽、不依赖任何规则被解析），保留原判。
     · `--cw-row-h`    单行高度（左瀑布条 / 右成本项 **必须同一个值** → 逐行对齐的关键）
     · `--cw-gap`      行间距（左右一致）
     · `--cw-block-gap` 块间距（收入块↔成本块、成本块↔结论块；左右在**同一位置**各加一份） */
  --cw-row-h: 26px;
  --cw-gap: 6px;
  --cw-block-gap: 14px;
}

.cc-scope {
  /* 作用域标记类（样式归属），配色变量继承自 :root，见上 */
}

/* ---------------- 布局：左环 + 右列表 ---------------- */
.cc-flex { display: flex; gap: var(--space-6); align-items: flex-start; flex-wrap: wrap; }
.cc-donut-box { position: relative; flex: none; }
.cc-donut-box svg { display: block; }
/* 环心文字：绝对定位盖在 SVG 上（SVG 内写字要自己算换行，不如 DOM 稳） */
.cc-center {
  position: absolute; left: 0; right: 0; text-align: center; pointer-events: none;
}
.cc-center__t { font-size: var(--font-size-xs); color: var(--color-muted); line-height: 1.5; }
.cc-center__v {
  font-size: var(--font-size-xl); font-weight: var(--font-weight-semibold);
  color: var(--color-fg); letter-spacing: -0.3px; font-variant-numeric: tabular-nums;
}
.cc-center__s { font-size: var(--font-size-xs); color: var(--color-meta); margin-top: 2px; }

/* 列表限宽：真实页面里这块卡片宽约 1240px，不限宽时「名称」那一列
   会把「金额 / 占比条 / 占比」一路推到最右侧，中间空一大片、三列读不成一组
   （实测截图：金额与占比之间隔着约 300px 空白）。限宽后三列成组、扫读顺畅。 */
.cc-list { flex: 1 1 300px; min-width: 280px; max-width: 720px; }

/* 列表行：可点（下钻）。⚠️ 悬停**不用换底色** ——
   卡片默认底就是 --color-surface-2（本项目当 hover 底用的那一档），同色等于没反馈；
   故用「左边框 + 底色淡染」表达可点与选中（与 cost-drill 卡片的选择态一致）。 */
.cc-li {
  display: flex; align-items: center; gap: var(--space-2);
  padding: 5px var(--space-2); border-radius: var(--radius-sm);
  font-size: var(--font-size-sm); border-left: 2px solid transparent;
  transition: background-color var(--motion-fast) var(--motion-ease-standard);
}
.cc-li--pick { cursor: pointer; }
.cc-li--pick:hover { background: var(--color-surface-2); border-left-color: var(--color-border); }
.cc-li.is-on { background: var(--color-primary-50); border-left-color: var(--color-primary-500); }
.cc-li.is-dim { opacity: 0.4; }

.cc-dot { flex: none; width: 9px; height: 9px; border-radius: 2px; }
.cc-li__n { flex: 1 1 auto; color: var(--color-fg); min-width: 0; }
.cc-li__n em {
  font-style: normal; font-size: var(--font-size-xs); color: var(--color-meta);
  margin-left: 5px; white-space: nowrap;
}
.cc-li__v {
  flex: none; width: 84px; text-align: right; color: var(--color-fg);
  font-variant-numeric: tabular-nums;
}
.cc-li__p {
  flex: none; width: 52px; text-align: right; color: var(--color-muted);
  font-variant-numeric: tabular-nums;
}
/* 表头行 */
.cc-li--hd {
  color: var(--color-muted); font-size: var(--font-size-xs); cursor: default;
  border-bottom: 1px solid var(--color-border); border-radius: 0;
  border-left-color: transparent; padding-bottom: 6px;
}
.cc-li--hd:hover { background: none; border-left-color: transparent; }
/* 分组小标题 */
.cc-grp {
  font-size: var(--font-size-xs); font-weight: var(--font-weight-medium);
  color: var(--color-muted); padding: 9px var(--space-2) 3px;
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-2);
}
.cc-grp b { color: var(--color-fg-2); font-weight: var(--font-weight-medium); }
/* 占比条（列表行内，极细） */
.cc-bar { flex: none; width: 56px; height: 6px; border-radius: 3px; background: var(--color-border-soft); overflow: hidden; }
.cc-bar > i { display: block; height: 100%; border-radius: 3px; }

/* ---------------- 扇形/图例交互 ---------------- */
.cc-seg { transition: opacity var(--motion-fast) var(--motion-ease-standard); cursor: pointer; }
.cc-seg:hover { opacity: 0.78; }
.cc-seg.is-dim { opacity: 0.22; }
.cc-seg.is-hl { opacity: 1; }

/* ---------------- 下钻面板（点某项后展开它的构成） ---------------- */
.cc-panel {
  margin-top: var(--space-4); padding: var(--space-4);
  background: var(--color-surface-2); border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
}
.cc-panel__hd {
  display: flex; align-items: center; gap: var(--space-2);
  font-size: var(--font-size-sm); font-weight: var(--font-weight-medium);
  color: var(--color-fg); margin-bottom: var(--space-2); flex-wrap: wrap;
}
.cc-panel__hd .cc-sub { font-weight: normal; font-size: var(--font-size-xs); color: var(--color-meta); }
.cc-panel__hd .cc-spacer { margin-left: auto; }
/* 「看逐笔」入口：走全站一致的虚线下划线可点样式语义，
   但这里是**按钮形态**（只有一个动作，不是表格里几十个可点格），故用 outline 按钮。 */
.cc-more {
  border: 1px solid var(--color-border); background: var(--color-surface);
  color: var(--color-accent-text); border-radius: var(--radius-sm);
  padding: 3px 10px; font-size: var(--font-size-xs); cursor: pointer;
  display: inline-flex; align-items: center; gap: 4px;
}
.cc-more:hover { border-color: var(--color-accent); }

/* 构成明细表（面板内）—— ⚠️ 数字列在这里**靠右**（乐哥 2026-09-15 要求），
   故显式覆盖，不依赖 .is-num 的全局对齐。 */
.cc-tbl { width: 100%; border-collapse: collapse; font-size: var(--font-size-sm); }
.cc-tbl th {
  text-align: left; color: var(--color-muted); font-weight: var(--font-weight-medium);
  font-size: var(--font-size-xs); padding: 6px var(--space-2);
  border-bottom: 1px solid var(--color-border); white-space: nowrap;
}
.cc-tbl td {
  padding: 7px var(--space-2); border-bottom: 1px solid var(--color-border-soft);
  color: var(--color-fg); vertical-align: middle;
}
.cc-tbl tr:last-child td { border-bottom: none; }
.cc-tbl .cc-num { text-align: right; font-variant-numeric: tabular-nums; }
.cc-tbl tfoot td { font-weight: var(--font-weight-medium); border-top: 1px solid var(--color-border); }
.cc-tbl .cc-hint { color: var(--color-meta); font-size: var(--font-size-xs); }
/* ⚠️ 2026-09-17 删除：此处原有 `.cc-tbl .cc-idx`（带描边的圆形徽章）——
   实测看着像"可点的按钮"，而它其实只是行号。新样式在下方「下钻面板 UI 优化」区
   （无边框、无底色、右对齐的纯数字），本节不再重复定义，避免同选择器两处各写一套。 */
/* 残差项：必须显眼 —— 它是"口径缺口"的信号，不能看着像普通项 */
.cc-residual td { background: var(--color-warning-bg); }
.cc-residual .cc-flag { color: var(--color-warning-text); font-weight: var(--font-weight-medium); }

/* 平台子行（成本下钻 · 构成表，2026-09-17）：
   构成项下面按平台展开（微信小店 / 抖音），子行左侧留出缩进 + 竖线，表明从属关系；
   子行第二行是「计算规则 + 数」（净实收 × 费率 × 税系 = 金额），可照着验算。 */
.cc-sub td { background: var(--color-surface); }
.cc-sub-name {
  display: inline-block; padding-left: var(--space-4); position: relative;
  color: var(--color-fg-2); font-weight: var(--font-weight-medium);
}
.cc-sub-name::before {
  content: ''; position: absolute; left: 4px; top: 0.62em;
  width: 6px; height: 6px; border-left: 1px solid var(--color-border);
  border-bottom: 1px solid var(--color-border);
}
.cc-rule { font-variant-numeric: tabular-nums; padding-left: var(--space-4); }
.cc-rule b { color: var(--color-fg-2); }

.cc-empty { font-size: var(--font-size-sm); color: var(--color-muted); line-height: 1.8; }
.cc-note {
  font-size: var(--font-size-xs); color: var(--color-muted); line-height: 1.8;
  margin-top: var(--space-3); padding-top: var(--space-2);
  border-top: 1px dashed var(--color-border);
}
.cc-note b { color: var(--color-fg-2); }

/* 面板内的小标题（如「谁花的」）——用于分开"同一张面板里的两个维度"：
   上面 = 这笔钱**是什么**（构成项）；下面 = 这笔钱**花在谁身上**（按达人）。
   改前它借用了 `.cc-sub`（表格子行的类名）且无任何样式 → 渲染成正文灰字、层级弱，
   两张表看着像一张连续的表。现在：上分隔线 + 加粗 + 右侧附注，明确"换了一个维度"。 */
.cc-sec {
  display: flex; align-items: baseline; gap: var(--space-2);
  margin: var(--space-5) 0 var(--space-2);
  padding-top: var(--space-3);
  border-top: 1px solid var(--color-border);
  font-size: var(--font-size-sm); font-weight: var(--font-weight-semibold);
  color: var(--color-fg);
}
.cc-sec__n { font-size: var(--font-size-xs); font-weight: 400; color: var(--color-meta); }

/* ==========================================================================
   下钻面板 UI 优化（乐哥 2026-09-17：「点击某一个成本项不是会展开吗？展开后的页面 ui 优化下」）
   ========================================================================== */

/* 标题行：标题独占视觉重心，口径标签跟随，动作按钮靠右 —— 不再让长句挤在中间。
   （长句已下沉到 `.cc-panel__sub`，见 cost-chart.js；间距见上方 `.cc-panel__hd` 本体） */
.cc-panel__t {
  font-size: var(--font-size-sm); font-weight: var(--font-weight-semibold);
  color: var(--color-fg); letter-spacing: -0.1px;
}
/* 副行：说明这一类的粒度（如「该类粒度是『订单』，没有单据可列」） */
.cc-panel__sub {
  font-size: var(--font-size-xs); color: var(--color-meta);
  margin-bottom: var(--space-3);
}

/* 序号：改前是「带描边的圆形徽章」，看着像可点的按钮（实测乐哥会以为能点）。
   改成**纯粹的行号**（无边框、无底色），只在视觉上起"第几项"的作用。 */
.cc-tbl .cc-idx {
  display: inline-block; width: 16px; margin-right: 6px;
  color: var(--color-meta); font-size: var(--font-size-xs);
  font-variant-numeric: tabular-nums; text-align: right;
  background: none; border: 0; border-radius: 0; height: auto;
}
/* 行 hover：这张表列多（构成项/金额/占比/占比条/深入），
   没有横贯整行的高亮时，眼睛容易串行（2026-09-17 加）。 */
.cc-tbl tbody tr:hover td { background: var(--color-primary-50); }
/* 残差行有底色（警告），hover 时不能把它盖掉 —— 它本身就是要显眼的信号 */
.cc-tbl tbody tr.cc-residual:hover td { background: var(--color-warning-bg); }

/* 脚注区：口径说明 + 同源校验合并成一段（改前是两行独立灰字，像随口附注、容易被当噪音跳过）。
   `__k` 是「口径」这个小标，`__v` 是内容 —— 让它读起来像"一个说明块"而不是"两句附注"。 */
.cc-note--foot { display: flex; gap: var(--space-2); align-items: flex-start; border-top-style: solid; }
.cc-note__k {
  flex: none; color: var(--color-fg-2); font-weight: var(--font-weight-medium);
  padding: 0 6px; border-radius: 3px; background: var(--color-surface);
  border: 1px solid var(--color-border-soft); line-height: 1.6;
}
.cc-note__v { min-width: 0; }
/* 未归类警告：单独一条、给足视觉重量（它是"口径缺口"，不是普通附注） */
.cc-note--warn {
  color: var(--color-warning-text); border-top-color: var(--color-warning);
  display: flex; align-items: baseline; gap: 5px;
}

/* 图例（下方，用于两个饼并排时压缩高度） */
.cc-mini-legend { margin-top: var(--space-3); }

/* 窄屏：环与列表上下排 */
@media (max-width: 900px) {
  .cc-flex { gap: var(--space-4); }
  .cc-list { flex: 1 1 100%; min-width: 0; }
}

/* ==========================================================================
   以下为「成本看板」v2（乐哥 2026-09-16 定稿）—— 图中图 / 双图对比 / 费比 / 三张图
   全部仍以 `cb-` 前缀作用域隔离，不动任何公共类。
   ========================================================================== */

.cb-scope { }

/* ---------------- 图中图：总图 + 旁边细分 ----------------
   ⚠️⚠️ 「主区 / 细分区」必须各自成组（2026-09-16 修）：
   把 4 个元素（主环/主列表/细分环/细分列表）平铺在同一个 flex 里时，点开细分它们会**互相抢宽度**——
   实测容器 1166px 下主列表被从 420px 压到 349px（三列文字立刻变挤）、整块长高 62px。
   分组后：主区宽度由内容决定（稳定），细分去吃剩余空间；容器不够时细分整体折到下一行。
   ⇒ 结论：**任何「点一下会多出一块」的并排布局，都要先分组再并排**，否则原内容会被挤。 */
.cb-flex { display: flex; gap: var(--space-6); align-items: flex-start; flex-wrap: wrap; }
.cb-fl { display: flex; gap: var(--space-6); align-items: flex-start; }
/* 主区：宽度由内容决定（环 208 + 间距 + 列表 420 = 652），**不参与收缩** → 永不被挤压 */
.cb-fl--main { flex: 0 0 auto; }
/* ⚠️ 主列表必须**写死宽度**：`.cb-lg` 原靠父级 flex 的剩余空间长大（到 max-width 420），
   分组后主区是 auto 宽、没有"剩余空间"可吃 → 只落到内容自然宽 315px（三列明显变紧）。
   写死 420 后主区 = 652，点开细分仍不变。 */
.cb-fl--main .cb-lg { flex: none; width: 420px; max-width: 100%; }
/* 细分区：吃剩余空间；basis 380 + min-width 380 是「能不能并排」的判据 ——
   主区 652 + 间距 24 + 380 = 1056：容器 ≥ 1056 就并排（满足"点大扇区→旁边出细分"），
   更窄则整体折到下一行（此时主区仍 652，绝不被压扁）。
   ⚠️ 不要写 `flex-basis: auto`：那会用 max-content（环188+间距+列表420=632），
      于是 652+24+632=1308 —— 连 1680 屏都放不下、总是折行，白白丢掉"旁边"的形态。 */
.cb-fl--sub { flex: 1 1 380px; min-width: 380px; }
/* ⚠️⚠️ 必须写这一条：`.cb-fl` 的 `display:flex` 是**作者样式**，会盖掉 `[hidden]` 的
   UA 样式 `display:none`（两层特异性同为 0,1,0，作者优先）→ 不写的话 hidden 完全失效、
   没有选中时细分也一直显示。用双类提到 0,2,0 即可。 */
.cb-fl[hidden] { display: none; }
/* 极窄屏（容器 < 约 680px）兜底：主区固定 652px 会溢出 → 放开让它收缩，
   此时主列表跟着缩（宁可窄，也不能横向溢出）。断点按**视口**写，容器宽 ≈ 视口 - 侧栏 - 内边距。 */
@media (max-width: 1100px) {
  .cb-fl--main { flex: 1 1 auto; min-width: 0; }
  .cb-fl--main .cb-lg { flex: 1 1 auto; width: auto; min-width: 0; }
}
.cb-ring { position: relative; flex: none; }
.cb-ring__c { position: absolute; left: 0; right: 0; text-align: center; pointer-events: none; }
.cb-ring__t { font-size: var(--font-size-xs); color: var(--color-muted); line-height: 1.5; }
.cb-ring__v { font-size: var(--font-size-lg); font-weight: var(--font-weight-semibold); color: var(--color-fg); font-variant-numeric: tabular-nums; }
.cb-seg { cursor: pointer; transition: opacity var(--motion-fast) var(--motion-ease-standard); }
.cb-seg:hover { opacity: 0.78; }
.cb-lg { flex: 1 1 240px; min-width: 230px; max-width: 420px; }
.cb-li { display: flex; align-items: center; gap: var(--space-2); padding: 5px var(--space-2);
  border-radius: var(--radius-sm); font-size: var(--font-size-sm); border-left: 2px solid transparent; cursor: pointer; }
.cb-li:hover { background: var(--color-surface-2); border-left-color: var(--color-border); }
.cb-li.is-on { background: var(--color-primary-50); border-left-color: var(--color-primary-500); }
.cb-li--hd { font-size: var(--font-size-xs); color: var(--color-muted); border-bottom: 1px solid var(--color-border);
  border-radius: 0; cursor: default; }
.cb-li--hd:hover { background: none; border-left-color: transparent; }
.cb-dot { flex: none; width: 9px; height: 9px; border-radius: 2px; }
.cb-li__n { flex: 1 1 auto; color: var(--color-fg); min-width: 0; }
/* ⚠️ 名称列**必须能省略，不能逐字换行**（2026-09-16 修，乐哥报障「文字内容挤压」）：
   窄屏下容器变窄，而 `.cb-ring` 是 `flex:none`（环不让位）→ 名称列被压到只剩十几个像素，
   中文没有空格，浏览器就**逐字换行**：表头「构成项」竖成"构/成/项"、数据「招待费成本」
   竖成"招/待/费/成/本"，金额列与占比列（固定 78+50px）被顶出卡片 100px 以上。
   ⇒ 单行 + 省略号：宁可把名字截短（有 title 可悬停），也不能让它竖排把整块撑坏。 */
.cb-li__n { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cb-li__v { flex: none; width: 78px; text-align: right; color: var(--color-fg); font-variant-numeric: tabular-nums; }
.cb-li__p { flex: none; width: 50px; text-align: right; color: var(--color-muted); font-variant-numeric: tabular-nums; }
.cb-bar { flex: none; width: 44px; height: 6px; border-radius: 3px; background: var(--color-border-soft); overflow: hidden; }
.cb-bar > i { display: block; height: 100%; border-radius: 3px; }
/* ⚠️⚠️ 窄屏兜底（2026-09-16 修，乐哥报障「文字内容挤压」）：
   实测容器只剩 ~300px 时，环（208px，flex:none **不让位**）+ 列表被挤在同一行，
   列表的 clientWidth 只剩 67px，而「金额 64 + 占比 44 = 108px」是**固定列宽** ——
   固定列已经超过容器 → 名称列被压到 **0 宽**（逐字竖排），金额/占比被顶出卡片 80px+。
   ⇒ 正确做法：拿不下就**让环与列表上下摞起来**（列向 stack），而不是硬挤在一行。
      环和列表各自获得整行宽度，任何一列都不会被压到 0。 */
@media (max-width: 900px) {
  .cb-flex { flex-direction: column; gap: var(--space-4); }
  .cb-fl { flex-direction: column; gap: var(--space-4); width: 100%; }
  .cb-fl--main { flex: 1 1 auto; }
  /* ⚠️ 细分区在窄屏也要放开 min-width（原来固定 380px，
     `.cb-scope` 只有 301px 时内容宽 380 → **细分列表被裁掉「占比」列**，实测踩到）。
     「能不能并排」只在宽屏有意义；窄屏已经改成上下摞，此时必须允许收缩到容器宽。 */
  .cb-fl--sub { min-width: 0; flex: 1 1 auto; width: 100%; }
  /* 列表占满整行；环居中 */
  .cb-fl--main .cb-lg, .cb-fl .cb-lg { flex: none; width: 100%; max-width: 100%; }
  .cb-fl .cb-ring { align-self: center; }
}
/* 更窄（手机竖屏）：金额/占比列再收一档，保证名称列至少有 ~60px 可读宽度 */
@media (max-width: 480px) {
  .cb-li__v { width: 58px; }
  .cb-li__p { width: 40px; }
}
@media (max-width: 1100px) {
  .cb-li .cb-bar { display: none; }
  .cb-li__v { width: 64px; }
  .cb-li__p { width: 44px; }
}
.cb-empty { color: var(--color-meta); font-size: var(--font-size-sm); text-align: center; padding: var(--space-6) 0; }

/* 筛选条（作用于总图） */
.cb-filter { display: flex; gap: var(--space-2); align-items: center; flex-wrap: wrap; margin-bottom: var(--space-4); }
.cb-filter__lb { font-size: var(--font-size-xs); color: var(--color-muted); }
/* ⚠️ 2026-09-26 两档制：这里是**裸 `<select>`（无类名）**，所以吃不到公共 `.select` 的样式，
   只能页面私有。改前 30px + `--radius-sm`(4px) —— 与全站 34px + `--radius-md`(10px) **两处都不一样**，
   放一起看就像"另一个系统里的控件"。
   ✅ 归两档：高度 34（紧凑档）、圆角与全站控件一致（radius-md）。 */
.cb-filter select { height: var(--control-height-sm); border: 1px solid var(--color-border); border-radius: var(--radius-md);
  background: var(--color-surface); color: var(--color-fg); font-size: var(--font-size-sm); padding: 0 8px; }
.cb-hint { font-size: var(--font-size-xs); color: var(--color-primary-600); background: var(--color-primary-50);
  padding: 2px 8px; border-radius: var(--radius-sm); }

/* ---------------- 双图对比（绝对值 + 百分比）----------------
   ⚠️⚠️ 三层结构缺一不可（本轮连踩两次，见 cost-board.js 顶部说明）：
      .cb-row__t 轨道（flex:1，提供百分比基准）
      .cb-row__b 条（width:X% 定量）
      .cb-row__b>i 分段（width:Y% 按占比切）
   两次踩的坑都表现为"数元素全绿、图是坏的"：
      ① 分段直接挂轨道 + flex-basis → flex-grow:1 把 13 条拉成一样长
      ② 省掉 .cb-row__b 一层 → <i> 是行内元素、width 不生效 → 条宽全为 0
   -------------------------------- */
/* ---------------- 竖向柱状图的画布容器（2026-09-16 乐哥：横坐标改成达人名）----------------
   ⚠️ Chart.js 必须有一个**有确定高度**的父容器（配 maintainAspectRatio:false），
      否则它会按宽度自己算高度、或塌成 0 高 —— 图就"看不见但元素都在"。
   ⚠️ 高度直接决定「柱内标注能标下几段」：段高 = 段值÷Y轴最大值×绘图区高，
      绘图区高 ≈ 容器高 − 30（轴与标签）。实测 320px 时 37 个段里有 24 段太矮标不下；
      加到 400px 后能标的段明显变多（乐哥要的是"柱子里看到得数"，故宁高勿矮）。
   ⚠️ X 轴达人名是斜排的（52°），容器也要预留标签高度，否则标签被裁。 */
.cb-canvas { position: relative; height: 400px; margin-bottom: var(--space-2); }
/* .cb-canvas--roi（投产比图高度 300px）已于 2026-09-20 随 ROI 删除 —— 无 JS 引用 */
/* 「所有达人成本对比」上下两张图（乐哥 2026-09-16：「这俩表稍微小点，太大了」）
   ⚠️ 降高会**减少柱内标注**（判据：段高 = 段值÷Y轴最大值×绘图区高 ≥ 18px），
      故实测过候选高度再定，不能凭感觉砍：
        400px → 绝对值标出 13/37 段、百分比 33/36 段
        300px → 绝对值标出 11/37 段、百分比 31/36 段（只差 2 段，省下 200px 页面高度）
      再往下（280px）开始明显掉标注，收益也小，故停在 300px。 */
.cb-canvas--cmp { height: 300px; }
.cb-canvas canvas { display: block; }

/* ---------------- 对齐行网格（乐哥 2026-09-16 第三轮）----------------
   「左边柱状图、右边列表，达人名字的行左边柱状和右边列表保持同一行吧，这样看着直观」

   做法：**不并排两个容器**，而是「一行一个 grid」—— 条与数值列写在同一行的不同列里。
   为什么不用"左右两栏各自渲染 + 靠行高相等来对齐"：左栏 `.cb-hb` 行高 24px、
   右栏 `<table>` 行高 30px，行数一多就逐行错开（14 行能错开近百像素），
   而且窄屏折成上下两栏后对齐关系彻底失效。同一个 grid 行天然同行，与行高无关。

   ⚠️ 列宽用 CSS 变量统一定义，**表头行与数据行共用同一套模板** ——
      两边各写一份 `grid-template-columns` 必然漂移（同类问题：
      表头与数据对不齐，比不居中更难发现）。
   ⚠️ 条仍是「轨道 → 条」两层（`.cb-g-bar > i`），`<i>` 必须 `display:block`
      否则 width 不生效、条宽全为 0（本项目在 cb-row/cb-hb 上踩过两次）。 */
.cb-grid { --cb-n: 136px; --cb-v: 96px; --cb-x: 92px;
  display: grid; align-items: center; gap: 10px;
  padding: 7px 6px; border-radius: var(--radius-sm); font-size: var(--font-size-xs); }
.cb-grid--n4 { grid-template-columns: var(--cb-n) minmax(80px, 1fr) var(--cb-v) var(--cb-x); }
.cb-grid--n5 { grid-template-columns: var(--cb-n) minmax(80px, 1fr) var(--cb-v) var(--cb-x) var(--cb-x); }
/* 6 列：名 + 条 + 4 个数值列（成本结构环比：上月/本月/占比变化/金额变化） */
.cb-grid--n6 { grid-template-columns: var(--cb-n) minmax(72px, 1fr) var(--cb-v) var(--cb-v) var(--cb-x) var(--cb-x); }
.cb-grid--hd { color: var(--color-muted); font-size: 11.5px; font-weight: var(--font-weight-medium);
  padding: 4px 6px 7px; border-bottom: 1px solid var(--color-border); }
/* 合计行（tfoot 的等价物）—— 与数据行**同一套列模板**，所以数字天然落在同一列上；
   只在视觉上做区分：顶部分隔线 + 字重。 */
.cb-grid--ft { font-weight: var(--font-weight-medium); border-top: 1px solid var(--color-border);
  padding-top: 9px; margin-top: 2px; }
.cb-grid--ft:hover { background: none; }
.cb-grid:not(.cb-grid--hd):hover { background: var(--color-surface-2); }
.cb-g-n { color: var(--color-fg); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cb-g-bar { height: 16px; border-radius: var(--radius-sm); background: var(--color-surface-2); overflow: hidden; }
.cb-g-bar > i { display: block; height: 100%; border-radius: var(--radius-sm); }
/* 多条（成本结构环比：上一条=上月、下一条=本月）—— 纵向叠放，整格仍只占一行的位置，
   所以「条」与同一行的数值列依然在同一条水平线上（这正是改这个形态的目的）。 */
.cb-g-bar--multi { display: flex; flex-direction: column; gap: 3px; justify-content: center; height: 24px; }
.cb-g-bar--multi > i { height: 8px; flex: none; box-sizing: border-box; }
/* 「上月」是**空心参考条**：细一档、描边淡一点（2026-09-17 实测调整）
   ----------------------------------------------------------------------------
   第一版让空心条与实心条同高同浓（都是 8px、1.5px 实色描边）→ 实测放大看**两团的
   视觉重量相当**，读者看到的是"两条并列的条"，而不是"一条参考线 + 一条主条"。
   ⇒ 空心条改为 **5px 高 + 1px 描边、透明度 0.85**：
      · 高度差本身就建立了层级（细=次要/过去，粗=当前）
      · 描边要**接近实色**（0.85，不是 0.4）—— 再淡在浅底上又会回到"看不见"的老问题
   语义不变：空心/细 = 上月，实心/粗 = 本月。 */
.cb-g-bar--multi > i:first-child { height: 5px; margin-top: 1.5px; }
.cb-g-bar--multi > i:first-child { opacity: 0.85; }
.cb-g-v { text-align: right; font-variant-numeric: tabular-nums; font-feature-settings: "tnum", "lnum";
  color: var(--color-fg); white-space: nowrap; }
/* ⚠️ 次要列常放**长文本**（如"涉及的达人"名单）—— 必须截断，否则 nowrap 会把它顶出行宽：
   实测 8 个达人名把 548px 的行撑到 1158px（行内横向溢出，看着像表格坏了）。
   完整内容仍可悬停查看（调用方传 title）。 */
.cb-g-v--sub { color: var(--color-muted); overflow: hidden; text-overflow: ellipsis; }
/* 数值格里的**第二行小字**（2026-09-17 加，给「成本结构环比」的本月占比用）——
   把补充信息塞进同一格，避免为了多给一个数而加一列（列一多每列就窄到没法看）。
   ⚠️ 用 `em` 元素承载：`<em>` 默认斜体，必须显式 `font-style: normal` 抹掉，
      否则"占 75.1%"会斜着显示（与旁边数字风格不一致）。 */
.cb-g-em { display: block; font-style: normal; font-size: 11px; line-height: 1.25;
  color: var(--color-muted); font-weight: var(--font-weight-regular); }
/* 「最后一列是长名单」的变体：把最后一列从固定窄列换成可伸展列，
   让名单能多显示几个字（约 200px），而不是只剩 2 个字 + 省略号。
   ⚠️⚠️ 列数必须与 `--n5` **完全一致（5 列）** —— 我第一版只写了 4 列，
      第 5 个格子被挤到第二行（几何断言实测"数值列中心偏离 598px"直接抓到）。
      改模板时先数格子：名 + 条 + (cols-2) 个数值列。 */
.cb-grid--n5.cb-grid--widelast { grid-template-columns: var(--cb-n) minmax(72px, 1fr) var(--cb-v) var(--cb-v) minmax(150px, 1.2fr); }
.cb-grid--hd .cb-g-v { text-align: right; }
@media (max-width: 1100px) {
  .cb-grid { --cb-n: 106px; --cb-v: 86px; --cb-x: 78px; }
  .cb-grid--n5 { grid-template-columns: var(--cb-n) minmax(56px, 1fr) var(--cb-v) var(--cb-x); }
  .cb-grid--n5 > .cb-g-v:last-child { display: none; }
  /* n6 窄屏直接砍掉最后一列（金额变化）—— 剩下 5 列仍能读，且**不会横向溢出**。
     ⚠️ 砍列前先确认「留下的列自己说得清」：上月/本月/占比变化三列已能表达环比的结论。 */
  .cb-grid--n6 { grid-template-columns: var(--cb-n) minmax(48px, 1fr) var(--cb-v) var(--cb-v) var(--cb-x); }
  .cb-grid--n6 > .cb-g-v:last-child { display: none; }
  /* ⚠️⚠️ `--widelast` 是**双类**选择器（特异性 0,2,0），比这里的单类规则（0,1,0）高 ——
     不补这两行的话，窄屏根本收缩不了（实测 1024px 仍算出 5 列、最右列不隐藏）。
     结论：**给某个变体提特异性时，必须同步检查它在媒体查询里的对应规则**。 */
  .cb-grid--n5.cb-grid--widelast { grid-template-columns: var(--cb-n) minmax(56px, 1fr) var(--cb-v) var(--cb-x); }
  .cb-grid--n5.cb-grid--widelast > .cb-g-v:last-child { display: none; }
}
/* 联动画板里的 KPI 小卡（汇总 / 盈亏 / 收入 三个视图共用） */
.cb-kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: var(--space-3); margin-bottom: var(--space-4); }
.cb-kpi { border: 1px solid var(--color-border); border-radius: var(--radius-md);
  padding: 11px 14px; background: var(--color-surface); }
.cb-kpi__t { font-size: var(--font-size-xs); color: var(--color-muted); margin-bottom: 4px; }
.cb-kpi__v { font-size: 19px; font-weight: var(--font-weight-semibold); color: var(--color-fg);
  font-variant-numeric: tabular-nums; }
.cb-kpi__h { font-size: 11.5px; color: var(--color-meta); margin-top: 3px; }

/* ---------------- 联动高亮（乐哥 2026-09-16 第三轮）----------------
   点了扇形 / 筛了达人之后：命中的照常显示，没命中的整体压暗。
   ⚠️ 只压暗、不改布局与字号 —— 变暗的元素仍要能读（用户可能正想核对它）。
   ⚠️ 不给 `.is-dim` 元素加 `pointer-events:none`：hover 仍要能看 tooltip。 */
.cb-focus { font-size: var(--font-size-xs); color: var(--color-fg-2); line-height: 1.7;
  background: var(--color-primary-50); border-left: 3px solid var(--color-primary-500);
  border-radius: var(--radius-sm); padding: 5px 10px; margin-bottom: var(--space-2); }
.cb-focus b { color: var(--color-fg); }
.cb-tbl tr.is-dim,
.cb-mo.is-dim,
.cb-bars__col.is-dim { opacity: .34; }
/* ⚠️⚠️ 行网格的行高亮（2026-09-16 补）——
   此前 `applyFocus` 会给 `.cb-grid` 挂 `is-hl/is-dim`，但**这里没有对应的规则**：
   类加上去了、视觉一点没变。旧断言只查"class 是否存在"所以一直显示绿
   （典型"断言有效但没测到东西"）。
   ⇒ 门禁必须查 **computed 样式**（background-color / opacity 真的变了），不是查 class 名。 */
.cb-grid.is-dim { opacity: .34; }
.cb-grid.is-hl { background: var(--color-primary-50); border-radius: var(--radius-sm); }
.cb-grid.is-hl:hover { background: var(--color-primary-50); }
.cb-tbl td.is-dim, .cb-tbl th.is-dim { opacity: .34; }
.cb-tbl tr.is-hl { background: var(--color-primary-50); }
.cb-tbl td.is-hl, .cb-tbl th.is-hl { background: var(--color-primary-50); color: var(--color-fg); }
.cb-mo.is-hl { background: var(--color-primary-50); border-radius: var(--radius-sm); }

.cb-chart { position: relative; padding-left: 104px; }
.cb-row { display: flex; align-items: center; gap: var(--space-2); padding: 2px 0; font-size: var(--font-size-xs); }
.cb-row__n { position: absolute; left: 0; width: 98px; text-align: right; color: var(--color-fg);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cb-row__t { flex: 1 1 auto; height: 20px; background: var(--color-surface-2); border-radius: var(--radius-sm); overflow: hidden; }
.cb-row__b { height: 100%; display: flex; }
.cb-row__b > i { display: block; height: 100%; }
.cb-row__v { flex: none; width: 74px; text-align: right; font-variant-numeric: tabular-nums; font-size: var(--font-size-xs); color: var(--color-fg); }
.cb-axis { position: relative; height: 18px; margin-top: 4px; }
.cb-axis span { position: absolute; transform: translateX(-50%); font-size: 10.5px; color: var(--color-muted); white-space: nowrap; }
/* ⚠️⚠️ 这条规则**必须限定在 .cb-chart 作用域内**（2026-09-16 修，乐哥报障「文字高度重叠、
   整页有看不清的虚线、图表全没了」）：
     它原本是旧「横条图」的**竖向网格线**（给 .cb-chart 里的刻度线用）。
     但 `.cb-grid` 这个类名后来被**对齐行网格组件**（cost-board.js 的 rowsGrid）占用了 ——
     一个类名两套语义，于是每个行网格都被这条规则变成
     `position:absolute; top:0; bottom:0; border-left:1px dashed` →
     实测：全部行网格叠在页面左上角、尺寸被拉成 549×1200px、互相遮挡，
     表现为「KPI 文字重叠 + 满页半透明虚线 + 图表块整片消失」（其实 DOM 里都在）。
     ⚠️ 断言层面它极难抓：DOM 元素一个不少、class 也全对，
     只有量 `getBoundingClientRect()` 才发现尺寸/位置是坏的。
   ⇒ 旧组件（.cb-chart/.cb-row/.cb-axis）如有使用，行为**完全不变**；新行网格不再受污染。 */
.cb-chart .cb-grid { position: absolute; top: 0; bottom: 0; border-left: 1px dashed var(--color-border-soft); pointer-events: none; }
.cb-legend { display: flex; gap: var(--space-3); flex-wrap: wrap; font-size: var(--font-size-xs); color: var(--color-muted); margin-bottom: var(--space-2); }
.cb-legend span { display: flex; align-items: center; gap: 5px; }
.cb-legend i { width: 9px; height: 9px; border-radius: 2px; display: block; }
/* ==========================================================================
   图表小节标题（乐哥 2026-09-17 提版式问题后重构）
   ---------------------------------------------------------------------------
   原状：`font-size-sm`(14px) / `margin: 16px 0 8px`。乐哥原话：
     「这个标题字号大一点，和上面的模块（包括图）留一点行间距，现在挨在一起看着非常不直观」

   现版式（三条都是实测取值，不是拍脑袋）：
     · 字号 14px → **16px**（`--font-size-lg`，与「卡片标题」同档；窄屏令牌自动降到 15px）
     · 字重 500 → **600**（标题要能从正文里"跳"出来；16px 用 medium 仍偏软）
     · 距上方 16px → **24px**（`--space-6`）—— 实测原 16px 时标题紧贴上方图表/列表底边，
       视觉上被读成"上一块的一部分"
     · 距下方 8px → **12px**（`--space-3`）—— 原 8px 时标题与其图贴得太近，像图的副标题

   结构改为**两级**（乐哥：「标题很多都是我之前口语化的」）：
     · 主标题 = **名词短语**（是什么），不带括号、不带问句、不带动词短语
     · 副标题 `.cb-sub__hint` = **口径与读法**（怎么读），小字弱色，跟在主标题右侧
   用 flex + baseline 让两者同行对齐；hint 过长时自动换到下一行（row-gap 2px 保证紧贴）。
   ========================================================================== */
.cb-sub {
  display: flex; flex-wrap: wrap; align-items: baseline;
  /* ⚠️ 主副标题间隔用 `--space-3`(12px) 而非 `--space-2`(8px)：
     实测 16px 主标题 + 12px 副标题时，8px 间隔视觉上仍像"粘连的一个词"
     （「成本构成6 大类」读起来像「成本构成6」+「大类」）。 */
  column-gap: var(--space-3); row-gap: 2px;
  font-size: var(--font-size-lg); font-weight: var(--font-weight-semibold);
  color: var(--color-fg); line-height: var(--font-line-height-tight);
  margin: var(--space-6) 0 var(--space-3);
}

/* ---------------- 图 + 表并排 ----------------
   ⚠️ 窄屏必须放开 min-width（2026-09-16）：原来写死 400/360px，
      容器 < 760px 时两栏都放不下 → 内容**越出卡片**（与构成列表同一个坑）。
      窄屏改成上下摞，每栏各得整行宽。 */
.cb-split { display: flex; gap: var(--space-6); align-items: flex-start; flex-wrap: wrap; }
.cb-split__chart { flex: 1 1 520px; min-width: 400px; }
.cb-split__table { flex: 1 1 440px; min-width: 360px; }
@media (max-width: 900px) {
  .cb-split { flex-direction: column; gap: var(--space-4); }
  .cb-split__chart, .cb-split__table { flex: 1 1 auto; min-width: 0; width: 100%; }
}

/* 横条（投产比等）—— 同样是三层结构 */
.cb-hb { display: flex; align-items: center; gap: var(--space-2); padding: 3px 0; font-size: var(--font-size-xs); }
.cb-hb__n { flex: none; width: 132px; text-align: right; color: var(--color-fg); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cb-hb__t { flex: 1 1 auto; height: 18px; background: var(--color-surface-2); border-radius: var(--radius-sm); overflow: hidden; }
.cb-hb__b { height: 100%; display: block; border-radius: var(--radius-sm); }
.cb-hb__v { flex: none; width: 66px; text-align: right; font-variant-numeric: tabular-nums; font-size: var(--font-size-xs); }
.cb-hb__x { flex: none; width: 84px; text-align: right; color: var(--color-muted); font-variant-numeric: tabular-nums; font-size: var(--font-size-xs); }

/* 分档柱（盈亏分布） */
.cb-bars { display: flex; align-items: flex-end; gap: var(--space-3); height: 180px; padding: 0 var(--space-1); }
.cb-bars__col { flex: 1 1 0; text-align: center; }
.cb-bars__n { font-size: var(--font-size-xs); font-weight: var(--font-weight-medium); margin-bottom: 3px; }
.cb-bars__bar { border-radius: var(--radius-sm) var(--radius-sm) 0 0; }
.cb-bars__lb { font-size: 10.5px; color: var(--color-muted); margin-top: 6px; line-height: 1.4; }

/* 环比双条 */
.cb-mo { padding: 5px 0; }
.cb-mo__hd { display: flex; gap: var(--space-2); align-items: center; font-size: var(--font-size-xs); margin-bottom: 3px; }
.cb-mo__nm { flex: none; width: 100px; color: var(--color-fg); }
.cb-mo__dl { margin-left: auto; font-weight: var(--font-weight-medium); }
.cb-mo__t { height: 9px; border-radius: 3px; }
.cb-up { color: var(--color-danger); }
.cb-down { color: var(--color-success); }

/* 明细表（图+表搭配用）
   ⚠️ 数值列对齐必须**与全站一致**（2026-09-16 被 verify-num-right 抓出）：
      全站规范（乐哥 2026-09-14 定）= **长文本左 / 其余居中 / 数值列靠右、表头居中**。
      我第一版把 `.cb-tbl th` 统一写成 `text-align:left`，导致数值列表头左对齐、
      正文靠右 → **同一列出现两条参考线**（正是我在原型里批评过的方案②那种别扭）。
      ⇒ 数值列表头保持**居中**（与全站 .table thead th 一致），正文靠右。 */
.cb-tbl { width: 100%; border-collapse: collapse; font-size: var(--font-size-xs); }
.cb-tbl th { text-align: left; color: var(--color-muted); font-weight: var(--font-weight-medium);
  font-size: 11.5px; padding: 6px 8px; border-bottom: 1px solid var(--color-border); white-space: nowrap; }
.cb-tbl th.is-num { text-align: center; }
.cb-tbl td { padding: 6px 8px; border-bottom: 1px solid var(--color-border-soft); color: var(--color-fg); }
.cb-tbl tr:last-child td { border-bottom: none; }
.cb-tbl tfoot td { font-weight: var(--font-weight-medium); border-top: 1px solid var(--color-border); }
.cb-tbl .cb-n { text-align: right; font-variant-numeric: tabular-nums; font-feature-settings: "tnum", "lnum"; }
.cb-tbl .cb-hint { color: var(--color-meta); font-size: 11px; }
.cb-warnrow { background: var(--color-warning-bg); }
.cb-hot { color: var(--color-danger); font-weight: var(--font-weight-semibold); }
.cb-pill { display: inline-block; padding: 1px 7px; border-radius: var(--radius-md); font-size: 10.5px;
  background: var(--color-surface-2); border: 1px solid var(--color-border); color: var(--color-muted); }
.cb-pill--warn { background: var(--color-warning-bg); border-color: transparent; color: var(--color-warning-text); font-weight: var(--font-weight-medium); }
.cb-note { font-size: var(--font-size-xs); color: var(--color-muted); line-height: 1.8;
  margin-top: var(--space-3); padding-top: var(--space-2); border-top: 1px dashed var(--color-border); }
.cb-note b { color: var(--color-fg-2); }

@media (max-width: 1100px) {
  .cb-split__chart, .cb-split__table { flex: 1 1 100%; min-width: 0; }
  .cb-chart { padding-left: 88px; }
  .cb-row__n { width: 82px; }
}

/* ══════════════════════════════════════════════════════════════════════════
   2026-09-16 乐哥报障后新增：构成项可点 / 横向堆叠条 / 项目成本分组
   ══════════════════════════════════════════════════════════════════════════ */

/* 构成项"可点"的显式外观 + 键盘可达。
   ⚠️ `.cb-li` 早就有 cursor:pointer 与 hover（所以**看着**能点），
      但**点击事件从来没挂上** → 乐哥报障「点击也没反应」（另一半原因见 cost-board.js 的 noPick）。
      现在由 `--pick` 明确声明"这一行确实可点"，并给出 focus 环（键盘可达）。
   ⚠️ 反过来：不可点的行必须收回 pointer，否则又变成"看着能点却点不动"。 */
.cb-li--pick:focus-visible { outline: 2px solid var(--color-primary-500); outline-offset: -1px; }
.cb-li:not(.cb-li--pick) { cursor: default; }

/* ⚠️⚠️ 多段**横向拼接**成一根构成条（每个达人的费用由不同色块组成）——
   与 `--multi`（**纵向叠放**，给"上月/本月两条对比"用）语义完全不同，
   绝不能共用同一个类：纵向叠放会让"构成"变成两条并列的细线，读不出占比。 */
.cb-g-bar--stack { display: flex; flex-direction: row; }
.cb-g-bar--stack > i { flex: none; border-radius: 0; }
.cb-g-bar--stack > i:first-child { border-top-left-radius: var(--radius-sm); border-bottom-left-radius: var(--radius-sm); }
.cb-g-bar--stack > i:last-child { border-top-right-radius: var(--radius-sm); border-bottom-right-radius: var(--radius-sm); }

/* 焦点态堆叠柱容器高度（比汇总态 400px 略低：柱数更少、本页更长） */
.cb-canvas--stack { height: 360px; }

/* 副标题：口径与读法（「怎么读」），跟在主标题右侧同一行基线对齐。
   ⚠️ 字号用 `--font-size-xs`(12px) 而非 sm(14px)：它必须与 16px 主标题**拉开层级**，
      同尺寸会让两级标题糊成一句话（这正是改造前"看着不清晰"的原因之一）。 */
.cb-sub__hint { font-weight: var(--font-weight-regular); color: var(--color-meta); font-size: var(--font-size-xs); }

/* 项目成本分组头：固定成本（工资） / 项目归属 */
.cb-grp { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3);
  margin: var(--space-3) 0 var(--space-2); padding: 6px var(--space-2);
  background: var(--color-surface-2); border-radius: var(--radius-sm);
  font-size: var(--font-size-xs); color: var(--color-fg); font-weight: var(--font-weight-medium); }
.cb-grp em { font-style: normal; font-weight: var(--font-weight-regular); color: var(--color-meta); }
.cb-grp b { font-variant-numeric: tabular-nums; }

/* 项目成本逐项行：名称 / 金额 / 占比 / 备注 */
.cb-pjrow { display: grid; grid-template-columns: minmax(120px, 1.4fr) 96px 72px minmax(80px, 1fr);
  align-items: center; gap: 10px; padding: 6px var(--space-2); border-radius: var(--radius-sm);
  font-size: var(--font-size-xs); }
.cb-pjrow:hover { background: var(--color-surface-2); }
.cb-pjrow__n { color: var(--color-fg); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cb-pjrow__v { text-align: right; font-variant-numeric: tabular-nums; color: var(--color-fg); }
.cb-pjrow__p { text-align: right; font-variant-numeric: tabular-nums; color: var(--color-meta); }
.cb-pjrow__h { color: var(--color-meta); font-size: 11px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* 「每个人的报销」平铺在报销行下面（只有几个人，平铺比"点进去才看得到"更好读） */
.cb-pjpeople { display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-4);
  padding: 2px var(--space-2) var(--space-3); }
.cb-pjpeople > span { display: inline-flex; align-items: center; gap: 5px;
  font-size: var(--font-size-xs); color: var(--color-meta); }
.cb-pjpeople i { width: 7px; height: 7px; border-radius: 2px; background: var(--color-chart-3); flex: none; }
.cb-pjpeople b { color: var(--color-fg); font-variant-numeric: tabular-nums; }
.cb-pjpeople em { font-style: normal; color: var(--color-meta); font-size: 11px; }

/* 窄屏：项目成本行的"备注"列会让名称被挤扁，直接收掉（备注是次要信息） */
@media (max-width: 1100px) {
  .cb-pjrow { grid-template-columns: minmax(96px, 1.2fr) 88px 60px; }
  .cb-pjrow__h { display: none; }
}

/* ==========================================================================
   收支瀑布（`cw-` 前缀）—— 月度利润明细「收支构成」卡，乐哥 2026-09-17 选定「方案 A」
   --------------------------------------------------------------------------
   为什么放在本文件而不是新开一个 css：
     它=用的是这里的同一套配色令牌（--cc-t* / --cc-h* / --cc-p*）。配色若分两处定义，
     环形图与瀑布条迟早不同色 —— 同一笔钱两个颜色，用户会以为是两个东西。
   前缀用 `cw-`（不蹭 `cc-`）：两者布局与语义不同，混在同一前缀下以后无法分别改。

   ⚠️ 盈亏颜色**不在这里定义**：用全站既有的 `num-pos` / `num-neg`
      （css/table.css：盈利=红、亏损=绿，涨红跌绿）。项目 2026-09-13 刚因
      "盈利=绿"写反而修过一轮（css/pages.css 有记录），不要再写第三份颜色定义。
   ========================================================================== */
.cw-scope {
  /* 作用域标记类（样式归属）。
     ⚠️ 行高变量 `--cw-row-h` / `--cw-gap` / `--cw-block-gap` **定义在文件顶部 `:root`**，
        不在这里 —— 变量放 `:root` 作用域更宽、也不依赖本规则被解析。
        ⚠️ 但注意：本规则**曾经真的整条不生效**，原因**不是**"东西写在这里"，
        而是**它上面那段注释被提前闭合**了（详见文件顶部 `:root` 处的订正说明）。
        这类"注释写坏 ⇒ 后面的规则被静默丢弃"已有静态门禁：`tests/css-comment-balance.test.js`。 */
}

/* ---------------- 顶部：先回答"本月赚了多少" ---------------- */
.cw-head {
  display: flex; align-items: flex-end; gap: var(--space-6); flex-wrap: wrap;
  padding-bottom: var(--space-4); border-bottom: 1px solid var(--color-border-soft);
  margin-bottom: var(--space-5);
}
.cw-big {
  font-size: 28px; font-weight: var(--font-weight-semibold);
  letter-spacing: -0.4px; line-height: 1.15; font-variant-numeric: tabular-nums;
}
.cw-big small { font-size: var(--font-size-sm); font-weight: 400; color: var(--color-muted); margin-right: 6px; }
.cw-head__sub { font-size: var(--font-size-sm); color: var(--color-meta); margin-top: 3px; font-variant-numeric: tabular-nums; }
.cw-head__r { margin-left: auto; text-align: right; }
.cw-rate { font-size: var(--font-size-xl); font-weight: var(--font-weight-semibold); font-variant-numeric: tabular-nums; }
/* 「占收入」这三个字要跟着数字，否则 58.7% 会被当成系统别处的「盈亏率(÷GSV) 13.9%」（同页同词两个数） */
.cw-rate small { font-size: var(--font-size-xs); font-weight: 400; color: var(--color-muted); margin-right: 5px; }
.cw-rate__t { font-size: var(--font-size-xs); color: var(--color-meta); }
/* 并排的第二个口径（盈亏率 ÷GSV）：小一号、明确写分母 —— 消除同页两个"率"的歧义 */
.cw-rate__alt { font-size: var(--font-size-xs); color: var(--color-meta); margin-top: 3px; font-variant-numeric: tabular-nums; }

/* ---------------- 主体：左瀑布 + 右清单 ---------------- */
.cw-side { display: flex; gap: var(--space-6); align-items: flex-start; flex-wrap: wrap; }
.cw-main { flex: 1; min-width: 420px; }
.cw-list { width: 300px; flex: none; display: flex; flex-direction: column; gap: var(--cw-gap); }

.cw-wf { display: flex; flex-direction: column; gap: var(--cw-gap); }
.cw-row {
  display: grid; grid-template-columns: 90px 1fr 106px 58px;
  gap: var(--space-3); align-items: center;
  /* 行高固定 —— 与右侧 `.cw-lr` 用同一个变量，这是逐行对齐的关键 */
  height: var(--cw-row-h);
}
.cw-lbl {
  font-size: var(--font-size-sm); color: var(--color-fg-2); text-align: right;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.cw-track { height: 20px; background: var(--color-surface-2); border-radius: var(--radius-sm); overflow: hidden; }
.cw-track > i { display: block; height: 100%; border-radius: var(--radius-sm); }
.cw-val { font-size: var(--font-size-sm); text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.cw-pct { font-size: var(--font-size-xs); color: var(--color-meta); text-align: right; font-variant-numeric: tabular-nums; }
/* 分隔线：与标签列右缘对齐（90px 标签 + 12px 间距），把"成本"与"最后剩下的"分开 */
/* 结论行（左侧「盈亏」/ 右侧「总成本」）之前的横线 = 原来的 `.cw-sep`。
   ⚠️ 改成 `border-top` 而**不是**独立 div（2026-09-17）：独立 div 会**多占一行**
      （1px 高 + 一个 gap），导致左侧比右侧多一行 → 盈亏行与总成本行错开。
      用 border 后左右行数严格相同，横线也自然跟着这一行走。 */
.cw-row--end, .cw-lr--sum {
  border-top: 1px solid var(--color-border);
  margin-top: var(--cw-block-gap);
}
/* ⚠️ 这里刻意**不写 `padding-top`**：`.cw-lr` 的 `padding: 0 6px` 定义在本规则**之后**，
   同特异性下后者胜，写了也会被重置成 0 —— 留下一条"看着有 padding、实际没有"的声明，
   比不写更容易误导。行高 26px + `align-items:center` 已有约 3px 的自然留白。 */
.cw-row--end .cw-lbl, .cw-row--end .cw-val { font-weight: var(--font-weight-semibold); }
/* 「收入」与第一个成本项之间留块间距（乐哥 2026-09-17：「收入那条线是不是应该和
   下面其他支出模块也留点间距」）——
   ⚠️ 必须**左右各加一份**（左侧加在收入行、右侧加在表头行）：
      只加一侧会让后续所有行错位。 */
.cw-wf > .cw-row:first-child { margin-bottom: var(--cw-block-gap); }
/* 收入拆项副行：仅在"确实有其他收入"时出现（见 cost-waterfall.js），多数月不占版面 */
/* ⚠️ 2026-09-17 删除 `.cw-revsub` 规则：收入拆项已并入卡片顶部副行，
   JS 不再生成这个元素；留着死规则会让后人以为它还在用。 */

.cw-row--pick { cursor: pointer; border-radius: var(--radius-sm); }
.cw-row--pick:hover, .cw-row--pick.is-on { background: var(--color-primary-50); }
.cw-row--pick:hover .cw-lbl, .cw-row--pick.is-on .cw-lbl { color: var(--color-accent-text); }
.cw-row--pick.is-on .cw-lbl { font-weight: var(--font-weight-medium); }
.cw-row--pick:focus-visible, .cw-lr--pick:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 1px; }

/* ---------------- 0 值项折叠 ----------------
   默认收起（实测 17 项里 9 项是 ¥0，全列出来会把有值的淹掉）；折叠行一点就展开。
   ⚠️ 恢复时必须还原成 `grid` —— `.cw-row` 与 `.cw-lr` 都是 grid 布局，
      写成 `display:block` 会让列全部串位（"展开后串列"这类错最难发现）。 */
.cw-scope .cw-zero { display: none; }
.cw-scope.is-showzero .cw-zero { display: grid; }

/* ---------------- 图例 ---------------- */
.cw-legend {
  display: flex; gap: var(--space-4); flex-wrap: wrap;
  margin-top: var(--space-4); font-size: var(--font-size-xs); color: var(--color-meta);
}
.cw-legend span { display: inline-flex; align-items: center; gap: 5px; }
.cw-dot { display: inline-block; width: 9px; height: 9px; border-radius: 2px; flex: none; vertical-align: middle; }

/* 口径/读法说明：这张卡的解释文字与其数字同等重要（不写清"基准是什么"就会被误读） */
.cw-note { margin-top: var(--space-3); font-size: var(--font-size-xs); color: var(--color-meta); line-height: 1.7; }
.cw-note b { color: var(--color-fg-2); }

/* ---------------- 右侧成本清单 ---------------- */
/* 左右两侧的每一行都用 `--cw-row-h` —— 右侧成本项与左侧瀑布条**逐行同高**。
   ⚠️ `.cw-lh`（成本项表头）也必须占**整整一行**：它对齐的是左侧的「收入」行。
      改前它只有 padding 撑出来的 27px，而左侧收入行是 28px 步进 → 从这里就开始错位。 */
.cw-lh, .cw-lr {
  display: grid; grid-template-columns: 1fr 96px 62px;
  gap: var(--space-2); align-items: center;
  height: var(--cw-row-h);
}
.cw-lh {
  font-size: var(--font-size-xs); color: var(--color-meta);
  padding: 0 6px; border-bottom: 1px solid var(--color-border);
  /* 与左侧「收入」行的 `margin-bottom` 成对出现（见 `.cw-wf > .cw-row:first-child`）——
     两侧都加同样的块间距，后续所有行才仍然逐行对齐。 */
  margin-bottom: var(--cw-block-gap);
}
.cw-lh span:not(:first-child) { text-align: right; }
.cw-lr {
  padding: 0 6px; font-size: var(--font-size-sm);
  border-bottom: 1px solid var(--color-border-soft); border-radius: var(--radius-sm);
}
.cw-lr__n {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--color-fg-2);
}
.cw-lr__n .cw-dot { margin-right: 6px; }
.cw-lr__v, .cw-lr__p { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.cw-lr__p { font-size: var(--font-size-xs); color: var(--color-meta); }
.cw-lr--sum { border-bottom: 0; font-weight: var(--font-weight-semibold); }
.cw-lr--sum .cw-lr__n { color: var(--color-fg); }
.cw-lr--pick { cursor: pointer; }
.cw-lr--pick:hover, .cw-lr--pick.is-on { background: var(--color-primary-50); }
.cw-lr--pick.is-on .cw-lr__n { color: var(--color-accent-text); }

.cw-fold {
  display: flex; align-items: center; gap: 4px; width: 100%;
  margin-top: var(--space-3); padding: 6px 10px;
  font-family: inherit; font-size: var(--font-size-xs); color: var(--color-meta);
  text-align: left; background: var(--color-surface-2); border: 0; border-radius: var(--radius-seg); cursor: pointer;
}
.cw-fold:hover { color: var(--color-fg-2); }
.cw-fold__i { display: inline-flex; transition: transform var(--motion-base) var(--motion-ease-in-out); }
.cw-scope.is-showzero .cw-fold__i { transform: rotate(180deg); }

/* 构成面板：点某一条后就地展开（复用 CostChart.compositionPanel，样式沿用 .cc-panel） */
.cw-panel:not(:empty) { margin-top: var(--space-5); }

@media (max-width: 900px) {
  .cw-main { min-width: 0; }
  .cw-list { width: 100%; }
  .cw-row { grid-template-columns: 76px 1fr 96px 54px; }
  .cw-sep, .cw-revsub { margin-left: 88px; }
}


/* ==== /css/overlays.css ==== */
/* ==========================================================================
   达播工作台 — overlays.css
   抽屉 / 弹窗 / 提示 / 表单布局 / 工具栏 / 提示条
   ========================================================================== */

/* ---- 抽屉 ---- */
/* ⭐⭐⭐ 2026-10-08 21:3x 抽屉在**蓝框内**、且**不随滚动移动**（本轮第二次做法，两次都留着记录）
   ────────────────────────────────────────────────────────────────────────────────────────
   乐哥先后两条要求（**别只看最后一条**）：
     ① 20:5x：「划入的那个**没有约束在蓝框内**，我们之前沟通的，内容页是在蓝框内的」
     ② 21:2x：「这个怎么滑动还能滑上去的？」
   ▸ 第①条当时用 `#drawer-root` 移进 `.app-main` + `absolute; inset: 0` 实现，
     边界确实对上了（实测差 0px）——**但第②条证明那是个不够的解法**：
     真机复现（`scripts/frontend-debug/diag-drawer-scroll-away.js`）：
         `.app-main` 是**滚动容器**（overflowY=auto, sh=1338 > ch=934）
         在抽屉内滚轮 +500  →  `.app-main.scrollTop 0 → 404`
         `.drawer` top 8 → **-396**（相对蓝框 -404）、`.drawer__header` 整个跑出框外被裁
     ⇒ `absolute` 定位在**内容坐标系**里 ⇒ 挂在滚动容器里必然跟着滚。
   ▸ 现行方案（同时满足两条）：**body 级 + `fixed` + JS 实测蓝框的视口坐标**
       · `fixed` 相对**视口** ⇒ 任何滚动容器都带不走它（解决第②条）；
       · `left/top/width/height` 直接吃 `getBoundingClientRect()` 的返回值（那**就是**视口坐标）
         ⇒ 与蓝框逐像素重合（解决第①条，且不再需要上一版"右扩滚动条宽"的补丁：
          rect 的宽度是 **border box**，本来就把滚动条那 15px 包在内）。
   ⚠️ 四个 `--drawer-frame-*` 会随「窗口尺寸 / 侧栏折叠 / 缩放」变 ⇒ 由 app.js 的
      `syncDrawerFrame()` 写入（ResizeObserver 盯 `.app-main`），**CSS 里不写死任何 px**。
   ⚠️ 用 `--shell-inset-radius`（与蓝框**同一档**，别写死数值）。
   ⚠️ `overflow: hidden` 让抽屉这个"贴边矩形"的直角自动跟着变圆（它自己不用写圆角）。
   ⚠️ `pointer-events: none` **必须给本层**：它是铺满蓝框的空壳，不关掉会挡住整页的点击；
     两个子层（遮罩/抽屉）各自 `pointer-events: auto` 开回来。
   ⛔ 别把 `fixed` 改回 `absolute`（会跟着滚动跑，第②条复发）；也别把本层收回 `.app-main` 里
      （`.app-main` 是滚动容器 ⇒ 同样复发）。 */
#drawer-root {
  position: fixed;
  left: var(--drawer-frame-left);
  top: var(--drawer-frame-top);
  width: var(--drawer-frame-width);
  height: var(--drawer-frame-height);
  z-index: var(--z-modal);
  border-radius: var(--shell-inset-radius);
  overflow: hidden;
  pointer-events: none;
}
#drawer-root > .drawer-overlay,
#drawer-root > .drawer {
  pointer-events: auto;
}

.drawer-overlay {
  position: absolute;   /* 2026-10-08：fixed → absolute（相对 #drawer-root = 蓝框，不再贴视口） */
  inset: 0;
  background: color-mix(in srgb, var(--color-gray-900) 40%, transparent);
  z-index: var(--z-modal);
  animation: fade-in var(--motion-slow) var(--motion-ease-sheet) both;
}
/* 退场（JS 加 [data-closing]）—— 遮罩与抽屉**同时**退，且用同一条时长 */
.drawer-overlay[data-closing] {
  animation: fade-out calc(var(--motion-slow) * 0.62) var(--motion-ease-exit) both;
}

.drawer {
  /* ⚠️ 这里 `absolute` 是**对的**：父层 `#drawer-root` 已是 `position: fixed`（铺满蓝框），
     本层相对它铺满即可。⛔ 别改回 `fixed` —— 那会重新变成"贴视口"（超出蓝框 8px）。 */
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: 560px;
  max-width: 92vw;
  background: var(--color-surface);
  box-shadow: var(--shadow-raised);
  z-index: var(--z-modal);
  display: flex;
  flex-direction: column;
  animation: slide-in-right var(--motion-slow) var(--motion-ease-sheet) both;
}
/* ⭐ 2026-10-18（乐哥：「抽屉展开的列宽一点吧，当前的不行，信息没一次性展示全」）——
   宽档抽屉（`.drawer--wide`）。560px 减去左右内边距只剩 ~496px，**抽屉里放表格**时
   7 列必然折行（一个账户占两行），信息就不可能"一眼全看到"。
   ⚠️ 用 **1000px**，与排期页的分栏抽屉（`.drawer--split`，pages-schedule.css）**同一档** ——
      全站只有"常规 560 / 宽 1000"两个宽度，⛔ 别再引入第三种（那样谁也说不清该用哪种）。
   ⚠️ 档位由 `UI.openDrawer({ size: 'wide' })` 开（白名单，只认这一个值）。
   ⚠️ 手机/窄屏仍由 `.drawer` 的 `max-width: 92vw` 兜底（不改那条，正是它保证不撑破屏）。 */
.drawer--wide { width: 1000px; }

/* 退场：只滑 24% 就淡出，比进场快一档（理由见文件上方「进出场成对」那段） */
.drawer[data-closing] {
  animation: slide-out-right calc(var(--motion-slow) * 0.62) var(--motion-ease-exit) both;
  pointer-events: none;
}

.drawer__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-6);
  border-bottom: 1px solid var(--color-border-soft);
  flex-shrink: 0;
}

.drawer__title {
  font-size: var(--font-size-lg);
  font-weight: var(--font-weight-semibold);
  color: var(--color-fg);
}

/* 标题右侧的额外操作位（`openDrawer({ headerExtra })` 的落点）——
   ⚠️ `margin-left: auto` 把本元素及其后的关闭按钮**一起推到右边**：
      不写的话，header 是 `space-between`，三元素时会被均分（extra 跑到正中间、离关闭按钮很远）。
   ⚠️ 不传 headerExtra 时本元素是空 span、宽为 0，加上 auto 后布局与改前一致（title 左 / 关闭右）。
   ⚠️ 用 `inline-flex` 而不是 block：里面的按钮组要横排、居中。 */
.drawer__header-extra {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}

.drawer__body {
  flex: 1;
  overflow-y: auto;
  padding: var(--space-5) var(--space-6);
  /* ⭐⭐ 2026-10-08 21:3x 滚到底/顶之后**不许把滚动链传给底层**。
     ▸ 病根（乐哥：「这个怎么滑动还能滑上去的？」同一轮的第二个成因）：抽屉里
       滚到边界后，滚动事件顺着冒泡链继续传 ⇒ 把底下的 `.app-main` 一起滚走，
       关掉抽屉发现页面位置变了（用户莫名其妙"页面自己滑上去了"）。
     ▸ 与 `.app-shell.has-drawer .app-main { overflow: hidden }`（layout.css）**是一对**：
       本条管"抽屉能滚、但滚到边就不许再往外传"；
       那条管"抽屉压根没得滚（内容不满一屏）时滚轮直接落到 `hidden` 的底层、什么都不动"。 */
  overscroll-behavior: contain;
}

.drawer__footer {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-6);
  border-top: 1px solid var(--color-border-soft);
  flex-shrink: 0;
}

/* ══════════════════════════════════════════════════════════════════════════════
   2026-09-26 动效升级：**进出场成对**（乐哥：「要求轻盈灵动，不要傻快傻快」）
   ──────────────────────────────────────────────────────────────────────────────
   ⛔ 改前的真实问题：**只进不出**。弹窗/抽屉/提示条的关闭都是直接
      `ov.remove(); modal.remove();` —— 动画只在"进场"那一侧有，
      关闭是**瞬间消失**。这是"机械感"的一大来源（进来很讲究、出去很粗暴）。
   ✅ 现在每对都齐了，且**退场刻意比进场快、位移更小**：
      · 退场用 --motion-ease-exit（easeInQuad，干脆收走）而不是进场的 easeOut
        —— 离场拖泥带水会让人觉得"卡"；进场才需要"慢下来"的从容感。
      · 退场时长 = 进场 × 0.62，位移只走一小段（抽屉退场只滑 24% 宽，不滑满整屏）。
      · 弹窗退场**下沉**（往下走一点）而不是上浮 —— 与进场方向一致，
        读起来是"缩回去"而不是"又飞出去了"。

   ⚠️ 用 `[data-closing]` 属性而不是再加一个 `.is-closing` 类：
      本项目类名已经很多，且这个标记**只服务于"正在退场"这一个瞬间状态**，
      用属性更不容易与业务类名撞车（`is-closing` 这种通用词很容易被别处复用）。
   ⚠️ 退场动画的 duration 必须与 JS 里的 setTimeout 一致（JS 用的是令牌派生值，
      见 ui.js 的 `_exitDur()`）—— 两处不一致会出现"动画没播完就被删"或"白等一截"。
   ══════════════════════════════════════════════════════════════════════════════ */
@keyframes slide-in-right {
  from { transform: translateX(100%); }
  to { transform: translateX(0); }
}
/* 抽屉退场：只滑 24% 就淡出（不必滑满整屏宽才消失，那样像"被推走"） */
@keyframes slide-out-right {
  from { transform: translateX(0); opacity: 1; }
  to { transform: translateX(24%); opacity: 0; }
}

@keyframes fade-in {
  from { opacity: 0; }
  to { opacity: 1; }
}
@keyframes fade-out {
  from { opacity: 1; }
  to { opacity: 0; }
}

/* ---- 弹窗 ---- */
.modal-overlay {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: color-mix(in srgb, var(--color-gray-900) 40%, transparent);
  z-index: var(--z-modal);
  padding: var(--space-4);
  animation: fade-in var(--motion-slow) var(--motion-ease-sheet) both;
}
/* 退场：遮罩与弹窗**同时**退，用同一条时长（错开会出现"内容没了、黑底还在"的瞬间） */
.modal-overlay[data-closing] {
  animation: fade-out calc(var(--motion-slow) * 0.62) var(--motion-ease-exit) both;
}

.modal {
  /* 默认宽度 720 → 880（乐哥 2026-09-13：「宽一点、高一点，不用那么小家子气」）。
     720px 下多列表单/明细要横向滚动、字段被挤成两行，观感局促；
     880px 在 1440 宽屏上仍留足两侧留白，且能容纳 3~4 列并排字段。
     ⚠️ 各处调用若显式传了 width（如 inventory 的 720）会覆盖本默认值，需同步改。 */
  width: 880px;
  max-width: 100%;
  max-height: 90vh;
  display: flex;
  flex-direction: column;
  background: var(--color-surface);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-raised);
  /* ⭐ 2026-09-26：进场曲线由 ease-standard 改为 **--motion-ease-pop（弹入）** + 起点带一点缩放。
     为什么加 scale：纯"上浮 + 淡入"读起来是"页面里冒出一块"；
     配 .94 → 1 的微缩放 + 弹入曲线之后才有"从远处推近并轻微过冲"的空间感
     （这是"灵动"的关键一笔）。
     ⚠️ 曲线必须是 pop 那条（y1=1.56）：ease 那条的过冲只有 2px 级，
        用在这里读不出来，弹窗会显得"只是滑了上来"。
     幅度刻意只到 .94 —— 到 .9 以下就成了弹跳玩具。 */
  animation: modal-in var(--motion-slow) var(--motion-ease-sheet) both;
}
/* 退场（JS 加 [data-closing] 后触发，播完才 remove） */
.modal[data-closing] {
  animation: modal-out calc(var(--motion-slow) * 0.62) var(--motion-ease-exit) both;
  pointer-events: none;   /* 正在消失时不再接受点击，避免"点了两次" */
}

.modal__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* ⭐⭐ 2026-09-28 左右内边距统一 24px（乐哥拍板：「弹窗：只统一左右到 24px」）——
     改前是 `var(--space-5)`（20px），比卡片的 24px 窄 4px ⇒ 弹窗里的表单与页面表单
     **左缩进对不齐**（同一套表单控件在弹窗里比在页面上靠左 4px）。
     ⚠️ **垂直不动**：头/脚保持 `--space-4`(16)、body 保持 `--space-5`(20) ——
        这是刻意的分层（标题栏与按钮栏更紧凑，不与内容区等重），拉到 24 反而松垮。
     ⚠️ 抽屉（.drawer__*）**一起改**：它与弹窗是同一套浮层语言（同圆角 `--radius-xl`、
        同头/身/脚结构），只改弹窗会让两者差 4px、又造出一处新的不一致。 */
  padding: var(--space-4) var(--space-6);
  border-bottom: 1px solid var(--color-border-soft);
  flex-shrink: 0;
}

.modal__title {
  font-size: var(--font-size-lg);
  font-weight: var(--font-weight-semibold);
}

.modal__body {
  padding: var(--space-5) var(--space-6);
  overflow-y: auto;
  flex: 1;
  /* ⭐⭐ 2026-10-09 **420 → 260**（表单统一第一批）
     ──────────────────────────────────────────────────────────────────────────
     🐞 乐哥 10-09 给的两组截图：「新增品牌」这种**只有 3 个字段**的弹窗下方空出约 290px。
     ▸ 根因：本值是 2026-09-13 为"内容少的弹窗不至于扁扁一条"加的**硬性保底**，
       但它是**所有弹窗的下限**，与内容多少无关 ⇒ 短表单被硬撑到 420px 高。
     ▸ 改成 260：仍保住"不扁"（260 ≈ 标题栏 56 + 一行字段 78 + 底部按钮 60 + 余量），
       内容多的自然撑高、超过 90vh 时内部滚动（`max-height` + `overflow-y:auto` 不变）。
     ⚠️ 确认框等"内容自适应"的弹窗照旧显式传 `bodyMinHeight: 0` 覆盖本值（ui.js openModal），
        它们与本值无关 —— **别因为"确认框没问题"就以为这条没影响**。
     ⛔ 不许改回 420（那是"短表单一大片空白"的直接原因，门禁 form-unify ② 锁着）。 */
  min-height: 260px;
}

.modal__footer {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-6);
  border-top: 1px solid var(--color-border-soft);
  flex-shrink: 0;
}

/* ⭐⭐⭐ 2026-09-28 弹窗 / 抽屉的操作按钮统一到**浮岛尺寸**（乐哥拍板：
   「弹窗的取消，确认按钮尺寸不对，整个系统这俩类似按钮都查下」+「都按照浮岛尺寸改」）。
   ──────────────────────────────────────────────────────────────────────────
   ▸ 普查数据（全站真机扫，按容器归类）：
       卡内工具栏  34px × 60+ 处   ← 全站绝对主导
       表单浮岛    34px × 4 页
       弹窗 footer **44px**        ← 唯一的两处例外之一（乐哥肉眼看出"不对"）
       页头主操作  **44px**        ← 另一处（**本次不改**，见下方说明）
     ⇒ 所以"不对"的根因是**弹窗是全站唯一还留着 44 的成对操作按钮** ——
        它不是"弹窗做小了"，而是"弹窗没跟上全站已经统一的 34"。
   ▸ 为什么取浮岛而不是反过来把浮岛提到 44：
     ① 34 是**全站绝大多数**（60+ 处卡内工具栏），改一处比改六十处稳；
     ② `--control-height-sm`(34) 的语义就是"紧凑档：列表工具栏 / 表格行内" ——
        弹窗 footer 与它们同级（都是"顺手的次级操作"，不是页面主操作）；
     ③ 乐哥上轮刚把浮岛定成 34 并说"尺寸和 tab 一样"，反向改会让那条决定作废。
   ▸ 为什么**不动页头主操作**（`.page-actions .btn--lg`，仍是 44）：
     它不是"取消/确认"这类成对操作，而是**页面级主操作**（「新增排期」「导入」），
     且与页头 tab 容器同排 —— 那里 44 是 2026-09-28 早上刚定的"与 tab 容器齐平"。
     乐哥说的是"这俩类似按钮"（取消/确认），不包含页面主操作。
   ⚠️ 宽度也走浮岛的 `min-width: 88px`（乐哥说"按照浮岛尺寸"）——
      两字按钮（取消/确定）会从 62px 撑到 88px，与浮岛的取消/创建**逐像素同宽**。
   ⚠️ 只作用于 **footer 内**：弹窗 body 里的按钮（表格行内"详情""添加"等）不受影响 ——
      它们本来就该跟表格走（34，已在卡内工具栏那档）。
   ⚠️ 确认框（`confirmDialog`）与所有 `openModal({footer})` 调用**自动生效**（同一个 footer）。 */
.modal__footer .btn,
.drawer__footer .btn {
  height: var(--control-height-sm);
  min-height: var(--control-height-sm);
  min-width: 88px;
}

@keyframes modal-in {
  /* ⭐ 2026-09-26：起点缩放 .97 → .94、位移 rise 由 8px 提为 10px（见 design-tokens）。
     ⭐⭐ 2026-09-29 曲线由 `--motion-ease-pop`（过冲 13.6%）换成
        **`--motion-ease-sheet`（Apple 式，无过冲）** + 时长 base(155) → slow(300)。
        原因：逐帧定格实测「配 pop 曲线时 **60ms 就已完全到位**，整段 155ms 里只有
        45ms 可见运动」⇒ 观感是"啪地冒出来"。乐哥：「要自然」。
        现在 0ms→.94 / 60ms→.979 / 120ms→.995 / 300ms→1.000，**全程单调趋近、不回弹**。
     ⚠️ 别再按"加回弹更活泼"改回去 —— 浮层是"展开"不是"蹦一格"，
        回弹归 `.switch__thumb` 那类小物件（它们仍用 `--motion-ease-pop`）。 */
  from { opacity: 0; transform: translateY(var(--motion-rise)) scale(0.94); }
  to { opacity: 1; transform: translateY(0) scale(1); }
}
/* 退场：往下沉一小段（与进场同向，"缩回去"），比进场快一档 */
@keyframes modal-out {
  from { opacity: 1; transform: translateY(0) scale(1); }
  to { opacity: 0; transform: translateY(4px) scale(0.985); }
}

/* ⭐ 2026-09-29 **面板族统一入场**（下拉 / 弹出面板）。
   为什么新增这条：勘察发现「日期面板 `.dtp-panel` 有淡入、而**选人框下拉
   `.fp__dropdown` 零动效**」—— 同族两个控件，一个有过程一个"瞬间出现"，
   并排放在同一个表单里就会读出"两个系统"。
   ⚠️ 与 `.dtp-panel` 的 `dtp-fade-in` **观感一致**（同为 -4px + 淡入），
      但这里用独立名字：`dtp-*` 是日期选择器专用命名，被别处复用会误导维护者。
   ⚠️ 曲线用 `--motion-ease-sheet`（无过冲）—— 面板是"展开"不是"弹"，且它紧贴
      输入框，带过冲会与输入框边框产生"撞一下"的错觉。
   ⚠️ 位移只用 4px（比 `--motion-rise` 的 12px 小）：面板紧贴触发控件，
      走太远会与输入框"脱开"（同 `.row-menu` 的取舍）。 */
@keyframes panel-in {
  from { opacity: 0; transform: translateY(-4px); }
  to { opacity: 1; transform: translateY(0); }
}

/* ---- 提示 toast ---- */
/* ── 行内「更多」菜单（2026-09-14 新增，配合 UI.rowMenu）──────────────────────────────
   用途：操作列动作多、表格宽度紧张时，把次要动作收进「⋯」菜单。
   与全站浮层的层级关系：dropdown(1000) < modal(1200) < toast(1300)
     —— 菜单是最轻的浮层，不该盖住弹窗。
   ⚠️ `position: fixed`（不是 absolute）：组件把它挂在 body 上、用 JS 算屏幕坐标，
      所以这里必须是相对视口的定位，否则坐标会错。
   ⚠️ 无 emoji、用统一描边图标；颜色全部走语义 token。 */
.row-menu {
  position: fixed;
  z-index: var(--z-dropdown);
  min-width: 136px;
  padding: var(--space-1) 0;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  /* ⭐ 2026-09-26：进场 —— 从锚点按钮方向轻轻展开（缩放 .96 + 上浮 4px）。
     ⚠️ `transform-origin: top left`：菜单挂在「⋯」按钮下方，从**左上角**长出来
        才符合"从这里点开的"因果；用中心缩放会看起来像凭空冒出来。
     ⚠️ 位移只用 4px（比 --motion-rise 的 8px 小）：菜单紧贴按钮，走太远会与按钮脱开。 */
  transform-origin: top left;
  animation: row-menu-in var(--motion-base) var(--motion-ease-sheet) both;
}
/* 向上翻的菜单（下方空间不够时 JS 会把它放到按钮上方，并打 data-flip）——
   展开原点跟着翻到左下，否则方向感是反的。 */
.row-menu[data-flip] { transform-origin: bottom left; }
.row-menu[data-closing] {
  animation: row-menu-out calc(var(--motion-base) * 0.7) var(--motion-ease-exit) both;
  pointer-events: none;
}
@keyframes row-menu-in {
  from { opacity: 0; transform: translateY(-4px) scale(0.96); }
  to { opacity: 1; transform: translateY(0) scale(1); }
}
@keyframes row-menu-out {
  from { opacity: 1; transform: translateY(0) scale(1); }
  to { opacity: 0; transform: translateY(-3px) scale(0.97); }
}
.row-menu__item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  padding: var(--space-2) var(--space-3);
  border: 0;
  background: none;
  cursor: pointer;
  font-family: inherit;
  font-size: var(--font-size-sm);
  color: var(--color-fg);
  text-align: left;
  white-space: nowrap;
}
.row-menu__item:hover:not(:disabled) { background: var(--color-surface-2); }
.row-menu__item:focus-visible { outline: 2px solid var(--color-accent); outline-offset: -2px; }
/* 禁用态：不只是变灰，还给出原因（title 里写清"需先确认"这类前置条件） */
.row-menu__item:disabled { color: var(--color-disabled); cursor: not-allowed; }
.row-menu__item svg { flex: none; color: var(--color-muted); }
.row-menu__item:disabled svg { color: var(--color-disabled); }
/* 危险动作（删除）：红色区分，避免与"编辑"误点 */
.row-menu__item--danger,
.row-menu__item--danger svg { color: var(--color-danger); }

.toast-root {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  z-index: var(--z-toast);
  pointer-events: none;
}

.toast {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  background: var(--color-gray-900);
  color: var(--color-gray-0);
  font-size: var(--font-size-sm);
  box-shadow: var(--shadow-raised);
  /* ⭐ 2026-09-26：`.toast-root` 是 `top:50%` 的居中容器（整组一起垂直居中），
     所以进出场的位移只能写在这条上 —— 基点用 `translateY`，**不要**再写 translate(-50%)
     （那是 root 的职责，重复写会把这条 toast 自己挪到屏幕左上）。 */
  animation: toast-in var(--motion-base) var(--motion-ease-sheet) both;
}
/* 退场：向上淡出（与进场"从上方落下"呼应），比进场快一档 */
.toast[data-closing] {
  animation: toast-out calc(var(--motion-base) * 0.62) var(--motion-ease-exit) both;
}

.toast--success { background: var(--color-success); color: var(--color-gray-0); }
.toast--error { background: var(--color-danger); color: var(--color-gray-0); }

@keyframes toast-in {
  from { opacity: 0; transform: translateY(calc(var(--motion-rise) * -1)); }
  to { opacity: 1; transform: translateY(0); }
}
@keyframes toast-out {
  from { opacity: 1; transform: translateY(0); }
  to { opacity: 0; transform: translateY(-6px); }
}

/* ══════════════════════════════════════════════════════════════════════════════
   2026-09-26 动效：**打勾"描线画出"**（成功提示）
   ──────────────────────────────────────────────────────────────────────────────
   改前：成功提示里的 ✓ 是**直接出现**的（和文字一起淡入）。
   改后：✓ 的笔画**从左往右画出来**，延迟 80ms 起跑（让 toast 先落位，再"盖章"）。
   为什么这一笔值得做：提示是"操作已完成"的唯一确认，画出来的勾比跳出来的勾
   **多一层"正在为你完成"的叙事感** —— 这是"灵动"里性价比最高的一处（纯 CSS，零 JS）。

   ⚠️ 判据用 `.toast:not(.toast--error)`：错误提示的图标是圆圈感叹号（alertCircle），
      给它做描线会变成"圆圈慢慢画出来"，看起来像加载中，反而误导。
   ⚠️ `stroke-dasharray/offset: 28` 是对 `I.icon('check', 16)` 那条折线的实测长度
      （M20 6 9 17l-5-5）。改图标路径时这个值要跟着改，否则会出现"画完了还有一截没画"
      或"提前画完"。28 留了一点余量，宁可略长（多出的部分在 offset 为 0 时不可见）。
   ⚠️ 不给 `.toast` 加 `both` 之外的 fill：动画结束态就是 `stroke-dashoffset: 0`（完整可见），
      与"没有动画时"的渲染结果一致 ⇒ 动画被禁用（减少动效偏好）也不会导致勾消失。 */
.toast:not(.toast--error) svg path {
  stroke-dasharray: 28;
  stroke-dashoffset: 28;
  /* ⭐ 2026-09-29：曲线改 sheet（无过冲）—— 描线走的是 stroke-dashoffset，
     过冲会让虚线"画过头再回缩"（末端抖一下）。 */
  animation: toast-check-draw 440ms var(--motion-ease-sheet) 80ms both;
}
@keyframes toast-check-draw {
  to { stroke-dashoffset: 0; }
}

/* ---- 表单布局 ---- */
.form-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  /* ⭐ 2026-09-29 乐哥全站审察：「不要太紧凑，要有呼吸感」——
     原 16px 偏紧（实测：字段行间距 16px、网格行净距 16px）。
     加大到 --space-5（20px），行与行、列与列都松一档。 */
  gap: var(--space-5);
}

.form-grid > * {
  min-width: 0; /* 防止长内容（如地点地址）撑宽 grid 列 */
}

/* 表单网格里的控件默认撑满列宽（输入框/下拉/文本域），避免左右留白 */
.form-grid .input,
.form-grid .select,
.form-grid .textarea {
  width: 100%;
}

/* ⭐ 2026-09-29 乐哥：「很多输入框没必要那么宽，可以稍微窄一点，这样右边的输入框
   就和左边的不用挨这么近」。实测（1920 视口，货品/达人表单）：控件撑满列宽时
   两列控件之间只剩 **16px**（那正是列间距本身），读起来像"两个框贴在一起"。
   ⇒ 单列控件限宽 380px：控件不再吃满整列，两列之间的净距由 16px 拉到 **51~71px**。
   ⚠️ 例外两条（都是"本来就该宽"的信号，别一起限）：
      · `.span-2`（整行字段，如合同链接/授权备注）保持满宽；
      · `textarea` 不限 —— 备注/说明类多行框限窄了反而难用（本次只限 input/select）。
   ⭐⭐ 2026-09-29 追加（乐哥：「排期节点里面，输入框的宽度有的不一致」）：
      表单控件不止 `.input/.select` 两种 —— 时间选择器 `.dtp`、飞书选人框 `.fp__box`
      都是独立的控件类、且自身**没有宽度约束**（`.dtp` 是 display:block）⇒
      只限前两种时，同一行会出现 **下拉 380px / 时间 435px / 选人 435px** 的参差。
      ⇒ 把这两种一并纳入同一档限宽，**一个网格里所有控件同宽**（这是本条的真正目的）。 */
.form-grid .input,
.form-grid .select,
.form-grid .dtp,
.form-grid .fp__box,
/* ⭐ 2026-10-09 乐哥：「合作状态那个框宽了非常多」。
   🐞 根因：`.sf-readonly` 是 `display:flex` 的**只读展示框**（pages-schedule.css），
      它**不在上面这串名单里** ⇒ 没有限宽、撑满整列（实测 629.8px），而同行 `.input` 只有 380px
      ⇒ 同一行两个控件差 **1.66 倍**，一眼就觉得"这个框坏了"。
   ✅ 只读框也是"字段的控件"，必须与同格的其他控件**同宽同列**。 */
.form-grid .sf-readonly,
/* ⭐ 2026-10-11：**弹窗只读信息块 `.hs-static` 同一条道理**（「登记发票」的开票主体、
   「维护开票」的店铺都是它）。它与普通输入框同行，不限宽就撑满整格（实测 406），
   而旁边 `.input` 只有 380 ⇒ 同一行两个框右缘差 26px，一眼就是"这个框宽了"。
   ⚠️ 与 `.sf-readonly` 合并成一条 —— 两种只读框在这个网格里承担**同一个角色**，
      分成两条规则迟早只改一条（本项目"同一件事两处实现"栽过多次）。 */
.form-grid .hs-static,
/* ⭐ 2026-10-13：**月份控件 `.mp` 也纳入同一档**（前置佣金页实测踩到，同一类病第三次复发）。
   ▸ 根因：表单里用的 `.mp--block` 是 `width:100%`，而它**不在上面这串名单里** ⇒
     同一格里其他控件被限到 380、月份控件却撑满整列（实测 **406**）⇒ 右缘差 26px。
   ▸ `.form-grid` 里可能出现的选择器控件共**六类**：`.input` / `.select` / `.dtp` /
     `.fp__box`（选人） / `.sf-readonly` / `.hs-static` / `.mp`（月份）——
     这份名单以后**只许在这里合并维护**，别再各写一处（本次就是漏了一个才分叉的）。 */
.form-grid .mp {
  max-width: 380px;
}
.form-grid .span-2 .input,
.form-grid .span-2 .select,
.form-grid .span-2 .dtp,
.form-grid .span-2 .fp__box,
.form-grid .span-2 .hs-static,
.form-grid .span-2 .mp {
  max-width: none;
}

.form-grid .span-2 {
  grid-column: span 2;
}

/* 三等宽（替代 2fr 1fr 1.4fr / 1fr 1fr 1fr / 220px 1fr 1fr 1fr 混用） */
.form-grid--3 {
  grid-template-columns: repeat(3, 1fr);
}

/* 开关+输入一行（替代 auto 1fr / auto 1fr 1fr） */
.form-grid--auto {
  grid-template-columns: auto 1fr;
}

/* ══════════════════════════════════════════════════════════════════════════
   表单分区头「① 基础信息」——**统一组件**（2026-10-09 第一批）
   ──────────────────────────────────────────────────────────────────────────
   乐哥 10-09：「整体评估下哪个效果好，包括可视性、操作逻辑，然后订一个方案，全局统一」。
   ▸ 勘察：同一件事"这是第几部分"原本**三套并行实现**、长得都不一样 ——
       项目 `.pms-group`：20×20 · r4 · 浅蓝底主色字 · 标题 **14/600**
       达人 `.ia-group` ：18×18 · r5 · 浅蓝底主色字 · 标题 **16/600**
       替票 `.rf-sec`   ：22×22 · **正圆+实心蓝** · 标题借用 `.module-card__title`(16/500)
     （⚠️ 上一版评估里我还列了 `.spc-group__head` 和 `.platform-group`，**复查后剔除**：
       前者是"特殊结算佣金"里的小分组（层级更低、`::before` 竖条），
       后者是"平台账号"的**卡片容器**（带边框+开关），两者都不是"①②③"这个角色。
       ⇒ 真·分区头是 **3 套**，别再按 5 套算。）
   ▸ 定稿（乐哥 10-09 09:0x 拍板 N1 + 16/600）：取「项目那版」的形态并升到令牌规范。

   ⛔⛔ **只有下面这一处定义外观** —— 3 个旧类只是被"别名"进同一组多选择器，
      它们各自的文件里**不许再写** font-size / font-weight / color / width / height /
      border-radius / background（门禁 `tests/form-unify.test.js ①` 扫这个）。
      这样"改一个值 = 四处一起变"，杜绝"修了项目漏了达人"。
   ⚠️ 间距（margin）**不并入**：三套的间距节奏不同，硬统一会改动卡片高度，
      留到第二批逐页核（见 docs/表单统一方案-20261009.md §四）。

   ⚠️ 为什么是"浅蓝底 + 主色字"而不是"实心蓝底 + 白字"（替票那版）：
      实心蓝圆在浅色表单里非常跳，一屏 3~5 个会抢标题；且与刚统一的
      「侧栏单色调实心图标」是两种视觉语言。浅底安静，序号照样读得出来。
   ⚠️ 为什么保留数字、不换成小图标：分区是**每个表单各不相同**的（3~6 段），
      图标得人工逐段配、配不齐就"有的有图有的没图"；数字天然带"共几段 / 在第几段"。
   ══════════════════════════════════════════════════════════════════════════ */

/* 字号 / 字重 / 字色（四处统一；`.rf-sec` 的标题文字长在 `.module-card__title` 上，
   所以那一条必须**限定在 `.rf-sec` 作用域内**，否则会波及全站 66 处卡片标题） */
.form-sec__title,
.pms-group__title,
.ia-group__title,
.rf-sec .module-card__title {
  font-size: var(--font-size-lg);              /* 16 = 令牌里的「卡片标题」档 */
  font-weight: var(--font-weight-semibold);    /* 600 */
  color: var(--color-fg);
}

/* 标题「行」的横排布局（flex + 徽标到字的间距）——`.rf-sec` 的标题行是 `.rf-sec__head`，
   形态不同（它包着两行），不并入这里，只在它自己文件里把 gap 对齐到同一档 */
.form-sec__title,
.pms-group__title,
.ia-group__title {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

/* 序号徽标：20×20 · 圆角 6 · 浅蓝底 · 主色字 · 12/600 · 等宽数字 */
.form-sec__no,
.pms-group__no,
.ia-group__no,
.rf-sec__no {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 20px;
  height: 20px;
  border-radius: var(--radius-seg);
  background: var(--color-primary-50);
  color: var(--color-accent-text);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-semibold);
  font-variant-numeric: tabular-nums;
}

/* 分区说明行（可选） */
.form-sec__hint,
.ia-group__hint,
.rf-sec__sub {
  font-size: var(--font-size-xs);
  color: var(--color-muted);
}

/* ---- 新组件自身的间距（旧三套的间距不动，见上）---- */
.form-sec { margin-top: var(--space-6); }
.form-sec:first-child { margin-top: 0; }
.form-sec > .form-sec__title { margin-bottom: var(--space-4); }
/* 说明行紧跟上方的标题：把标题的 16px 下间距吃掉 8px，视觉上仍是一组 */
.form-sec > .form-sec__hint { margin: calc(-1 * var(--space-2)) 0 var(--space-4); }

/* 基本信息卡片：图片居左 + 右侧字段网格（货品/套餐新增编辑页） */
.sf-basic {
  display: flex;
  gap: var(--space-6);
  align-items: flex-start;
}
.sf-basic__media {
  flex: 0 0 160px;
}
.sf-basic__main {
  flex: 1;
  min-width: 0;
}

/* 移动端：表单多列降为单列 */
@media (max-width: 768px) {
  .form-grid,
  .form-grid--3,
  .form-grid--auto {
    grid-template-columns: 1fr;
  }
  .form-grid .span-2 {
    grid-column: span 1;
  }
  .sf-basic {
    flex-direction: column;
  }
  .sf-basic__media {
    flex: 0 0 auto;
  }

  /* ⭐⭐⭐ 2026-09-29 工具栏里「分段 tab」在手机端被裁掉 —— 加横滑
     ══════════════════════════════════════════════════════════════════════
     ⛔ 根因（实测，不是推测）：`.seg` 自身是 `overflow: visible`、又不可收缩，
        宽度按**内容**撑开；当它比工具栏宽时不会换行（wrap 只在子项间生效，
        单个子项内部溢出不算），于是内容跑到 `.toolbar` 之外；
        而 `.toolbar` 的祖先 `.app-main` 是 `overflow: clip` ⇒ 溢出部分**被裁掉且不可滑**。

     实测两页（390 视口）：
       · 费用明细 `#c-type`（报销费用/滴滴费用/协议酒店费用/其他费用）
         宽 **380** 溢出 **41px** ⇒ **最右边「其他费用」看不见**；
       · 用户管理 `#u-dept-tabs`（全部/财务部/电商事业部/直播旗舰/…）
         宽 **888** 溢出 **549px** ⇒ 「未填部门」「BEATEAM事业部」「总经办」
         「中台服务中心」**等多项完全看不见**（部门多，这个最严重）。

     ✅ 修法：手机上让它**独占一行 + 横向可滑**（继承项目其它横滑容器的一贯做法，
        如 `.table-tabs--in-header` 的 `overflow-x: auto`）。
     ⚠️ `flex: 1 1 100%` 让它独占一行（不与右边筛选控件挤在一起），
        否则横滑区域太窄、一屏只露一两个选项。
     ⚠️ 内部 `.seg__btn` 必须 `flex: none` —— 否则 flex 会把按钮压扁
        （文字被压成两行或截断），横滑就没意义了。
     ⚠️ 隐藏滚动条（各端一致），与 `.table-tabs--in-header` / 顶栏工具区同做法。 */
  .toolbar > .seg,
  .toolbar--join-card > .seg,
  .toolbar--has-page-tabs > .seg {
    flex: 1 1 100%;
    min-width: 0;
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }
  .toolbar > .seg::-webkit-scrollbar,
  .toolbar--join-card > .seg::-webkit-scrollbar,
  .toolbar--has-page-tabs > .seg::-webkit-scrollbar {
    display: none;
  }
  .toolbar > .seg .seg__btn,
  .toolbar--join-card > .seg .seg__btn,
  .toolbar--has-page-tabs > .seg .seg__btn {
    flex: none;
    white-space: nowrap;
  }

  /* ⭐⭐⭐ 2026-09-29 卡内的「分段 tab」（`.seg.table-tabs`）在手机端折 5 行
     ══════════════════════════════════════════════════════════════════════
     ⛔ 上面那组 `.toolbar > .seg` 只覆盖了**工具栏里**的形态；本轮全站扫描
        （83 条路由）另扫出一处漏网：**字典配置页**的分类选择器
        —— `.card > .seg.table-tabs`，**16 个分类按钮**在 390 视口折成
        **5 行、高 317px**（视口才 844px，一屏被它吃掉 37%）。
        它不在 `.toolbar` 里，所以上面那条没命中。

     ⛔⛔ 还有第二层原因（只加 CSS 是**改不动**的，实测踩到）：
        该 seg 由 `pages/settings-dictionary.js:134` 渲染，模板里写死了
        **内联样式 `style="flex-wrap:wrap"`** —— 内联优先级压过一切选择器，
        于是"加一条手机端 nowrap 规则"看起来生效了（overflow-x 变了），
        但 flex-wrap 仍是 wrap、**照样折 5 行**。
        ⇒ 本批同时删掉了那处内联（它本来就是**冗余**的：`.seg` 自带
          `table-tabs` 类，而 `.table-tabs { flex-wrap: wrap }`（table.css:410）
          已提供同样的桌面兜底）。删掉后：桌面由 `.table-tabs` 兜底、
          手机由本规则 nowrap，两边各取所需。
        ⚠️ **教训**：改不动某条 CSS 时，先 `el.getAttribute('style')` 看一眼
          有没有内联 —— 而不是一路加特异性往上怼（本批差点就这么干）。

     ✅ 统一成与工具栏形态同做法：一行 + 横滑。
     ⛔⛔ **范围必须收窄到 `.seg.table-tabs`**（"tab 条"形态），不能写成通用的
        `.page .seg` —— 第一、二版都写成通用，A/B 实测把**别的地方改坏了**：
          · 「达人经营」页那个纯 `.seg`（「亏损优先 / 按花费」两按钮，
            父容器是 `display:flex` 且**没有类名**，所以上面 `.toolbar > .seg`
            从来没覆盖到它）：
              改前  seg 宽 **169**（= 内容宽）⇒ 两个按钮**都完整可见**；
              加 `overflow-x:auto` 后 ⇒ flex 项的自动最小尺寸从"内容宽"
              变成 **0** ⇒ seg 被压到 **122**，内容在内部溢出、「按花费」
              被挤出可视窗（实测 right 378 > 页面右缘）⇒ **要横滑才看得见**。
            ⇒ 这是**回归**（本来好好的），已按 `.table-tabs` 收窄规避。
        ⚠️ 判据：**父是 flex 容器的 seg 不能被加 overflow**（会被压缩）；
          父是 block 的（如字典页的 `.card > .seg`）才需要靠 overflow 自救。
          CSS 判不了"父是不是 flex"，故用类名代理：只有被 app.js 打成
          `table-tabs` 的 tab 条才套这组规则（实测全站仅字典页一处命中）。
        ⚠️ 教训：这类"给一类元素统一加属性"的改动，**必须做 A/B**（改前/改后
           同页对比可见性），光看"目标那一处修好了"会把别处改坏而不自知。

     ⚠️ `width: 100%` 与 overflow 必须**成对**出现：`.seg` 是 `display: inline-flex`
        （宽度由内容决定），只 nowrap 不限制宽度 ⇒ 内容撑到约 1100px 溢出父卡，
        而父 `.card` 是 `overflow: hidden` ⇒ 内容被裁且够不着。
     ⚠️ `overflow-y: hidden` —— 同 `.tabs__tray`：`overflow-x:auto` 会连带把 y 变
        auto，可能长出竖滚动条。
     ⚠️ 已用全站 `.seg` 几何快照（83 路由 / 21 个 seg）逐项对比：收窄后
        只有字典页从 5 行变 1 行，其余 20 个几何**零变化**。 */
  .page .seg.table-tabs {
    flex-wrap: nowrap;
    width: 100%;
    max-width: 100%;
    min-width: 0;
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }
  .page .seg.table-tabs::-webkit-scrollbar {
    display: none;
  }
  .page .seg.table-tabs .seg__btn {
    flex: none;
    white-space: nowrap;
  }
}

.form-actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-3);
  margin-top: var(--space-5);
}

/* ---- 工具栏 ---- */
.toolbar {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
  margin-bottom: var(--space-4);
}

/* 工具栏与列表「合并为同一模块」（2026-09-14 乐哥：「微信小店…他们和列表都属于同一个模块」）
   ============================================================================
   ⛔ 先说被否决的方案（务必不要改回去）：**用 JS 把工具栏搬进卡片**。
      实测会在 100ms 后**丢失控件** —— 时间线铁证（华大对账）：
        73.5s 工具栏在卡内 ✓ → 73.6s 工具栏消失 ✗
      根因：工具栏进卡后，卡片区域的任何局部重渲染都会连它一起抹掉，
      而页面只在整页渲染时创建工具栏 → 不可自愈。详见 app.js 的说明。

   ✅ 现行：**纯 CSS 视觉合并**，零 DOM 操作。
      用 `:has(+ .card)` 精准选中「后面紧邻卡片」的工具栏，把两者拼成一张卡：
        · 工具栏：白底 + 上圆角 + **无下边框**
        · 紧邻的卡片：**无上边框** + 下圆角
      → 视觉上是一个从筛选区连贯到表格的整体卡片，与微信小店的模块感一致。

   ⚠️ 为什么不用纯 CSS 的 `:has(+ .card)`：**漏得多**。实测只能覆盖 3~4 页，漏掉两类：
      · 卡片被包在容器里（`.toolbar → #cs-body → .card`）
      · 中间隔着 **0px 空容器**（`.kw-scope` / `.batch-bar`，不可见但会打断相邻选择器）
      → 所以改由 JS 用**实测垂直间距**判断（见 app.js 的 markToolbarJoin），CSS 只负责表现。
   ⚠️ JS **只加类、绝不搬 DOM**：搬 DOM 会让工具栏随卡片的局部重渲染一起被销毁
      （实测 100ms 后控件消失，不可自愈）。
   ⚠️ 中间隔着**实质内容**的页面（如达人结算：警示条 37px + KPI 137px + 口径 38px）
      **有意不合并** —— 工具栏在那些"页面级概览"之上，硬拼进表格卡会切断概览的从属关系。
      判据是几何量（间距 > 24px 就不合并），不是结构量。 */
.toolbar--join-card {
  /* ⭐⭐ 2026-09-26 乐哥拍板：「只管一张表的工具栏，并进表格卡当卡头」——
     推翻 2026-09-25 Gashora 试妆时的「两卡分离」。
     ──────────────────────────────────────────────────────────────────────────
     ▸ 当时为什么分离：乐哥看参考图后说「工具栏是一个框，下面的表格又是一个框」，
       于是工具栏恢复完整四角圆角 + 与表格卡之间留 16px 缝隙（露出白大卡底）。
     ▸ 现在为什么并回去：真机普查发现「工具栏有 3 种摆法」——
       9 页"只管一张表"的页面里，**7 页是"自成一卡"、1 页（业绩总表）并进表格卡**，
       同一个语义两种形态。而"自成一卡"（带边框圆角）让它读起来是**第三张卡**，
       与它上方的页面概况模块、下方的表格卡并列 ⇒ 内容区看起来"一堆盒子"。
       乐哥 2026-09-26：「2 3 按照你说的做」——按「它管的是谁」收敛：
         · **只管一张表**（上方无 KPI/图表、与表格间距只差一个 margin）→ **并进卡**（本条）
         · **管整页**（上方有 KPI/图表，如看板首页/排期日历）→ 保持裸放独立成条、**不做成卡**
     ▸ 判据仍是几何量（`markToolbarJoin` 实测垂直间距 ≤ 24px 才加本类），不新增任何判断。
     ──────────────────────────────────────────────────────────────────────────
     `margin-bottom: 0`：与下方表格卡**零缝隙**贴合。实测（4 页）原 16px 归零后
     gap 直接变 0，**中间没有空容器占位**，因此**不需要**去动 markToolbarJoin 那段
     踩过两次坑的负 margin 逻辑 —— 本次是纯 CSS 改动。 */
  margin-bottom: 0;
  padding: var(--space-3) var(--space-5);
  /* ⭐⭐ 2026-10-12 乐哥拍板「全站标准」：工具栏 = **淡蓝玻璃托盘**（财务中心截图里的
     那一条）。原 `--color-surface`（白底卡头）→ `--liquid-tray-bg` + 毛玻璃。
     ▸ 为什么改：乐哥看财务中心「我的报销」页的工具栏（淡蓝半透明 + 毛玻璃）觉得很好，
       「整个系统全局都按照这个样子做，把这个定成标准」。
     ▸ 本类是"表格上方的筛选工具栏与表格卡拼合"的形态（markToolbarJoin 动态加，
       全站几十页），改这一处 = 全站列表页统一；财务原先靠 .fin-main 专属规则拿到的
       淡蓝，现在由本类兜底（那条专属规则的背景两行已随之删除，见 pages-finance.css）。
     ▸ 玻璃色是全站唯一的 `--liquid-tray-bg`（与 tab 吸顶托盘同一套，不新造色值）；
       底色统一后（#E8EEF8）各处观感一致。
     ▸ 半透明 + 毛玻璃 ⇒ 底下滚动内容会透出一点来，这是"玻璃"该有的质感，
       与 tab 吸顶托盘同源（乐哥 10-07 定过「托盘透明但不能没有」）。 */
  background: var(--liquid-tray-bg);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  /* ⭐⭐ 2026-09-26 边框色修正（乐哥实报「工具栏和表格栏的边框不一样」）：
     原为 `--color-border-soft`(#EDF0F4，带蓝调) ⇒ 与下方表格卡的
     `--color-frame-border`(#EBECEF，中性) 差一个色调，两块并排能看出"两个灰"。
     ✅ 改用 `--color-frame-border` —— 与 `.card` / `.module-card` 同源。
     ⚠️ 别改回 --color-border-soft：那个令牌是"分隔线/描边"的通用灰（182 处在用），
        而本元素是**一张卡**，应当跟卡片族走（--color-frame-border）。 */
  border: 1px solid var(--color-frame-border);
  /* 下边框去掉：与下方表格卡拼成**一张卡**后，交界处不该有线（否则读起来还是两个框）
     ⚠️ 必须写 `border-bottom-width: 0`，**不能写 `border-bottom: 0`** ——
        后者是简写、会把 `border-bottom-color` 一并重置成 `currentColor`（文字色 #1C1E21）。
        宽度是 0 时看不见，但**读计算样式会读到一个深色**（实测探针按"卡片描边应全站同色"
           断言时被误报成 rgb(28,30,33) 异色）⇒ 只清零宽度，颜色留着由 .card 定。 */
  border-bottom-width: 0;
  /* 上圆角跟卡片、下圆角归零 —— 外轮廓只有最上面一对角 */
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  /* ⭐⭐ 2026-10-06：**与 .card 一起改用 `--shadow-card`**（原为 none，注释写"与 .card 对齐"）。
     为什么这半也必须带影：本元素与下方 `.card` 拼成**一张卡**；
     影只画在下面那半 ⇒ 拼接卡的上半圈（本元素的左/上/右缘）没有影，
     整体看起来"上边漂着、下边浮着"。加上之后两半的影在交界处自然连续
     （本元素下半部分的影被不透明的 `.card` 盖住，不会出现双影）。
     ⚠️ 与 `.toolbar--join-target`（下方那半）**成对**：两者都带影才是完整一张卡的影。 */
  box-shadow: var(--shadow-card);

  /* ── 并成一排（2026-09-14 乐哥选 A 方案）──────────────────────────────
     背景：合并前表格上方堆了**两排筛选控件**（微信小店只有一排）——
       第1排 .toolbar : 搜索 + 下拉筛选
       第2排 .table-toolbar : 分段 + 共N条/每页
     两排都是筛选控件、视觉重量相近 → 看着重复、层级不清（乐哥：「怎么感觉怪怪的」）。
     做法：JS 把第2排的分段与右侧工具搬进本容器（见 app.js），此处负责排布。

     ⚠️ 用 `flex-wrap: wrap` 而**不是 nowrap**：实测订单明细要 1658px 而只有 1296px
        （它第1排本就有 8 个筛选器），nowrap 会溢出。wrap 让它在同一容器内折行，
        仍是一个连续工具栏，比"两个分离工具栏"好，且绝不溢出。
     ⚠️ 统一控件高度（原 44/42/30 三种混排 → 边缘参差）：
        下拉/输入/按钮 34px、分段项 30px（分段是"标签"，略矮于输入框才协调）。
     ⚠️ 不要给分段加撑满高度的规则 —— 它内部有 padding，撑高会把文字压在中线以下。 */
  flex-wrap: wrap;
  row-gap: var(--space-2);
}
/* ⭐ 2026-09-28 分段控件**恢复外框**（乐哥：「所有页面 tab 胶囊这个 ui 不统一，
   有的有外框，有的没有，需要补进去」）——
   原为 `background: transparent; border: 0; padding: 0`（2026-09-14「两排并一排」时为
   视觉融合而去框）；但同排的下拉/搜索框都是「白底 + 1px 边框」，分段却透明无框 ⇒
   一排控件里"有的有框有的没有"。
   现回基类形态（`.seg` / `.pill-filter` 白底 + 1px 边框）。
   ⚠️ **高度必须与同排控件齐平（34px）**：容器 = 项 32 + 1px×2 边框 + 上下 padding 0
      ⇒ `padding: 0 2px`（上下 0 才能压到 34；左右 2px 给选中块留一丝呼吸）。
      实测：基类 padding 4 会让容器高 42px，比旁边 select(34) 高 8px —— 那是"新的不齐"。
   ⚠️ `height: auto; align-self: center` **保留**：容器按内容自适应、与同排控件中线对齐。 */
.toolbar--join-card > .pill-filter,
.toolbar--join-card > .seg {
  height: auto;
  align-self: center;
  padding: 0 2px;
}
.toolbar--join-card > .table-toolbar__right {
  margin-left: auto;      /* 右侧工具组推到最右 */
  align-self: center;
}
/* 控件高度统一 */
.toolbar--join-card > .select,
.toolbar--join-card > .input,
.toolbar--join-card > .input--search,
.toolbar--join-card > .btn {
  height: var(--control-height-sm);
  min-height: var(--control-height-sm);
}
.toolbar--join-card > .input--search > .input { height: var(--control-height-sm); }
/* ⚠️ 日期选择器（月份）也要一起降（2026-09-14 乐哥：「日期选择的控件尺寸和其他的不对」）
   它是 `.mp__trigger`、被包在 `<span class="mp-host">` 里，**不是**工具栏的直接子元素，
   所以上面那条 `.toolbar--join-card > .select` 覆盖不到它 —— 实测它仍是 44px，
   与同排的 34px 下拉并排就"高出一截"。

   ⚠️⚠️ 必须用**纯后代选择器**，不能写 `.mp-host > .mp__trigger`（实测踩坑）：
      真实 DOM 层级是 `SPAN.mp-host > DIV.mp > BUTTON.mp__trigger` ——
      `.mp__trigger` 是 `.mp-host` 的**孙子**（中间还夹着一层 `DIV.mp`）。
      第一版写了 `>`，选择器**一条都没匹配上**，改了半天没反应
      （排查方式：在页面里遍历 document.styleSheets，用 el.matches(selector) 逐条验证）。
      → 教训：**不要凭想象写选择器的层级**，尤其本项目 DOM 层级不统一；
        能用后代选择器就别用子选择器。 */
.toolbar--join-card .mp__trigger {
  height: 34px;
  min-height: 34px;
}
/* 紧随的卡片去掉上边框与上圆角，与工具栏拼成一体 */
.toolbar--join-target {
  /* ⭐⭐ 2026-09-26 乐哥拍板「工具栏并进表格卡」—— 本块从「恢复独立卡片」改回「拼接态」。
     与 `.toolbar--join-card` 配对：上方那条去掉下边框/下圆角，本条去掉上边框/上圆角，
     两个盒子的外轮廓合起来才是**一张卡**（中间只有一条由自身内容分隔，不画线）。
     ──────────────────────────────────────────────────────────────────────────
     ⚠️ 为什么**不是**改 `.toolbar--join-card` 的 `margin-bottom: 0` 就完事：
        还要同时压掉 `!important`？—— 不，恰恰相反：**必须保留 `margin-top: 0 !important`**。
     ⚠️⚠️ 内联负 margin 仍挂在 DOM 上（实测 -16 / -18.22 / -17.9275px，由 markToolbarJoin
        的补偿逻辑写入），本条 `!important` 把它压住不生效。**这是刻意的**：
        · 不动那段 JS：它 2026-09-18 踩过两次坑（清补偿、双重补偿），注释里写得很细，
          属"已验证但不轻易碰"的代码；
        · 本次实测（4 页）`margin-bottom: 0` 后交界 gap 直接为 0，**没有需要负 margin 去吃的缝**
          ⇒ 让 `!important` 继续压着，行为最可预测。
        ⛔ 若将来有人去掉这个 `!important`：那些 -18.22px 会立刻生效，
           把表格卡**压进工具栏**、盖住末行控件（正是 2026-09-18 修复的那个故障）。
     ⭐ 2026-09-26 边框色：本条原写 `--color-border-soft`（与同卡其余三边不一致，被乐哥实报）。
       现在上边框直接归零、不再画线，该不一致自然消失；其余三边由 `.card` 统一为
       `--color-frame-border`。
     ⚠️ 同 `.toolbar--join-card`：用 `border-top-width: 0` 而非 `border-top: 0`
        （后者会把颜色重置成 currentColor，读计算样式会读到深色文字色）。 */
  border-top-width: 0 !important;
  border-radius: 0 0 var(--radius-lg) var(--radius-lg) !important;
  margin-top: 0 !important;
  /* ⭐ 2026-10-06：拼接卡的下半也要有影（上半由 `.toolbar--join-card` 提供）。
     ⚠️ 本元素是 `.card`（已有 `var(--shadow-card)`），这里只写注释说明"成对"这回事，
        不重复声明 —— 重复声明会让"改影子改哪一处"变成两个地方。
         但 `.pms-card` 那类**只带 toolbar--join-target 不带 card** 的页面要显式给上，
         见下方那条规则。 */
}
/* 合并后卡内首个 .table-toolbar 不需要顶部分隔（工具栏与它本属同一控制区） */
.toolbar--join-target > .table-toolbar:first-child {
  border-top: 0;
}

.toolbar__spacer {
  flex: 1;
}

/* 工具栏右侧分组（2026-09-20 补 — 财务模块需要"左侧筛选 + 右侧操作"的工具栏）。
   ⚠️ 为什么在 overlays.css 补而不是 pages-finance.css：`.toolbar` 是本文件里的
      **标准组件**，给它加一个通用子元素应当加在这里，其它页面才能复用。
      （原来只有 `.toolbar__spacer`，用它把右侧内容顶过去、但分组本身没有容器，
        多控件时得各自写 inline style。）
   ⚠️ `margin-left: auto` 与 `.toolbar__spacer` 效果等价、二选一即可 ——
      **不要两个都写**（会让间距出现双重推力，窄屏换行时位置变得难以预测）。 */
.toolbar__right {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-left: auto;
}

/* ---- 冲突 / 提示条 ---- */
.alert {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  font-size: var(--font-size-sm);
}

.alert--warning {
  background: var(--color-warning-bg);
  color: var(--color-warning);
}

/* ⭐ 2026-10-17 乐哥：「系统里有很多这样的标题，把内容和图表隔开了」
   ⇒ 「月度利润明细」页那条口径警示**不再是一整条横幅**，改成一行警示文字。
   ▸ 原来：浅橙底 + `--radius-md` 圆角 + 12/16 内距 ⇒ 高约 44px，横贯整页，
     夹在页头和表格卡头之间，把"标题"和"内容"切成两段（乐哥截图圈的正是它）。
   ▸ 现在：透明底、无边框、字号降到 xs、图标 14px ⇒ 高约 20px，读起来是
     「页头说明的最后一句」，不是独立区块。
   ⛔ 用**修饰类** `.pl-alert--inline` —— 全站还有 12 处 `.alert`（表单校验、导入结果、
      错误提示），那些是**页内局部提示**，改全局会让它们失去该有的重量。
   ⛔ 别把颜色也去掉：橙色是「警告」语义（这些月份不可直接比），改灰就没人当回事了。 */
.alert.pl-alert--inline {
  margin: 0 0 var(--space-3);
  padding: 0;
  gap: 6px;
  align-items: center;
  background: transparent;
  border: 0;
  border-radius: 0;
  font-size: var(--font-size-xs);
  line-height: 1.5;
  color: var(--color-warning-text);
}

.alert--danger {
  background: var(--color-danger-bg);
  color: var(--color-danger);
}

.alert--info {
  background: var(--color-info-bg);
  color: var(--color-info);
}


/* ==== /css/pages.css ==== */
/* ==========================================================================
   达播工作台 — pages.css
   数据看板 / 登录 / 回调
   ========================================================================== */

/* ============================ 数据看板 ============================ */

.dashboard-overview {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
  /* ⭐ 2026-09-25（第二轮）：标题（首页）与描述（达人排期与业绩概览）都已隐藏
     （隐藏规则在 layout.css 的 `.page-title / .dashboard-overview__title / .dashboard-overview__date`），
     本容器成了**空盒**，但原来那对 margin（上 20 + 下 16）仍在占 36px 空白
     （乐哥：「看板首页……留白太多」的实测构成之一）。
     ⇒ 空盒时不占位：用 `:has()` 判"里面有没有**非 hidden 类**的其它元素"。
     ⚠️ 不写死 `display:none` / `margin:0`：将来哪天把标题或描述恢复，或往里加别的元素，
        这条自动失效 ⇒ 盒子照常复活，不需要回来改 CSS。 */
  margin-top: var(--space-5);
  margin-bottom: var(--space-4);
}
.dashboard-overview:not(:has(> * > :not(.dashboard-overview__title):not(.dashboard-overview__date))) {
  margin: 0;
}

.dashboard-overview__title {
  font-size: var(--font-size-2xl);
  font-weight: var(--font-weight-semibold);
  letter-spacing: var(--font-letter-spacing-display);
}

.dashboard-overview__date {
  margin-top: var(--space-1);
  font-size: var(--font-size-sm);
  color: var(--color-muted);
}

.kpi-row {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--space-4);
  margin-bottom: var(--space-6);
}

/* 3 张 KPI 卡（经营总览）：必须恒为 3 列，否则复用默认 4 列会让右侧空出一格
   （2026-09-12 乐哥反馈「右边留白」的根因即此）。
   ⚠️ 用 .kpi-row.kpi-row--three 双类提高权重：页面下方还有一组 `.kpi-row` 的媒体查询
      （1400px→4列 / 1100px→3列 / ≤1099px→2列），它们的权重相同但位置更靠后，
      只写单类会被它们翻掉（实测：单类写法下网格仍是 4 列、留白照旧）。
      双类写法与文件顺序无关，稳。 */
.kpi-row.kpi-row--three { grid-template-columns: repeat(3, minmax(0, 1fr)); }
@media (min-width: 1100px) and (max-width: 1399px) {
  .kpi-row.kpi-row--three { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 1099px) {
  .kpi-row.kpi-row--three { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 699px) {
  .kpi-row.kpi-row--three { grid-template-columns: minmax(0, 1fr); }
}

/* 经营总览三卡：作为页面结论，用更足的间距与下方明细拉开层级。
   （字号不用改：.kpi-card__value 本身已是 --font-size-3xl=30px。）
   预估态不再整卡套黄框（那会让重点指标看着像警告卡），见下方 .kpi-card--estimate 覆盖。 */
.kpi-row--overview { margin-bottom: var(--space-5); gap: var(--space-5); }
.kpi-row--overview .kpi-card { padding: var(--space-5) var(--space-5) var(--space-4); }

/* 5 张 KPI 卡（达人经营）：默认 4 列会变成 4+1、右侧空三格 —— 必须显式给 5 列；
   窄屏依次退到 3 / 2 / 1 列（与 --three 的断点保持同一套节奏）。
   ⚠️ 不复用 .kpi-row--six：那套规则的 nth-child 跨列是按「6 张 + 特定列数」推的，
      5 张套进去会在 1100~1399 区间留白（2026-09-13 复核其媒体查询时发现）。 */
.kpi-row.kpi-row--five { grid-template-columns: repeat(5, minmax(0, 1fr)); }
@media (max-width: 1399px) {
  .kpi-row.kpi-row--five { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 899px) {
  .kpi-row.kpi-row--five { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 599px) {
  .kpi-row.kpi-row--five { grid-template-columns: minmax(0, 1fr); }
}

/* ⭐⭐ 2026-10-15 乐哥：「开票的 kpi 卡片做小，不要那么大」（华大对账页）
   ---------------------------------------------------------------------------
   这页的 KPI 行（本月应收 / 未满 35 天 / 已开票 / 已回款 / 未回款，共 5 张）
   默认规格偏大：数值 `--font-size-3xl`(32px) + 卡片内距 `--space-5`。
   而这页的主角是下面的对账表，KPI 只是抬头概览 ⇒ 收一档：
     · 数值 32 → 24px（= `--font-size-2xl`，与 `.kpi-row--grid6` 宽屏档同源）
     · 卡内距 `--space-5` → `--space-4 / --space-3`
     · 副标题行高收到 1.45（副标题是长句，默认 1.5 会把卡撑高）
   ⛔ 不改 `.kpi-card` 本体（14 个页面在用，改本体就是全站放大/缩小）。
   ⛔ 不动栅格列数（仍是 5 列 / 窄屏逐级降列，见上面 `.kpi-row--five`）。
   ▸ 只有带 `.kpi-row--compact` 的行受影响 —— 目前仅华大对账页用它。 */
.kpi-row--compact .kpi-card { padding: var(--space-4) var(--space-4) var(--space-3); }
.kpi-row--compact .kpi-card__value { font-size: var(--font-size-2xl); }
.kpi-row--compact .kpi-card__sub { line-height: 1.45; }

/* 6 张 KPI 卡（经营看板）：宽屏下让「利润 / 利润率」各跨 2 列，把第二行铺满。
   不做的话 4+2 会在右侧留出两格空白（2026-09-11 扫图发现）。 */
.kpi-row--six > :nth-child(5),
.kpi-row--six > :nth-child(6) { grid-column: span 2; }



/* ⚠️⚠️ 表头瘦身（2026-10-15 乐哥：「所有表格的表头注释，都放感叹号里面吧……不然表格很乱」）
   —— 收掉 `.th-sub` 副标题时**必须同时检查列宽**，理由：
   `.table thead th` 有 `height: 44px` + `white-space: nowrap`（见 table.css）。
   · 44px 是**固定高度**，本来就够装「标题 + 副标题」两行（18×2=36 < 44）
     ⇒ **所以"表头变高"不能当瘦身是否生效的判据**（改前改后都是 44px，实测过）；
        瘦身的收益是"少一层小字、不再噪杂"，不是高度。
   · `nowrap` ⇒ 表头文字**永远不会折行**。在 `table-layout: fixed` 的表里
     （如 `.cm-table--detail`），**列宽一旦小于表头所需宽度，文字会溢出、压到相邻列上**。
     ⇒ 收掉副标题 + 加感叹号图标后，必须逐列核对
        「列宽 ≥ 表头所需宽度」（含图标 ≈ 13px + 间距）；放不下的表改用 auto 布局或加宽。
     量法：`deploy/measure-th-slim.js`（真 CSS 渲染，量表头所需宽度与是否溢出）。 */

/* ⭐⭐ 2026-10-05 卡片数 < 列数时**自动收列**（乐哥实报：「我的提成」页 KPI 卡
   只占左边一小块、右边一大片空）
   ─────────────────────────────────────────────────────────────────────────────
   ▸ 根因：`.kpi-row` 默认 `repeat(4, 1fr)`，「我的提成」在该月只有 1 张卡
     （月度合计；角色卡按 categories 动态生成）⇒ 卡只占 **1/4 宽（实测 454px）**，
     右侧 3/4 全空。`--three` 变体只解决"恰好 3 张"，2 张 / 1 张照样空。
   ▸ 为什么用 `:has()` 自动判定而不是再加 `--one` / `--two` 类：
     ① 卡片数是**运行时数据决定的**（角色数逐月不同），加类得在 JS 里算，
        且每个页面都要记得算 —— 本项目已因"同一约束多处实现、漏一处"踩过多次；
     ② 写在 CSS 里 ⇒ **全站所有 `.kpi-row` 自动受益，改 0 处 JS**。
   ▸ 权重与位置：本组单类 `:has()` 特异性 (0,2,0)，与 `--three` / `--five` **相同**
     ⇒ 靠"文件更靠后"取胜（故必须放在它们之后）。只在"卡片数确实少于声明列数"时匹配。
   ⚠️⚠️ 必须**包在 `@media (min-width: 1100px)` 里** —— 否则会盖掉 `--three`/`--five`
      在窄屏的降级断点（≤1099→2 列 / ≤699→1 列）：本项目 `--three` 落在 53-62 行、
      本段在 90+ 行，同特异性下**后者赢** ⇒ 不包媒体查询的话，窄屏会出现
      "3 张卡硬挤 3 列"（本该 1 列）的回归。窄屏列数本来就少，不需要收列规则。 */
@media (min-width: 1100px) {
  .kpi-row:has(> .kpi-card:only-child) { grid-template-columns: minmax(0, 1fr); }
  .kpi-row:has(> .kpi-card:nth-child(2):last-child) { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .kpi-row:has(> .kpi-card:nth-child(3):last-child) { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

@media (min-width: 1100px) and (max-width: 1399px) {
  /* 3 列时：第 6 张跨 2 列，第二行 1 + 2 刚好铺满 */
  .kpi-row--six > :nth-child(5) { grid-column: span 1; }
  .kpi-row--six > :nth-child(6) { grid-column: span 2; }
}
@media (max-width: 1099px) {
  .kpi-row--six > :nth-child(5),
  .kpi-row--six > :nth-child(6) { grid-column: span 1; }
}

/* 经营看板 · 成本大类行内标记（「公共」等）。
   必须不可折行 —— 否则窄列下「公共」会被拆成「公 / 共」两行（2026-09-11 扫图发现）。 */
.an-tag {
  display: inline-block;
  white-space: nowrap;
  font-size: var(--font-size-xs);
  line-height: 18px;
  padding: 0 6px;
  margin-left: 4px;
  color: var(--color-muted);
  background: var(--color-surface-2);
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-sm);
  vertical-align: 1px;
}

/* 「预估」标记（经营看板：T+35 口径下含未满 35 天订单时）——
   橙色 = 提示「这个数还会变」，与红色的错误态区分开。
   同时给整张 KPI 卡换橙色描边，扫一眼就知道这一屏不是最终结算数。
   ⚠️ 选择器必须写成 .kpi-card.kpi-card--estimate（双类）：
      .kpi-card 的单类定义在本文件**更后面**（第 79 行起），单类写法同优先级下会被它覆盖，
      描边看起来像没生效（实测踩过：算出 rgb(237,239,241) 默认灰）。 */
.an-tag--warn {
  color: var(--color-warning-text);
  background: var(--color-warning-bg);
  border-color: var(--color-warning);
}
.kpi-card.kpi-card--estimate,
.kpi-card.kpi-card--estimate:hover {
  border-color: var(--color-warning);
}
/* 经营总览的预估态：不套黄框（重点指标不该看着像警告），改用标签上的小 pill 提示口径。
   乐哥 2026-09-12：三张 KPI 卡是页面结论，视觉上要「亮」而不是「告警」。 */
.kpi-row--overview .kpi-card--estimate,
.kpi-row--overview .kpi-card--estimate:hover { border-color: var(--color-border-soft); }

/* ==========================================================================
   （已删除）盈亏公式条 .an-formula 系列 —— 2026-09-13 清理死 CSS
   背景：这一组共 9 条规则（.an-formula / __item / __item--result / __item--in /
   __item--out / __op / --empty 等）是「盈亏板块」的公式条样式。
   该板块在 2026-09-13 的「达人维度整合」中**已整体搬到「达人经营」页**，
   而公式条的渲染代码同时被移除 → 全项目（public/js + index.html）**零引用**，
   属确认死 CSS（grep 双向核对过）。留着会让「这组类到底谁在用」反复产生误判，
   故整组删除。**若将来要用，从 git 历史取回即可。**
   ========================================================================== */

/* 7 张 KPI 卡：宽屏 4 列，中等屏 3 列，窄屏 2 列 */
@media (min-width: 1400px) {
  .kpi-row { grid-template-columns: repeat(4, 1fr); }
}
@media (min-width: 1100px) and (max-width: 1399px) {
  .kpi-row { grid-template-columns: repeat(3, 1fr); }
}
@media (max-width: 1099px) {
  .kpi-row { grid-template-columns: repeat(2, 1fr); }
}

.kpi-card {
  /* ⭐ 2026-09-25：加 position:relative —— 供右上角的口径 ⓘ（.th-note.is-corner）锚定。
     为什么放这里而不是给 ⓘ 加 `position:absolute` 就完事：绝对定位必须有定位祖先，
     否则会跑到视口/最近的定位祖先上（那就不在卡片里了）。 */
  position: relative;
  min-width: 0;
  padding: var(--space-5);
  background: var(--color-surface);
  /* ⭐⭐ 2026-09-26 全站卡片划界收敛为「纯线条」（乐哥拍板：按建议做第 3 条）。
     ▸ 改前：`border: 1px solid var(--color-border-soft)` + `box-shadow: var(--shadow-sm)`，
       注释写"卡片在近白底上微微浮起"—— 但 KPI 卡住在 **#FCFCFC 的白大卡内部**（不是灰底上），
       白底上阴影没有可压暗的空间（实测最深 Δ11、3px 内归零，而 1px 线条 Δ18），
       投影等于多一层看不见的"雾"，只留下"两种卡片两种划界"的不一致。
     ▸ 改后：边框统一 `--color-frame-border`（与 .card / .chart-card 同源），无阴影。
     详见 .chart-card 处的完整说明。 */
  border: 1px solid var(--color-frame-border);
  border-radius: var(--radius-lg);
  /* ⭐ 2026-10-06：加回阴影（乐哥：「其他模块，也加个悬浮」）——
     底盘已回接近白，三层（底盘/内容白卡/KPI 卡）都靠阴影叠出层次。
     与 `.card` 共用同一配方 `--shadow-card`，保证全站"浮起程度"一致。 */
  box-shadow: var(--shadow-card);
  transition: border-color var(--motion-fast) var(--motion-ease-standard),
    transform var(--motion-fast) var(--motion-ease-standard);
}

.kpi-card:hover {
  border-color: var(--color-accent);
  /* ⭐ 2026-09-26：hover 阴影（原 `--shadow-md`）一并去掉 —— 白底上"升一档阴影"看不见，
     等于空转；可点的反馈由**描边变主色 + 上移 1px** 承担，比阴影更明确也更克制。 */
  transform: translateY(-1px);
}

/* 强调卡片（如「成本总金额」总览项）：主色浅底 + 主色边框，突出关键数字 */
.kpi-card--accent {
  background: var(--color-primary-50);
  border-color: var(--color-primary-200);
}
.kpi-card--accent .kpi-card__label { color: var(--color-accent-text); }

/* KPI 卡「语义柔底」（乐哥 2026-09-15 起用于工作台首页）
   动机：首页四张卡原本全白底，只能靠读文字分辨指标性质；用底色区分后可一眼分辨
        （蓝=成交 / 绿=净成交 / 橙=排期 / 紫=达人规模）。
   实现：把 `dashboard.js` 里**原本就有的 tone 语义**（原先只染右上角图标）延伸到卡片底色 ——
        不新增概念，只是让已有色调换个位置呈现。
   ⚠️ 为什么做成**修饰类**而不是直接改 `.kpi-card` 的 background：
      `.kpi-card` 被 **14 个页面共用**（见 grep kpi-card public/js/pages），
      直接改 = 全站换底，属结构性改动。加类 → 只有显式加类的页面生效，其余页零影响。
   ⚠️ 类名 `--tone-*` 与 JS 侧 `UI.kpiTones()` 的键一一对应（blue/teal/orange/green/purple）；
      写错键名不会报错、只会**静默退回白底**，故只允许用这 5 个键。
   ⚠️ `border-color: transparent` 是本设计的组成部分：柔底卡片再描一圈边会显脏。
      悬停高亮仍生效 —— 但**靠的不是书写顺序**：`.kpi-card:hover`（特异性 0,2,0）
      高于 `.kpi-card--tone-*`（0,1,0），故它实际写在本段**之前**也能覆盖回来。
      （别按"后面覆盖前面"的直觉判断，本项目已多次踩特异性陷阱，见上方 estimate 段注释。） */
/* ⚠️ 彩色 KPI 卡（tone-*）：只换底与边框色，**阴影不再置 none** ——
   2026-10-06 起全站卡片靠 `--shadow-card` 浮起，把阴影写成 none 会让这几张卡
   在"浮起的页面"里显得是平的（同一页两套语言）。 */
.kpi-card--tone-blue   { background: var(--color-tone-blue-bg);   border-color: transparent; }
.kpi-card--tone-teal   { background: var(--color-tone-teal-bg);   border-color: transparent; }
.kpi-card--tone-orange { background: var(--color-tone-orange-bg); border-color: transparent; }
.kpi-card--tone-green  { background: var(--color-tone-green-bg);  border-color: transparent; }
.kpi-card--tone-purple { background: var(--color-tone-purple-bg); border-color: transparent; }

/* ⭐ 2026-09-25 Gashora 像素级试妆（乐哥拍板「像素级模仿」）：
   看板 KPI 卡从**柔底整块**改为**白底 + 彩色图标块**（参考图 Gashora 的 KPI 卡形态：
   白卡 + 左上彩色方块图标 + 黑大数字 + 灰标签）。
   ⚠️ 这推翻了 2026-09-19 定的「柔底区分指标」—— 是乐哥看参考图后的新方向，特此前注。
   ⚠️ 只作用于**看板首页**（#kpi-row 作用域）：其他页的柔底卡（经营看板/达人经营等）
      未在本次试妆范围内逐一核对，先不动，待乐哥看过看板效果后再决定是否全站统一。
   图标块颜色改由 `.kpi-card__icon--{tone}` 承载（上方定义），彩色从"整卡"收缩为"图标块"。 */
#kpi-row .kpi-card--tone-blue,
#kpi-row .kpi-card--tone-teal,
#kpi-row .kpi-card--tone-orange,
#kpi-row .kpi-card--tone-green,
#kpi-row .kpi-card--tone-purple {
  background: var(--color-surface);
  /* ⭐ 2026-09-26：描边色统一到 `--color-frame-border`。
     ⭐⭐ 2026-10-06：**阴影改成显式跟随卡片族**（原写 `none`）——
        全站卡片已靠 `--shadow-card` 浮起，还把看板首页这 5 张写成 none
        ⇒ 同一页里"别的卡浮着、这 5 张是平的"，正是乐哥说的"乱"。
     为什么仍要**显式声明**（而不是删掉让它继承）：这里本就是"以显式覆盖为前提"的规则，
     显式跟随 `--shadow-card` 才能保证"改影子时这 5 张一定跟着变"。 */
  border-color: var(--color-frame-border);
  box-shadow: var(--shadow-card);
}

/* 经营总览行（.kpi-row--overview）里的柔底卡：描边必须也是透明
   ⚠️ 为什么单独写一条、且要写成「.kpi-row--overview .kpi-card.kpi-card--tone-x」三重选择器：
      该行有 `.kpi-row--overview .kpi-card--estimate { border-color: var(--color-border-soft) }`
      —— 它是 **0,2,0**，而上面的 `.kpi-card--tone-*` 只有 **0,1,0** → **会被它盖掉**，
      结果就是「柔底上又描了一圈灰边」，正是本设计要避免的「脏」。
      所以这里必须凑到 **0,3,0**（多带一个 `.kpi-card`）才能稳定压过它 ——
      **不要**简化成 `.kpi-row--overview .kpi-card--tone-x`（那是 0,2,0，与它同权重，
      只能靠书写顺序分胜负，属于本项目已反复踩过的隐性依赖）。
   ⚠️ 另一个边界：`--result` 卡片（利润来源链条）**不在这里管** —— 它是 `.an-src-card`，
      不是 `.kpi-card`，走自己那套规则。 */
.kpi-row--overview .kpi-card.kpi-card--tone-blue,
.kpi-row--overview .kpi-card.kpi-card--tone-teal,
.kpi-row--overview .kpi-card.kpi-card--tone-orange,
.kpi-row--overview .kpi-card.kpi-card--tone-green,
.kpi-row--overview .kpi-card.kpi-card--tone-purple { border-color: transparent; }

/* 售后分析三页（售后总览 / 达人售后 / 商品售后）的柔底卡：同样要保证描边透明。
   ⚠️ 先说清这条的**真实作用**（实测过，别误以为它是"新加透明"）：
      `.kpi-card--tone-*`（第 185 行起）**自身已经写了 `border-color: transparent`**，
      而 `.kpi-card`（147 行）的基础边框与它**同为 (0,1,0)** → 目前是**靠书写顺序**让 tone 胜出。
      本组 (0,3,0) 规则的价值是**去掉这个顺序依赖**：将来谁把 `.kpi-card` 挪到 tone 类之后、
      或在该行加一条 (0,2,0) 的边框规则（`--overview` 行就有这种情况，见上方 191 行注释），
      柔底卡都不会突然长出灰边。
   ⚠️ 行类名必须是 `--grid6` / `--grid5`（**不是** `--overview`）——
      售后三页用的是这两个行类，选择器对不上就完全不生效。 */
.kpi-row--grid6 .kpi-card.kpi-card--tone-blue,
.kpi-row--grid6 .kpi-card.kpi-card--tone-teal,
.kpi-row--grid6 .kpi-card.kpi-card--tone-orange,
.kpi-row--grid6 .kpi-card.kpi-card--tone-green,
.kpi-row--grid6 .kpi-card.kpi-card--tone-purple,
.kpi-row--grid5 .kpi-card.kpi-card--tone-blue,
.kpi-row--grid5 .kpi-card.kpi-card--tone-teal,
.kpi-row--grid5 .kpi-card.kpi-card--tone-orange,
.kpi-row--grid5 .kpi-card.kpi-card--tone-green,
.kpi-row--grid5 .kpi-card.kpi-card--tone-purple { border-color: transparent; }

.kpi-card__top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--space-3);
}
/* ⭐ 2026-09-25 Gashora 试妆：看板 KPI 卡 top 区 = 图标块 + 标签（横排、垂直居中）。
   参考图是「彩色方块图标在左、灰色标签紧跟其右」。 */
#kpi-row .kpi-card__top {
  justify-content: flex-start;
  gap: 12px;
  margin-bottom: var(--space-4);
}
#kpi-row .kpi-card__label {
  font-size: 13px;
  color: var(--color-muted);
  font-weight: var(--font-weight-medium);
}

.kpi-card__label {
  font-size: var(--font-size-sm);
  color: var(--color-muted);
}

/* ⭐ 2026-09-25 Gashora 像素级试妆：KPI 图标改为**彩色实心圆角方块 + 白图标**
   （参考图特征：~44px 彩色方块，紫/橙/粉/蓝）。原浅蓝底+蓝字太"轻"，压不住大数字。 */
.kpi-card__icon {
  width: 44px;
  height: 44px;
  border-radius: 12px;
  background: var(--color-primary-50);
  color: #FFFFFF;
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
}
.kpi-card__icon svg { width: 22px; height: 22px; }
.kpi-card__icon--blue   { background: var(--kpi-icon-blue); }
.kpi-card__icon--green  { background: var(--kpi-icon-green); }
.kpi-card__icon--orange { background: var(--kpi-icon-orange); }
.kpi-card__icon--purple { background: var(--kpi-icon-purple); }
.kpi-card__icon--pink   { background: var(--kpi-icon-pink); }

/* ⚠️ 盈亏正负色必须在这里再声明一次：`.kpi-card__value`（本文件、特异性 0,1,0）
   与 table.css 的 `.num-pos`（0,1,0）**特异性相同**，而本文件**后加载** → 会把正负色覆盖掉。
   2026-09-13 实测：KPI 卡上的「月最终盈亏 -¥13.3万」显示为深灰，而表格里同值已是绿色 —— 两处不一致。
   故用 0,2,0 显式覆盖（全站约定：盈利=红、亏损=绿）。 */
.kpi-card__value.num-pos,
.card__value.num-pos { color: var(--color-danger); }
.kpi-card__value.num-neg,
.card__value.num-neg { color: var(--color-success); }

/* ⭐ 2026-10-13 乐哥：「左上角那个汇总金额，需要精确的数字，保留两位小数点，文字颜色用红色吧」
   ▸ 只管**颜色**（"精确到分"是取值那侧的事，由 `Api.fmtMoney` 承担，见 expense-batches.js）。
   ▸ 为什么**单类**就够（0,1,0）：本类加在 `.kpi-card__value` **里面那个 span** 上，
     而 `.kpi-card__value` 的 color 是它**父级 div** 的规则 —— span 自己没有 color 声明，
     **继承值永远输给直接声明** ⇒ 不需要凑特异性。
     ⚠️ 第一版按"双类压过父级"写成 `.kpi-card__value.kpi-card__value--money` ⇒ **恒不匹配**
     （span 上只有 `--money` 这一个类）⇒ 颜色静默不生效（靠真机量 `getComputedStyle().color` 才发现）。
   ▸ 颜色用 `--color-danger`：它就是本项目「红」这个色相的唯一载体
     （`.num-pos` 盈利红也是它；DESIGN-SPEC §2.4.1 把「危险 / 价格」一并归它），
     ⛔ 不许裸 hex。 */
.kpi-card__value--money { color: var(--color-danger); }

.kpi-card__value {
  font-size: 32px;
  font-weight: 700;
  letter-spacing: -0.02em;
  font-feature-settings: "tnum", "lnum";
  font-variant-numeric: tabular-nums;
  /* ⭐ 2026-09-25 Gashora 像素级试妆：品牌蓝 → **近黑**（参考图 KPI 数字是黑色超大粗体，
     "信息聚焦、分得清重点"的核心：大数字必须是视觉锚点，不能被颜色分散）。
     ⚠️ 盈亏语义色仍由上方 0,2,0 的 `.num-pos`/`.num-neg` 覆盖（红/绿不受影响）。 */
  color: var(--color-fg);
  line-height: var(--font-line-height-tight);
}

.kpi-card__delta {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  margin-top: var(--space-2);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-medium);
}

.kpi-card__delta.is-up { color: var(--color-success); }
.kpi-card__delta.is-down { color: var(--color-danger); }
/* 一张卡上有**多个环比**时的容器（如总盈亏卡：盈亏涨跌幅 + 利润率百分点差）。
   ⚠️ 必须 column 排：`.kpi-card__delta` 自身是 inline-flex，两个直接相邻会挤在同一行
      连成一串（"↑721.8% 较上月 ↑5.08 个百分点 利润率较上月"），读不出这是两组数据
      —— 乐哥 2026-09-16 报障的「文字内容挤压」就是这处。
   容器内单个 delta 不再各自留上边距（间距统一由 gap 控制）。 */
.kpi-card__moms { display: flex; flex-direction: column; align-items: flex-start; gap: 3px; margin-top: var(--space-2); }
.kpi-card__moms > .kpi-card__delta { margin-top: 0; }
/* 「无环比可算」态（全部月份 / 预估口径 / 上月无数据）：灰色说明，不用涨跌色 ——
   否则灰色的「不与上月对比」配上红绿色会被误读成"持平"。 */
.kpi-card__delta.is-none { color: var(--color-muted); font-weight: var(--font-weight-regular); }
/* 「较上月」后缀小字：与涨跌数字同排，弱化以免抢数字的注意力 */
.kpi-card__delta-label { color: var(--color-muted); font-weight: var(--font-weight-regular); }

.kpi-card__sub {
  margin-top: var(--space-2);
  font-size: var(--font-size-xs);
  color: var(--color-muted);
  line-height: 1.5;
}
/* ⭐ 2026-10-16 乐哥：「把『净成交（GSV）』这类基数留在卡面上。或者这样做：注释文字放在
   标题下面、数字上面，层级比标题的小一点」⇒ 本页 KPI 卡的说明从"数字下方"移到
   **标签下方、数字上方**。
   ▸ 字号不变（仍是 `--font-size-xs` 12px；标签是 `--font-size-sm` 14px）⇒ 天然比标题小一档；
   ▸ 外边距要改：基础类的 `margin-top: 8px` 是给"数字下方"用的，夹在标签与数字中间会把它们撑开，
     改成上下各 4px（`--space-1`）——它是"两个元素之间的注解"，不是"结尾的补充"。
   ⚠️ 只在本页用（`hs-` 前缀 + 挂在基础类之后）；⛔ 不动全站 `.kpi-card__sub`（其它页照旧在数字下面）。 */
/* ⭐⭐ 2026-10-16 乐哥（截图圈住整排 KPI）：「这你觉得好看吗？主次不分」
   问题：说明插到"标签与数字之间"之后，**标签(14px 灰) 与 说明(12px 灰) 同色** ——
        两行糊成一块灰，数字被压在最下面，三级层次全没了。
   ⇒ 按本项目「拉开两档」的既有做法（见发票面板 `.eb-invpanel__f` 那段注释：标签最浅、
     值加深半粗、关键金额最深；并明写"最长的那段再加深就成了最抢眼的，反而更糊"）重排三级：
        · **标签**（"这是哪个数"，是这张卡的身份）→ `--color-fg-2` + medium（见下一条规则）；
        · **说明**（最长的那段注解）→ 降到 **`--color-meta`**（最浅一档；强调由"标签 + 位置"承担，
          ⛔ 不靠颜色 —— 一加深就变成"最长的那段最抢眼"）；
        · **数字**（值）→ 沿用 `.kpi-card__value`（近黑 + 700 + 最大字号），一个字没动。
   ▸ 三级实测亮度严格递减（说明 最浅 < 标签 < 数字 最深）：渲染校验有断言守着，
     以后谁把某两级调成同色就会红（这条"主次"就是本轮缺的判据）。
   ⚠️ 只在本页用（`.hs-kpi-*`）；⛔ 不动全站 `.kpi-card__sub` / `.kpi-card__label` ——
      其它 14 个页面是"灰标签 + 大数字"两级结构，本来就不糊，改全站会把它们一并改掉。 */
.kpi-card__sub.hs-kpi-note {
  margin-top: var(--space-1);
  margin-bottom: var(--space-1);
  color: var(--color-meta);
}
/* 标签（"这是哪个数"）—— 与说明、数字拉开两档的**中间那档**，见上一条注释。 */
.kpi-card__label.hs-kpi-title {
  color: var(--color-fg-2);
  font-weight: var(--font-weight-medium);
}

/* 净实收卡按渠道（微信小店/抖音）拆分 */
.kpi-src {
  display: flex;
  align-items: center;
  gap: 5px;
  font-size: var(--font-size-xs);
  color: var(--color-fg);
}
.kpi-src + .kpi-src { margin-top: 3px; }
.kpi-src__dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex-shrink: 0;
}

/* 成本构成横向条形 */
.cost-bar-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}
.cost-bar-item__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-2);
  font-size: var(--font-size-sm);
}
.cost-bar-item__head span:first-child { color: var(--color-fg); }
.cost-bar-item__head .num { color: var(--color-fg-2); font-variant-numeric: tabular-nums; }
.cost-bar-item__track {
  height: 8px;
  border-radius: var(--radius-pill);
  background: var(--color-gray-100);
  overflow: hidden;
}
.cost-bar-item__fill {
  height: 100%;
  border-radius: var(--radius-pill);
  background: var(--color-accent-text);
  transition: width var(--motion-base) var(--motion-ease-standard);
}

.chart-grid {
  display: grid;
  grid-template-columns: 1.6fr 1fr;
  gap: var(--space-4);
  margin-bottom: var(--space-4);
}

.chart-grid--bottom {
  grid-template-columns: 1fr 1fr;
}

.chart-card {
  min-width: 0;
  overflow: hidden;
  background: var(--color-surface);
  /* ⭐⭐ 2026-09-26 全站卡片划界收敛为「纯线条」（乐哥拍板：按建议做第 3 条）。
     ▸ 改前：`border: 1px solid var(--color-border-soft)` + `box-shadow: var(--shadow-sm)` ——
       这是 2026-09-25「UI 美化第二刀」加的（当时注释写"与 .card / .kpi-card 一致"），
       但同一次改版里 Gashora 骨架又把手 `.card` / `.module-card` 改成了「只描边、不投影」
       ⇒ 那句注释过期了，**同一套卡片留下了两种划界语言**（实测全站 20 个 chart-card
         带阴影、28 个 card 不带），这正是乐哥反复觉得"乱"的一处来源。
     ▸ 改后：边框色统一到 `--color-frame-border`（= 白大卡内模块卡的描边色，与 .card 同源），
       阴影去掉。**理由不是审美偏好，是实测**：逐像素扫边缘，白底 #FCFCFC 上阴影最深只有 Δ11
       且 3px 内衰减到 0，而 1px 线条本身 Δ18 —— 白底上阴影没有可压暗的空间、纯属重复。
     ⚠️ 阴影只留给「浮在灰底上的浮层」（.page-stack 本身 / 弹窗 / 下拉 / 抽屉），
        以及二级 tab 的选中白块（它在**灰色托盘**上，那里阴影有效，见 pages-influencer.css）。 */
  border: 1px solid var(--color-frame-border);
  border-radius: var(--radius-lg);
  /* ⭐⭐ 2026-10-06：与 `.card` / `.kpi-card` 一起改用 `--shadow-card` 浮起。
     ⚠️ 上面那段 09-26 的"纯线条"结论**前提已变**（当时卡片住在白色大卡里、白上白，
        阴影没有可压暗的空间）；现在底盘是接近白 + 卡片靠阴影表达层次 ⇒ 必须带影。
     ⛔ 但**边框保留**：边框 Δ18 比阴影 Δ11 更硬，边界清晰靠它（门禁 card-boundary-line-only ② 锁着）。 */
  box-shadow: var(--shadow-card);
}

.chart-card__body {
  padding: var(--space-5);
  min-height: 240px;
  position: relative;
  min-width: 0;
}

.chart-card__body canvas {
  max-width: 100%;
  display: block;
}

.chart-fallback {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 240px;
  color: var(--color-muted);
  font-size: var(--font-size-sm);
}

/* 排行条形 */
.ranking-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.ranking-item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

.ranking-item__rank {
  width: 20px;
  flex-shrink: 0;
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-semibold);
  color: var(--color-meta);
  text-align: center;
  font-feature-settings: "tnum", "lnum";
  font-variant-numeric: tabular-nums;
}

.ranking-item__name {
  width: 96px;
  flex-shrink: 0;
  font-size: var(--font-size-sm);
  color: var(--color-fg-2);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ranking-item__bar {
  flex: 1;
  height: 8px;
  border-radius: var(--radius-pill);
  background: var(--color-surface-2);
  overflow: hidden;
}

.ranking-item__bar-fill {
  height: 100%;
  border-radius: var(--radius-pill);
  background: var(--color-accent);
}

.ranking-item__value {
  width: 88px;
  flex-shrink: 0;
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  color: var(--color-fg);
  font-feature-settings: "tnum", "lnum";
  font-variant-numeric: tabular-nums;
  text-align: right;
}

/* 快捷入口（卡片内横排 4 个） */
.quick-links {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--space-3);
}

.quick-link {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: var(--space-4);
  border-radius: var(--radius-md);
  /* ⭐⭐ 2026-10-09 乐哥：「tab 的卡片颜色样式感觉不好看…你整体看下」——
     底色由 `--color-surface-2`（浅灰）改为 **白色**，层次改由描边承担。
     为什么：① 4 张卡里 1 张浅蓝（高亮）+ 3 张浅灰 ⇒ 灰块与蓝块**不同深浅**，
     并排像"一个被选中、三个是失效态"，而不是"一个推荐、三个普通"；
     ② 全站卡片语言是「白底 + 1px 描边」（近白页面上靠描边界定边界），
     入口卡用灰底成了这条语言里唯一的例外。改白后与全站同源，高亮那张的浅蓝也更清楚。
     ⚠️ 2026-09-25 那条注释（"灰底与页面底同色⇒软塌塌"）仍然成立 ——
        所以描边**必须留着**，它是这里唯一的边界。 */
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  color: var(--color-fg-2);
  cursor: pointer;
  text-align: center;
  transition: border-color var(--motion-fast) var(--motion-ease-standard),
    background var(--motion-fast) var(--motion-ease-standard),
    box-shadow var(--motion-fast) var(--motion-ease-standard);
}

/* ⚠️ 2026-10-09：基类已是白底 ⇒ hover 不能再靠"白→白"表达反馈。
   改用**主色描边 + 轻阴影**（描边是这里唯一的边界，hover 让它变色最直接）。 */
.quick-link:hover {
  border-color: var(--color-accent-border);
  box-shadow: var(--shadow-sm);
}

/* 高亮快捷入口（排期日历）：主色浅底 + 主色图标，克制不抢眼 */
.quick-link--highlight {
  background: var(--color-primary-50);
  /* 浅蓝底配同族描边（2026-10-09）—— 否则它用的是中性描边，与底色不同族、看着发灰 */
  border-color: var(--color-accent-border);
}
.quick-link--highlight .quick-link__icon {
  background: var(--color-accent);
  color: var(--color-accent-on);
  box-shadow: none;
}
.quick-link--highlight .quick-link__label {
  color: var(--color-fg);
}
.quick-link--highlight .quick-link__desc {
  color: var(--color-muted);
}
.quick-link--highlight:hover {
  border-color: var(--color-accent);
  background: var(--color-primary-50);
}

.quick-link__icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: var(--radius-md);
  /* ⭐ 2026-09-25 UI 美化第二刀：由 `--color-surface-2`（与卡片底**同色** ⇒ 图标盒形同消失、
     只剩一个蓝图标飘着）改为品牌浅蓝底。图标盒现在有明确形状，与高亮入口形成同族层次：
       普通入口 = 浅蓝盒 + 蓝图标 ／ 高亮入口 = 实心蓝盒 + 白图标（见上方 --highlight）。 */
  background: var(--color-primary-50);
  color: var(--color-accent-text);
}

.quick-link__text {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.quick-link__label {
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-semibold);
  color: var(--color-fg);
}

.quick-link__desc {
  font-size: var(--font-size-xs);
  color: var(--color-muted);
}

/* 移动端：快捷入口降为两列；高亮的「排期日历」占满整行更醒目 */
@media (max-width: 768px) {
  .quick-links {
    grid-template-columns: repeat(2, 1fr);
  }
  .quick-link--highlight {
    grid-column: 1 / -1;
  }
}

/* 首页底部备案号（合规：主页底部中央位置，滚动到底部才出现，非悬浮） */
.site-footer {
  margin-top: var(--space-8);
  padding: var(--space-6) 0;
  text-align: center;
  font-size: var(--font-size-xs);
  color: var(--color-muted);
}
.site-footer a {
  color: var(--color-muted);
  text-decoration: none;
}
.site-footer a:hover {
  color: var(--color-fg-2);
}

/* ============================ 登录（split-screen，参考 Arco Pro） ============================ */

/* ⚠️⚠️ 2026-09-22 视觉重做（乐哥确认原型后落地）—— 左侧 hero 的三条关键变化：
   ① **渐变色 → 背景图**：原来的 `linear-gradient + 装饰环 + 装饰方块` 全部删除。
      装饰环/方块是为纯色渐变做点缀的，压在照片上只会变成"漂浮的白色方块"；
      而且历史上就出过事故（见下方被删掉的 .login-hero__shape 注释里记的
      "装饰横条压住副标题"那次）。现在背景图本身就是视觉主体，不需要再叠装饰。
   ② **居中 → 左对齐**：原型构图是"左侧文字 + 右侧卡片"，
      左对齐让文字与右侧卡片形成明确的视觉分区。
   ③ **文字色 白 → 深**：背景图统一重调成**浅底高调**（三张图文字区亮度 p50 分别是
      门户 0.43 / 财务 0.50 / 达播 0.37）⇒ 深字压浅底最坏 5.7:1；
      白字在浅底上反而会糊（门户原图深蓝底白字只有 2.4:1，这正是乐哥报的"看不清"）。
      ⚠️ 这条是**算出来的，不是审美选择**：重调工具与实测数据见
      `.render-check/login-bg-retune.js`（幂等，每次从原图重算）+ `verify-login-readability.js`
      （多视口 × p05/p95 最坏情况，117 条全过）。 */
.login-root {
  position: relative;
  min-height: 100vh;
  display: flex;
  /* ⚠️ 兜底底色：小屏下 hero 被隐藏（见文件末尾媒体查询），
     此时页面上没有任何背景图 ⇒ 少了这行会变成"白底 + 白卡片"看不出边界。 */
  background: var(--color-bg);
}

/* 左侧品牌视觉区（背景图铺满整屏 + 品牌文字）
   ⚠️⚠️ 用 `position: fixed` 而不是 `absolute` —— 这是"整屏背景图"的一个必踩点：
      项目在 `base.css` 里给 html 设了 **`scrollbar-gutter: stable`**
       （乐哥 2026-09-18 要求："同页面切 tab 也会向右移动然后闪一下" ⇒ 永久预留滚动条槽，
         让各页面的 `width:100%` 容器宽度恒定、不抖）。
       而 `absolute` 的包含块是 `.login-root`，它的宽度**已经扣掉了那 15px 槽**
       ⇒ 背景图只铺到 `视口宽 − 15px`，最右 15px 露出 `.login-root` 的底色
       ⇒ 白底页面看不出来，但**登录页铺满照片时就是一条明显的浅色竖条**（实测抓到）。
       ⇒ 改用 `fixed`：包含块是**视口**（含那 15px）⇒ 背景真正铺满，且**不动全局机制**
         （不去改 `scrollbar-gutter`，否则其他页面的抖动问题会回来）。
     ⚠️ `fixed` 后 hero 脱离文档流 ⇒ 高度靠 `.login-root` 的 `min-height: 100vh` 撑住，
        卡片由 `.login-form-wrap` 自己居中 —— 与改前表现一致（登录页本来就不滚动）。
     ⚠️ 层叠：hero 无 z-index（auto），`.login-form-wrap` 是 `z-index: 2` ⇒ 卡片仍在上层、可点。
     ⚠️ 小屏 `display: none`（见文件末尾媒体查询）对 fixed 同样有效。 */
.login-hero {
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  /* ⚠️⚠️ `width: 100vw` 是**必须的**，不能用 `right: 0`（等价 inset:0）：
      项目在 base.css 给 html 设了 **`scrollbar-gutter: stable`**
      （乐哥 2026-09-18 要求修"同页面切 tab 也会向右移动然后闪一下" ⇒ 永久预留滚动条槽，
       让各页面的 `width:100%` 容器宽度恒定不抖）⇒ **包含块宽度已被扣掉那 15px**。
      实测（1440 视口，three-domain 登录页）：
        `inset: 0`          → hero 宽 1425，最右 15px 露出 body 底色 rgb(245,246,248)
                              ⇒ 白底页面看不出来，但**登录页铺满照片时是一条明显的浅色竖条**
        `width: 100vw`      → hero 宽 **1440**，整屏铺满 ✓
      ⚠️ `100vw` **不会**造成横向滚动条：fixed 元素不撑开文档宽度（实测横向溢出仍为 −15px）；
         真有垂直滚动条时它盖在 hero 之上，视觉上也正常。
      ⚠️ 不去改 `scrollbar-gutter` 本身 —— 那是为**所有**页面修的抖动问题，
         在登录页单独关掉会把那个老毛病带回来（且登录页小屏内容变高时真会出现滚动条）。
      ⚠️ 小屏 `display: none`（见文件末尾媒体查询）对 fixed 同样有效。 */
  width: 100vw;
  display: flex;
  flex-direction: column;
  justify-content: center;
  /* 左对齐 + 左侧缩进 9.5%（与原型一致；太贴边在宽屏上显得局促） */
  align-items: flex-start;
  padding: 0 6% 0 9.5%;
  overflow: hidden;
  background-repeat: no-repeat;
  background-position: center;
  background-size: cover;
  /* 文字色（深字压浅底）—— 可达性结论见上方注释 */
  --lh-ink: #0b1524;
  --lh-ink-sub: #0b1016;
}

/* 三套门面的背景图 —— ⚠️ 只在**桌面**生效（包在媒体查询里）：
   小屏下 hero 是 display:none，图就白下载了（单张约 400KB）。
   ⚠️ 用 class 规则而不是内联 style：内联 style 里的 url 会被**无条件解析**
     ⇒ 三张图（1.3MB）在首屏全部下载。CSS 规则只在匹配时才发请求 ⇒ 只下一张。
   ⚠️ 文件名与门面 key 要能对上（hub→portal / finance→fin / main→dabotai），
     这里刻意保持"物理文件名 = 内容语义"，别为了省事缩写成 a.jpg/b.jpg。 */
@media (min-width: 769px) {
  .login-root--hub .login-hero { background-image: url('/img/login/portal.jpg'); }
  /* ⚠️⚠️ 类名是 `--finance`（**不是** `--fin`）：由 `login.js` 的
     `rootClass() = 'login-root login-root--' + entryKind()` 直接拼出，
     而 `entryKind()` 对财务返回的就是 **`'finance'`**。
     我一度把这里写成 `--fin`（沿用旧名），结果财务门面**没有背景图、强调色也没生效**
     —— 页面照常渲染、控制台零报错，左侧文字落在纯白底上，肉眼看只是"这张图没出来"。
     ⇒ 探针里那条「背景图指向 finance.jpg」就是专门抓这个的（它当场红了）。
     改这里必须与 login.js 的 entryKind() 返回值**逐字对齐**。 */
  .login-root--finance .login-hero { background-image: url('/img/login/finance.jpg'); }
  /* ⭐ 2026-10-09 新增：**项目管理**门面背景图（青绿 · 层叠玻璃模块）。
     ⚠️ 比其余三条**多叠了一层极轻的白色横向渐变**，这不是装饰，是为了**文字对比度**：
        这版图的左侧渐变偏深（实测最坏背景亮度 0.205）⇒ 深色副标题压上去只有
        **4.64:1**（标准 ≥4.5，余量仅 3%；而达播/门户/财务是 5.87~8.33，余量 30%+）。
        超扁视口（乐哥的 1837×540）下 `cover` 会多裁上下、左侧落点更暗，所以必须按
        最坏情况处理。⇒ 左侧叠 24% 白提亮，把余量拉回与其余门面同档。
     ⭐ 为什么不改图：**图是已定稿的素材**（乐哥确认过），版式层的问题不该回头动素材 ——
        图=视觉主体、CSS=版式，分工如此；且遮罩可回滚、换图后依然生效。
     ⚠️ 其余三条**不要照抄这层遮罩**（它们的图本身左侧就够亮，叠了会发白、失去质感）。
     ⚠️ 类名 `--project` 必须与 login.js 的 entryKind() 返回值**逐字对齐**（同 finance 那条的理由）。 */
  .login-root--project .login-hero {
    /* ⚠️ 多层背景时 `background-size/position/repeat` 是**按层循环取值**的，
       所以基础规则里的单值（cover / center / no-repeat）会自动应用到这层渐变上
       —— 渐变是无限尺寸，cover 等价 100%，行为正常。只需覆盖 background-image。 */
    background-image:
      linear-gradient(90deg,
        rgba(255, 255, 255, .24) 0%,
        rgba(255, 255, 255, .12) 36%,
        rgba(255, 255, 255, 0) 60%),
      url('/img/login/project.jpg');
  }
  /* ⭐ 2026-10-10 新增：**人事行政**门面背景图（蓝灰 · 玻璃质感团队人形）。
     至此**五个板块**门面齐了（门户蓝 / 财务金 / 达播橙 / 项目青绿 / 人事蓝灰）。
     ⚠️ 为什么是蓝灰：① 避开已达播橙、财务金、门户蓝、项目青绿；
        ② 与 HR 板块的侧栏域色（`tool` 灰蓝 #8A94A6）呼应。
     ⚠️ 这张**不叠提亮遮罩** —— 与 project 那条相反，理由用数据说话：
        本图左侧半幅实测 p05 = **113**（project 那版是 52）⇒ 深色副标题压上去约
        **5.4:1**，已在标准（≥4.5）之上、与达播/门户/财务同档 ⇒ 叠了只会发白、失去质感。
        改图后要**重新量 p05**（脚本里那条"左侧最坏亮度"），跌破 90 才需要补遮罩。
     ⚠️ 类名 `--hr` 必须与 login.js 的 entryKind() 返回值**逐字对齐**（同 finance 那条的理由）。 */
  .login-root--hr .login-hero { background-image: url('/img/login/hr.jpg'); }
  .login-root--main .login-hero { background-image: url('/img/login/dabotai.jpg'); }
}

/* ⛔ 这里原本是「装饰网格 + 3 个装饰圆环 + 4 个装饰方块」共 8 个纯装饰元素
   —— 2026-09-22 换成背景图后**连同它们的 CSS 一并删除**（login.js 里的对应节点也删了）。
   为什么必须删、而不是"留着反正看不见"：
     ① 它们都是**白色半透明**（rgba(255,255,255,.06~.15)），是给**深色渐变底**做点缀的；
        现在底色是浅色照片 ⇒ 白块压在浅底上什么也看不出来，纯属白占渲染。
     ② 更要紧的是**历史教训**（教训本身保留在下面，代码已删）：
        装饰块用**绝对定位 + 百分比**、内容用**居中** ⇒ 两者在某个视口尺寸必然撞上。
        实测就发生过"装饰横条正好穿过副标题、把文字压花"，蓝底时只是隐约、
        换金色门面后非常明显 —— 这也是"装饰与内容抢位置"的典型。
     ③ 背景图本身就是视觉主体，再叠装饰只会显脏。
   将来若确实要加装饰：**别用绝对定位的百分比**（必撞），
   且必须重新过一遍 `.render-check/verify-login-readability.js`（多视口 × 最坏情况）。 */

/* 左侧品牌区（定位在 hero 之上；hero 本身是背景层，见上） */
.login-hero__brand {
  position: relative;
  /* ⚠️ 层级说明：hero 是 absolute 背景层（z-index:0），表单区是 z-index:2 ⇒
     卡片可点击。brand 在 hero 内部，给它 1 就够（不需要 2，那是更早的写法）。 */
  z-index: 1;
  text-align: left;
  /* 给右侧卡片留呼吸位；窄屏时也不让文字顶到屏幕边 */
  max-width: 520px;
}
/* 公司标识：**透明直接放在背景图上**（乐哥「用透明的，不要白底」）
   ⚠️ 这里推翻了我 2026-09-20 加的白底衬，理由是**背景条件变了**：
      当时 hero 底色是 `linear-gradient(135deg, primary-500, ...)`，
      而公司标识自身含**主色蓝 #0064E0** ⇒ 实测 vs 渐变起点只有 **1.00**
      （标识的蓝块压在蓝底上完全看不见）⇒ 那时白底衬是**必须**的。
      现在 hero 换成了**重调过的浅底照片**（三张图文字区亮度 p50 = 0.43 / 0.50 / 0.37），
      深色标识压浅底的对比度足够 ⇒ 白底衬反而成了"莫名多出来的白方块"。
      而且标识**自带深灰描边**（#595757），那正是为浅底设计的，白/青部分不会消失。
   ⚠️ 但"透明"≠"不做处理"：仍加一层**白色柔光**（drop-shadow 无偏移、大模糊），
      把标识从照片的亮暗交界上轻轻"托"起 —— 它贴图形走、不形成任何块状边界
      （与文字用 text-shadow 是同一个思路）。 */
.login-hero__logo { margin-bottom: var(--space-4); }
.login-hero__logo svg,
.login-hero__logo img {
  width: auto;
  /* ⚠️ 64px 与原型一致（原为 44px）。仍用 height 定尺而不是 64×64 + object-fit：
     后者元素框是正方形、图形在框内缩放，**看起来等比但量不出来**
     （getBoundingClientRect() 返回元素框，核验脚本会误判"被压扁"，实测误报过一次）。 */
  height: 64px;
  object-fit: contain;
  display: block;
  filter: drop-shadow(0 1px 6px rgba(255, 255, 255, 0.75));
}
.login-hero__title {
  font-size: var(--font-size-3xl);
  font-weight: var(--font-weight-semibold);
  letter-spacing: .5px;
  margin-bottom: var(--space-3);
  /* ⚠️⚠️ 文字色与阴影方向 2026-09-22 **同时**改过，两者必须配对：
      原来是「白字 + 黑色 text-shadow」（配深色渐变底）；
      现在字改**深色**（配浅色照片）⇒ 黑色阴影只会把深字糊进背景花纹里。
      ⇒ 深字必须配**白色光晕**：贴着字形把字从亮暗交界上托出来，且不形成块状边界。
      ⚠️ 这条极容易漏：只改字色、不改阴影，症状与"背景太花"一模一样，换背景图也治不好
         （本项目实测：门户左侧大标题最坏只有 2.42:1，正是这个组合造成的）。 */
  color: var(--lh-ink);
  text-shadow: 0 1px 2px rgba(255, 255, 255, 0.62), 0 2px 12px rgba(255, 255, 255, 0.45);
}
.login-hero__subtitle {
  font-size: var(--font-size-base);
  color: var(--lh-ink-sub);
  font-weight: var(--font-weight-medium);
  /* ⚠️ 440px 是按新文案（"广州星盟传媒 · Star Hub 达人管理"）定的宽度；
     三个门面共用，文案最长的达播/财务都是 1~2 行。 */
  max-width: 440px;
  line-height: 1.7;
  /* 与标题同一套：深字配**白色光晕**（不是黑色阴影 —— 理由见 .login-hero__title） */
  text-shadow: 0 1px 2px rgba(255, 255, 255, 0.70), 0 2px 10px rgba(255, 255, 255, 0.50);
  /* ⚠️ 这里删掉了原来的 `opacity: 0.85` —— 它会让文字**实际**对比度低于算出来的值，
     而探针是读 CSS `color` 计算的 ⇒ 会把 opacity 算漏、报出**假绿**
     （本项目在"元素自带半透明背景"上踩过同源问题，判据已改成"先合成再算"）。 */
}
/* 板块功能标签（三个门面各自的功能点，给访客"这里面有什么"的第一印象） */
.login-hero__tags {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-4);
}
.login-hero__tag {
  font-size: var(--font-size-xs);
  padding: 5px 11px;
  border-radius: var(--radius-pill);
  /* ⭐ 白胶囊 + 深字：胶囊自身是**接近不透明的白**（.88），
     压在任何亮度的背景上都保证内部文字 ≥4.5:1（实测最坏 17:1）。
     这是"在照片上放小字"最稳的做法 —— 比给文字加阴影可靠得多。 */
  background: rgba(255, 255, 255, 0.88);
  border: 1px solid rgba(255, 255, 255, 0.96);
  color: var(--lh-ink-sub);
}

/* 右侧表单区 —— ⚠️ 两层要点：
   ① 背景**透明**（原来是不透明的 `--color-bg`）：卡片要"浮在整屏背景图上"，
      中间夹一层不透明底会把图切断、露出一条白色竖区，与左侧照片割裂
      （原型阶段踩过：一层白色渐变蒙层把右侧整块刷白）。
   ② 自身不占背景、靠 `flex:1 + 右对齐` 把卡片推到右边（背景由绝对定位的 hero 铺满）。
   ⚠️ `z-index: 2` 必须高于 hero(0)，否则背景层会盖住卡片、**点不动按钮**。 */
.login-form-wrap {
  position: relative;
  z-index: 2;
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  /* ⚠️ 右侧留白 2026-09-22 从 **6% 调到 10%**（乐哥：「那个框框再大一点，往左一点，
     太靠右了」）。依据是实测的**左右不对称**：
       1440 视口下，左侧文字只到 x=433，卡片却在 x=940 ⇒ 中间空了 **507px**，
       而右侧只留 101px ⇒ 重心明显偏右。
     改后（1440）：卡片 x≈841，右侧留白≈159 ⇒ 左右都留出了呼吸位。
     ⚠️ 往左移**不能只看 1440**：最窄的桌面视口是 1024（再窄就走移动端布局、
       hero 隐藏了）。1024 下改后卡片 x≈467、左侧文字止于 393 ⇒ 仍有 74px 间距，
       **不重叠**。今后再往左调，必须重新验这个视口的下限。 */
  padding: var(--space-6) 10% var(--space-6) var(--space-6);
  background: transparent;
}

/* 登录卡片：**毛玻璃**（乐哥：「登录框左右有玻璃效果的这种质感」）
   ⭐ 层次（三层叠加，缺一层就显平）：
     ① 半透明底色 rgba(255,255,255,.62) —— 透出背景的**色相**、但保证卡片主体可读
     ② 毛玻璃 backdrop-filter: blur(22px) saturate(180%)
     ③ 高光边框 1px 半透明白边 + 内侧亮线（玻璃顶边的"棱"）
   再加一层 ::before 斜向高光（玻璃最标志性的一笔，见下）。
   ⚠️⚠️ 必须是 `backdrop-filter` 而**不是** `filter`：
      `filter: blur()` 会把**卡片自己的内容**一起糊掉（连字都糊），
      `backdrop-filter` 只虚化卡片**背后**的东西 —— 那才是玻璃。
   ⚠️ 透明度 .62 是实测的平衡点：再透会让卡片里的小字掉到 3.84:1（不达标），
      再实就没玻璃感了。⚠️ 它与"卡片内次级文字加深"是**一组改动**，别只改一个
      （见 .login-card__footer 的注释）。 */
.login-card {
  position: relative;
  /* ⚠️ 宽度 2026-09-22 从 400px 加到 **448px**（乐哥：「那个框框再大一点」）。
     448 与原型（.lform width: 428px）同一档、略大一点；
     ⚠️ 别再往 480+ 加：卡片越宽，右侧留白被挤压得越少，
        "整体偏右"的观感会回来（那正是这次要修的问题）。
     ⚠️ `max-width: 100%` 必须保留 —— 小屏/窄屏靠它兜底，删了会横向溢出。 */
  width: 448px;
  max-width: 100%;
  background: rgba(255, 255, 255, 0.62);
  -webkit-backdrop-filter: blur(22px) saturate(180%);
  backdrop-filter: blur(22px) saturate(180%);
  border: 1px solid rgba(255, 255, 255, 0.72);
  border-radius: var(--radius-xl);
  box-shadow:
    0 24px 60px rgba(10, 22, 40, 0.22),      /* 外层投影：把卡片从背景里"抬"起来 */
    0 4px 14px rgba(10, 22, 40, 0.10),
    inset 0 1px 0 rgba(255, 255, 255, 0.95); /* 内侧亮线：玻璃顶边的棱 */
  padding: var(--space-8) var(--space-8);
  overflow: hidden;
}
/* 斜向高光：玻璃最标志性的一笔（左上亮、右下透，边缘全透明不显"色块"）
   ⚠️ 用 ::before 而不是给卡片叠 background-image：后者会盖住毛玻璃的透感。
   ⚠️ 它必须 `pointer-events: none`，否则会挡住卡片内所有点击
      （本项目在"装饰层压住交互"上踩过同源问题）。 */
.login-card::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  background: linear-gradient(135deg,
    rgba(255, 255, 255, 0.50) 0%,
    rgba(255, 255, 255, 0.16) 32%,
    rgba(255, 255, 255, 0) 58%,
    rgba(255, 255, 255, 0.10) 100%);
}
/* 卡片内容必须在高光层之上 */
.login-card > * { position: relative; z-index: 1; }

.login-card__header {
  text-align: center;
  margin-bottom: var(--space-6);
}
.login-card__title {
  font-size: var(--font-size-xl);
  font-weight: var(--font-weight-semibold);
  color: var(--color-fg);
}
.login-card__desc {
  margin-top: var(--space-2);
  font-size: var(--font-size-sm);
  /* ⚠️ 不再用 `var(--color-muted)`（#5D6C7B，次级色）。
     原因：卡片改成**毛玻璃、浮在照片上**之后，`--color-muted` 压在半透明卡上
     实测最坏只有 **3.75:1**（不够 4.5）—— 背景图越暗的地方越糊。
     ⇒ 登录卡内的次级文字统一加深到 #46505c，与 `.login-card__footer` 同一档。
     ⚠️ 这是"卡片底色/透明度一变，卡内所有浅色文字都要重算"的典型：
        不能只验"卡片标题"（深色、本来就够），**次级文字才是先掉队的那一批**。 */
  color: #46505c;
}

/* ── 扫码登录按钮（布局方案 A：扫码为主，乐哥 2026-09-22 选定）─────────────
   ⚠️⚠️ 按钮底色**不是**品牌标准色，理由是可以算出来的：
       品牌标准色压白字达不到 4.5:1 —— 财务金 #A8741A 是 **4.06**、
       达播橙 #C96E08 是 **3.67**、微信绿 #07C160 只有 **2.38**（白字会发糊）。
       ⇒ 按钮底统一走**同色相深档**；品牌标准色仍用于图标本体 / 焦点框 / 悬停。
       实测（白字压深档）：飞书 #1a6ae0 = 5.02 · 微信 #04803f = 5.04 ·
                          财务 #8f6111 = 5.40 · 达播 #a85505 = 5.29 */
.sso {
  width: 100%;
  height: 48px;
  margin-top: var(--space-3);
  border: none;
  border-radius: var(--radius-lg);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-medium);
  font-family: inherit;
  color: #fff;
  cursor: pointer;
  transition: filter var(--motion-fast) var(--motion-ease-standard),
    box-shadow var(--motion-fast) var(--motion-ease-standard);
}
.sso:hover { filter: brightness(1.07); box-shadow: 0 6px 18px rgba(14, 26, 43, 0.18); }
.sso:active { transform: translateY(1px); }
.sso--feishu { background: #1a6ae0; }
.sso--wechat { background: #04803f; }
/* 账号密码的"登 录"按钮：强调色**按门面走**（变量定义见各 .login-root--* 规则） */
.sso--primary { background: var(--login-brand-deep, var(--color-accent)); margin-top: var(--space-4); }

/* 品牌图标白片：官方标识是**彩色**的（飞书双色飞鸟 / 微信绿双气泡），
   不能染成白色 ⇒ 用白片托住。这也是各家官方"第三方登录按钮"的通行做法。
   ⚠️ 尺寸（26 / 17）写在这里一处，不在 icons.js 里再传一遍 —— 避免两处各写一份。 */
.brand-chip {
  flex: none;
  width: 26px;
  height: 26px;
  border-radius: 7px;
  background: #fff;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);
}
.brand-chip img { display: block; width: 17px; height: 17px; object-fit: contain; }

/* 「或」分隔线 */
.login-or {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin: var(--space-5) 0 var(--space-2);
  color: #46505c;
  font-size: var(--font-size-xs);
}
.login-or::before,
.login-or::after {
  content: '';
  flex: 1;
  height: 1px;
  background: rgba(15, 30, 50, 0.14);
}

/* 「使用账号密码登录」链接（方案 A：不占一个按钮位）
   ⚠️ 原先这里是"飞书扫码 / 账号密码"两个 tab。改链接的理由：
      tab 把两种方式摆成**同等重要**，而实际绝大多数人走扫码（内部用飞书、外部用微信），
      账号密码只是兜底 ⇒ 层级不该平齐。 */
.login-switch {
  display: block;
  width: 100%;
  padding: var(--space-3);
  background: none;
  border: none;
  border-radius: var(--radius-md);
  font-family: inherit;
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  color: #2f3a46;
  text-align: center;
  cursor: pointer;
  transition: color var(--motion-fast) var(--motion-ease-standard),
    background var(--motion-fast) var(--motion-ease-standard);
}
.login-switch:hover { color: var(--color-accent); background: rgba(255, 255, 255, 0.55); }

/* 输入框 —— ⚠️ 在玻璃卡上**不能用半透明填充**：
   卡片本身半透明、输入框再半透明 = 两层透 ⇒ 里面的字发糊。
   ⇒ 用近实白（.94）+ 清晰边框，"这是输入框"一眼可见。 */
.login-field {
  height: var(--control-height);   /* 两档制：46 → 44（标准档，登录表单） */
  display: flex;
  align-items: center;
  padding: 0 var(--space-3);
  margin-top: var(--space-3);
  border: 1px solid rgba(40, 60, 85, 0.28);
  border-radius: var(--radius-lg);
  background: rgba(255, 255, 255, 0.94);
  transition: border-color var(--motion-fast) var(--motion-ease-standard),
    box-shadow var(--motion-fast) var(--motion-ease-standard);
}
.login-field:focus-within {
  border-color: var(--color-accent);
  background: #fff;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-accent) 14%, transparent);
}
.login-field input {
  flex: 1;
  min-width: 0;
  height: 100%;
  border: none;
  outline: none;
  background: transparent;
  font-family: inherit;
  font-size: var(--font-size-sm);
  color: #0b1420;
}
.login-field input::placeholder { color: #5a6675; }
.login-field__eye {
  flex: none;
  display: flex;
  padding: 4px;
  border: none;
  background: none;
  color: #46505c;
  cursor: pointer;
}

.login-card__footer {
  margin-top: var(--space-5);
  font-size: var(--font-size-xs);
  /* ⚠️ 颜色从 `var(--color-meta)`（浅灰）**加深到 #46505c**：
     玻璃卡是半透明的，浅灰小字在它上面只有 3.84:1（不够 4.5）。
     ⇒ 这是与"卡片透明度 .62"配套的改动，两者必须一起看（见 .login-card 注释）。 */
  color: #46505c;
  text-align: center;
}

/* 登录页小屏：隐藏品牌区，表单占满（否则左侧 hero 被压没、480px 表单溢出）
   ⚠️⚠️ 这条与前面「背景图只在 min-width:769px 生效」是**一对**，别拆开：
      hero 在小屏是 `display:none` ⇒ 背景图就算加载了也看不见。
      把背景图规则放进 `min-width:769px` 后，小屏**完全不发图片请求**（单张约 400KB）。
      反过来若把背景图写成小屏也生效，手机上会白下载一张看不见的图。
   ⚠️ hero 隐藏后页面上没有任何背景图 ⇒ 靠 `.login-root` 的 `background: var(--color-bg)`
      兜底（见该处注释），否则会变成"白底 + 玻璃卡"看不出边界。 */
@media (max-width: 768px) {
  .login-hero { display: none; }
  .login-form-wrap { flex: 1 1 100%; }
  .login-card { width: 100%; }
}

/* ⚠️ 这里原本是「密码输入框小眼睛」（`.input--password` + `.pwd-toggle`）——
   2026-09-22 登录页改用 `.login-field` / `.login-field__eye`（近实白输入框 +
   玻璃卡上更清晰的边界）后，**这三条已无使用点**，故删除。
   ⚠️ `components.css` 里还有同名的一份定义**故意保留**：那是通用组件层，
      删它没有收益、却可能影响未 grep 到的动态用法（改动面/风险不成正比）。
      若将来确认全仓无引用，再一起清。 */

/* 回调加载页 */
.callback-box {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-12) 0;
}

.spinner {
  width: 28px;
  height: 28px;
  border-radius: var(--radius-pill);
  border: 3px solid var(--color-border);
  border-top-color: var(--color-accent-text);
  animation: spin 0.8s linear infinite;
}

@keyframes spin {
  to { transform: rotate(360deg); }
}

@media (max-width: 1024px) {
  .kpi-row {
    grid-template-columns: repeat(2, 1fr);
  }
  .chart-grid,
  .chart-grid--bottom {
    grid-template-columns: 1fr;
  }
}

/* === 排期时间轴（详情抽屉内）=== */
.timeline {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  padding: var(--space-2) 0 var(--space-2) var(--space-1);
}
.timeline__item {
  display: flex;
  gap: var(--space-3);
  position: relative;
}
.timeline__item::before {
  content: '';
  position: absolute;
  left: 4px;
  top: 14px;
  bottom: -18px;
  width: 2px;
  background: var(--color-border-soft);
}
.timeline__item:last-child::before { display: none; }
.timeline__dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--color-accent);
  flex-shrink: 0;
  margin-top: 4px;
  z-index: 1;
}
.timeline__body { flex: 1; min-width: 0; }
.timeline__head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
}
.timeline__time {
  font-size: var(--font-size-sm);
  color: var(--color-muted);
}
.timeline__detail {
  font-size: var(--font-size-sm);
  color: var(--color-fg-2);
  margin-top: 2px;
}

/* === 排期时间轴节点卡片（表单内动态节点）=== */
.node-card {
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-lg);
  padding: var(--space-4);
  margin-top: var(--space-3);
  background: var(--color-surface);
}
.node-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-2);
}
.node-title {
  font-weight: var(--font-weight-semibold);
  font-size: var(--font-size-base);
  color: var(--color-fg);
}
.form-actions {
  position: sticky;
  bottom: 0;
  z-index: 10;
  display: flex;
  justify-content: flex-end;
  gap: var(--space-3);
  padding: var(--space-4) 0;
  background: var(--color-bg);
  border-top: 1px solid var(--color-border-soft);
}

/* ⭐⭐ 2026-09-28 「右下角玻璃浮岛」—— 表单底部保存/取消统一改浮岛
   （乐哥：「保存取消按钮……根据按钮宽度做浮岛，用玻璃效果」+「原来那个又高又窄，不协调」）
   ────────────────────────────────────────────────────────────────────────
   ▸ 尺寸：高度 64px（岛内边距 10 + 按钮 44 + 10）。
     🐞 上一版做成 98px 高（"又高又窄"）的根因：**垂直内边距叠了两层** ——
        内层 .sf-actions__inner 自带 `padding: 16px 0`、外岛又加了 10px。
        ⇒ 本版把内层 padding 归零，垂直间距只有岛这一层。
   ▸ 宽度：交给按钮撑开（按钮 min-width 88 ⇒ 两键岛 ≈224px），不再是"细高条"。
   ▸ 位置：fixed 视口右下（右 40 / 下 28）—— 滚动到哪都能点，不必滚回页尾。
   ▸ 玻璃：**克制档**（与悬浮 Tab 同一套：透光渐变 + blur20 + saturate180 + 上缘高光），
     并保留 1px 边框（--color-frame-border）——白底内容上"白边框 = 没有框"，实测过。
   ⚠️ 只作用**页面级**表单、覆盖两套系统：
      · #page-root .sf-actions     —— 排期/成本/业绩/机制比价/达人/签约公司 表单（6 页）
      · #page-root .form-actions   —— 货品/套餐/样品包 表单
        （排除 --total：报销提单的"合计条"是另一个经调研定稿的形态，不动）
   ⚠️ 弹窗（#modal-root）与抽屉（#drawer-root）挂在 body 级、**不在 #page-root 内**
      ⇒ 弹层里的操作栏保持原样（那里也不该浮动）。
   ⚠️ 岛脱离文档流后不再占位 ⇒ 由下方 `.page:has(...)` 规则补足页底空间（勿删）。 */
#page-root .sf-actions,
#page-root .form-actions:not(.form-actions--total) {
  /* ⭐⭐ 2026-09-28 二次定位（乐哥：「不应该在页面右下角吧，应该在**正文内容**的右下角，
     就是悬浮在内容上面」）——
     改前是 `position: fixed` + 视口右 40px ⇒ 在宽屏/窄内容列下**超出正文卡片右缘**（实测超 39px）。
     改 **sticky + margin-left:auto**：元素在 `.page`（内容列）的文档流内 ⇒ 水平位置天然
     跟随内容列右缘（不再靠视口坐标算），滚到哪都浮在底部 24px。垂直仍"悬浮"、水平"在内容上"。
     ▸ margin-right 24px = 内容列右 padding 减去正文容器内边距 ⇒ 与**卡片右缘对齐**（实测 1347px）。
     ▸ margin-top 16px：与上方最后一张卡留出间距（sticky 在文档流占位，紧贴会顶死）。
     ⚠️ 不再需要 `.page:has(...)` 的页底补位 —— sticky 自身占位（那条已删）。 */
  position: sticky;
  bottom: 24px;
  z-index: 90;
  width: fit-content;
  margin: var(--space-4) var(--space-6) 0 auto;
  /* ⭐⭐ 2026-09-28 与 tab 一起矮 10%（乐哥：「这个 tab 还有右下角的悬浮保存取消，
     感觉有一点点高了，要不矮个 10%」）——
     改前 10px → **12px**：岛高 = 12×2 + 按钮34 + 2(边框) = **60**（原 66，矮 9%）。
     ⚠️ 垂直**加大**但整体变矮，是因为按钮同时从 44 降到 34（见下一条）——
        两者要一起看：只降按钮不加大内边距，岛会缩到 56、按钮贴边。
     ⚠️ 水平 14px 不动（它决定"岛左右与按钮的呼吸"，与高度无关）。 */
  padding: 12px 14px;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-3);
  flex-wrap: nowrap;
  border: 1px solid var(--color-frame-border);
  border-radius: var(--radius-lg);
  /* ⭐ 统一「克制玻璃」令牌（与顶栏/侧栏/Tab 同一套，见 design-tokens） */
  background: var(--glass-bg);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  box-shadow: var(--glass-lit), var(--glass-lift);
}

/* 岛内的按钮行：清掉两套旧"限宽内层"的 max-width / 垂直 padding（叠层就是它引起的） */
#page-root .sf-actions > .sf-actions__inner,
#page-root .sf-actions > .form-actions__inner,
#page-root .form-actions > .form-actions__inner {
  max-width: none;
  margin: 0;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-3);
  flex-wrap: nowrap;
}
/* ⭐⭐ 2026-09-28 浮岛里的按钮**降档到与 tab 胶囊同尺寸**（乐哥：
   「然后右下角保存取消的尺寸，和 tab 得一样」）——
   改前 44px（默认 .btn 档），与 tab 胶囊（34）差 10px ⇒ 同屏里两个"操作元素"体量不齐。
   改后取 `--control-height-sm`（34，= 胶囊高，也是列表工具栏那档）。
   ⚠️ 为什么用令牌而不是写 34：它与 tab 胶囊同源（都走 sm 档），
      将来若整体调档，改令牌即可两处同时跟随。
   ⚠️ 只作用于**浮岛内**（`#page-root .sf-actions / .form-actions`）——
      页头按钮（`.page-actions .btn--lg`，一级页的「新增排期」等）**不动**：
      那是"页面级主操作"，本就该比 tab 大一号；乐哥说的是"右下角保存取消"。 */
#page-root .sf-actions .btn,
#page-root .form-actions:not(.form-actions--total) .btn {
  height: var(--control-height-sm);
  min-height: var(--control-height-sm);
}

/* 岛宽由按钮宽度决定（乐哥：「根据按钮宽度」）—— 给按钮一个下限，两键即协调横条 */
#page-root .sf-actions .btn,
#page-root .form-actions:not(.form-actions--total) .btn {
  min-width: 88px;
}

/* ⭐ 2026-09-28：页底补位**已不需要**（原为 fixed 版设计）——
   改为 sticky 后浮岛自身在文档流占位（66px + margin-top 16px），页尾天然留出空间。
   ▸ 原 fixed 版的补位写法（`#page-root.page:has(...) { padding-bottom: ... }`）留作记录：
     当时踩过"后代选择器 `#page-root .page` 永不匹配"的坑（#page-root 就是 main.page 本身）。 */

/* 窄屏：岛退化为贴底全宽条（两键等分），避免悬浮岛压住表单 */
@media (max-width: 760px) {
  #page-root .sf-actions,
  #page-root .form-actions:not(.form-actions--total) {
    margin: var(--space-3) 0 0;    /* 全宽：清掉 auto/右缩进 */
    width: auto;
  }
  #page-root .sf-actions .btn,
  #page-root .form-actions:not(.form-actions--total) .btn {
    flex: 1;
  }
}

/* 手机安全区：岛底距要让开 iPhone 小黑条（替代旧的"给条加 padding-bottom"做法） */
body.is-standalone #page-root .sf-actions,
body.is-standalone #page-root .form-actions:not(.form-actions--total) {
  bottom: calc(24px + env(safe-area-inset-bottom, 0px));
}
/* ⭐ 2026-10-08 报销提单的合计条也成了浮岛 ⇒ 安全区同样要让位
   （它不在 #page-root 那条公共规则里，见 `.sf-shell > .form-actions--total` 处的说明）。 */
body.is-standalone .sf-shell > .form-actions.form-actions--total {
  bottom: calc(24px + env(safe-area-inset-bottom, 0px));
}

/* ---- 套餐商品选择器 ---- */
.bundle-picker {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  max-height: 220px;
  overflow-y: auto;
  padding: var(--space-2);
}
.bundle-pick-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
}
.bundle-pick-row:hover {
  background: var(--color-surface-2);
}

/* ---- 样品发放记录：发放样品列（小字，超长折叠） ---- */
.s-prod-summary {
  font-size: var(--font-size-xs);
  color: var(--color-fg-2);
  line-height: 1.5;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  word-break: break-all;
}
.bundle-pick-name {
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-medium);
  color: var(--color-fg);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ---- 商品选择器（全站统一）：PC 一行（表格式对齐），手机两行 ---- */
.product-picker {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  max-height: 220px;
  overflow-y: auto;
  padding: var(--space-2);
}
.product-pick-row {
  display: grid;
  grid-template-columns: 40px minmax(0, 1fr) 130px 80px 90px auto;
  grid-template-areas: "img name code stock price action";
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  cursor: pointer;
}
.product-pick-row:hover { background: var(--color-surface-2); }
.pp-img { grid-area: img; width: 40px; height: 40px; border-radius: var(--radius-seg); object-fit: cover; }
.pp-img--empty { display: inline-flex; align-items: center; justify-content: center; background: var(--color-surface-2); color: var(--color-meta); }
.pp-name {
  grid-area: name;
  display: flex; align-items: center; gap: var(--space-2);
  min-width: 0;
}
.pp-name-text {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-weight: var(--font-weight-medium); color: var(--color-fg);
}
/* PC：meta 用 display:contents 不占位，让编码/库存/价格各自独立成列、像表格一样对齐 */
.pp-meta { display: contents; }
.pp-code { grid-area: code; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--font-size-sm); color: var(--color-fg-2); }
.pp-code--missing { color: var(--color-warning); }
.pp-stock { grid-area: stock; white-space: nowrap; font-size: var(--font-size-sm); color: var(--color-fg-2); }
.pp-price { grid-area: price; color: var(--color-price); font-weight: var(--font-weight-semibold); white-space: nowrap; }
.pp-action { grid-area: action; justify-self: end; }
.pp-picked { color: var(--color-accent); font-size: var(--font-size-sm); font-weight: var(--font-weight-semibold); }

@media (max-width: 640px) {
  .product-pick-row {
    grid-template-columns: 48px minmax(0, 1fr) auto;
    grid-template-areas: "img name action" "img meta action";
    row-gap: 2px;
  }
  .pp-img { width: 48px; height: 48px; }
  /* 手机：meta 恢复占位，编码/库存/价格合并到一行 */
  .pp-meta {
    display: flex; align-items: center; gap: var(--space-3);
    grid-area: meta; min-width: 0; overflow: hidden;
    font-size: var(--font-size-sm); color: var(--color-fg-2);
  }
  .pp-code, .pp-stock, .pp-price { grid-area: auto; }
}

/* ---- 排期横向时间轴 ---- */
.sch-timeline {
  position: relative;
  padding: var(--space-4) var(--space-2);
  overflow-x: auto;
}
.sch-tl {
  position: relative;
}

/* 住宿泳道：块左右边界对齐事项节点（圆心到圆心） */
.sch-tl-acc {
  position: relative;
  height: 56px;
  margin-bottom: var(--space-3);
}
.sch-tl-acc__block {
  position: absolute;
  top: 0;
  height: 100%;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 0 var(--space-2);
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--acc, var(--color-muted)) 14%, #fff);
  border: 1px solid color-mix(in srgb, var(--acc, var(--color-muted)) 38%, #fff);
  overflow: hidden;
}
.sch-tl-acc__dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--acc, var(--color-muted));
  flex-shrink: 0;
}
.sch-tl-acc__body {
  display: flex;
  flex-direction: column;
  min-width: 0;
  line-height: 1.35;
}
.sch-tl-acc__hotel {
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  color: var(--color-fg);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.sch-tl-acc__sub {
  font-size: var(--font-size-xs);
  color: var(--color-fg-2);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* 节点容器 + 虚线（虚线从首个节点圆心到末个节点圆心） */
.sch-tl-nodes-wrap {
  position: relative;
}
.sch-tl-line {
  position: absolute;
  left: 85px;   /* NODE_W/2：对齐首节点圆心 */
  right: 85px;  /* 对齐末节点圆心 */
  top: 6px;     /* dot 高 14 → 圆心 7，线高 2 → top 6 */
  height: 2px;
  background: var(--color-gray-400);
  z-index: 0;
}
.sch-timeline__nodes {
  display: flex;
  gap: 24px;
  position: relative;
}
.sch-timeline__node {
  position: relative;
  width: 170px;
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  z-index: 1;
}
.sch-timeline__dot {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  border: 2px solid #fff;
  box-shadow: 0 0 0 1px rgba(0,0,0,0.08);
  flex-shrink: 0;
}
.sch-timeline__date {
  margin-top: var(--space-2);
  font-size: var(--font-size-xs);
  color: var(--color-fg-2);
}
.sch-timeline__time {
  margin-top: 2px;
  font-size: var(--font-size-xs);
  color: var(--color-muted);
}
.sch-timeline__label {
  margin-top: 2px;
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
  color: var(--color-fg);
}
.sch-timeline__card {
  margin-top: var(--space-2);
  width: 100%;
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-md);
  padding: var(--space-2);
  font-size: var(--font-size-sm);
  color: var(--color-fg);
  line-height: 1.5;
}
/* 时间轴卡片信息行：图标固定 + 标签 + 头像 + 文字，尺寸统一 */
.tl-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  min-height: 20px;
}
.tl-row-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 14px;
  color: var(--color-muted);
  flex-shrink: 0;
}
.tl-row-label {
  color: var(--color-muted);
  flex-shrink: 0;
}
.tl-avatar {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--color-primary-100);
  color: var(--color-accent-text);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  font-weight: var(--font-weight-medium);
  flex-shrink: 0;
  overflow: hidden;
}
.tl-avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.tl-row-text {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.sch-timeline__session {
  margin-top: 4px;
  padding-top: 4px;
  border-top: 1px dashed var(--color-border-soft);
}
.sch-timeline__session-title {
  font-weight: var(--font-weight-medium);
}
.sch-timeline__session-date {
  font-size: var(--font-size-xs);
  color: var(--color-fg-2);
}
.sch-timeline__session-time {
  font-size: var(--font-size-xs);
  color: var(--color-muted);
}

/* ---- 列表展开行 ---- */
.row-expand {
  background: var(--color-surface-2);
}
/* 主播业绩：展开明细按「大类」着色（乐哥 2026-09-11 定：不同大类不同颜色）。
   刻意避开左侧色条——项目铁律明确「不存在左侧色条这类通用强调手法」，改用整行淡底 + 名称前小圆点。 */
.ip-sub--live {
  background: var(--color-primary-50);
}
.ip-sub--private {
  background: var(--color-warning-bg);
}
.ip-dot {
  display: inline-block;
  width: 6px;
  height: 6px;
  border-radius: var(--radius-pill);
  margin-right: 8px;
  flex: 0 0 auto;
}
.ip-dot--live {
  background: var(--color-accent);
}
.ip-dot--private {
  background: var(--color-warning-text);
}
.row-expand__inner {
  padding: var(--space-4);
}
/* ⛔ 2026-10-11 乐哥：「**排期列表一级结束的排期颜色不要变淡了，恢复正常**」
   ⇒ 原 `.table tr.is-past { opacity: .55 }`（已结束排期整行淡化、悬停恢复）**整条移除**。
   为什么删而不是调大透明度：`.55` 是"用透明度表达状态"的做法 ——
   它在**浅色表格上会把文字一并压灰**（对比度掉到不合格），而"这批已经结束了"
   本来就已经由**状态列**（`.badge`「已结束」）说清楚了，同一件事表达两遍。
   ⚠️ `is-past` 这个类**仍在排期列表的行上挂着**（`schedule-list.js` 的 `pastCls`）——
   别把它当成"可以顺手删的死类名"，留着它语义明确；这里只是不再为它写淡化样式。
   ⚠️ 日历里的 `.cal-event.is-past`（`pages-schedule.css`）是**另一回事**（月历格子里的场次），
      它淡化是对的，**不受本条影响**。 */
/* 订单业绩天维度展开子表：去掉内缩，让子表列与主表对齐 */
.row-expand__inner.so-expand {
  padding: var(--space-2) 0;
}

/* ---- 展开详情网格 ---- */
.detail-grid {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-6);
}
.detail-grid > div {
  min-width: 120px;
}

/* ---- 佣金商品选择器 ---- */
.cm-picker {
  margin-top: var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  max-height: 220px;
  overflow-y: auto;
}
.cm-pick {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  cursor: pointer;
  font-size: var(--font-size-sm);
}
.cm-pick:hover {
  background: var(--color-surface-2);
}
.chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  /* 上 3 下 1：补偿中文字面偏上，视觉居中（总高不变） */
  padding: 3px 8px 1px;
  margin: 2px;
  border-radius: var(--radius-pill);
  background: var(--color-surface-2);
  font-size: var(--font-size-sm);
}
/* 表格内紧凑胶囊（费用构成等）：高度对齐 .tag(22)，文字视觉居中 */
.cell-chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 22px;
  padding: 1px 8px 0;
  margin: 1px 4px 1px 0;
  border-radius: var(--radius-pill);
  background: var(--color-surface-2);
  color: var(--color-fg-2);
  font-size: var(--font-size-xs);
  white-space: nowrap;
}
.chip__x {
  border: none;
  background: none;
  cursor: pointer;
  color: var(--color-muted);
  display: inline-flex;
  padding: 0;
}
.chip__x:hover {
  color: var(--color-danger);
}

/* ---- 费用列表：原始数据展开（横向网格排列，字段卡片式） ---- */
.raw-data {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
  gap: var(--space-2);
}
.raw-row {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--space-2) var(--space-3);
  background: var(--color-surface-2);
  border-radius: var(--radius-sm);
  min-width: 0;
}
.raw-row--wide {
  grid-column: 1 / -1; /* 长字段（用途/备注等）横跨整行 */
}
.raw-row__key {
  font-size: var(--font-size-xs);
  color: var(--color-muted);
  line-height: 1.3;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.raw-row__val {
  font-size: var(--font-size-sm);
  color: var(--color-fg);
  word-break: break-all;
  line-height: 1.5;
}

/* 订单业绩「直播·私域」维度的区块标题 */
.so-section-title {
  /* ⭐ 2026-09-28 提档：14(sm) → 16(lg) + 500 → 600 —— 与 .detail-section__title（板块标题 16/600）
     同档，区块标题不再与正文(14)同号。 */
  font-size: var(--font-size-lg);
  font-weight: 600;
  color: var(--color-fg);
  margin: 4px 0 10px;
}

/* ---- 工作台首页（对齐设计稿） ---- */
/* 卡片头右侧「查看全部」 */
.card-more {
  margin-left: auto;
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  color: var(--color-accent-text);
  text-decoration: none;
  cursor: pointer;
  white-space: nowrap;
}
.card-more:hover { color: var(--color-accent-hover); }

/* 卡片标题内的「口径」小字（乐哥 2026-09-15 加）
   动机：首页两块图口径不同（达人业绩=GMV、业绩占比=GSV），而标题都不写口径 ——
        两图合计分别是 89.4 万与 80.3 万，数字都对，但并排看容易被误读成
        "同一份数据画了两遍"。把口径贴在标题旁，是成本最低的解法。
   ⚠️ 必须**弱化**（小一号 + muted + 常规字重）：它是标题的限定词、不是标题主体，
      抢视觉就本末倒置了（标题是 --font-size-lg / semibold）。 */
.card__unit {
  margin-left: var(--space-2);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-regular);
  color: var(--color-muted);
  white-space: nowrap;
}

/* 平台分布：横向条形列表 */
.plat-list { display: flex; flex-direction: column; gap: var(--space-4); }
.plat-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}
.plat-row__name {
  width: 56px;
  flex-shrink: 0;
  font-size: var(--font-size-sm);
  color: var(--color-fg-2);
}
.plat-row__bar {
  flex: 1;
  height: 8px;
  border-radius: var(--radius-pill);
  background: var(--color-surface-2);
  overflow: hidden;
}
.plat-row__fill {
  display: block;
  height: 100%;
  border-radius: var(--radius-pill);
  transition: width var(--motion-base) var(--motion-ease-standard);
}
.plat-row__pct {
  width: 40px;
  flex-shrink: 0;
  text-align: right;
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  color: var(--color-fg);
  font-variant-numeric: tabular-nums;
}

/* 今日排期列表 */
.today-list { display: flex; flex-direction: column; }
.today-item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3);
  border-radius: var(--radius-md);
  background: var(--color-surface-2);
}
.today-item + .today-item { margin-top: var(--space-2); }
.today-item__time {
  flex-shrink: 0;
  width: 92px;
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
  color: var(--color-fg-2);
  font-variant-numeric: tabular-nums;
}
.today-item__name {
  flex: 1;
  min-width: 0;
  font-size: var(--font-size-sm);
  color: var(--color-fg);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* 待办提醒列表 */
.todo-list { display: flex; flex-direction: column; gap: var(--space-3); }
.todo-item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}
.todo-item__dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex-shrink: 0;
}
.todo-item__text {
  flex: 1;
  font-size: var(--font-size-sm);
  color: var(--color-fg-2);
}
.todo-item__count {
  min-width: 24px;
  height: 20px;
  padding: 0 8px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-pill);
  background: var(--color-surface-2);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-medium);
  color: var(--color-fg-2);
  font-variant-numeric: tabular-nums;
}
/* ⭐ 2026-10-11：**可点**的待办条目 —— 项目板块那条「充值申请待审批」
   （2026-10-11 随"项目待办"从「项目管理」页搬到首页，见 `pages/dashboard.js` 的 renderTodos）。
   ⚠️ 与上面"标签 + 数字"那三条（不可点）**形态必须区分**：可点的带箭头 + hover 变色。
      ⛔ 别把可点的做得跟不可点的一样 —— 用户会以为点不动；
      ⛔ 也别把不可点的加上箭头 —— 点下去没反应更糟。 */
.todo-item--link {
  text-decoration: none;
  border-radius: var(--radius-sm);
  transition: color var(--motion-fast) var(--motion-ease-standard);
}
.todo-item--link:hover .todo-item__text,
.todo-item--link:hover .todo-item__go { color: var(--color-accent-text); }
.todo-item__go {
  display: inline-flex;
  flex-shrink: 0;
  color: var(--color-muted);
}
.todo-item__sub {
  margin-left: var(--space-2);
  font-size: var(--font-size-xs);
  color: var(--color-muted);
}
.todo-empty {
  padding: var(--space-6) 0;
  text-align: center;
  font-size: var(--font-size-sm);
  color: var(--color-muted);
}

/* ---- 移动端：看板/首页紧凑适配 ---- */
@media (max-width: 768px) {
  .chart-card__body {
    padding: var(--space-3);
  }
  .today-item {
    flex-wrap: wrap;
  }
  .today-item__time {
    width: auto;
  }
}
/* 超小屏（手机竖屏）：KPI 卡单列，一眼扫一个指标 */
@media (max-width: 480px) {
  .kpi-row {
    grid-template-columns: 1fr;
    gap: var(--space-3);
  }
  .dashboard-overview {
    margin-top: var(--space-3);
    margin-bottom: var(--space-3);
  }
}

/* ============================ 业绩总表（/performance） ============================ */
/* 卡片统一留白 + Tab 栏与筛选工具栏之间留出呼吸
   （原来 .card 本身没有内边距，一级 Tab 与搜索工具栏直接贴在一起） */
.pf-card {
  padding: var(--space-6);   /* ⭐ 2026-09-28：20(--space-5) → 24(--space-6) —— 与 .module-card/.card__body
                                同标准（乐哥：内部排版乱，卡片内边距全站统一 24px） */
}
/* ⭐ 2026-09-26（乐哥：「点了私域业绩后，一级 tab 不见了」）——
   私域视图下，右上角的「导出订单 / 导入订单」会被 JS 搬进**私域面板的工具栏**里，
   并且整块页面标题行会收起（见 performance.js 的 syncPanelVisibility）。
   本条负责那个按钮组在工具栏里的形态。
   ⚠️⚠️ **刻意不给 `margin-left: auto`**：私域工具栏里的 `#pf-pv-total`（计数）
      已有一处 `margin-left:auto` 负责"把右侧内容整体推过去"。
      若这里再加一个 auto，两个 auto 会**平分**剩余空白 ⇒ 计数被挤到中间、
      与按钮之间拉开一大段（实测过：变成 `[月份][平台]  [计数]  [导出][导入]`）。
      ✅ 正解：让计数那一个 auto 负责定位，按钮组紧跟其后 ⇒
         `[月份][平台] …… [计数][导出订单][导入订单]`（右侧成一簇，间距由 gap 控制）。 */
.toolbar > .toolbar__page-actions {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}
.pf-card > .tabs {
  /* ⭐ 2026-09-28 下外边距 24 → **16**（乐哥：「tab 栏和下面内容模块间距太大了，
     全局排查要一致」）—— 与全站卡片间距（16）同档。 */
  margin-bottom: var(--space-4);
  /* ⚠️ 左右缩进归零（2026-09-18 修，全站对齐普查发现）：
     外层的 `.card > .tabs` 给了左右 20px 缩进，而 `.pf-card` 自身也有 `padding: 20px`
     → **叠成 40px 双重缩进**，导致本页 TAB 文字落在 **313px**，而同页搜索框在 293px
     （差 20px，TAB 明显偏右）。全站其他 12 个有 TAB 的页面都在 293
     （它们的 TAB 在 `.toolbar` 内、且 `.table-tabs` 的缩进已在 2026-09-14 清零）。
     ⚠️ 只改本页：`.card > .tabs` 那条规则对"卡片本身无内边距"的页面是**正确**的
        （如排期表单 `card sf-card`、达人详情），不能全局去掉。
     ⚠️ 加载顺序保证了本规则生效：pages.css 在 components.css **之后**（见 index.html），
        特异性同为 (0,2,0) → 后者胜。
     ⚠️⚠️ 2026-09-26 托盘化：这里**不能写 `padding: 0`**（原值）——
        它会覆盖 `.tabs` 自己的托盘内边距（`padding: 4px`），把托盘的呼吸清掉。
        缩进一律用 margin 表达（右边距归零即可，因 `.pf-card` 自带 20px 内边距）。
     ⭐ 2026-09-26（第二轮）乐哥实报「业绩总表的二级 tab 样式不对」——
        根因是**上边距双重叠加**：`.card > .tabs` 给了 `margin: 16px 20px`，
        而本页卡片自带 `padding: 20px` ⇒ 托盘上方 = 20 + 16 = **36px**，左右却只有 20px。
        实测：托盘 top=261、卡片内容顶=245（上 36px）；托盘 bottom=301、工具栏 top=321（下 20px）
        ⇒ 托盘**明显偏下**，且与左右不齐。
        ✅ 归零 margin-top：让托盘上方只吃卡片自己的 padding（20px）——
           于是上 20 / 左右 20 / 下 20（`margin-bottom: var(--space-5)`），三边一致。 */
  margin-top: 0;
  margin-left: 0;
  margin-right: 0;
}

/* ============================ 经营看板（/analytics） ============================ */
/* 板块标题：照 7 月版参照看板的「一、二、三」编号 + 副标题（2026-09-11 重做） */
.an-sec {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: var(--space-6) 0 var(--space-4);
  padding-bottom: var(--space-2);
  border-bottom: 1px solid var(--color-border-soft);
}
.an-sec:first-of-type { margin-top: var(--space-4); }
.an-sec__no {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  flex: none;
  border-radius: var(--radius-sm);
  background: var(--color-accent-bg);
  color: var(--color-accent-text);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-semibold);
  align-self: center;
}
.an-sec__title {
  font-size: var(--font-size-lg);
  font-weight: var(--font-weight-semibold);
  color: var(--color-fg);
}
.an-sec__sub {
  font-size: var(--font-size-xs);
  color: var(--color-muted);
}

/* ⭐⭐ 2026-09-28 板块卡（乐哥拍板：「经营总览那个标题，和那几个看板卡片，应该在一个白底的卡片里，
   就和本期洞察那个一样」「一个小模块一个板块，这样不是更直观吗」）
   ----------------------------------------------------------------------------
   参照物：电商罗盘（乐哥给的 4 张参考图）/ 项目管理系统原型 v2 ——
   两者都是「**一个小模块 = 一个板块 = 一个白底圆角卡，标题嵌在卡内**」。
   本站既有的样板就是「本期洞察」（.card an-ins，标题在 card__header 里）。

   实现：`cardizeSections()`（analytics.js，渲染后 DOM 包裹）把「.an-sec 标题 + 其后续兄弟」
   统一包进一个 `.an-card`。
   ⛔ **不改各 section 函数的字符串拼接** —— 那 3 个函数（overview/source/five）内部有
      联动与事件绑定（bindSourcePanel / drawSectionFive），包裹只为视觉，不该动其结构；
      DOM 移动节点**保留**监听器 ⇒ 安全。
   ============================================================================ */
.an-card {
  background: var(--color-surface);
  border: 1px solid var(--color-frame-border);
  border-radius: var(--radius-lg);
  /* ⭐ 2026-10-06：经营分析的板块卡也是**顶层卡片**（父链只有 .app-main）⇒ 同样浮起，
     否则同一页里 `.card` 浮着、`.an-card` 是平的 —— 又回到"同一套卡片两种语言"。 */
  box-shadow: var(--shadow-card);
  padding: var(--space-5) var(--space-5) var(--space-4);
  margin-bottom: var(--space-5);   /* 板块之间 20px（与全站卡片间距一致）*/
}
/* 卡内标题：① 去掉原来的下边线（卡内再多一道横线会与"一个板块一张卡"的边界打架）；
   ② 上边距归零（卡片的 padding 已提供呼吸，否则标题会被推得离卡顶太远）；
   ③ 字号 16 → **20（--font-size-xl「模块标题」）** —— 乐哥：「标题 字号要大点，这样才协调」。 */
.an-card > .an-sec {
  margin: 0 0 var(--space-4);
  padding-bottom: 0;
  border-bottom: 0;
}
.an-card > .an-sec .an-sec__title { font-size: 18px; }   /* ⭐ 乐哥 2026-09-28：「文字标题字号小一点点吧」
                                                            20(--font-size-xl) → **18**：卡内标题是板块内的一级标题，
                                                            比正文(14)和卡片小标题(16)大一档即可；
                                                            20 略压过"本期洞察"的卡片标题，18 与之更协调。
                                                            （18px 在本项目已有先例：base.css 的移动端标题、
                                                             layout.css 的 A6 栏标题） */

/* 板块目录条（#an-nav）：常驻吸顶、点击平滑滚动、滚动高亮。
   定位：贴在全局顶栏（.topbar，sticky top:0，高 --header-height）下沿。
   z-index 900 < 顶栏 --z-sticky(1100)：顶栏始终压在其上，层叠不打架。
   底色取页面底 --color-bg（与 .page 留白同色）→ 视觉最轻，只靠底部细线区分内容；
   当前项用主色字 + 浅主色胶囊，未选中用 muted —— 刻意弱于一级口径 Tab（下划线式），不抢风头。
   结构拆「固定区 + 可滚动区」：overflow 只挂在滚动区，固定区（口径徽章）不被横向滚动带走，
   且固定区自身无 overflow，故徽章的下拉不会被裁剪。 */
.an-nav {
  position: sticky;
  top: var(--header-height);
  z-index: 900;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  height: 46px;
  margin: 0 0 var(--space-4);
  background: var(--color-bg);
  border-bottom: 1px solid var(--color-border-soft);
}

/* 固定区：口径徽章，不参与横向滚动（flex:none —— 窄屏不被压扁也不被滚走） */
.an-nav__fixed {
  flex: 0 0 auto;
  position: relative;
  display: flex;
  align-items: center;
  height: 100%;
  padding-right: var(--space-2);
  border-right: 1px solid var(--color-border-soft);
}

/* 可滚动区：右侧目录项独立横滚，窄屏不换行挤成两行；滚动条隐藏，避免吸顶时高度抖动 */
.an-nav__scroll {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: var(--space-1);
  height: 100%;
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.an-nav__scroll::-webkit-scrollbar { display: none; }

.an-nav__item {
  flex: 0 0 auto;
  height: 30px;
  padding: 0 var(--space-3);
  border: none;
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--color-muted);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-regular);
  line-height: 30px;
  white-space: nowrap;
  cursor: pointer;
  transition: background-color var(--motion-fast) var(--motion-ease-standard),
              color var(--motion-fast) var(--motion-ease-standard);
}
.an-nav__item:hover { background: var(--color-surface-2); color: var(--color-fg); }
.an-nav__item.is-active {
  background: var(--color-accent-bg);
  color: var(--color-accent-text);
  font-weight: var(--font-weight-medium);
}
.an-nav__item:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; }

/* 口径徽章：胶囊 + 主色描边 + 浅主色底，明显区别于目录项（纯文字无边框）——
   一眼看出是「状态」而非「跳转项」，也不与一级口径 Tab（下划线式）撞脸。 */
.an-caliber { position: relative; display: flex; align-items: center; height: 100%; }
.an-caliber__btn {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  height: var(--control-height-sm);   /* 两档制：30 → 34（紧凑档，分析页口径切换按钮） */
  padding: 0 var(--space-3);
  border: 1px solid var(--color-accent);
  border-radius: var(--radius-pill);
  background: var(--color-accent-bg);
  color: var(--color-accent-text);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  line-height: 1;
  white-space: nowrap;
  cursor: pointer;
  transition: background-color var(--motion-fast) var(--motion-ease-standard),
              border-color var(--motion-fast) var(--motion-ease-standard);
}
.an-caliber__btn:hover { background: var(--color-surface-2); }
.an-caliber__btn:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; }
.an-caliber__label { line-height: 1; }
.an-caliber__caret {
  display: inline-flex;
  align-items: center;
  color: var(--color-accent-text);
  transition: transform var(--motion-fast) var(--motion-ease-standard);
}
.an-caliber__btn[aria-expanded="true"] .an-caliber__caret { transform: rotate(180deg); }

/* 静态口径指示（2026-09-17：经营看板收起口径 tab 栏后，目录条上只剩一个口径）。
   形态与上面的「胶囊按钮」一致（同色同尺寸），但**去掉了按钮感**：
     · 无 caret（不可下拉）、无 :hover 变色（不可点）—— 点击光标会是「坏了的下拉」的错觉；
     · 用 <span> 而不是 <button> —— 屏幕阅读器不该把它报成可操作控件（可访问性，不是样式问题）。
   ⚠️ 必须自己写 `line-height:1`：胶囊按钮里有 `.an-caliber__label{line-height:1}`，
      静态版把文字直接放外层，漏了就比按钮版高 2px（目录条里一眼看出不齐）。 */
.an-caliber__static {
  display: inline-flex;
  align-items: center;
  height: var(--control-height-sm);   /* 两档制：30 → 34（紧凑档，与上方按钮同高） */
  padding: 0 var(--space-3);
  border: 1px solid var(--color-accent);
  border-radius: var(--radius-pill);
  background: var(--color-accent-bg);
  color: var(--color-accent-text);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  line-height: 1;
  white-space: nowrap;
}

/* 下拉：相对徽章绝对定位（.an-caliber 已 position:relative）→ 目录条吸顶时位置依然正确。
   z-index 950 > 目录条 900、< 顶栏 --z-sticky(1100)：不会盖住顶栏。 */
.an-caliber__menu {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  min-width: 168px;
  padding: var(--space-1);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  z-index: 950;
}
.an-caliber__menu[hidden] { display: none; }
.an-caliber__opt {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  width: 100%;
  padding: var(--space-2) var(--space-3);
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--color-fg-2);
  font-size: var(--font-size-sm);
  line-height: 1.4;
  text-align: left;
  white-space: nowrap;
  cursor: pointer;
  transition: background-color var(--motion-fast) var(--motion-ease-standard);
}
.an-caliber__opt:hover { background: var(--color-surface-2); color: var(--color-fg); }
.an-caliber__opt:focus-visible { outline: 2px solid var(--color-accent); outline-offset: -2px; }
.an-caliber__opt.is-active { color: var(--color-accent-text); font-weight: var(--font-weight-medium); }
.an-caliber__opt-check { display: inline-flex; align-items: center; color: var(--color-accent); opacity: 0; }
.an-caliber__opt.is-active .an-caliber__opt-check { opacity: 1; }

/* 滚动补偿：走 scrollIntoView / 锚点跳转时，标题不被吸顶条（顶栏 56 + 目录条 46 + 8 呼吸）盖住。
   主路径用 JS 的 window.scrollTo 精确补偿，此处作为兜底，值须与 JS 的 NAV_GAP 保持一致。 */
#an-body .an-sec { scroll-margin-top: calc(var(--header-height) + 54px); }

/* 口径提示条：info（订单归档）/ warn（快照兜底）
   ⚠️ 2026-09-14 乐哥「警示提醒方式没有微信小店的好」→ 去色块，改**细左边框**形态
   ----------------------------------------------------------------------------
   改前：`--color-accent-bg`(#EDF4FE) 或 `--color-warning-bg`(#FEF5E7) 整条铺底 +
         1px 边框 + 圆角，宽 1456px —— 一块 12 万+ 像素的色块。
   为什么要改：大色块与表格内状态色抢注意力，且满屏黄条会导致「警示疲劳」。
       实测微信小店全图彩色像素只占 0.72%。
   为什么**这条保留左边框**：本 banner 是**多行口径说明**（3~5 行），
       完全没有边界会被读成正文的一部分。
       左边框是"引文/注释"的通用形态，既轻量又保留"这是一段说明"的语义。
   ⚠️ 文字色改为常规色（--color-fg-2）：原来整条用 warning-text 橙字，
      在**有底色**时靠底色衬托尚可；去掉底色后整段橙字会过抢。
      现在层次是：正文灰 → `<b>` 深色加粗（口径关键值）→ `.an-warn-text` 橙字（真需注意）。
      即「说明用深色、警示用橙色」，与微信小店「正文灰 + 关键橙」同一逻辑。 */
/* 提示条 · 统一形态（2026-09-14 乐哥定稿：浅底 + 圆角 + 左侧图标，**不带关闭叉**）
   ---------------------------------------------------------------
   改前：透明底 + 左侧 3px 竖线（"引文"观感），与提成总览页的"近白底"又是两套。
   改后：与全站统一 —— 引用 --notice-* 那组规格（浅蓝底 + 8px 圆角 + 蓝图标）。
   ⚠️ 字号 12px → 14px：对齐 --font-size-sm（项目规范：中文 12px 以下发虚），
      也与 .st-rule__inner / .alert 同档，全站提示条只有一种字号。 */
.an-banner {
  display: flex;
  align-items: flex-start;
  gap: var(--notice-gap);
  padding: var(--notice-pad-y) var(--notice-pad-x);
  margin-bottom: var(--space-4);
  border-radius: var(--notice-radius);
  border: 1px solid var(--notice-border);
  background: var(--notice-bg);
  font-size: var(--font-size-sm);
  line-height: 1.6;
  color: var(--notice-fg);
}
/* 提示条图标 · 垂直对齐（2026-09-14 乐哥：「刚刚做的警示提醒，那个图标没有上下居中」）
   ────────────────────────────────────────────────────────────────────────────
   改前：每条提示条各写一个 `margin-top`（1/2/3px 共 5 个魔数），而且**全都算得不对**：
         实测 .st-rule__inner 偏上 2.0px；.an-banner 更隐蔽 —— 它的 <span> 里 svg 是
         inline 元素，按 baseline「当字符」排版，位置由字体度量决定（恰好只差 1px 是巧合，
         换个字号就会歪）。
   机制：图标中心必须对齐**首行行盒中心**（提示条可能多行 → 不能 align-items:center，
         那会让多行时图标跑到整块中间）。
         · 有包裹容器 → 「盒子法」：容器高 = 首行行高，svg 在容器内 align-items:center。
           单行时视觉居中、多行时对齐首行，两种情况都对。
         · 裸 svg → 用可推导的 margin：⌈(行高 − 图标高) / 2⌉（16px 是提示条图标的统一尺寸）。
           用 em 表达行高 → 字号一变 margin 自动跟随，不再是手算魔数。 */
.an-banner__ico {
  flex: none;
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  height: 1.6em;          /* = 本条的 line-height:1.6，即首行行高（22.4px @14px） */
  color: var(--notice-ico);
}
.an-banner b { color: var(--color-fg); font-weight: var(--font-weight-semibold); }
.an-banner--info {
  border-color: var(--notice-border);
  background: var(--notice-bg);
}
.an-banner--info .an-banner__ico { color: var(--notice-ico); }
/* 警示类**保留橙色语义**（只统一形态）——全改蓝会让"警告"失去辨识度 */
.an-banner--warn {
  border-color: var(--notice-warn-border);
  background: var(--notice-warn-bg);
}
.an-banner--warn .an-banner__ico { color: var(--notice-warn-ico); }

/* 图卡片标题行（参照看板是 13px / 次要字色 + 右侧 hint）
   ==========================================================================
   2026-09-17 乐哥统一图表标题版式（「标题字号大一点」「和上面留一点行间距」）：
     · 字号 14px → **16px**（`--font-size-lg`，与经营看板的 `.cb-sub` 同档）
     · 字重 500 → **600**（16px 用 medium 仍偏软，标题要能从正文里跳出来）
     · 左 padding 16px → **20px**（原比 `.chart-card__body` 的 20px 少 4px，
       标题左边界与图表左边界**对不齐** —— 这是实测发现的额外瑕疵）
     · bottom 0 → **12px**（原为 0，标题底边紧贴图表顶边，"挨在一起"正是如此）
     · 字色 fg-2 → **fg**（标题是主信息，不该用次级色）
   ⚠️ 本类被 3 个页面共用（analytics / aftersale / influencer-profit）→ 改一处三处生效。 */
.an-chart-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-5) var(--space-5) var(--space-3);
  font-size: var(--font-size-lg);
  font-weight: var(--font-weight-semibold);
  color: var(--color-fg);
}
.an-chart-head__h {
  font-size: var(--font-size-xs);
  font-weight: 400;
  color: var(--color-muted);
  /* ⚠️ 不再 `nowrap`（2026-09-16 修）：这类小字常是一整句说明（如
     「数据自 2026-06 起（此前月份无订单，不补零）」），窄屏下 nowrap 会让它
     以单行 502px 的宽度**越出卡片 215px**。改成可换行 + 允许收缩。 */
  white-space: normal;
  min-width: 0;
  overflow-wrap: anywhere;
}
/* ⚠️ 已弃用（2026-09-11）：图表高度改由 JS 侧 chartCard() 传像素 + 内联 style 控制
   （之前是「tall 布尔 + 这里的 340px」隐式耦合，改一个图要同时翻 JS 和 CSS 两处）。
   保留此规则只为兜底，当前没有任何元素使用。 */
.an-chart--tall { height: 340px; }

/* 单列铺满 / 三列分类型 网格 */
.an-grid-1 { grid-template-columns: 1fr; }
.an-type-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-4);
}
@media (max-width: 1099px) {
  .an-type-grid { grid-template-columns: 1fr; }
}

/* 分类型对比卡（专场 / 私域 / 混播）：标题行加一个语义色点 + 主色标题字（乐哥 2026-09-15）
   动机：三张卡是同质并列项，需要一个「一眼分成三块」的标识。
   ⚠️ 为什么只在标题行上色、**不给整卡上色**：
      ① 数字区保持白底 → 不影响盈亏红绿的可读性。实测「混播」当前是亏的（绿字），
         绿字放到彩色底上对比度会下降，读数字更费劲；
      ② 三张卡装的是同一类指标、只是分类不同，整卡铺色的视觉分量过重。
   ⚠️ 用**具名修饰类** `--special/--private/--mixed`（由 JS 按 TYPE_META 的 key 挂上），
      不用 `:nth-child(N)` —— 卡片顺序一变，nth-child 会**静默错配颜色**且很难被发现。
   ⚠️ 色点用 `::before` 伪元素而不是新增 `<span>`：卡内容是 innerHTML 拼的，
      加一个真实节点就要同步改渲染代码，纯样式能解决就不动 DOM。
   ⚠️ 颜色一律走令牌（本文件禁止硬编码 hex），这三个正是 `UI.kpiTones()` 用的同族色，
      与 KPI 柔底同源 —— 将来换主色只需改 design-tokens 一处。 */
.an-type-card--special .an-card-title { color: var(--color-tone-blue-fg); }
.an-type-card--private .an-card-title { color: var(--color-tone-teal-fg); }
.an-type-card--mixed   .an-card-title { color: var(--color-tone-purple-fg); }
.an-type-card--special .an-card-title::before,
.an-type-card--private .an-card-title::before,
.an-type-card--mixed   .an-card-title::before {
  content: ''; display: inline-block; width: 8px; height: 8px; border-radius: 50%;
  margin-right: 8px; vertical-align: 1px;
}
.an-type-card--special .an-card-title::before { background: var(--color-tone-blue-fg); }
.an-type-card--private .an-card-title::before { background: var(--color-tone-teal-fg); }
.an-type-card--mixed   .an-card-title::before { background: var(--color-tone-purple-fg); }
/* 「N 个场次」副标题仍用中性灰 —— 它是量词不是分类标识，跟着主色走会喧宾夺主 */
.an-type-card--special .an-card-title__sub,
.an-type-card--private .an-card-title__sub,
.an-type-card--mixed   .an-card-title__sub { color: var(--color-muted); }

/* 分类型卡内的小统计 */
.an-card-title {
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  color: var(--color-fg);
  margin-bottom: var(--space-3);
}
.an-card-title__sub {
  font-size: var(--font-size-xs);
  font-weight: 400;
  color: var(--color-muted);
  margin-left: 6px;
}
.an-stat-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-2);
}
.an-stat__label { font-size: var(--font-size-xs); color: var(--color-muted); }
.an-stat__value { font-size: var(--font-size-sm); font-weight: var(--font-weight-medium); margin-top: 2px; }
.an-card-foot {
  margin-top: var(--space-3);
  font-size: var(--font-size-xs);
  color: var(--color-muted);
}

/* 成本大类下钻行 */
.an-cat-grid {
  display: grid;
  grid-template-columns: 150px 1fr 110px 72px;
  gap: 10px;
  align-items: center;
  font-size: var(--font-size-sm);
}
.an-cat-grid--head {
  margin-bottom: var(--space-2);
  font-size: var(--font-size-xs);
  color: var(--color-muted);
}
.an-cat-grid--head > :nth-child(3),
.an-cat-grid--head > :nth-child(4) { text-align: right; }
.an-cat-track { background: var(--color-surface-2); border-radius: var(--radius-sm); height: 16px; }
.an-cat-fill { height: 16px; border-radius: var(--radius-sm); }
.an-cat-num { text-align: right; font-variant-numeric: tabular-nums; }
.an-cat-num--muted { color: var(--color-muted); }

/* 面包屑 */
.an-crumb { margin-bottom: var(--space-3); font-size: var(--font-size-xs); }
.an-crumb a { color: var(--color-accent-text); text-decoration: none; }
.an-crumb__sep { color: var(--color-muted); margin: 0 6px; }

.an-flex-between { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-3); }
.an-link { color: var(--color-accent-text); text-decoration: none; }
.an-warn-text { color: var(--color-warning-text); }

/* ---------------- 盈亏状态标记（圆点 + 文字） ----------------
   状态标记「圆点 + 文字」：圆点取 currentColor，颜色由 JS 内联写在元素上，
   与该行「盈亏」数字用**同一套色规**（正=红 / 负=绿）。
   ⚠️ 刻意不用 .badge 上色：badge 的色是语义色（success=绿），与本系统「涨红跌绿」的数值色相反 ——
      一个绿底 badge 写「亏损」会同时发出两个相反信号，读者不知道该信哪个。

   使用方（两处，同一语义共用同一套样式，不各造一个类）：
     · 达人经营 · 明细表「状态」列 + 页头「本月有 N 位达人在亏」
     · 达人结算 · 展开区（若后续用到）
   2026-09-13：原属经营看板「三、达人盈亏」，该板块并入「达人经营」后样式与语义一并迁移。 */
.pnl-flag { display: inline-flex; align-items: center; gap: 6px; font-size: var(--font-size-xs); white-space: nowrap; }
.pnl-flag::before { content: ''; width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex: none; }
.pnl-flag--muted { color: var(--color-muted); }

/* 合计行底色。不复用 .so-subtotal：它只在 .table--sub / .table--zebra 下才有样式，
   挂在普通 .table 上其实是"裸"的（看着像没有合计），这里显式给一条本表专用规则。 */
.an-pnl-table tbody tr.an-pnl-total { background: var(--color-surface-2); }
.an-pnl-table tbody tr.an-pnl-total td { font-weight: var(--font-weight-medium); }

/* ---------------- 一、利润来源：公式链 + 展开面板（2026-09-12 新增） ----------------
   视觉参照乐哥给的示例图：横向链条、加粗深色描边的圆角卡片、标签在上金额在下、`=` 与 `−` 连接。
   描边用 1.5px（比常规卡片粗）+ 深色（--color-fg），这是示例图的关键特征，不是笔误；
   描边色通过 --chain-border 间接引用 token，不写裸色值。 */
:root { --chain-border: var(--color-border-soft); }   /* 乐哥 2026-09-12：原为 --color-fg（近黑），与全站卡片浅灰描边不一致，改为统一 */

.an-src-chain-wrap { overflow-x: auto; padding-bottom: 2px; }
/* ⚠️ 2026-09-16 修（乐哥报障「公式是不是漏减了项目成本」）：
   原来是 `display:flex; min-width:150px/卡 + overflow-x:auto`。
   公式在 2026-09-16 从 5 张卡增加到 7 张（总盈亏 / 收入 / 投流 / 达人佣金 /
   达人招待费用 / 提成&人力成本 / 项目成本）后，**实测视口 ≤1512px 时
   最后一张「项目成本」正好被裁出可视区**：
     视口 1512 → 溢出 86px ｜ 1440 → 158px ｜ 1366 → 232px ｜ 1280 → 318px
   而 macOS 的滚动条默认隐藏、容器也没给任何提示 → 用户完全看不出右边还有内容，
   会以为「公式里漏了这项」。**内容被裁掉等于没写**，这是必须修的可用性问题。
   → 改用 grid auto-fit：容器够宽就一行、不够就自动折行，**永不裁切**，且各卡等宽。
   实测修复后：1512/1440/1366/1280 全部完整可见（一行或两行）。 */
.an-src-chain {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(158px, 1fr));
  gap: var(--space-2);
  align-items: stretch;
}
/* 每一项 = 「操作符 + 卡片」打包成一个 grid 单元。
   为什么要包：折行时保证减号**跟着卡片走**，不会出现
   「减号留在上一行末尾、卡片孤零零掉到下一行」的割裂观感。

   ── 分组配色（乐哥 2026-09-16：「用颜色区分达人侧 / 公司侧」）────────────
   双通道设计，两个信息各走一路、互不抢占：
     · 左侧色条 = **分组色** → 回答"这笔钱算谁头上"
     · 标题前色点 = **类目色** → 回答"这是哪一项"，**与成本看板同色**
   ⚠️ 为什么只能走"色条 + 色点"、不能整卡上淡底（2026-09-16 实测否决）：
      人力橙底与结果卡暖底、达人蓝底与展开态浅底，**各是同一个 token**（实测色差 0.0）
      → 整卡上底会同时"分不出结果 vs 过程"和"卡片日常就像被点开"，且点开只剩描边反馈。 */
.an-src-term { display: flex; align-items: stretch; gap: var(--space-2); min-width: 0; --grp-c: transparent; }
.an-src-term[data-grp="talent"] { --grp-c: var(--color-chart-1); }   /* 达人侧（投流 / 佣金 / 招待） */
.an-src-term[data-grp="hr"] { --grp-c: var(--color-chart-5); }       /* 提成&人力 */
/* ⚠️ 公司侧**刻意不用绿**（乐哥 2026-09-16：「绿这个色换掉」）：
   绿 --color-chart-3(#2BA471) 与「亏损绿」--color-success(#31A24C) 同色系（色差仅 21），
   而本系统红涨绿跌、**绿 = 亏** → 绿出现在成本卡上容易被读成"这一项亏了"。
   改用靛紫：与达人组的蓝明显区分，也不会与「结果卡暖橙」「展开态浅蓝」撞色。 */
.an-src-term[data-grp="project"] { --grp-c: var(--color-tone-purple-fg); }

/* 组与组之间加宽一格间距 → 5 张成本卡自然聚成 3 簇（达人侧 3 张 / 人力 1 张 / 公司 1 张）。
   只在**每组首项**加左间距，故二、三组各加一次。 */
.an-src-term[data-grp="hr"], .an-src-term[data-grp="project"] { margin-left: 12px; }

/* 「多链共用一个网格」时的强制换行点（2026-09-16 新增，达人经营页的利润构成用它）
   --------------------------------------------------------------------------
   场景：一个页面有**两条公式链**（上面算达人利润、下面算最终盈亏），它们必须
   **卡片同宽、左右对齐**，否则一眼能看出"上下两行不是一套"。
   为什么不能各用一个 `.an-src-chain`：那是 `repeat(auto-fit, …)`，而 **auto-fit 会折叠
   没有项目的空轨道** → 项目少的那条链每张卡被拉伸摊满容器。
   实测（容器均 1414px）：第一条链 5 项 → 276px/卡；第二条链 4 项 → 348px/卡，差 72px。
   → 把两条链放进**同一个** `.an-src-chain`（列宽只由容器决定），第二条链的首项用本类
     强制回到第 1 列开新行。
   ⚠️ 用"强制换行"而不是"靠正好排满"：列数随视口变化，靠数量的边界是脆的（一变就串行）；
      `grid-column: 1` 在任何列数下都成立（列数 5 → 链 1 占整行、链 2 从下一行首列起；
      列数 3 时也可用）。
   ⚠️ `grid-column: 1` 不能配合 `margin-left`（本类的组间距走 padding/无外边距），
      否则第 1 列会被外距推出网格线、与上方卡片对不齐。 */
/* ⚠️ `grid-column: 1` 时**必须把 margin-left 归零**：本类的组间距（data-grp）是 margin-left，
   而网格的第 1 列若带上外边距，卡片会被推出网格线、与上方卡片左边界错开 12px
   （肉眼就是"没对齐"）。
   ⚠️⚠️ `margin-top` **不能只加在换行那一项上**：实测那样只有它自己下移 8px，
      与**同一行的其他项差一行高**（y=788 vs 780），比不加还难看。
      必须用兄弟选择器把整行一起下移 —— `~` 覆盖它后面的所有项，
      换行后即使再折行也在同一行里保持一致。
   （只靠 grid 的 row-gap 8px 读不出"这是两个不同的算式"，两条链会糊成一整片。） */
.an-src-term--brk { grid-column: 1; margin-left: 0; }
.an-src-term--brk, .an-src-term--brk ~ .an-src-term { margin-top: var(--space-2); }
.an-src-card {
  /* 宽度由父级 grid 列宽决定；`.an-src-term` 内用 flex 撑满。
     ⚠️ 不要在这里写 min-width —— 那会重新把链条撑出容器、又变成"最后一张被裁掉"。 */
  flex: 1 1 auto; min-width: 0; display: block; text-align: left;
  /* 1px 浅灰 + 12px 圆角 —— 与 .kpi-card 完全对齐（乐哥 2026-09-12 统一；原为 1.5px 深色 + 8px） */
  background: var(--color-surface); border: 1px solid var(--chain-border);
  /* 左侧 3px 分组色条（方案 F 的"通道一"）。用 inset box-shadow 而不是 border-left：
     ① 不改变盒模型宽度 → 与 grid 列宽、折行计算无冲突（改 border 会挤掉内容宽度）
     ② 描边（border）留给 hover / 选中态用，两个通道互不覆盖
     ⚠️ 只有"成本项"用分组色；结果卡与收入卡不是成本项，--grp-c 未定义 → 回落成 --chain-border，
        视觉上等于没有色条（正是想要的：它们不属于任何费用组）。 */
  box-shadow: inset 3px 0 0 0 var(--grp-c, transparent);
  border-radius: var(--radius-lg); padding: 11px 15px;
  font-family: inherit; cursor: pointer;
  transition: border-color var(--motion-fast) var(--motion-ease-standard), background var(--motion-fast) var(--motion-ease-standard);
}
.an-src-card:hover { border-color: var(--color-accent); }
.an-src-card[aria-pressed="true"] { border: 2px solid var(--color-accent); background: var(--color-accent-bg); padding: 10px 14px; }
.an-src-card__n { display: block; font-size: var(--font-size-sm); color: var(--color-muted); margin-bottom: 5px; }
/* ⭐ 2026-10-14：「含预估」小标 —— 挂在数值含「未满 35 天」的卡片上（当前只有「提成&人力成本」）。
   乐哥：「经营看板里的提成人力成本统计口径不对…只是预估的需要预估标签」。
   ⚠️ 用 warning 语义色（＝"这个数还会变"），**不要**用 danger（那是"亏/异常"）。
   ⚠️ 挂在 `.an-src-card__n`（标签行）里，与前面的色点 `::before` 共存 —— 色点在文字前、本标在文字后。 */
.an-src-card__est {
  display: inline-block; margin-left: 4px; padding: 0 4px;
  font-size: 11px; line-height: 16px;
  color: var(--color-warning-text); background: var(--color-warning-bg);
  border-radius: 3px; vertical-align: 1px; font-weight: var(--font-weight-medium);
}
/* 类目色点（方案 F 的"通道二"）：回答"这是哪一项"。
   ⚠️ 色值**必须与成本看板同色**（cost-board.js 的 BIG_COLOR → --color-chart-*），
      否则同一个「投流」在公式链与成本看板是两种颜色 ——
      乐哥 2026-09-16 明确："每个费用都用固定颜色，不要不同地方不一致"。
      改动时两边要一起改（这也是 5/5 一致性断言的依据）。 */
.an-src-card[data-src="ad"] .an-src-card__n::before,
.an-src-card[data-src="commission"] .an-src-card__n::before,
.an-src-card[data-src="ent_talent"] .an-src-card__n::before,
.an-src-card[data-src="hr_all"] .an-src-card__n::before,
.an-src-card[data-src="project_all"] .an-src-card__n::before {
  content: ''; display: inline-block; width: 8px; height: 8px; border-radius: 2px;
  margin-right: 6px; vertical-align: 1px;
}
.an-src-card[data-src="ad"] .an-src-card__n::before { background: var(--color-chart-1); }
.an-src-card[data-src="commission"] .an-src-card__n::before { background: var(--color-chart-2); }
.an-src-card[data-src="ent_talent"] .an-src-card__n::before { background: var(--color-chart-5); }
.an-src-card[data-src="hr_all"] .an-src-card__n::before { background: var(--color-chart-3); }
.an-src-card[data-src="project_all"] .an-src-card__n::before { background: var(--color-chart-4); }
.an-src-card__v {
  display: block; font-size: var(--font-size-2xl); font-weight: 500; line-height: 1.15;
  color: var(--color-fg); font-variant-numeric: tabular-nums;
}
/* ⚠️⚠️ 这里曾有一个「亏损显示成红色」的真 bug，2026-09-15 修 —— 别改回去：
   原写法是一条 `.an-src-card--result .an-src-card__v { color: var(--color-danger) }`（0,2,0），
   本意只是「结果卡默认给个突出的红色」，但它**盖过了** table.css 的 `.num-neg`（0,1,0）。
   而 JS（analytics.js）明明按盈亏正负挂了 `num-pos` / `num-neg` ——
   于是 `num-neg` 那条**永远不生效**：**亏损月的结果卡也显示红色**。
   实测（生产库快照）：2026-06 总盈亏 −¥2.0万、2026-08 为亏损，卡片却都是红色
   （`-¥2.0万` 颜色 = rgb(228,30,63) 红，应为绿）→ 会让人把亏损看成盈利，属会误导决策的那类错。
   → 正确写法：**让符号决定颜色**，按正负各写一条并凑到 0,3,0 压过基类。
      （基类 `.an-src-card__v`（0,1,0）与 `.num-pos`（0,1,0）同权重，而 **pages.css 后加载**，
        所以只写 `.num-pos` 也会被基类的 `color: var(--color-fg)` 盖掉 —— 必须显式写复合选择器。） */
.an-src-card--result .an-src-card__v.num-pos { color: var(--color-danger); }   /* 盈利 = 红 */
.an-src-card--result .an-src-card__v.num-neg { color: var(--color-success); }  /* 亏损 = 绿 */
/* 结果卡加一层暖底，和右边三组成本卡分开（乐哥 2026-09-15）
   用途：现在链条上五张卡长得一模一样，只能靠 = / − 符号判断谁是结果、谁是过程。
   为什么只用 1 个颜色、且只给结果卡：三组成本是**同质并列项**，多色只是噪音；
       而这张卡点一下会展开明细 —— 展开态是「主色描边 + 主色浅底」（见下方 aria-pressed 规则），
       若成本卡也上固定底色，常态色与选中色就会糊在一起，分不出点开了哪张。 */
.an-src-card--result { background: var(--color-tone-orange-bg); border-color: transparent; }
.an-src-card__h { display: block; font-size: var(--font-size-xs); color: var(--color-meta); margin-top: 5px; }
/* 「邀请点击」的副标题用主色蓝（2026-09-19 乐哥：「利润公式里那个点击展开要不要用蓝色字？
   不然很多人都不知道」）。
   ⚠️⚠️ 判据是**副标题在不在邀请动作**，不是**卡片能不能点** ——
      实测 7 张卡**全都可点**（含写着「本月无支出」的两张：点开下方联动画板照样切到
      该项分析、画布正常渲染）。所以用"可点/不可点"来决定颜色是分不出来的，
      而且会让「本月无支出」配着蓝字自相矛盾。
      ⇒ 只有「点击展开 / 点击看走势」这类**动作词**才变蓝；
        「本月无支出 / 口径不支持」是**陈述状态**，没有动作可做，保持灰。
   ⚠️ 颜色复用 `--color-accent-text`（= primary-500 钴蓝）—— 与全站同一套"蓝=可操作"语言：
      `.an-caliber__btn`（蓝色胶囊=可下拉）与 `.an-caliber__static`（刻意去蓝=不可点）就是这对。
      不新造颜色。
   ⚠️ 卡片本身 hover 时**边框**已变蓝（`.an-src-card:hover`），所以副标题用同色是呼应、
      不是冲突（两者一起构成"这张卡活着"的反馈）。 */
.an-src-card__h.is-clickable { color: var(--color-accent-text); font-weight: var(--font-weight-medium); }
/* 选中的卡（aria-pressed）已有蓝底蓝框，副标题若还是蓝会糊在一起 —— 保持蓝但不再加粗 */
.an-src-card[aria-pressed="true"] .an-src-card__h.is-clickable { font-weight: var(--font-weight-regular); }
/* 本月无支出的组：卡片仍展示（否则链条缺一段、看不出「这组没花钱」），但弱化处理，
   避免它和可点的卡长一样、让人白点一下却发现没东西可看 */
.an-src-card.is-zero { border-color: var(--color-border); }
.an-src-card.is-zero .an-src-card__v { color: var(--color-muted); }
.an-src-op { flex: 0 0 auto; align-self: center; font-size: var(--font-size-2xl); font-weight: 500; color: var(--color-fg); padding: 0 2px; }
.an-src-ladder {
  margin-top: var(--space-4); padding-top: var(--space-3);
  border-top: 1px solid var(--color-border-soft);
  font-size: var(--font-size-xs); color: var(--color-muted); line-height: 1.8;
}
.an-src-ladder b { color: var(--color-fg-2); font-weight: 600; }

/* 展开面板 */
.an-src-panel__title { font-size: var(--font-size-lg); font-weight: 600; color: var(--color-fg); }
.an-src-panel__total { font-size: var(--font-size-sm); color: var(--color-muted); }
.an-src-pg { display: grid; grid-template-columns: 300px minmax(0, 1fr); gap: var(--space-4); }
.an-src-cap { font-size: var(--font-size-xs); color: var(--color-muted); margin-bottom: 6px; }
.an-chart-src--ring { height: 230px; }
.an-chart-src--line { height: 220px; }
.an-chart-src--rank { height: 320px; }
.an-src-item {
  display: grid; grid-template-columns: minmax(0, 1fr) 96px 56px; gap: var(--space-2);
  align-items: center; padding: 8px 10px;
  border: 1px solid transparent; border-radius: var(--radius-md);
}
.an-src-item[role="button"] { cursor: pointer; }
.an-src-item[role="button"]:hover { background: var(--color-surface-2); }
.an-src-item.is-on { background: var(--color-accent-bg); border-color: var(--color-primary-200); }
.an-src-item__n { font-size: var(--font-size-sm); color: var(--color-fg-2); }
.an-src-item__v { font-size: var(--font-size-sm); color: var(--color-fg-2); text-align: right; font-variant-numeric: tabular-nums; }
.an-src-item__p { font-size: var(--font-size-xs); color: var(--color-meta); text-align: right; }
.an-src-note { margin-top: var(--space-3); line-height: 1.8; }
.an-src-note-line {
  font-size: var(--font-size-xs); color: var(--color-muted); line-height: 1.9;
  background: var(--color-surface-2); border-radius: var(--radius-md); padding: 10px 12px;
}
.an-src-block { margin-top: var(--space-4); padding-top: var(--space-4); border-top: 1px solid var(--color-border-soft); }
.an-src-block__t { font-size: var(--font-size-sm); font-weight: 500; color: var(--color-fg); margin-bottom: 10px; }
.an-src-empty {
  display: flex; align-items: center; justify-content: center; min-height: 92px;
  padding: 14px 16px; border: 1px dashed var(--color-primary-200);
  border-radius: var(--radius-md); background: var(--color-surface-2);
  color: var(--color-meta); font-size: var(--font-size-xs); line-height: 1.7; text-align: center;
}

@media (max-width: 900px) {
  .an-src-pg { grid-template-columns: minmax(0, 1fr); }
}

/* ==========================================================================
   粉丝体验装（pages/fan-experience.js）
   明细的商品全名很长（含「达人专属」「规格」），必须在单元格内截断，否则表格会被撑爆
   ========================================================================== */
.fe-prod {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* 达人维度看板：达人分组行（可点折叠）+ 组内排期行 */
.fe-group td {
  background: var(--color-surface-2);
  font-weight: var(--font-weight-medium);
}
.fe-child td { background: var(--color-surface); }
.fe-child td:first-child { box-shadow: inset 2px 0 0 var(--color-border); }
/* 折叠箭头与达人胶囊之间留口气，否则箭头像贴在名字上 */
.fe-caret { display: inline-flex; vertical-align: middle; margin-right: 6px; color: var(--color-muted); }
/* KPI 行与表格同处一个 card：#fe-body 里的 .kpi-row 拿不到 .card > .kpi-row 的样式，
   用这个类补上与表头左右对齐的内边距（比行内 margin 拼间距可控） */
.kpi-row.fe-board-kpi {
  padding: var(--space-4) var(--space-5) var(--space-3);
  margin-bottom: 0;
}
/* 达人维度表尾说明：与表格左对齐 */
.fe-foot-note { margin: var(--space-3) var(--space-5) var(--space-5); }

/* --------------------------------------------------------------------------
   粉丝体验装 · 导入页（pages/fan-experience-import.js）
   替代原弹窗：三步式「选排期 → 传清单 → 核对明细」
   -------------------------------------------------------------------------- */
.fei-step {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
}
.fei-step__no {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--color-accent-bg);
  color: var(--color-accent-text);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-semibold);
  line-height: 1;
}
.fei-step__label {
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  color: var(--color-fg);
}
/* 排期选择器在页面里占满整行会太宽，收一收；下拉是文档流内的，不会被卡片裁切 */
.fei-sched-wrap { max-width: 520px; }

/* --------------------------------------------------------------------------
   导入页 · 第 3 步「按商品设置成本」
   从文件里提取去重商品，一个商品设一次单价（同商品成本相同）；
   与下方明细表拉开层次，让人一眼看到「哪里是花钱的地方」
   -------------------------------------------------------------------------- */
.fei-cost-block {
  margin: var(--space-3) 0 0;
  padding: var(--space-4);
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-md);
  background: var(--color-surface-2);
}
.fei-cost-block .table-wrap {
  background: var(--color-surface);
  border-radius: var(--radius-sm);
  overflow: hidden;
}
.fei-cost-head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  margin-bottom: var(--space-3);
}
.fei-cost-head .fei-step__label { display: inline-flex; align-items: center; gap: 5px; }
.fei-cost-foot {
  display: flex;
  align-items: baseline;
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  border-top: 1px solid var(--color-border-soft);
  font-size: var(--font-size-sm);
  color: var(--color-fg-2);
}
.fei-cost-foot b { color: var(--color-fg); font-size: var(--font-size-base); }
/* 商品下拉：窄一点，避免把「文件货品名」一列挤没 */
.fei-sel { width: 100%; max-width: 260px; }

/* ==========================================================================
   售后分析 · 页内下钻（达人售后：点达人名 → 整页切成该达人下钻页）
   2026-09-11 乐哥选定「页内切换视图」：像进入一个页面，但不换路由、筛选不丢。
   ========================================================================== */
.af-crumb {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
  margin-bottom: var(--space-4);
}
.af-back {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 4px 10px 4px 6px;
  font-size: var(--font-size-sm);
  color: var(--color-accent-text);
  background: var(--color-accent-bg);
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: background-color var(--motion-fast) var(--motion-ease-standard);
}
.af-back:hover { background: var(--color-primary-100); }
.af-back:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 1px; }
.af-crumb__path { font-size: var(--font-size-sm); color: var(--color-muted); }
.af-crumb__path b { color: var(--color-fg); font-weight: var(--font-weight-medium); }
.af-crumb__sep { padding: 0 2px; color: var(--color-muted); }

/* ==========================================================================
   售后分析 · 核算明细清单（乐哥 2026-09-11 要求：每个指标都要能自己核算）
   把所有「分子 ÷ 分母 = 结果」连同数据来源摊开成表，主指标行浅底凸显。
   ========================================================================== */
.af-audit {
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-lg);
  background: var(--color-surface);
  padding: var(--space-4);
  margin-bottom: var(--space-4);
}
.af-audit__head {
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-medium);
  color: var(--color-fg);
  margin-bottom: var(--space-3);
}
.af-audit__sub {
  font-size: var(--font-size-sm);
  font-weight: 400;
  color: var(--color-muted);
}
.af-audit__table { width: 100%; }
.af-audit__table th { font-size: var(--font-size-xs); color: var(--color-muted); font-weight: var(--font-weight-medium); }
.af-audit__name { white-space: nowrap; color: var(--color-fg-2); }
.af-audit__calc { color: var(--color-fg-2); font-variant-numeric: tabular-nums; }
.af-audit__note { margin-top: 2px; font-size: var(--font-size-xs); color: var(--color-muted); }
.af-audit__res {
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  color: var(--color-fg);
}
/* 主指标行（能直接与上方 KPI 卡对上）：浅底 + 名称/结果加重，一眼区分于参考项 */
.af-audit__main { background: var(--color-surface-2); }
.af-audit__main .af-audit__name { font-weight: var(--font-weight-medium); color: var(--color-fg); }
.af-audit__main .af-audit__res { font-weight: var(--font-weight-medium); }
.af-audit__foot {
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  border-top: 1px solid var(--color-border-soft);
  font-size: var(--font-size-xs);
  color: var(--color-muted);
  line-height: 1.7;
}

/* 售后分析 · 表头口径标注（如「退款率 / 金额口径」两行）
   两个率口径不同，不标出来会被误以为可直接比较。 */
.af-th-sub {
  display: inline-block;
  margin-top: 1px;
  font-size: var(--font-size-xs);
  font-weight: 400;
  color: var(--color-muted);
}

/* ==========================================================================
   售后分析 · 口径定义说明（乐哥 2026-09-11 提供电商标准口径，作为页面注释依据）
   退货率＝衡量「货有没有回来」；退款率＝衡量「钱有没有出去」；两者口径不同。
   ========================================================================== */
.af-def {
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-lg);
  background: var(--color-surface);
  padding: var(--space-4);
  margin-bottom: var(--space-4);
}
.af-def__head {
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-medium);
  color: var(--color-fg);
  margin-bottom: var(--space-3);
}
.af-def__sub { font-size: var(--font-size-sm); font-weight: 400; color: var(--color-muted); }
/* 两个指标上下排列，各自一块 */
.af-def__item {
  padding: var(--space-3) 0;
  border-top: 1px solid var(--color-border-soft);
}
.af-def__title {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-medium);
  color: var(--color-fg);
}
/* 口径徽标：单数口径 / 金额口径 —— 一眼区分两个指标的口径类型 */
.af-def__badge {
  padding: 1px 8px;
  font-size: var(--font-size-xs);
  font-weight: 400;
  border-radius: var(--radius-pill);
  background: var(--color-info-bg);
  color: var(--color-info);
}
.af-def__meaning { font-size: var(--font-size-sm); font-weight: 400; color: var(--color-muted); }
/* 公式行：等宽数字、浅底，像「公式」 */
.af-def__formula {
  margin: var(--space-2) 0;
  padding: 6px 10px;
  font-size: var(--font-size-sm);
  font-variant-numeric: tabular-nums;
  background: var(--color-surface-2);
  border-radius: var(--radius-md);
  color: var(--color-fg);
}
.af-def__points {
  margin: 0 0 var(--space-2);
  padding-left: 1.2em;
  font-size: var(--font-size-sm);
  color: var(--color-fg-2);
  line-height: 1.8;
}
.af-def__points li { list-style: disc; }
/* 退款率的三类来源：紧凑两列（名称 + 单数） */
.af-def__bd {
  margin: 0 0 var(--space-2);
  padding: 0;
  font-size: var(--font-size-sm);
}
.af-def__bd li {
  display: flex;
  justify-content: space-between;
  gap: var(--space-3);
  padding: 3px 0 3px 1.2em;
  color: var(--color-fg-2);
}
.af-def__bd-label { min-width: 0; }
.af-def__bd-num { color: var(--color-fg); font-variant-numeric: tabular-nums; white-space: nowrap; }
/* 本页实测算式 —— 最关键的复核依据，浅主色底强调 */
.af-def__calc {
  margin-top: var(--space-2);
  padding: 6px 10px;
  font-size: var(--font-size-sm);
  font-variant-numeric: tabular-nums;
  background: var(--color-accent-bg);
  border-radius: var(--radius-md);
  color: var(--color-fg-2);
}
.af-def__calc b { color: var(--color-fg); }
.af-def__rel {
  padding-top: var(--space-3);
  border-top: 1px solid var(--color-border-soft);
  font-size: var(--font-size-sm);
  color: var(--color-fg-2);
  line-height: 1.8;
}
.af-def__warn {
  margin-top: var(--space-2);
  padding: 6px 10px;
  font-size: var(--font-size-xs);
  background: var(--color-warning-bg);
  color: var(--color-warning-text);
  border-radius: var(--radius-md);
}

/* 售后分析 · 退款率分子构成（三类来源 + 在途，互斥相加 = 退款单数） */
.af-bd {
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-lg);
  background: var(--color-surface);
  padding: var(--space-4);
  margin-bottom: var(--space-4);
}
.af-bd__head {
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-medium);
  color: var(--color-fg);
  margin-bottom: var(--space-3);
}
.af-bd__sub { font-size: var(--font-size-sm); font-weight: 400; color: var(--color-muted); }
.af-bd__idx { color: var(--color-muted); font-variant-numeric: tabular-nums; }
.af-bd__total { background: var(--color-surface-2); }
.af-bd__foot {
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  border-top: 1px solid var(--color-border-soft);
  font-size: var(--font-size-xs);
  color: var(--color-muted);
}

/* ==========================================================================
   售后分析 · KPI 一行 6 个（乐哥 2026-09-11）
   与 .kpi-row--six 的区别：--six 是「4 列 + 第5/6张跨2列」的 4+2 布局；
   --grid6 是**强制一行 6 等分**（售后分析三处卡片都用它）。
   窄屏逐级降列，避免字被挤爆。
   ========================================================================== */
/* 默认 3 列（窄屏安全），≥1440px 才升到一行 6 个。
   实测：6 列时每格约 186px（1440）~ 213px（1600）；「¥1,052,382.50」在 28px 字号下需 194px，
   1400px 下会溢出（实测 194>167），故 6 列模式下把数值字号降到 24px，并把换行阈值提到 1440px。 */
.kpi-row--grid6 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
@media (min-width: 1440px) {
  .kpi-row--grid6 { grid-template-columns: repeat(6, minmax(0, 1fr)); }
  .kpi-row--grid6 .kpi-card__value { font-size: var(--font-size-2xl); }
}
@media (max-width: 899px) { .kpi-row--grid6 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
/* 6 列时卡片更窄：副标题收紧行高，长数字不允许撑破卡片 */
.kpi-row--grid6 .kpi-card__sub { line-height: 1.45; }
.kpi-row--grid6 .kpi-card__value { white-space: nowrap; }

/* 售后分析 · 商品页 KPI 一行 5 个（乐哥 2026-09-12 去掉「未匹配商品行」后）
   与 --grid6 同源，列数从 6 降到 5。
   ⚠️ 5 不能被 3 整除也不能被 2 整除 → 若不管，中窄屏最后一行会留空格
   （实测：3 列时留 384px、2 列时留 376px）。
   故默认（3 列）与窄屏（2 列）都让**第 5 张跨 2 列**，保证每行都铺满：
     3 列：3 + (1+2) 铺满   ／  2 列：2 + 2 + 2 铺满
   ≥1440px 用 5 列一行铺满（每格约 224px，比 6 列的 186px 更宽松，数值字号无需下调）。 */
.kpi-row--grid5 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.kpi-row--grid5 > :nth-child(5) { grid-column: span 2; }
@media (min-width: 1440px) {
  .kpi-row--grid5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }
  .kpi-row--grid5 > :nth-child(5) { grid-column: span 1; }
}
@media (max-width: 899px) {
  .kpi-row--grid5 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .kpi-row--grid5 > :nth-child(5) { grid-column: span 2; }
}
.kpi-row--grid5 .kpi-card__sub { line-height: 1.45; }
.kpi-row--grid5 .kpi-card__value { white-space: nowrap; }

/* ==========================================================================
   售后分析 · 口径解释入口 + 弹窗（2026-09-12 乐哥改造）

   原先「核算明细 / 退款率分子构成 / 指标口径说明」是三个各自独立的 <details> 折叠条目，
   要分三次点开才看得全（乐哥：「这种展开折叠不直观」）。
   现改为 **一个入口 → 弹窗**：弹窗内三块全部展开 + 顶部锚点跳转定位。
   ⚠️ 弹窗内刻意不再用折叠/Tab —— 折叠的毛病就是「内容被藏起来」，
      换弹窗后再套一层隐藏等于没改。
   ========================================================================== */
/* 提示条 · 统一形态 + 保留可点击（2026-09-14 乐哥定稿）
   ---------------------------------------------------------------
   本类特殊：它是个**可点击的入口**（点开看「指标口径与核算明细」）→
   统一成浅蓝底 + 同规格圆角，但**保留右侧 `>` 箭头**（那是"可展开"的指示，不能去掉）。
   改前是**白底卡**（--color-surface + 边框），正是乐哥说的"白底的"那一类。 */
.af-explain {
  display: flex;
  align-items: center;
  gap: var(--notice-gap);
  width: 100%;
  text-align: left;
  padding: var(--notice-pad-y) var(--notice-pad-x);
  margin-bottom: var(--space-4);
  background: var(--notice-bg);
  border: 1px solid var(--notice-border);
  border-radius: var(--notice-radius);
  cursor: pointer;
  font-family: inherit;
  /* ⚠️ 必须显式声明字号（2026-09-14 门禁抓出）：
     本元素是 `<button>`，只继承 font-family 时**字号仍取浏览器默认的 13.3333px**
     （不是全站的 14px）→ 实测提示条比其它页小半号，视觉不一致。
     门禁 probe-notice-standard.js 会断言 14px，不要删。 */
  font-size: var(--font-size-sm);
  transition: border-color var(--motion-fast) var(--motion-ease-standard),
              background var(--motion-fast) var(--motion-ease-standard);
}
.af-explain:hover { border-color: var(--color-primary-300); background: var(--color-primary-100); }
.af-explain:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; }
.af-explain__ico { display: inline-flex; color: var(--color-primary-500); flex: none; }
.af-explain__main { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
.af-explain__t { font-size: var(--font-size-base); font-weight: var(--font-weight-medium); color: var(--color-fg); }
.af-explain__s { font-size: var(--font-size-xs); color: var(--color-muted); }
/* 右箭头：明确提示「点了会打开新东西」（比折叠三角更指向"弹出"） */
.af-explain__chev { display: inline-flex; color: var(--color-meta); flex: none; }

/* ---- 弹窗内容 ---- */
.af-ex__nav {
  position: sticky;   /* 锚点条吸顶：滚动长内容时始终能跳转 */
  top: calc(-1 * var(--space-5));
  z-index: 2;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  padding: var(--space-2) 0 var(--space-3);
  margin: calc(-1 * var(--space-2)) 0 var(--space-3);
  background: var(--color-surface);
  border-bottom: 1px solid var(--color-border-soft);
}
.af-ex__navbtn {
  height: var(--control-height-sm);   /* 两档制：30 → 34（紧凑档，弹窗内锚点导航） */
  padding: 0 var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-full);
  background: var(--color-surface);
  color: var(--color-fg-2);
  font-family: inherit;
  font-size: var(--font-size-sm);
  cursor: pointer;
  transition: border-color var(--motion-fast) var(--motion-ease-standard),
              color var(--motion-fast) var(--motion-ease-standard),
              background var(--motion-fast) var(--motion-ease-standard);
}
.af-ex__navbtn:hover { border-color: var(--color-primary-300); color: var(--color-primary-500); }
.af-ex__navbtn.is-on {
  border-color: var(--color-primary-500);
  background: var(--color-primary-50);
  color: var(--color-primary-600);
}
.af-ex__sec + .af-ex__sec { margin-top: var(--space-5); padding-top: var(--space-5); border-top: 1px solid var(--color-border-soft); }
.af-ex__head { display: flex; align-items: baseline; flex-wrap: wrap; gap: var(--space-2); margin-bottom: var(--space-3); }
.af-ex__no {
  display: inline-flex; align-items: center; justify-content: center;
  width: 20px; height: 20px; flex: none;
  border-radius: var(--radius-sm);
  background: var(--color-primary-500); color: #fff;
  font-size: var(--font-size-xs); font-weight: var(--font-weight-medium);
}
.af-ex__t { font-size: var(--font-size-base); font-weight: var(--font-weight-semibold); color: var(--color-fg); }
.af-ex__h { font-size: var(--font-size-xs); color: var(--color-muted); }
.af-ex__body { min-width: 0; }

/* 口径标记（乐哥 2026-09-12）：KPI 副标题里把「金额口径 / 单数口径」做成蓝色小标记，
   与后面的算式（灰色）区分开。蓝色 = info 语义，与弹窗内 .af-def__badge 同色，全页一致。
   目的：本页退款率（看钱）与退货率（看单数）口径不同，副标题原先一片灰色、扫读会混。 */
.af-caliber {
  display: inline-block;
  padding: 0 6px;
  border-radius: var(--radius-sm);
  background: var(--color-info-bg);
  color: var(--color-info);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-medium);
  line-height: 1.6;
  vertical-align: baseline;
}

/* ==========================================================================
   售后分析 · 列头口径提示（乐哥 2026-09-11）
   表头里的「金额口径 / 单数口径」文字太长 → 改成圆圈感叹号图标，悬停显示说明。
   ⚠️ 关键约束：表格外层 .table-wrap 是 overflow-x: auto，按 CSS 规范该方向的 visible
      会被计算成 auto → **tooltip 上下左右都可能被裁**。故：
      · 向右不溢出：tip 用 right:0 定位（贴着图标右缘向左展开）
      · 向下不溢出：tip 向下弹，落在表头下方的表格内容区（仍在容器高度内）
      实测（无头浏览器）确认可见后才上线；若某天表头变成最后一行元素，需改为向上弹。
   ========================================================================== */
.af-hint {
  position: relative;
  display: inline-flex;
  align-items: center;
  vertical-align: -2px;
  margin-left: 3px;
  color: var(--color-muted);
  cursor: help;
}
.af-hint:hover,
.af-hint:focus-visible { color: var(--color-accent-text); }
.af-hint:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 1px; border-radius: 2px; }
.af-hint__tip {
  display: none;
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 60;
  width: max-content;
  max-width: 240px;
  padding: 6px 10px;
  font-size: var(--font-size-xs);
  font-weight: 400;
  line-height: 1.55;
  text-align: left;
  white-space: normal;
  color: var(--color-accent-on);
  background: var(--color-fg);
  border-radius: var(--radius-md);
  box-shadow: 0 2px 8px rgba(0, 0, 0, .18);
}
.af-hint:hover .af-hint__tip,
.af-hint:focus-visible .af-hint__tip { display: block; }

/* ==========================================================================
   售后分析 · 金额「万」展示（乐哥 2026-09-12）
   过万的金额显示为 ¥160.2万，鼠标悬停（或键盘聚焦）浮出精确值。
   复用 .af-hint 的定位策略（向下弹 + right:0 对齐）—— 表格外层是 overflow-x:auto，
   浮层必须落在容器内，否则会被裁（已实测同策略可行）。
   注意：**不加虚线下划线**（乐哥 2026-09-12：到万的那些数字下面不用加虚线，没必要）——
        数字本身就是可读的，加下划线反而让整列显得毛糙。
        「可悬停」的提示由 cursor: help（鼠标移到数字上变问号光标）承担，无静态视觉噪音。
   ========================================================================== */
.af-money {
  position: relative;
  cursor: help;
}
.af-money:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 1px; border-radius: 2px; }
.af-money__tip {
  display: none;
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 60;
  width: max-content;
  padding: 6px 10px;
  font-size: var(--font-size-xs);
  font-weight: 400;
  line-height: 1.5;
  letter-spacing: normal;
  white-space: nowrap;
  color: var(--color-accent-on);
  background: var(--color-fg);
  border-radius: var(--radius-md);
  box-shadow: 0 2px 8px rgba(0, 0, 0, .18);
}
.af-money:hover .af-money__tip,
.af-money:focus-visible .af-money__tip { display: block; }

/* ==========================================================================
   售后分析：斑马纹 · **已按乐哥要求去掉**（2026-09-25）
   --------------------------------------------------------------------------
   ▸ 历史：2026-09-12 乐哥要求给售后分析这 8 张表加斑马纹
     （「这些表格为了看的清楚，错行用很浅的背景色做区分」）；
     2026-09-15 又把它升级成**全站默认**（见 table.css，实现方式改为 `:where()` 低特异性）。
     ⇒ 本段 `.table--zebra` 从那时起就是**历史遗留**（与全站规则重复）。
   ▸ 2026-09-25 乐哥：「表格斑马纹，我觉得是不是可以不可以不要」+「我的报销页也有斑马纹」
     ⇒ 全站去掉斑马纹，本段一并清理（全站共 4 处实现，见 table.css 里的汇总清单）。

   ⚠️ 本段删除后，**特殊行的底色不受影响**（这是删之前专门核对过的）：
       `.af-audit__main`（核算清单主指标行）、`.af-bd__total`（构成合计行）、
       `.so-subtotal`（交叉表合计行）**各自有独立定义**，不依赖 `.table--zebra`：
         · `.af-audit__main` → 本文件 :2501（`.af-audit__main { background: var(--color-surface-2); }`）
         · `.af-bd__total`   → 本文件 :2641
         · `.so-subtotal`    → table.css 的 `.table--sub tbody tr.so-subtotal`（结算明细子表）
       原来那三条 `.table--zebra tbody tr.xxx` 只是"为了压过斑马纹"的**保险规则**，
       斑马纹没了，保险也就不需要了。
   ⚠️ hover 也不受影响：`.table tbody tr:hover`（table.css）本就是 (0,2,2)、优先于一切。
   ⛔ 原规则已删除（保留在此便于回溯）：
        .table--zebra tbody tr:nth-child(even) { background: var(--table-row-alt); }
        .table--zebra tbody tr.af-audit__main,
        .table--zebra tbody tr.af-bd__total,
        .table--zebra tbody tr.so-subtotal { background: var(--color-surface-2); }
        .table--zebra tbody tr:hover { background: var(--table-row-hover); }
      ⚠️ JS 里的 `table--zebra` 类名（`pages/aftersale.js` 共 9 处）**刻意保留不删** ——
         它现在不影响渲染（无对应规则），但留着"这里曾经有斑马纹"的线索；
         若以后要恢复，只需把上面 4 条取消注释即可，不必回头改 JS。
   ========================================================================== */

/* ==========================================================================
   提成页面（2026-09-12）——「我的提成」方案 1 模块卡堆叠式 +「提成总览」方案 2 侧栏主从式
   依据：docs/UI方案-提成与结算确认.md、.render-check/ui-plan-1.png / -2.png（乐哥拍板）
   铁律：容器一律 .module-card 体系（不新造模块容器）；颜色全走 CSS 变量（零裸 hex）；
        相邻卡片 24px 由 .module-card + .module-card 保证；工具栏控件 44px。
   ⚠️ 类名用 .cm-*（产品类）；对比页专用的 .cmp-* 不进产品代码。
   ========================================================================== */

/* —— 方案 1：金额头 + 类目拆解条 —— */
.cm-hero {
  display: grid;
  grid-template-columns: 300px minmax(0, 1fr);
  gap: var(--space-8);
  align-items: center;
}
.cm-hero__label { font-size: var(--font-size-sm); color: var(--color-muted); }
.cm-hero__value {
  margin-top: var(--space-1);
  font-size: var(--font-size-4xl);
  font-weight: var(--font-weight-semibold);
  letter-spacing: var(--font-letter-spacing-display);
  line-height: var(--font-line-height-tight);
  color: var(--color-fg);
  font-variant-numeric: tabular-nums;
}
.cm-hero__sub {
  margin-top: var(--space-3);
  display: flex; flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
  font-size: var(--font-size-sm);
  color: var(--color-muted);
}
.cm-cat__sub { margin-top: var(--space-1); font-size: var(--font-size-xs); color: var(--color-meta); font-variant-numeric: tabular-nums; }

/* 类目分组头（明细卡内每组一行） */
.cm-grouphead { display: flex; align-items: baseline; gap: var(--space-3); margin-bottom: var(--space-3); }
.cm-grouphead__name {
  display: inline-flex; align-items: center; gap: var(--space-2);
  font-size: var(--font-size-sm); font-weight: var(--font-weight-semibold); color: var(--color-fg);
}
.cm-grouphead__meta { font-size: var(--font-size-xs); color: var(--color-muted); }
.cm-grouphead__amt { margin-left: auto; font-size: var(--font-size-sm); font-weight: var(--font-weight-semibold); color: var(--color-fg); font-variant-numeric: tabular-nums; }
.cm-more { margin-top: var(--space-2); display: flex; justify-content: flex-end; }
/* 窄屏专用副行（桌面隐藏）：把被折叠的「计算基数 · 点位」并进达人单元格，保证 AC-04 三项不缺 */
.cm-narrow-sub { display: none; }
/* 金额列禁止折行（窄屏下必须整串完整显示） */
.cm-table td:last-child, .cm-table th:last-child { white-space: nowrap; }

/* —— 方案 2：侧栏主从式 —— */
.cm-master {
  display: grid;
  grid-template-columns: 340px minmax(0, 1fr);
  gap: var(--space-6);
  align-items: start;
}
.cm-plist { display: flex; flex-direction: column; gap: 2px; max-height: 460px; overflow-y: auto; }
.cm-plist__hd { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); margin-bottom: var(--space-3); }
.cm-plist__count { font-size: var(--font-size-xs); color: var(--color-meta); font-variant-numeric: tabular-nums; }
.cm-pitem {
  display: flex; align-items: center; gap: var(--space-3);
  width: 100%; padding: var(--space-2) var(--space-3);
  border: none; border-radius: var(--radius-md);
  background: transparent; text-align: left; cursor: pointer;
  transition: background-color var(--motion-fast) var(--motion-ease-standard);
}
.cm-pitem:hover { background: var(--color-surface-2); }
.cm-pitem:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.cm-pitem.is-active { background: var(--color-primary-50); }
.cm-pitem__meta { flex: 1; min-width: 0; }
.cm-pitem__name { display: block; font-size: var(--font-size-sm); font-weight: var(--font-weight-medium); color: var(--color-fg); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cm-pitem__roles { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 3px; }
.cm-pitem__amt { font-size: var(--font-size-sm); font-weight: var(--font-weight-semibold); color: var(--color-fg); font-variant-numeric: tabular-nums; white-space: nowrap; }

.cm-heroline { display: flex; align-items: center; gap: var(--space-3); }
.cm-heroline__meta { min-width: 0; }
.cm-heroline__name { font-size: var(--font-size-lg); font-weight: var(--font-weight-semibold); color: var(--color-fg); }
.cm-heroline__sub { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: 3px; }
.cm-heroline__amt { margin-left: auto; text-align: right; }
.cm-heroline__amt-label { font-size: var(--font-size-xs); color: var(--color-muted); }
.cm-heroline__amt-val {
  font-size: var(--font-size-2xl); font-weight: var(--font-weight-semibold); color: var(--color-fg);
  font-variant-numeric: tabular-nums; letter-spacing: var(--font-letter-spacing-display);
}
.cm-catgrid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-3); }
.cm-catbox { padding: var(--space-3); border: 1px solid var(--color-border-soft); border-radius: var(--radius-md); background: var(--color-surface-2); }
.cm-catbox__label { font-size: var(--font-size-xs); color: var(--color-muted); }
.cm-catbox__val { margin-top: var(--space-1); font-size: var(--font-size-lg); font-weight: var(--font-weight-semibold); color: var(--color-fg); font-variant-numeric: tabular-nums; }
.cm-catbox__sub { margin-top: 2px; font-size: var(--font-size-xs); color: var(--color-meta); font-variant-numeric: tabular-nums; }
.cm-catbox--total { background: var(--color-accent-bg); border-color: var(--color-primary-200); }
.cm-catbox--total .cm-catbox__label { color: var(--color-accent-text); }

/* 可点的类目卡 = 提成明细页的**类目筛选器**（2026-09-15 乐哥：「点某个类目 → 只看它 → 快速核对」）。
   ⚠️ 新增 `--pick` 修饰类承载"可点"三态，**不动 .cm-catbox 本体** ——
      达人结算页也用同一套类名，在本体上加 cursor:pointer 会让那页的纯展示卡"看着能点"（假可点）。
   三态都要给反馈：默认（能点）/ hover（即将生效）/ is-active（当前生效）。
   只做 hover 不做 active 的话，用户切完看不出"现在是哪一个在生效"，表格少了一半行会以为漏数。 */
.cm-catbox--pick { cursor: pointer; transition: border-color var(--motion-fast) var(--motion-ease-standard), box-shadow var(--motion-fast) var(--motion-ease-standard); }
/* hover 用**边框 + 阴影**而不是换底色：卡片默认底就是 `--color-surface-2`（项目里当 hover 底用的那一档），
   两者同色 → 改背景等于没反馈。 */
.cm-catbox--pick:hover { border-color: var(--color-primary-200); box-shadow: var(--shadow-sm); }
.cm-catbox--pick:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.cm-catbox--pick.is-active { border-color: var(--color-accent); background: var(--color-accent-bg); }
.cm-catbox--pick.is-active .cm-catbox__label { color: var(--color-accent-text); }
/* 「筛选中」小字：给当前生效的那张卡一个明确标记（只靠边框色差，在浅色屏上不够醒目） */
.cm-catbox__on { font-size: var(--font-size-xs); color: var(--color-accent-text); }
.cm-catgrid__tip { margin-top: var(--space-2); }

/* ══ 类目「筛选条」（2026-10-21 乐哥：「有的不用那么大，或者可以不用 kpi 卡片的方式」）══
   原状：类目用 `.cm-catbox` 卡（kpi 字号）铺 3 列 —— 一个筛选项比主结论还抢眼，
   且其中有张「合计全部」与顶部 KPI 完全重复。
   ⇒ 类目改成一行胶囊：类目 · 场次 · 金额。可点、有 hover、有选中态（功能不变，形态降级）。
   ⚠️ 不复用 `.tag`：tag 是标注语义（不可点、无选中态）；筛选器需要三态与指针，故另立。
   ⚠️ 只在**提成明细页**用（`.cm-catchip` 前缀 cm = 提成模块），不影响其它页。 */
.cm-catchips { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-3); }
.cm-catchip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px var(--space-3);
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-pill);
  background: var(--color-surface-2);
  color: var(--color-fg-2);
  font-size: var(--font-size-xs);
  font-variant-numeric: tabular-nums;
  cursor: pointer;
  transition: border-color var(--motion-fast) var(--motion-ease-standard),
    background-color var(--motion-fast) var(--motion-ease-standard);
}
.cm-catchip:hover { border-color: var(--color-primary-200); }
.cm-catchip:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.cm-catchip.is-active {
  background: var(--color-accent-bg);
  border-color: var(--color-primary-200);
  color: var(--color-accent-text);
  font-weight: var(--font-weight-medium);
}

/* ══ 提成明细**详情页**的逐场明细表（2026-10-15 乐哥）════════════════════════════
   乐哥原话：「预估提成和未满 35 天业绩，是不是应该靠前一点，然后这个属于一个模块」。
   根因不在那两列的位置，而在**「达人」列超宽**：本表是 `table-layout: auto`，
   浏览器把多余宽度摊回各列 ⇒ 右侧数字列被推到最右（实测写了列宽也不生效）。
   ⚠️ 只作用于本页：总览列表页同用 `.cm-table`，`table-layout: fixed` 是全站级大改，
      所以用 `--detail` 修饰类，⛔ 不许直接改 `.cm-table` 本体。
   列宽由 JS 内联写在 th 上（成套 132/62/达人弹性/108/96/64/96/100/116）。
   ⭐ 2026-10-15 追加（乐哥：「空这么多干嘛」）：`table-layout: fixed` 下**没给宽度的列会独吞全部余量**。
      原先只有最后一列没给宽度 ⇒ 「预估提成」被撑到 213px（内容仅 37px）、数字右对齐 ⇒
      未满与预估之间约 176px 空洞。
      ⇒ 现在**除「达人」（弹性列）外每列都给宽度**，余量归「达人」——
        它带「基数 · 提成比例」副行、能把宽度填满，视觉上不是洞。
      ⛔ 铁律：**弹性列只能有一个，且必须是文本列**；把余量给右对齐的数字列，就一定会再长出一个洞。 */
.cm-table--detail { table-layout: fixed; }
/* 「未满 35 天 + 预估提成」自成一**组**：浅底 + 组首竖线（组内不再画线）
   ⚠️⚠️ 底色**只作用于表体（tbody）**（乐哥 2026-10-15：「表头不要加那个颜色了吧，就是最后两列」）：
   原选择器 `.cm-table--detail .cm-c-pend` 会**同时命中 `th`** ⇒ 把表头本该统一的
   `--table-header-bg` 覆盖成 `--color-surface-2`，最后两列表头比别处深一档。
   表头底色必须全站一致（`.table thead th`）；分组底色标的是"这一组的数据"，与表头无关。 */
/* ⭐⭐ 2026-10-21 深夜 乐哥：「三级页面，未满 35 天，不要用那个灰色底了，去掉」
   ⇒ **删掉 `.cm-c-pend` 的浅灰底**（原 `--color-surface-2`）与它配套的 hover 底色。
   为什么可以不要：这一组的分组信息已经由**组首竖线**（`.cm-c-grp`）表达，
   再叠一层灰底等于把同一条信息说两遍；而且灰块与斑马纹 / hover 底互相打架 ——
   原来必须额外写一条 hover 规则去覆盖它，否则鼠标移进这两格"整行只有它们不亮"。
   ⛔ 别把 background 加回来：一旦存在，就得同步维护 hover 覆盖，否则行反馈断裂。 */
.cm-table--detail .cm-c-grp { border-left: 1px solid var(--color-border); }
.cm-table--detail .st-col-fcast { border-left: 0; }
/* 未满组不再有底色 ⇒ 这条 hover 覆盖也一并删掉（留着是死规则，会让人以为还有底色） */
/* 标题行的筛选态胶囊（含取消按钮）：滚动到表格时卡片已出视野，必须在这儿再写一次"已筛选" */
.cm-filter-tag {
  display: inline-flex; align-items: center; gap: var(--space-1);
  padding: 2px var(--space-2); border-radius: var(--radius-pill);
  background: var(--color-accent-bg); color: var(--color-accent-text);
  font-size: var(--font-size-xs); font-variant-numeric: tabular-nums;
}
.cm-filter-tag__x {
  display: inline-flex; align-items: center; justify-content: center;
  width: 16px; height: 16px; padding: 0; border: 0; border-radius: 50%;
  background: transparent; color: inherit; cursor: pointer;
}
.cm-filter-tag__x:hover { background: var(--color-primary-200); }
/* 总览页「可下钻」的角色金额：默认与其它数字同观感（不做成蓝链接，财务表格里满屏蓝会乱），
   但 hover 变主色 + 虚线下划线 —— 既保持扫读性，又让人发现"这里能点"。 */
.co-drill { cursor: pointer; border-bottom: 1px dashed transparent; transition: color var(--motion-fast) var(--motion-ease-standard); }
.co-drill:hover { color: var(--color-accent-text); border-bottom-color: var(--color-accent); }

/* rebuild dry_run 影响面报告（方案 2 顶部「重建人员明细」弹窗） */
.cm-report { display: flex; flex-direction: column; gap: var(--space-4); }
.cm-report__kpis { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-3); }
.cm-report__kpi { padding: var(--space-3); border: 1px solid var(--color-border-soft); border-radius: var(--radius-md); background: var(--color-surface-2); }
.cm-report__kpi-label { font-size: var(--font-size-xs); color: var(--color-muted); }
.cm-report__kpi-val { margin-top: 2px; font-size: var(--font-size-lg); font-weight: var(--font-weight-semibold); color: var(--color-fg); font-variant-numeric: tabular-nums; }
.cm-report__sec { font-size: var(--font-size-sm); font-weight: var(--font-weight-semibold); color: var(--color-fg); }

/* —— 工作台首页「我的提成」卡片 —— */
.my-comm-card { cursor: pointer; transition: box-shadow var(--motion-fast) var(--motion-ease-standard); }
.my-comm-card:hover { box-shadow: var(--shadow-md); }
.my-comm-card:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.my-comm-card__title { display: inline-flex; align-items: center; gap: var(--space-2); color: var(--color-fg); }
.my-comm-card__amount { font-size: var(--font-size-3xl); font-weight: var(--font-weight-semibold); color: var(--color-fg); font-variant-numeric: tabular-nums; letter-spacing: var(--font-letter-spacing-display); line-height: var(--font-line-height-tight); }
.my-comm-card__label { margin-top: var(--space-1); font-size: var(--font-size-xs); color: var(--color-muted); }
.my-comm-card__cats { margin-top: var(--space-3); font-size: var(--font-size-sm); color: var(--color-fg-2); }

/* —— 排期表单：每人点位（三类角色）—— */
.member-rate-list { display: flex; flex-direction: column; gap: var(--space-2); }
.member-rate-row { display: flex; align-items: center; gap: var(--space-3); min-height: 44px; }
.member-rate-row__name { flex: 1; min-width: 0; display: flex; align-items: center; gap: var(--space-2); font-size: var(--font-size-sm); color: var(--color-fg); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.member-rate-row__input { flex: 0 0 132px; }
.member-rate-row__state { flex: 0 0 auto; font-size: var(--font-size-xs); }
.member-rate-hint { margin-top: var(--space-2); display: flex; align-items: flex-start; gap: var(--space-2); font-size: var(--font-size-xs); line-height: 1.7; }
.member-rate-hint--warn { color: var(--color-warning-text); }
.member-rate-hint--info { color: var(--color-muted); }

/* —— 窄屏（≤768px）：方案 1 / 2 统一的收缩策略 —— */
@media (max-width: 768px) {
  .cm-hero { grid-template-columns: minmax(0, 1fr); gap: var(--space-5); }
  .cm-master { grid-template-columns: minmax(0, 1fr); gap: var(--space-4); }
  /* 左栏人员列表 → 横向可滑的人员条 */
  .cm-plist { flex-direction: row; overflow-x: auto; overflow-y: hidden; max-height: none; padding-bottom: var(--space-1); }
  .cm-pitem { flex: 0 0 auto; width: auto; padding-right: var(--space-4); }
  .cm-pitem__roles { display: none; }
  .cm-catgrid { grid-template-columns: minmax(0, 1fr); }
  /* 明细细表渐进披露：隐去 直播时间 / 计算基数 / 点位，语义折进达人单元格副行（AC-04 三项仍在） */
  .cm-c-time, .cm-c-base, .cm-c-rate { display: none; }
  .cm-narrow-sub { display: block; }
  .cm-table th, .cm-table td { padding-left: var(--space-2); padding-right: var(--space-2); }
  .cm-table td { overflow-wrap: break-word; }
  .cm-table td:first-child .mono, .cm-table th:first-child { font-size: var(--font-size-xs); }
  .cm-table td:first-child { width: 118px; }
  .cm-table td:last-child { min-width: 78px; }
  .cm-heroline { flex-wrap: wrap; row-gap: var(--space-3); }
  .cm-heroline__amt { margin-left: 0; width: 100%; text-align: left; }
  .cm-plist__count { white-space: nowrap; }
  .cm-report__kpis { grid-template-columns: minmax(0, 1fr); }
  .member-rate-row { flex-wrap: wrap; }
  .member-rate-row__input { flex: 1 1 132px; }
}

/* ⚠️ 已废弃（2026-09-15 晚）：`.member-rate-group`（每人点位分组壳）随
   「按人配点位（助播 / 商务助理）」一起下线 —— 它只被已删除的 `rateGroupHtml()` 使用。
   商务侧的逐人点位是**平铺列表**，只用下面的 `.member-rate-list` / `.member-rate-row`。 */

/* 人员成本页 tab 条：窄屏 6 个 tab 排一行会撑出横向溢出 → 允许换行。
   只作用于本页（#ps-tabs），不动全局 .seg（其他页 tab 数少、行为不变）。 */
@media (max-width: 768px) {
  #ps-tabs { flex-wrap: wrap; padding: 4px; }
  #ps-tabs .seg__btn { flex: 1 1 auto; }
}

/* ==========================================================================
   提成总览：人员表格（2026-09-12 乐哥要求「一行一行」替代原左右分栏）
   · .co-row 整行可点展开（所以 cursor:pointer + hover 反馈要明显）
   · .co-expand 是展开区容器：用 surface-2 与表体区分，避免与相邻行视觉粘连
   ========================================================================== */
.co-row { cursor: pointer; }
.co-row.is-open { background: var(--color-surface-2); }
.co-toggle { color: var(--color-muted); }
.co-ops { white-space: nowrap; }
.co-ops .btn + .btn { margin-left: var(--space-2); }
.co-expand { padding: var(--space-4); background: var(--color-surface-2); }
/* 手动校准弹窗的「校准范围」提示条（2026-09-15 乐哥：「校准是校准那个岗位、那一场，
   还是所有的提成」）—— 校准的实际粒度是「人 × 月」，会把该人该月**全部岗位、全部场次**
   的提成合在一起改。不写清楚，财务会以为是按岗位/按场校准（那两者都不存在）。
   用浅主色底 + 左侧主色竖条：一眼可见、但不与弹窗里的输入区抢注意力。 */
.co-adj-scope {
  margin-top: var(--space-3);
  padding: var(--space-3);
  border: 1px solid var(--color-primary-200);
  border-left-width: 3px;
  border-radius: var(--radius-md);
  background: var(--color-accent-bg);
  color: var(--color-accent-text);
  font-size: var(--font-size-sm);
  line-height: 1.6;
}

/* ==========================================================================
   达人结算：行内展开「业绩构成」（2026-09-12 乐哥需求）
   关联直播的按场次一行、未关联的归当月私域一行；系数只在展开层展示。
   ========================================================================== */
.st-row { cursor: pointer; }
/* 展开中的主行：底色与展开区一致 + 去掉下边框 → 主行与展开块**读成一个整体**（不是两块） */
.st-row.is-open,
.st-row.is-open:hover { background: var(--color-bg); }
.st-row.is-open > td { border-bottom-color: transparent; }
/* （已删除）.st-toggle —— 首列「展开箭头」单元格已随「点行跳结算明细页」一并移除（乐哥 2026-09-13），
   这两条（.st-toggle 与展开态点亮）不再匹配任何元素。 */

/* ==========================================================================
   展开区（乐哥 2026-09-12 二次优化：要"显眼、好扫读"）
   三条视觉手段，都在既有 token 内：
     ① 分界：底色下沉到页面底（--color-bg）+ 2px 上边界线 → 与上方白表格明确分开
     ② 主次：汇总从"一行平铺 14px"改为「标签在上 / 数字在下的 KPI 格」，合计基数放大加粗
     ③ 顺序：表头用 .th-sub 标出 计算基础 → 各维度之和 → 乘积结果，引导从左到右读
   ========================================================================== */
.st-drill {
  padding: var(--space-4) var(--space-4) var(--space-4);
  background: var(--color-bg);                 /* 下沉一档（原 surface-2 太接近白色，看不出展开） */
  border-top: 2px solid var(--color-border);   /* 明确上边界：一眼看出"从这里开始是展开内容" */
}
/* ⚠️ 桌面端把展开面板宽度**钳到卡片可见宽**（实测踩过，必须保留这条）：
   主表 13 列的内容宽度是 1379px，比卡片可见宽 1134px 宽 → `.table-wrap` 会横向滚动；
   展开行 `<td colspan=13>` 实测宽 1205px，若面板跟着撑满，
   最关键的「合计基数」KPI 格与「该行佣金」列会**落到可见区之外**（实测右侧还差 43px），
   财务得先横向滚动才看得到金额 —— 正是乐哥说的"看不出重点"的元凶之一。
   宽度算式全部用既有 token：视口 − 侧边栏 − 页面左右留白 − 展开行左右内边距 − 卡片边框。
   sticky left 让面板在横向滚动时钉在可见区左侧，不会跟着跑出去。 */
@media (min-width: 1024px) {
  .st-drill {
    position: sticky;
    left: 0;
    width: calc(100vw - var(--sidebar-width) - var(--page-padding) * 2 - var(--space-3) * 2 - 2px);
    max-width: 100%;
  }
}
/* 展开区标题条：给这块内容一个"名字"，扫读时先看到它 */
.st-drill-head {
  display: flex; align-items: center; gap: var(--space-2);
  margin-bottom: var(--space-3);
}
.st-drill-head svg { flex: none; color: var(--color-muted); }
/* ⭐ 2026-10-15 14 → 16px，与 `.st-comp__t` 同档（规范 §3：`lg 16` = 卡片标题）。
   两条必须**同时**改 —— 它们是同一页里并排的两个区段标题，一个 16 一个 14 会立刻看出错位。 */
.st-drill-head__t { font-size: var(--font-size-lg); font-weight: var(--font-weight-semibold); color: var(--color-fg); }
.st-drill-head__meta { margin-left: auto; font-size: var(--font-size-xs); color: var(--color-meta); }
.st-drill-hint {
  display: flex; align-items: flex-start; gap: var(--space-2);
  padding: var(--space-3); margin-bottom: var(--space-3);
  border-radius: var(--radius-md);
  background: var(--color-warning-bg); color: var(--color-warning-text);
  font-size: var(--font-size-sm);
}
/* 汇总：KPI 格（标签在上、数字在下）——「合计基数」为主，放大一级 + 主文字色
   ⚠️ 2026-09-14 与提成明细的 `.cm-catbox` 统一（乐哥反馈「达人结算二级页和提成总览不一样」）：
      原本这里是 `surface 底 + 阴影无边框`，提成那边是 `surface-2 底 + 1px 边框`，
      同为「二级页顶部的 3 格类目汇总」，两套皮 → 看着不是一套东西。
      现统一取提成那套（乐哥拍板「统一为有边框」）：浅底 + 1px 细边框、去掉阴影。
      边框比阴影在浅色底上边界更清晰，也更接近全站卡片的统一语言。
   ⚠️ 改这里会**同时影响**达人结算列表页的行内展开区（同一个 .st-drill 组件）——
      这是有意的：同一个组件只该有一套皮。 */
.st-drill-kpis {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-3); margin-bottom: var(--space-4);
}
.st-drill-kpi {
  padding: var(--space-3);
  background: var(--color-surface-2);
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-md);
}
.st-drill-kpi__k { font-size: var(--font-size-xs); color: var(--color-muted); }
.st-drill-kpi__v {
  margin-top: var(--space-1);
  font-size: var(--font-size-lg); font-weight: var(--font-weight-semibold); color: var(--color-fg-2);
  font-variant-numeric: tabular-nums;
}
/* 主数字：合计 —— 财务最关心的那个，放大到 20px + 主文字色。
   底色/边框对齐提成明细的 `.cm-catbox--total`（强调底 + 强调边框），
   使两页的「合计格」在整套格子里以同样方式跳出来。 */
.st-drill-kpi--main {
  background: var(--color-accent-bg);
  border-color: var(--color-primary-200);
}
.st-drill-kpi--main .st-drill-kpi__v {
  font-size: var(--font-size-xl); color: var(--color-fg);
}
.st-drill-diff { color: var(--color-warning-text); }
.st-drill-tag {
  display: inline-block; padding: 0 var(--space-2); border-radius: var(--radius-sm);
  background: var(--color-surface); color: var(--color-fg-2); font-size: var(--font-size-xs);
  /* nowrap 必须加：「当月私域」4 字在固定列宽里会被折成两行（实测踩过），
     标签一旦换行就把整行撑高、看着像坏了 */
  white-space: nowrap;
}
.st-drill-tag--priv { background: var(--color-warning-bg); color: var(--color-warning-text); }
/* ---- 展开区「分列展示」：线上佣金 / 线下应结（乐哥 2026-09-12）----
   原来在一个单元格里纵向堆叠各维度，线上金额往往占大头、把"我方要付的线下额"淹没了。
   改为横向分列：一列线上、一列线下应结 → 老板一眼看出"还要给这个达人结多少钱"。
   · 线上佣金：平台直付，我方不付 → **次要色弱化**
   · 线下应结：**我方实际要付的** → **主色加粗**，该行的重点读数
   每列下方 .st-col-sub 给构成（如「线下 2% + 年框 3%」），细分金额放 title 悬停看。 */
.st-col-on {
  color: var(--color-muted);            /* 弱化：不是我们要付的钱 */
  font-feature-settings: "tnum", "lnum";
}
.st-col-off {
  /* ⭐ 「线下应结」是全表最重要的读数：本期我方要付给达人的钱（乐哥 2026-09-13 要求更突出）
     → 主色蓝 + 字号升一档 + 加粗。
     与左侧可跳转的场次编号（.cell-link，主色 500、常规字重、小字号）靠"更深 + 更大 + 更粗"区分，
     一个是"能点"，一个是"要付的钱"，不会混。 */
  color: var(--color-primary-600);
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-semibold);
  font-feature-settings: "tnum", "lnum";
}
.st-col-off--zero { color: var(--color-muted); font-weight: var(--font-weight-regular); }
.st-col-sub {
  margin-top: 1px;
  color: var(--color-muted);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-regular);
  line-height: 1.35;
  white-space: nowrap;
}
/* 展开区里两列的单元格：不再参与"行高撑开"，让小字紧贴金额 */
.st-drill .table--sub tbody td.st-col-online,
.st-drill .table--sub tbody td.st-col-offline { vertical-align: top; padding-top: var(--space-2); }

/* ⚠️ 2026-09-14 乐哥拍板「所有列表统一标准」→ **删除本块的表头皮覆盖**（第四套皮）。
   原设计与注释：
     「展开区明细表：与主表拉开差异 —— 表头更弱、行更高、佣金列加重
      （主表表头 44px 灰底；这里表头透明、行高更大，避免"两处表头长得一样"看串行）」
     → 实测：`transparent 底 + 34px 高 + 底对齐`。
   为什么删：乐哥的原话是「列与列之间的间隔、列头的高度和颜色，很多表格都不一致」、
   「你要不统一定一个标准，所有列表页面都按照这个标准改」——
   项目里因此累计了**四套表头皮**（主表 44px 灰底 / 子表 28px 透明 / st-comp 34px 透明 /
   本块 34px 透明底对齐）。统一优于局部区分：展开区与主表的层次现在由**容器边界 + 缩进**承担
   （`.st-drill` 自身的内边距与左侧留白），不再靠"表头长得不一样"。
   保留 `vertical-align: bottom` 已无意义（主表未设、两行表头本就是居中），一并移除。 */
.st-drill .table--sub tbody td {
  padding-top: var(--space-3);
  padding-bottom: var(--space-3);
  background: transparent;
}
.st-drill .table--sub tbody tr:hover td { background: var(--color-surface-2); }
/* 场次编号：字号略小、等宽，作为"行标签"；其下的人员小字弱化 */
.st-drill .table--sub tbody td:first-child { font-size: var(--font-size-sm); }
.st-drill .table--sub tbody td:first-child .cell-sub { margin-top: var(--space-1); color: var(--color-meta); }
/* 佣金列：整块最重的数字 —— 加粗 + 主文字色 + 稍大一号 */
.st-drill .table--sub tbody td.st-drill-amt {
  font-weight: var(--font-weight-semibold);
  font-size: var(--font-size-base);
  color: var(--color-fg);
}
/* 私域行：**不加底色**（乐哥 2026-10-15：「这两行没必要有底色」）
   ⭐ 沿革：原为 `--color-warning-bg`（警示橙黄，与警示条撞令牌，10-15 上午改中性浅灰）
   ⇒ 乐哥再看实物后定为**完全不要底色** —— 行分组已由「当月私域」标签承担，
   底色纯属噪音（且与 hover 底同色，反而看不出鼠标在哪一行）。
   ⛔ 别再给这两类行（私域行 / 合计行）加底色。 */
/* （底色的 hover 反馈由下方 `tbody tr:hover td` 的通用规则统一提供，此处不再单独写） */

/* ==========================================================================
   达人结算：业绩归属「取数规则」备注（乐哥 2026-09-12）
   文案来自服务端 /shop-orders/attribution-rule，前端原样渲染。
   ========================================================================== */
.st-rule { margin-bottom: var(--space-3); }
/* 提示条 · 统一形态（2026-09-14 乐哥：「不要做成现在提成总览里这种**白底**的」）
   ---------------------------------------------------------------
   改前：`background: --color-surface-2`（#FAFBFC，几乎与卡片同色）+ 4px 圆角 + 左竖线
        → 看着就是"白底"，不够醒目。这正是乐哥点名要改的那一条。
   改后：浅蓝底（--notice-bg）+ 8px 圆角 + 蓝图标，与 .an-banner / .alert 完全同规格。
   ⚠️ 去掉左侧竖线：图片里那种是"整块浅底"，没有色条。 */
.st-rule__inner {
  display: flex; align-items: flex-start; gap: var(--notice-gap);
  padding: var(--notice-pad-y) var(--notice-pad-x);
  border-radius: var(--notice-radius);
  border: 1px solid var(--notice-border);
  background: var(--notice-bg);
  color: var(--notice-fg);
  font-size: var(--font-size-sm);
  line-height: 1.6;
}
/* 图标垂直对齐：16px 图标在首行行盒（1.6em = 22.4px @14px）内居中
   → 下移 (1.6em − 16px) / 2 = 3.2px。用 em 表达行高，字号一变自动跟随。
   ⚠️ 必须用 align-self: flex-start（对齐首行）而不是容器的 align-items:center，
      提示条常是多行，整体居中会让图标跑到段落中间。 */
.st-rule__inner svg { flex: none; align-self: flex-start; margin-top: calc((1.6em - 16px) / 2); color: var(--notice-ico); }

/* ⭐ 2026-09-27：'.st-rule' 的危险变体（审批详情里的「钱相关」提醒专用）——
   乐哥规范：「提醒的文字用红色」+ 配套红底更醒目（与 .alert--danger 同色系）。
   用法：`<div class="st-rule st-rule--danger"><div class="st-rule__inner">…</div></div>`
   ⚠️ 只用于**需要审批人停下来看清楚**的提醒（影响历史成本 / 锁定排期不随之更新）；
      普通说明仍用默认的浅蓝形态，不要滥用到所有提示上（全红=没有重点）。 */
.st-rule--danger .st-rule__inner {
  background: var(--color-danger-bg);
  border-color: var(--color-danger-border);
  color: var(--color-danger-strong);
}
.st-rule--danger .st-rule__inner svg { color: var(--color-danger-strong); }

/* ⭐ 2026-10-21（财务四页展示统一）：警示变体（橙）—— 介于默认(蓝)与危险(红)之间。
   用途：**口径不一致 / 数字会变**这类"要当心但不用停手"的提示。
   例：月度利润明细「本表月份口径不一致，不可直接对比」原来是自己写的黄底条，
   统一到本变体后与另外三页同一形态（见 docs/财务四页展示规范-20261021.md 第 2 条）。
   ⚠️ 不要用它表达"出错"（那是 --danger），也不要用它做普通说明（那是默认蓝）。 */
.st-rule--warn .st-rule__inner {
  background: var(--color-warning-bg);
  border-color: var(--color-warning-border);
  color: var(--color-warning-text);
}
.st-rule--warn .st-rule__inner svg { color: var(--color-warning-text); }

/* 表头副标题：用于在列名下方标注口径（如「无需结算」「不含线上」）—— 2026-09-12
   2026-09-12 二次：改 10.5px 太小不可读 → 用 --font-size-xs，并在展开区表头里兼作"读法引导" */
.th-sub {
  display: block;
  font-weight: var(--font-weight-regular);
  font-size: var(--font-size-xs);
  color: var(--color-muted);
  white-space: nowrap;
  line-height: 1.35;
}
/* 表头旁的「口径提示」图标：圆圈感叹号，**说明只在悬停时以浏览器原生气泡显示**
   （乐哥 2026-09-12：不要把说明文字摊在表头占地方）。
   ⚠️ 用原生 title 而非 CSS 气泡：表格外层 .table-wrap{overflow-x:auto} 会裁掉自定义气泡。
   这里只做视觉：图标与列名同行对齐、悬停时有颜色反馈表示"可查看说明"。 */
.th-note {
  display: inline-flex;
  align-items: center;
  vertical-align: middle;
  margin-left: 3px;
  color: var(--color-muted);
  cursor: help;              /* 提示可悬停查看 */
  transition: color var(--motion-fast) var(--motion-ease-standard);
}
.th-note:hover { color: var(--color-accent-text); }
.th-note svg { flex: none; }

/* ⭐ 字段标签末尾的「?」变体（2026-10-09 乐哥：「在对应类型名字后面加个 ？，
   然后放进去，不然现在很乱」）—— 把填表区一行行的灰字说明收进这里。
   与页头标题旁那个 `?` 是**同一手法**（`UI.tip` + `helpCircle` 圆问号），只是降了一级：
     页头说明"这页干什么"，这里说明"这一格怎么填"。
   ⚠️ 图标沿用 `UI.tip` 的默认 13px —— 它跟的是 13px 的字段名，
      与页头那个 17px 是**两档**（照搬页头尺寸会明显顶出标签行）。
   ⚠️ `vertical-align: -1px`：基础样式的 `middle` 是按父元素 x-height 居中，
      挂在标签文字后会略高；下移 1px 与字段名的视觉重心齐平（真机实测值）。
   ⚠️ 悬停气泡由 `UI.bindTips` 全局代理（幂等，挂在 app.js 的 relayoutActive）。 */
.field-label .th-note { margin-left: var(--space-1); vertical-align: -1px; }

/* ⭐ 2026-09-25 「小字注释收进 ⓘ」方案（乐哥选定）——
   变体：把 ⓘ 钉在**父容器右上角**（父容器需 position:relative，如 .kpi-card）。
   ──────────────────────────────────────────────────────────────────────────
   什么内容该收进来（分级，不是一刀切）：
     ✅ 该收：**算式**（¥1,548,186.41 ÷ ¥5,189,547.80 —— 核对用，不常看）
             **长解释**（「这张表回答…」—— 读一次就够）
     ⛔ 不该收：**限定词**（「含已取消」「货已寄回」「占实付总额 29.8%」
                —— 读数字时**必须知道**，收起来反而有害）
              **数据本身**（时间、渠道、分页——那不是注释）
   为什么用右上角而不是跟在文字后面：跟在文字后会**参与换行**、把行撑高（等于没收）；
   右上角是"这块区域的补充信息"的自然位置，且**完全不占内容高度**。
   为什么图标用 info（ⓘ）而不是 alertCircle（❗）：口径说明是"查得到更好"，
   不是"出问题了"——用感叹号会让人误以为有异常。 */
.th-note.is-corner {
  position: absolute;
  top: var(--space-3);
  right: var(--space-3);
  margin-left: 0;              /* 覆盖基础样式的 margin-left:3px（角落定位不需要文字间距） */
  z-index: 1;                  /* 保证在卡片内容之上可点（卡片内可能有滚动/覆盖层） */
}
/* 该列不参与应付（如线上佣金由平台直付）→ 金额用次要色，从视觉上"退出主读数"
   ⚠️⚠️ 本类**特异性不够**（0,1,0），会被 `.table tbody td { color: var(--color-fg) }`（0,2,1）
      **静默压过** —— 也就是说挂着它的格子**从来就没真的变灰过**，而页面看着"没报错"。
      （table.css 268/283 行早写过这条坑，但当时只在别处用；2026-10-11 线上佣金列踩了同一次。）
   ⇒ 线上佣金统一改用 `.st-col-online`（规则在 `.table tbody td.` 下，特异性 0,2,2）。
      本类**保留**（可能还有历史引用 / 将来要在没有 `.table` 上下文的地方用），但新代码别再用它。 */
.st-col-muted { color: var(--color-muted); }
/* 展开区表头副标题：承接「计算基础 → 各维度之和 → 乘积结果」的读法引导，语气更明确 */
.st-drill .table--sub thead .th-sub { color: var(--color-meta); }

/* 窄屏：KPI 三格 → 单列；展开区左右内边距收紧（表格交给 .table-wrap 横向滚动） */
@media (max-width: 768px) {
  .st-drill { padding: var(--space-3); }
  .st-drill-kpis { grid-template-columns: minmax(0, 1fr); gap: var(--space-2); }
  .st-drill-kpi { display: flex; align-items: baseline; gap: var(--space-2); padding: var(--space-2) var(--space-3); }
  .st-drill-kpi__v { margin-top: 0; }
  .st-drill-head { flex-wrap: wrap; }
}


/* 展开区「线下应结」列的年框未计原因提示（资格被拦时出现）：
   写明是「未签年框 / 合同日期未填 / 不在合同期」，避免看到 0 以为算错 */
.st-col-note {
  margin-top: 2px;
  color: var(--color-warning-text);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-regular);
  line-height: 1.35;
  white-space: nowrap;
}

/* 展开区「合计」行（乐哥 2026-09-12）：各行的线上/线下应结必须能加总，
   且「线下应结」合计要与顶部结算单的「应付总额」对得上 */
.st-drill .table--sub tbody tr.st-drill-sumrow td {
  /* ⭐ 2026-10-15 乐哥：「这两行没必要有底色」⇒ 去掉整片浅灰底。
     合计行靠 **2px 上边框 + 加粗** 表达（比底色更干净，也不会与 hover 底撞色）。 */
  border-top: 2px solid var(--color-border);
  font-weight: var(--font-weight-semibold);
  white-space: nowrap;
}
.st-drill .table--sub tbody tr.st-drill-sumrow td:first-child {
  color: var(--color-muted);
  font-weight: var(--font-weight-regular);
}

/* 展开区：该行「未满 T+35」金额（暂不计入结算）—— 灰色提示，避免与已计金额混淆 */
.st-col-pending {
  margin-top: 2px;
  color: var(--color-warning-text);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-regular);
  line-height: 1.35;
  white-space: nowrap;
}

/* 展开区底部：「未满 T+35」合计提示条 —— 醒目（财务给达人结算前必须看到） */
.st-pending-bar {
  display: flex;
  align-items: flex-start;
  gap: var(--notice-gap);
  margin-top: var(--space-3);
  padding: var(--notice-pad-y) var(--notice-pad-x);
  /* 与全站提示条同规格（2026-09-14）：圆角 4→8px、描边用更柔和的 warning-border
     （原来是 --color-warning 实色，#F2A918 太跳）。**橙色语义保留**（这是警示不是说明）。 */
  border: 1px solid var(--notice-warn-border);
  border-radius: var(--notice-radius);
  background: var(--notice-warn-bg);
  color: var(--color-warning-text);
  font-size: var(--font-size-sm);
  line-height: 1.5;
}
/* 图标垂直对齐（首行行高 1.5em = 21px @14px → 下移 (1.5em − 16px) / 2 = 2.5px，原手写 2px 偏上 0.5px） */
.st-pending-bar svg { flex: none; align-self: flex-start; margin-top: calc((1.5em - 16px) / 2); }
.st-pending-bar b { font-weight: var(--font-weight-semibold); }
/* ⭐ 2026-10-15 方案甲：提示条内可容纳多行（首行放「单是旧的」，第二行放未满说明）——
   原本两条提示各占一行、各说一遍（乐哥：「这部分乱七八糟的是啥？」）。 */
.st-pending-bar__body { flex: 1; min-width: 0; }
.st-pending-bar__stale { display: flex; align-items: flex-start; gap: var(--space-1); margin-bottom: var(--space-1); }
.st-pending-bar__stale svg { flex: none; margin-top: calc((1.5em - 14px) / 2); }

/* 展开区「非福利款 GSV」列（= GSV − 福利款，线下四类的基数）—— 乐哥 2026-09-12 独立成列，
   不再挤在线下应结格里。颜色与 GSV 列一致（都是基数），靠副标题区分用途。 */
.st-col-ns { font-feature-settings: "tnum", "lnum"; }
.st-drill .table--sub tbody td.st-col-nscol { vertical-align: top; padding-top: var(--space-2); }
.st-drill .table--sub tbody tr.st-drill-sumrow td:nth-child(6) { font-feature-settings: "tnum", "lnum"; }

/* 展开区「未满35天」列（下期再结）—— 乐哥 2026-09-12 独立成列。
   它是**业绩**（本期未计入），故用 warning 色提示；与左侧「本期」各列之间加分隔线，
   避免误读成"本期也算过它"。 */
.st-col-pendcol {
  vertical-align: top;
  padding-top: var(--space-2);
  border-left: 1px dashed var(--color-border);
  border-right: 1px dashed var(--color-border);
}
.st-drill .table--sub thead th.st-col-pendhead {
  border-left: 1px dashed var(--color-border);
  border-right: 1px dashed var(--color-border);
}
.st-col-pending { font-feature-settings: "tnum", "lnum"; }
.st-col-none { color: var(--color-muted); }

/* 页顶归属规则条里的「规则」入口（2026-09-13）：
   完整规则收进 hover 气泡，常驻只留精简一句 → 用下划虚线提示"可悬停/聚焦查看"。 */
.st-rule__more {
  margin-left: var(--space-2);
  color: var(--color-primary-500);
  border-bottom: 1px dashed currentColor;
  cursor: help;
  transition: color var(--motion-fast) var(--motion-ease-standard);
}
.st-rule__more:hover { color: var(--color-primary-600); }

/* ==========================================================================
   提成总览：合计行 + 月度合计突出（乐哥 2026-09-13 要求对齐达人结算页的做法）
   ========================================================================== */
/* 月度合计：财务最关心的数 → 主色 + 加粗（与达人结算页「线下应结」同一套视觉语言）
   ⚠️ 选择器必须带 .cm-table td（特异性 0,2,1）：单类 `.cm-co-total` 会被
      pages.css **之后**加载的 pages-influencer.css / pages-schedule.css 里的表格规则覆盖，
      实测表现为"class 加上了、颜色却没变"（2026-09-13 踩过）。 */
.cm-table td.cm-co-total {
  color: var(--color-primary-600);
  font-weight: var(--font-weight-semibold);
  font-feature-settings: "tnum", "lnum";
}
/* 表尾合计行：与数据行区分（上边框 + 浅底 + 加粗），且不可点、不参与展开 */
.cm-foot-row td {
  border-top: 2px solid var(--color-border);
  background: var(--color-surface-2);
  font-weight: var(--font-weight-semibold);
  white-space: nowrap;
}
/* 「合计（N 人 / N 行）」这一个标签格：置灰、不加粗 —— 它是**说明**，不是数字。
   ⚠️ 2026-09-17 从 `td:nth-child(2)` 改成 `td:first-child`：
   原来绑第 2 格，是因为当时两处合计行（华大对账 / 提成总览）多写了一个空 `<td>`，
   让标签落在了第 2 格；而那两处本身是**错的**（表头 9 列、合计行 10 格 → 整行右移一列，
   数字会显示在错误的列下）。现已把它们改成标签落第 1 格（与其它 3 处合计行一致：
   排期明细 colspan=2、提成总览展开区 colspan=6、带货明细 colspan=3 —— 都用 colspan 合并首格）。
   绑 `:first-child` 后三种写法都能命中，不再依赖"首格是不是空的"。 */
.cm-foot-row td:first-child {
  color: var(--color-muted);
  font-weight: var(--font-weight-regular);
}
/* 同理：合计行里的总额也要保证生效（同上，提高特异性） */
.cm-table td.cm-co-sumtotal,
.cm-foot-row td.cm-co-sumtotal {
  color: var(--color-primary-600);
  font-size: var(--font-size-base);
}

/* 达人结算 · KPI 卡里的「无权查看」占位（金额为 null 时显示「—」）：
   用 muted 而不是 0 —— 0 会被读成"确实没有这笔钱"，与"看不到"是两回事。 */
.st-kpi-hidden { color: var(--color-muted); }

/* ============================================================================
   达人结算 · 结算单覆盖提示条（乐哥 2026-09-13）
   「达人结算，8月里为啥只有2个达人」—— 因为列表只装已生成结算单的达人。
   这条提示把「本月有 N 位做过直播、M 位还没结算单」摆出来并给一键补齐入口。
   警示色：这不是错误，但是**该处理的事**（不补生成，这些达人这个月就看不到）。
   ============================================================================ */
/* ⚠️ 2026-09-14 乐哥：「警示提醒方式没有微信小店的好」→ 改**行内强调**（去色块）
   ----------------------------------------------------------------------------
   改前：`#FEF5E7` 浅黄底 + 1px 边框 + 圆角，整条 1456px 通栏色块（12 万+ 像素）。

   为什么改（像素级对比微信小店参考图）：
     · 微信小店全图彩色像素只占 **0.72%**，唯一强调色是橙色 rgb(251,157,59)，
       且**只用在「待发货」这类几个字**上；信息层级靠**文字深浅**，不用色块。
     · 我们的大色块会与表格内的状态色（红「否」#E41E3F、橙「未授权」#F2A918）**抢注意力**；
       而且一旦哪页都有黄条，用户就"看不见"了（**警示疲劳**）。

   改后：透明底 + 只留下方 1px 细线；**关键数字用橙字**挑出；按钮改文字链。
   保留「警示」语义靠三处：图标色 + 关键数字橙字 + 与内容之间那条细线。
   ⚠️ 橙色文字用的是 --color-warning-text（#B25C0C），白底对比度 **4.73** > 4.5 达标；
      不能用 --color-warning（#FF8C42），那个对比度只有 2.4，做正文色不合格。
   ⚠️ 不要改回"有底色"：那种做法已被验证会与表内状态色打架（见上）。 */
/* ⛔ `.st-cover*`（结算单覆盖黄色提示条）2026-10-11 **已整段删除** ——
   乐哥：「**那个标黄的注释就不要了**」。
   原因是它存在的**前提没了**：这条提示回答的是"本月有谁做过直播但还没结算单"，
   那是**手动生成时代**的兜底（那会儿没人点「生成本月结算单」，那个月就是空的）。
   现在结算单由 `server/jobs/settlement-generate.js` 每天凌晨 04:00 自动生成（当月 + 上月）
   ⇒ 缺口最多存在到次日凌晨，页面不再需要这条提示（前端 `#st-cover` 同步删）。
   ⚠️ 若日后要恢复，形态要求是：**色底 + 圆角 + 图标 + 「带操作按钮」**（按钮右对齐），
      与同排的其它提示条统一 —— 这段旧实现就是按那个要求调出来的。 */

/* ============================================================================
   品牌佣金 · 通用佣金条 + 待维护（乐哥 2026-09-13）
   需求：「增加个待维护佣金的筛选（我导入订单导入的商品，但是系统里没维护上游佣金的）」
        「或者设置个通用佣金……通用佣金所有商品都是这个佣金率，我特殊维护的除外」
   ============================================================================ */

/* 通用佣金条：已设置用主色（正常态），未设置用警示色（因为没设时待维护商品会算成 0% → 负数） */
/* 提示条 · 统一形态（2026-09-14）：色底/圆角/内边距对齐 --notice-* 规格；
   本条**带「设置通用佣金」按钮**（保留，右对齐）。 */
.cm-general {
  display: flex; align-items: center; gap: var(--notice-gap); flex-wrap: wrap;
  padding: var(--notice-pad-y) var(--notice-pad-x); margin-bottom: var(--space-3);
  border: 1px solid var(--notice-border); border-radius: var(--notice-radius);
  background: var(--notice-bg);
}
.cm-general--warn { border-color: var(--notice-warn-border); background: var(--notice-warn-bg); }
/* 图标（原本**没有图标**，2026-09-14 补上以与全站一致） */
.cm-general__ico { flex: none; display: inline-flex; align-items: center; color: var(--notice-ico); }
.cm-general--warn .cm-general__ico { color: var(--notice-warn-ico); }
/* 右侧按钮推到最右 */
.cm-general > .btn, .cm-general > button:last-child { margin-left: auto; }
/* 未设置时隐藏空壳（还没加载完数据时不该闪一条空框） */
.cm-general:empty { display: none; }
.cm-gb__label { font-size: var(--font-size-sm); font-weight: 600; color: var(--color-primary-700); flex: none; }
.cm-gb__label--warn { color: var(--color-warning-text); }
.cm-gb__v {
  font-size: 22px; font-weight: 700; color: var(--color-primary-700);
  font-variant-numeric: tabular-nums; flex: none;
}
.cm-gb__v--warn { color: var(--color-warning-text); font-size: 18px; }
.cm-gb__d { font-size: var(--font-size-sm); color: var(--color-fg-2); line-height: 1.7; flex: 1; min-width: 220px; }
.cm-gb__d b { color: var(--color-fg); }
.cm-general .btn { flex: none; }
/* 弹窗里的影响面说明块 */
.cm-gb-note {
  grid-column: 1 / -1; margin-top: var(--space-2); padding: var(--space-3);
  border-radius: var(--radius-md); background: var(--color-surface-2);
  border: 1px solid var(--color-border);
  font-size: var(--font-size-sm); color: var(--color-fg-2); line-height: 1.8;
}
.cm-gb-note b { color: var(--color-fg); }

/* 「待维护」徽标：这不是错误、是需要处理的缺口 —— 用警示色（与"未开票"同一档语义） */
.cm-pend-badge {
  display: inline-block; font-size: 11px; line-height: 16px; padding: 0 6px;
  border-radius: var(--radius-sm); white-space: nowrap; font-weight: var(--font-weight-semibold);
  background: var(--color-warning-bg); color: var(--color-warning-text);
  border: 1px solid var(--color-warning-border);
}
/* 待维护行整行浅底纹：一屏扫过去能直接看出"这几行还没配" */
.table tbody tr.cm-pend-row > td { background: var(--color-warning-bg-soft); }
.table tbody tr.cm-pend-row:hover > td { background: var(--color-warning-bg-hover); }
/* 已卖出的金额：红色（已经在算错、最紧急） */
.cm-pend-sold { color: var(--color-danger-strong); font-weight: var(--font-weight-semibold); font-size: var(--font-size-sm); }

/* 提成总览：身份来源标记（2026-09-13，「兼职助播」专用）。
   刻意与角色 badge **视觉区分**：角色 badge 说的是「在直播里担任什么」（商务/助播/商助），
   这个说的是「以什么身份参与」（成员账号 / 兼职助播）。两者若用同一种 badge 样式，
   并排会出现「助播」+「兼职助播」两个看起来同类的标签，容易误读成"身兼两个角色"。
   虚线边框 = "另一维度"的视觉语言，与实心 badge 拉开。 */
.co-src {
  display: inline-flex;
  align-items: center;
  padding: 0 6px;
  height: 18px;
  font-size: var(--font-size-xs);
  color: var(--color-fg-2);
  border: 1px dashed var(--color-border);
  border-radius: var(--radius-sm);
}
.co-src + .pnl-flag { margin-left: 4px; }

/* 提成「待补」标记（2026-09-13）：已确认发放后，又到期进来的提成 = 要补给本人的钱。
   用**橙色**（与"预估"同一套 warning 语义）：都是"钱还没落定"的状态。
   ⚠️ 不用红色：红色在本系统是"涨/正数"的数值色，借来表达"待办"会与金额色冲突。 */
.co-carry {
  margin-top: 3px;
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-semibold);
  color: var(--color-warning-text);
  white-space: nowrap;
  cursor: help;
}
/* 页头汇总徽标：进页面第一眼就要看到「有几笔钱要补」 */
.co-carry-head__badge {
  display: inline-flex;
  align-items: center;
  height: 22px;
  padding: 0 var(--space-2);
  margin-right: var(--space-2);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-semibold);
  color: var(--color-warning-text);
  background: var(--color-warning-bg);
  border: 1px solid var(--color-warning);
  border-radius: var(--radius-sm);
  cursor: help;
}

/* ==========================================================================
   排期盈亏（schedule-detail 的「盈亏」tab）—— 乐哥 2026-09-13
   结论卡最显眼（盈利/亏损一眼看到），其次收入/成本两侧，再逐笔明细。
   ========================================================================== */
.pf-hero {
  display: flex;
  align-items: center;
  gap: var(--space-6);
  padding: var(--space-4);
  border-radius: var(--radius-md);
  margin: var(--space-3) 0;
}
/* ⚠️ 着色遵循项目统一约定（table.css 的 .num-pos / .num-neg）：
   **盈利=红、亏损=绿**（国内红涨绿跌习惯）。2026-09-13 修正 ——
   原来我用「盈利=绿」写反了，与「经营看板」「主播业绩」的红绿语义冲突
   （同一系统里绿色既表示亏损又表示盈利，看的人会误判）。 */
.pf-hero.is-win { background: var(--color-danger-bg); }
.pf-hero.is-loss { background: var(--color-success-bg); }
.pf-hero__main { flex: none; min-width: 220px; }
.pf-hero__k {
  font-size: var(--font-size-sm);
  color: var(--color-muted);
  margin-bottom: var(--space-1);
}
.pf-hero__v {
  font-size: var(--font-size-3xl);
  font-weight: var(--font-weight-semibold);
  font-feature-settings: "tnum", "lnum";
  line-height: 1.2;
}
.pf-hero.is-win .pf-hero__v { color: var(--color-danger); }
.pf-hero.is-loss .pf-hero__v { color: var(--color-success); }
.pf-hero__sub {
  font-size: var(--font-size-sm);
  color: var(--color-muted);
  margin-top: var(--space-1);
}
.pf-hero__side { flex: 1; display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--space-2) var(--space-5); }

/* 收入 / 成本 两侧并排 */
.pf-sides { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-4); margin-bottom: var(--space-4); }
.pf-side {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-4);
}
.pf-side__t {
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
  color: var(--color-muted);
  margin-bottom: var(--space-3);
}
.pf-kv {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-1) 0;
  font-size: var(--font-size-sm);
}
.pf-kv span { color: var(--color-muted); }
.pf-kv b { font-feature-settings: "tnum", "lnum"; font-weight: var(--font-weight-medium); }
.pf-kv--strong b { color: var(--color-primary-600); font-size: var(--font-size-base); font-weight: var(--font-weight-semibold); }
.pf-note {
  margin-top: var(--space-3);
  padding-top: var(--space-2);
  border-top: 1px dashed var(--color-border);
  font-size: var(--font-size-xs);
  color: var(--color-muted);
  line-height: 1.5;
}

/* 逐笔费用记录：类目分组行 */
.pf-cat td {
  background: var(--color-surface-2);
  font-weight: var(--font-weight-semibold);
  color: var(--color-fg-2);
}

@media (max-width: 900px) {
  .pf-hero { flex-direction: column; align-items: flex-start; }
  .pf-sides { grid-template-columns: 1fr; }
}

/* 展开区「预估应结」列（本期 + 未满35天预估）—— 乐哥 2026-09-13
   这是财务跟达人核对"总共要结多少"的数，故给一个浅底与左侧「本期」区隔开。
   颜色用主色（与「线下应结」同族），但加浅底让它更醒目；副标题说明差额来自未满部分。 */
.st-col-fcast {
  color: var(--color-primary-600);
  font-weight: var(--font-weight-semibold);
  font-feature-settings: "tnum", "lnum";
}
/* 主表（达人结算列表页）也用「预估应结」列了 —— 乐哥 2026-09-13 要求提到列表页，不必只能展开看。
   ⚠️ 必须写成 `.table tbody td.st-col-fcast`（0,2,2）：上面那条 `.st-col-fcast`（0,1,0）
      会被全局 `.table tbody td`（0,1,2）压过 → 主色**静默失效**，数字仍是默认近黑。
      实测：改前 getComputedStyle(...).color === rgb(28, 30, 33)。
      这是本项目反复踩的同一个坑（`.num-pos` / `.cm-co-total` / `.kpi-card__value` 都中过），
      **规则：给表格单元格上色 / 加粗，选择器里至少要带上 `.table tbody td` 这一层。**
      （展开区那侧不需要：它的规则是 `.st-drill .table--sub tbody tr.st-drill-sumrow td.st-col-fcast`，
        权重 (0,3,3) 本来就压得住。） */
.table tbody td.st-col-fcast {
  color: var(--color-primary-600);
  font-weight: var(--font-weight-semibold);
}
/* 列表页「线下应结」列（乐哥 2026-09-13：「颜色也改下，不要黑色」）。
   与展开区 `.st-col-off` 是**同一个数**（settleTotal 四类之和），故用**同一套视觉**：
   主色蓝 + 半粗，两处看起来一致，不会让人以为是两个不同的值。
   （此前列表页这格只挂了 `is-num num` + 内联 font-weight，没挂颜色类 → 默认近黑。） */
.table thead th.st-col-payable,
.table tbody td.st-col-payable {
  color: var(--color-primary-600);
  font-weight: var(--font-weight-semibold);
}
/* ══ 「线上佣金」列弱化（2026-10-11 乐哥）════════════════════════════════════
   乐哥原话：「所有线上的佣金，就是在结算单里面，颜色都不要用强调的蓝色了，
              **因为这个钱不是我们结算，我只能看到就行**」

   ⚠️ 类名 `.st-col-online` **不是新造的** —— 它是线上佣金列本来的类名，
      前端 4 处早就在用（主表 / 佣金构成 / "无数据"占位 / 未满段），
      但**此前只有明细表里一条 `vertical-align` 规则（3567 行）、从没有颜色定义**
      ⇒ 那些格一直是默认近黑，与旁边「我方要付的」金额视觉上分不出来。
   ⇒ 这里补的就是"弱化"这一条（缺口一直在）。与 `.st-col-on`（3543 行，
      明细格里单个线上金额，它的色值就是 `var(--color-muted)`，行内注释写着"弱化：不是我们要付的钱"）
      **用同一个颜色** —— 同一个含义，不该有两种灰。
   ⚠️ 同时把字重压回 400：`.table .is-num` 给数值列统一 500（数值要突出），
      但这一列的性质是"给你看、不用你付"，不该跟着突出。
   ⚠️ 与 `.st-col-payable`（应结 = 主色蓝 + 600）形成**明确对比**：
      蓝 = 我们要付给达人的钱；灰 = 平台直付、只是给你看。 */
.table thead th.st-col-online,
.table tbody td.st-col-online,
.table tfoot td.st-col-online {
  color: var(--color-muted);
  font-weight: 400;
}
/* 「本期 | 预估」的分界线：两列同色同粗，靠这条竖线区分主次（本期 = 现在付，预估 = 含未满）。
   与展开区表头 `th.st-col-fcasthead` 的 border-left 同一种处理，两处观感一致。 */
.table thead th.st-col-fcast,
.table tbody td.st-col-fcast {
  border-left: 1px solid var(--color-border);
}

/* ==========================================================================
   T+35 数值展示规范（2026-09-13 乐哥定：全系统统一，不然很乱）
   核心：**一个数 + 状态标签**。含未满 35 天时挂橙色「预估」标签，全满了不挂任何标签
        （标签的出现本身就是信号：没标签 = 这个数已经定了）。
   三类列的处理：
     · 看业绩/看赚亏（GSV、收入、利润）→ 一列（含未满）+「预估」标签
     · 要结算的钱（应结给达人/员工）    → 两列：截止目前应结 ／ 预估应结
     · 结算依据（结算基数、提成基数）    → 一列（**不含**未满）+ 下面这个灰色小字提示
   ⚠️ 结算依据类**不能用**橙色「预估」标签：它的语义与「预估」相反（这列不含未满），
      而且它乘费率就是应结金额 —— 用同一种橙色标签会让人以为两列是一回事。
      所以另立一种视觉：灰色小字，说明"未满多少没算进来"。
   ========================================================================== */
.t35-hint {
  display: block;
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-regular);
  color: var(--color-muted);
  white-space: nowrap;
  cursor: help;
}
/* 金额格里的标签不吃掉小数点的对齐：标签是 inline 的，跟在小字后面不换行 */
td .t35-hint { margin-top: 1px; }

/* 表格单元格里的「预估」标签：比 KPI 卡上的小一号，避免在密集表格里抢视线。
   ⚠️ 用 `.table tbody td .an-tag` 带上表格层：`.an-tag` 本体权重 (0,1,0)，
      会被 `.table tbody td`（0,1,2）的字号压回表格默认值（本项目反复踩的同一个坑）。 */
.table tbody td .an-tag {
  font-size: 11px;
  line-height: 16px;
  padding: 0 5px;
  vertical-align: 1px;
}
.st-drill .table--sub tbody td.st-col-forecast {
  vertical-align: top;
  padding-top: var(--space-2);
  /* ⭐ 2026-10-15 颜色收敛（乐哥：「问题不是紧凑，是颜色」，方案甲）：
     原为 `--color-primary-50` 整列浅蓝铺底 —— 与「截止目前应结」的蓝字、
     表头浅蓝底叠成**三重蓝**，钱列反而读不出重点。⇒ 列体去掉铺底，
     识别交给**表头浅蓝底 + 左侧分界线 + 字色**（都在，语义不减）。 */
  background: transparent;
}
/* ⭐⭐ 2026-10-15 第二次修（乐哥截图圈出「这个表头字体颜色不对」）——
   上面那条规则写的是 `.st-drill .table--sub thead th.st-col-fcasthead`，**只覆盖展开区**，
   而 `st-col-fcasthead` 这个类**主列表页（达人结算主表）也在用**
   ⇒ 主列表的「预估应结」表头一直是**近黑**，紧邻的「截止目前应结」（`.st-col-payable`）是主色蓝
      ⇒ 同排两列表头不平级，乐哥一眼看出来。
   ⇒ 把着色**提到通用作用域**（`.table thead th...`）：主列表 + 展开区**同时命中**。
   ⚠️ 只补 `color` / `font-weight` 两项，**不碰 `background` 与 `border-left`**：
      · 底色：主列表表头本来就是 `--table-header-bg`，与 `.st-col-payable` 一致，无需覆盖；
      · 左边框：那是「本期 | 预估」的**分组线**，展开区保留（上面那条规则里仍在），
        主列表**不加**（它的「截止目前应结」表头本来也没有），加了反而与左边不齐。
   ⚠️ 同一个毛病还有三处同类列（都用 `st-col-fcast` 不带 head）：
      `profit-settlement` 逐场明细「预估提成」、`my-commissions`「预估提成」、`huada-settlement`「预估应收」——
      它们旁边的「应结 / 应收」表头是蓝的，自己却是近黑 ⇒ 下面一并补上。 */
.table thead th.st-col-fcasthead,
.table thead th.st-col-fcast {
  color: var(--color-primary-600);
  font-weight: var(--font-weight-semibold);
}
.st-drill .table--sub thead th.st-col-fcasthead {
  /* ⭐ 2026-10-15 乐哥：「（预估应结）那个标题，应该和截止目前应结一模一样」
     —— 「截止目前应结」是**白底 + 主色蓝字 + 半粗**（`.st-col-payable`），
        而「预估应结」原为**浅蓝底块 + 深灰字**，同排两列表头不同款 ⇒ 对齐同款。
     ⚠️ 左侧 1px 分界线**保留**：它是「本期 | 预估」的**分组线**（正文格 `.st-col-fcast`
        也有同一条，2026-09-13 定的"靠这条竖线区分主次"），不属于标题样式。
        若要去掉，两处（表头 + 正文）要一起改，别只改一处留半条线。
     ⚠️ 底色写 `var(--table-header-bg)`（与基础表头同令牌），**不写 transparent**：
        两者肉眼都是白，但计算值不同（258 vs rgba(0,0,0,0)），且令牌变了它不跟随
        —— 真机验收就抓到过这一处（"一模一样"要连计算值都一致）。 */
  background: var(--table-header-bg);
  color: var(--color-primary-600);
  font-weight: var(--font-weight-semibold);
  border-left: 1px solid var(--color-border);
}
.st-drill .table--sub tbody tr.st-drill-sumrow td.st-col-fcast {
  /* ⭐ 2026-10-15 颜色收敛（方案甲）：合计行里的预估格原本也铺浅蓝
     ⇒ 与整列铺底叠加后"蓝上加蓝"。列体已去铺底，这里同步去掉（合计行靠加粗和字号表达）。 */
  background: transparent;
  font-size: var(--font-size-base);
}

/* ============================================================================
   华大营养对账（应收 / 开票台账）— 2026-09-13
   与「达人结算」同构：分达人主表 + 点行展开商品明细；多一块开票/回款台账。
   ============================================================================ */
/* 达人分账附表 · 工具栏（2026-09-27 乐哥：「这个页面的UI不对，包括日期、时间还有这些」）
   ──────────────────────────────────────────────────────────────────────────
   改前三个毛病：
     ① 标签误用 `.hs-static`（那是**弹窗只读信息块**：灰底 + 边框 + 44px 高，
        与同行输入框同档 —— 2026-10-11 乐哥要求把"间距/宽高"归位到控件标准），
        放进工具栏像个灰方块，与全站"纯文字标签"不是一个语言。
     ② 日期用浏览器原生 `type="date"` —— 自带黑色方块图标，与全站控件不搭。
     ③ 内联 `gap:8px` 覆盖了 `.toolbar` 标准的 16px ⇒ 整排看着挤。
   现在：标签走纯文字（与业绩总表「筛选：」同规格）、日期走系统 `DateTimePicker`、
       间距回归 `.toolbar` 默认值（页面不再内联 gap）。 */
.ax-label {
  font-size: var(--font-size-sm);
  color: var(--color-muted);
  flex: none;
}
/* 日期之间的连接符：与两侧 34px 控件垂直居中对齐的短横线 */
.ax-tilde {
  color: var(--color-muted);
  font-size: var(--font-size-sm);
  flex: none;
  user-select: none;
}

/* 店铺多选（乐哥 2026-09-27：「店铺选择需要支持多选，因为这里面会涉及到主体」）。
   原生 select 不支持好看的多选 ⇒ 自绘：按钮外观对齐 .select（列表档 34px），
   点开下面板逐项勾选；面板绝对定位、悬浮在内容之上。
   ⚠️ 按钮与面板都走设计令牌（--radius-md / --shadow-md / --color-surface-2），
      不写死颜色 —— 亮/暗主题都跟着走。 */
.ax-stores { position: relative; flex: none; }
.ax-stores__btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 34px;
  padding: 0 10px;
  min-width: 186px;
  max-width: 300px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  color: var(--color-fg);
  font-size: var(--font-size-base);
  cursor: pointer;
}
.ax-stores__btn:hover { background: var(--color-surface-2); }
.ax-stores__sum {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ax-stores__caret {
  margin-left: auto;
  color: var(--color-muted);
  display: inline-flex;
  align-items: center;
}
.ax-stores__panel {
  position: absolute;
  z-index: 40;
  top: calc(100% + 4px);
  left: 0;
  min-width: 100%;
  max-height: 260px;
  overflow-y: auto;
  padding: 4px;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
}
.ax-stores__opt {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 8px;
  border-radius: var(--radius-sm);
  cursor: pointer;
  white-space: nowrap;
  font-size: var(--font-size-sm);
}
.ax-stores__opt:hover { background: var(--color-surface-2); }
.ax-stores__opt input { margin: 0; flex: none; }

/* ⛔ 2026-10-16：`.hs-ledger` / `.hs-ledger__*`（开票·回款两列键值块）与 `.hs-warn`
   （对账金额已变的黄条）**已随"每店铺一张大卡"的台账布局一起删掉** ——
   乐哥：「开票与回款，我觉得做小点吧，现在这个卡片太大了…甚至都不用做成 kpi 卡片」
   ⇒ 台账改成 `pages/huada-settlement.js` 里的一张普通表格（一行一个店铺、一列一个字段）。
   ⚠️ 那个黄条的信息没丢：收进了店铺名旁边的 ⚠️ 图标悬停里（见 `ledgerTable()` 的 staleTip）。
   ⛔ 别把这几条样式加回来 —— 它们对应的 DOM 已经不存在了，加回来只会让人以为卡片还在。 */

.hs-row:hover { background: var(--color-surface-2); }

/* 华大对账主表：**分组表头行**（两级表头的第一行，乐哥 2026-10-16 定的四个模块：
   全部商品(3) / 正常佣金商品(4) / 福利款(4) / 应收(3)）。
   与「月度利润明细」的 `.pl-grouphd` 是同一个形态（那边是三级表头）。
   ⚠️⚠️ 选择器必须写到 `.table thead th.hs-grouphd`（0,2,2）——
      `.pl-grouphd` 那种只写类名（0,1,0）的做法**打不过** `.table thead th`（0,1,2），
      底色/字重会被表头默认样式吃掉（等于白写）。这里显式提权，不靠源码顺序。
   ⚠️ 高度压到 34px：表头本来是 44px；两级表头会变成 88px，太高、把表挤下去。
      分组行本身字少，34px 够（叶子行仍是 44px）。
   ⭐ 2026-10-16 乐哥：「『全部商品/普通商品/福利款/应收』这几个大类的标题，就不要做底色了。
      直接把分隔线拉上去，拉到顶就行了」
      ⇒ ① 去掉 `background`（原来那块浅灰色带）；
         ② 分组靠**竖线**表达：分组头那一格也打 `.col-sep`（见 groupThead），
            竖线从表头顶一直贯到叶子行底 —— 色块是"面"，竖线是"界"，后者不占视觉重量。
      ⚠️ 底色去掉后组名不能再比叶子表头"灰"：`--color-fg-2` 换成主文字色，
         否则二级表头看着像 disabled（组名是一级信息，本该更重）。
      ⛔ 保留 `border-bottom`（组名 与 列名 之间的那条横线）—— 它现在承担"分层"的职责，
         删了会变成一片糊的字。 */
.table thead th.hs-grouphd,
/* ⭐⭐ 2026-10-16「达人结算」主表也改成两级表头（乐哥：「按照我们刚做的华大对账单，
   达人结算这个表你觉得应该怎么改」）⇒ 这条规则**多页共用一处实现**：
     · 华大对账用 `.hs-grouphd`（2026-10-16 那批定的名字，保留不动）
     · 达人结算用 `.thead-grouphd`（中性名 —— 新页面别再挂别人页面前缀的类）
     · **月度利润明细用 `.pl-grouphd`（2026-10-16 夜并入）**
   ⛔ 不新写一条规则、也不复制样式：同一种"分组头"写两处必然漂移（本项目的老毛病）。
   ⭐⭐ 2026-10-16 夜 乐哥：「月度利润明细这个表，我们也需要按照华大结算对账表（达人应收明细）
      去改造」—— 实测发现本页的 `.pl-grouphd` 正是**上面第 4434 行注释点名的那个反例**：
      `#page-root` 那边早已写明「只写类名（0,1,0）打不过 `.table thead th`（0,1,2），
      底色/字重会被表头默认样式吃掉（**等于白写**）」，而本页恰好就是那么写的 ⇒ 实测：
        分组头字重 **500**（本该 600）、高度 **44px**（本该 34）、浅灰底色**从未生效**。
      ⇒ 并入本规则后与华大**逐项一致**：600 字重 · 34px 高 · 无底色（分隔靠竖线）。
      ⚠️ 高度 44 → 34 会让两级表头合计 88 → **78px**，与华大那张表**同一个数**
         （省下的 10px 直接从"表头占位"里拿回来，正文行高/合计行都不动）。 */
.table thead th.pl-grouphd,
.table thead th.thead-grouphd {
  height: 34px;
  text-align: center;
  color: var(--color-fg);
  font-weight: var(--font-weight-semibold);
  border-bottom: 1px solid var(--color-border);
}
/* ⭐ 2026-10-16 乐哥（截图圈出标题栏与表格之间那条空白带）：「标题第一行和顶栏间隙不要空着了」
   ⇒ 本页（华大对账）所有表格卡的卡头与表格之间不再留 24px 空白：表头紧贴淡蓝标题栏，
     标题栏 + 表头读成**一个头部区块**。
   ▸ 只去**上**边距：左 / 右 / 下仍是 24（表格两侧与卡片内其它内容保持同一条对齐线）。
   ⚠️ 只作用于本页的表格卡（`tableShell` 统一给 `.card__body` 挂这个类），
      ⛔ 不动全站 `.card__body` 的默认内边距（其它页的卡片没有淡蓝标题栏，去掉会顶到卡边）。 */
.card__body.hs-cardbody--flush-top { padding-top: 0; }

.hs-drill { padding: var(--space-2) 0 var(--space-2) var(--space-3); background: var(--color-surface-2); }
.hs-drill .table--sub { background: var(--color-bg); }
/* ⚠️ 2026-09-14 二级明细页去灰底（乐哥：「华大对账明细的 UI 也不一样，列表怎么有灰色底？」）
   上面两行的灰底是**列表页行内展开区**的设计意图（用浅灰把"展开的明细"和"主表"分层）。
   但二级明细页复用了同一套 HTML（detail.js 渲染 `.detail-drill > .hs-drill > table`），
   于是整个表格区域被铺成灰底、看着像"整块禁用/只读"。
   修法：**只在二级页作用域内**改回白底（transparent 露出白卡片），
         列表页展开区的灰底分层**原样保留**（那里是有意的，改了会破坏主表/明细的层次）。
   ⚠️ 不能直接删上面两行 —— 那会连带改掉列表页展开区的观感。 */
.detail-drill .hs-drill,
.detail-drill .hs-drill .table--sub {
  background: transparent;
}
.hs-sub-sep td {
  font-size: var(--font-size-xs); color: var(--color-muted);
  background: var(--color-surface-2); padding: var(--space-2) var(--space-3);
}

/* 合计行（tfoot 是**独立的行组**，thead/tbody 的规则一律覆盖不到它，必须单独写）：
   · 「应收」主色：本条与上文 `.cm-foot-row td.cm-co-sumtotal` 都能给它上色，
     特异性上**本条胜出**（0,2,2 vs 0,2,1）—— 所以改颜色时两条都要看，
     只改一条会出现「改了不生效」的错觉（2026-09-13 反向验证实测踩到：
     只抹掉 cm-co-sumtotal 时合计行仍是主色，排查后才发现本条在管它）。
   · 分界线：tbody/th 的规则覆盖不到 tfoot，漏写合计行那条竖线会断掉。 */
.table tfoot td.st-col-payable { color: var(--color-primary-600); font-weight: var(--font-weight-semibold); }
.table tfoot td.st-col-fcast { border-left: 1px solid var(--color-border); }

/* 「本月订单都还没满 35 天」的行内提示：用警示色而不是 muted —— 它解释的是"为什么这里是 0"，
   muted 会读成"没数据"，而实际是"数据还没到期"。 */
.hs-pend-note { color: var(--color-warning-text); }

/* ---- 分店铺（2026-09-13 追加：华大分店铺结算）---- */

/* 店铺标签：主表每行、展开区标题、台账卡片标题都用它标出「这是哪个店」。
   用主色系而不是中性灰 —— 分店铺是本页的核心区分维度，扫读时要一眼抓得到。 */
.hs-store {
  display: inline-block; font-size: var(--font-size-xs); line-height: 18px; padding: 0 6px;
  border-radius: var(--radius-sm); white-space: nowrap;
  background: var(--color-primary-50); color: var(--color-primary-700);
  border: 1px solid var(--color-primary-200);
}

/* 展开区标题：店铺标签 + 达人说明 + 右侧合计（合计靠右、要显眼 —— 财务对的就是这个数） */
.hs-drill__head {
  display: flex; align-items: center; gap: var(--space-2);
  padding: var(--space-1) 0 var(--space-2);
}
.hs-drill__sum { margin-left: auto; font-size: var(--font-size-sm); color: var(--color-fg-2); }
.hs-drill__sum b { color: var(--color-primary-700); font-variant-numeric: tabular-nums; }

/* 展开区分段标题（乐哥 2026-09-13：明细分「已满 35 天」+「未满 35 天」两段）——
   一段是已经能开票的、一段是要等订单满期的，视觉上必须一眼能分开。 */
.hs-drill__sec {
  display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap;
  margin: var(--space-3) 0 var(--space-2); padding-top: var(--space-2);
  border-top: 1px solid var(--color-border);
  font-size: var(--font-size-sm); font-weight: var(--font-weight-semibold); color: var(--color-fg);
}
.hs-drill__sec:first-of-type { margin-top: var(--space-1); }
/* 「未满」段用暖色：不是错误，但必须让人意识到"这批钱还没到账期" */
.hs-drill__sec--pend { color: var(--color-warning-text); }
.hs-drill__sec-tag {
  font-size: 11px; line-height: 17px; padding: 0 7px; border-radius: var(--radius-sm);
  background: var(--color-surface-2); color: var(--color-fg-2); border: 1px solid var(--color-border);
  font-weight: var(--font-weight-regular); white-space: nowrap;
}
.hs-drill__sec--pend .hs-drill__sec-tag {
  background: var(--color-warning-bg); color: var(--color-warning-text); border-color: var(--color-warning-border);
}
.hs-drill__sec-num { margin-left: auto; font-size: var(--font-size-sm); font-weight: var(--font-weight-regular); color: var(--color-fg-2); }
.hs-drill__sec-num b { color: var(--color-fg); font-variant-numeric: tabular-nums; }
.hs-drill__sec--pend .hs-drill__sec-num b { color: var(--color-warning-text); }

/* 分段合计行：两段各有一行合计，用来跟主表「截止目前 / 预估」对账 */
/* ⚠️ 必须带 `.table tbody` 层（0,1,2）—— 单类选择器（0,1,0）会被 .table tbody td 压过 */
.table tbody tr.hs-drill__sum-row > td {
  /* 2026-10-12：与全站 is-sum 合计行统一为淡蓝柔底（首尾呼应玻璃工具栏） */
  background: var(--color-info-bg); font-weight: var(--font-weight-semibold); color: var(--color-fg);
}

/* 福利款标记（2026-09-13）：福利款的线上佣金按「福利款对外佣金率」算、不按达人常规线上率，
   所以那一行线上率会明显更低（10% vs 40%）。不打标记会被当成"线上率填错了"。
   用暖色（与"需要注意的特例"语义一致）而不是警示红 —— 它本身不是错误。 */
.hs-wel {
  display: inline-block; margin-left: 6px; font-size: var(--font-size-xs); line-height: 16px;
  padding: 0 5px; border-radius: var(--radius-sm); vertical-align: middle; white-space: nowrap;
  background: var(--color-warning-bg); color: var(--color-warning-text);
  border: 1px solid var(--color-warning-border); font-weight: var(--font-weight-semibold);
}
/* 应付星盟为负的行：标记原因（多半是上游佣金率没配）。用警示红 —— 这确实是需要处理的问题。 */
.hs-neg-tag {
  display: inline-block; margin-left: 6px; font-size: var(--font-size-xs); line-height: 16px;
  padding: 0 5px; border-radius: var(--radius-sm); vertical-align: middle; white-space: nowrap;
  background: var(--color-danger-bg); color: var(--color-danger-strong);
  border: 1px solid var(--color-danger-border); font-weight: var(--font-weight-semibold);
}

/* 福利款行的整行底纹：让它在明细里成片可辨（一屏多行时比逐行找标签快） */
.table tbody tr.hs-prod-wel > td { background: var(--color-warning-bg); }
.table tbody tr.hs-prod-wel > td:first-child { box-shadow: inset 3px 0 0 var(--color-warning-accent); }

/* 自定义 SKU：等宽字体（编码类内容用等宽更好逐位核对），过长时省略号 + 悬停看全 */
.hs-sku {
  display: inline-block; max-width: 140px; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; vertical-align: bottom;
  font-family: var(--font-family-mono); font-size: var(--font-size-xs); color: var(--color-fg);
}

/* 弹窗里的只读信息块（店铺 + 该店铺应收 / 开票主体 + 账期），与输入框区分开。
   ⭐⭐ 2026-10-11 乐哥（截图圈着这个块）：「**这个间距，还有宽高没按照标准做**」——
   它当初（2026-09-13 建时）是随手定的值，**三处都跟同行的输入框不一致**，实测：
       高 38 vs `.input` 44      （§6.2 五类基础控件一律 `--control-height`）
       内留白 左右 12 vs 16      （§6.3 表单区控件左右留白 16px）
       圆角 `--radius-sm` 4 vs `--radius-md`        （与同行输入框不是一个形）
   ⇒ 全部归位到**与同行 `.input` 同档**。只读块也是"字段的控件"（与 §6.2 那条
      `.form-grid .sf-readonly` 限宽是同一条道理），尺寸必须跟旁边那个框同档。
   ⚠️ 边框色**刻意不同**（`--color-border` vs 输入框的 `--input-border`）+ 灰底，
      这是"只读"的语义信号，别一起改成输入框的样子。
   ⚠️ `padding` 走 `8px 16px` 而不是 `0 16px`：本块是 `flex-wrap`，内容换行时
      上下要留呼吸（单行时仍由 `min-height` 决定高度 = 44，与输入框齐平）。 */
.hs-static {
  display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap;
  padding: 8px var(--space-4);
  border-radius: var(--radius-md);
  background: var(--color-surface-2); border: 1px solid var(--color-border);
  min-height: var(--control-height);
}

/* 发票文件选择器（支持 PDF，故不复用只收图片的 .img-upload） */
.hs-file { display: flex; align-items: center; gap: var(--space-3); }
.hs-file__box {
  flex: 1; min-width: 0; padding: 8px var(--space-3);
  border: 1px dashed var(--color-border); border-radius: var(--radius-sm);
  font-size: var(--font-size-sm); color: var(--color-fg-2); cursor: pointer;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.hs-file__box:hover { border-color: var(--color-primary-400); color: var(--color-primary-600); }

/* ==========================================================================
   排期详情 · 订单明细页签（乐哥 2026-09-13）
   点排期编号从提成总览/达人结算下钻过来对数用。规格：订单 + 商品 + 两口径对照 + 自检。
   ========================================================================== */
.sdo-filters { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; }

/* 口径说明条：左侧主色竖线，与页面其他"口径提示"一致（st-pending-bar / hs-tip 同风格） */
.sdo-tip {
  display: flex; gap: var(--space-2); align-items: flex-start;
  background: var(--color-accent-bg); border: 1px solid var(--color-border-soft);
  border-left: 3px solid var(--color-accent);
  border-radius: var(--radius-sm);
  padding: var(--space-3) var(--space-4);
  margin-bottom: var(--space-3);
  font-size: var(--font-size-xs); line-height: 1.9; color: var(--color-fg-2);
}
.sdo-tip b { color: var(--color-fg); }
.sdo-em { color: var(--color-fg); font-weight: var(--font-weight-semibold); }

/* 订单状态构成 */
.sdo-chips { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; margin-bottom: var(--space-3); }
.sdo-chips__k { font-size: var(--font-size-xs); color: var(--color-meta); }
.sdo-chip {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: var(--font-size-xs); color: var(--color-meta);
  border: 1px solid var(--color-border-soft); border-radius: var(--radius-lg);
  padding: 3px 10px; background: var(--color-surface);
}
.sdo-chip b { color: var(--color-fg); }

/* 商品单元格：多商品单一行内纵向排列，行间虚线分隔 */
.sdo-prod { line-height: 1.5; padding: 2px 0; }
.sdo-prod + .sdo-prod { border-top: 1px dashed var(--color-border-soft); margin-top: 4px; padding-top: 4px; }
/* 商品第 1 行 = 商品名 +（售后状态标签）。
   ── 三次演进（都记着，避免以后来回改）──
   ① 最初：标签放在第 2 行（.sdo-prod__meta）里 → 那一行被挤爆，标签断成两行、该行 90px。
   ② 2026-09-13 上午：标签移到第 1 行，配 `.sdo-prod__head{display:flex}` + 商品名 `nowrap + ellipsis`。
      挤压解决了，但埋下新问题：商品名 nowrap 会把列撑到 **459px**、整表横向滚 **106px**
      （搜「畅」这类长套餐名时，「净额」「状态」列要左右拖才看得到）。
   ③ 2026-09-13 傍晚（乐哥拍板「商品名可折两行」）：商品名**允许折行**、取消截断。
      商品名既已能折行，就不需要 flex + ellipsis 那套了 → head 改回普通流式布局，
      售后标签改成 inline 紧跟文字（折行时自然跟在**末行之后**，不会孤零零浮在右边）。 */
.sdo-prod__head { line-height: 1.45; }
.sdo-prod__head .sdo-after { white-space: nowrap; margin-left: 4px; }
/* 商品名：**允许折行**（乐哥 2026-09-13 傍晚拍板，订单明细表 / 商品汇总表 两处统一）。
   原来是 `max-width:420px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap` —— 单行截断，
   两个表都被它坑过：
     · 商品汇总表：nowrap 把富余宽度霸住不放 → 窄列（自定义SKU / 类型）被压到断行 / 竖排；
     · 订单明细表：nowrap 使该列的 min-content = 全文宽（实测 344px）→ 列被撑到 459px，整表横向滚。
   改成折行后：窄列可以锁死单行、表格不再横向滚动，长名也能看全（占几行由内容自然决定）。
   `overflow-wrap: break-word` 是兜底 —— 万一出现超长且无断点的片段（长英文串 / 长编码）也不会溢出。 */
.sdo-prod__nm {
  white-space: normal; overflow-wrap: break-word; line-height: 1.45;
}
/* 第 2 行（SKU · 类型 · 数量）：子项**一律不许被压缩**（`flex: 0 0 auto` + nowrap）。
   背景（乐哥 2026-09-13 修）：这一行原来是 `flex-wrap:nowrap` 且子项可压缩，
   当总宽超出单元格时子项会被挤扁 →「套餐」竖排、文字在盒内断行、行高被撑高（实测 90px vs 72px）。
   `flex-wrap: wrap` 作为兜底：万一将来又有超宽内容，它会整块换到第二行，而不是挤成一团。 */
.sdo-prod__meta {
  display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap;
  font-size: var(--font-size-xs); color: var(--color-meta); margin-top: 2px;
}
.sdo-prod__meta > * { flex: 0 0 auto; white-space: nowrap; }
/* SKU / 类型 胶囊：**必须显式给字号**（乐哥 2026-09-13 统一）。
   原来不写字号 → 跟着父级走：在「订单明细表」里它们位于 .sdo-prod__meta（12px）内 → 12px；
   在「商品汇总表」里直接放在 td 里（14px）→ 14px。**同一个胶囊在同一页出现两种字号**。
   标签/胶囊按项目惯例统一 12px（与 .st-drill-tag 等一致），改这里两表一起生效。 */
/* ⚠️ 这一行必须 `white-space: nowrap`（乐哥 2026-09-13 修）。
   它是个 15~16 位、带连字符的编码（如 YY013501-0000005）：`normal` 下浏览器会**在连字符处断行**，
   于是「商品汇总」表里每格变 2 行（实测高 35px），11 行累计多占近 300px。
   nowrap 之后它的 min-content = 整串宽（约 115px），表格再也压不动它，只能去压商品名列。 */
.sdo-sku {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: var(--font-size-xs);
  background: var(--color-surface-2); padding: 0 5px; border-radius: var(--radius-sm);
  white-space: nowrap;
}
/* ⚠️ 同样必须 `white-space: nowrap`（乐哥 2026-09-13 修）。
   中文**任意两个字之间都允许断行** → `normal` 下「套餐」被拆成竖排的「套 / 餐」（实测高 40px、2 行），
   这是本次「商品维度 UI 有问题」里最难看的一处。
   注：在「订单明细表」里它位于 .sdo-prod__meta（`> * { white-space:nowrap }`）→ 本来就正常；
   只有「商品汇总表」里它是 td 的直接子元素，才暴露了这个缺陷。 */
.sdo-kind {
  font-size: var(--font-size-xs); border: 1px solid var(--color-border-soft);
  padding: 0 5px; border-radius: var(--radius-sm); white-space: nowrap;
}
.sdo-after { color: var(--color-warning); }
.sdo-unmatched { color: var(--color-danger); font-size: var(--font-size-xs); }

/* ── 「商品汇总」表：把宽度精确让给唯一可换行的商品名列（乐哥 2026-09-13）──────────────
   问题：`table-layout: auto` 下所有列都参与"分配剩余宽度"，商品名列虽然是弹性列却没优先权，
        结果只有 302px（最长的商品名要 344px）→ 长名折成双行、同表行高 46/66 不一致。
   修法：让**除商品名以外的所有列锁死单行**（`nowrap`）。这样它们的 min-content == max-content，
        浏览器压不动它们，只能把富余宽度全部让给唯一还能换行的商品名列。
   ⚠️ 为什么不直接改全站的 `.is-num`：那是 `table.css` 里的公共类，全站几十张表都在用，
      本项目铁律「改公共类前先查复用范围」—— 所以这里**新开一个只给本表用的作用域类**，
      原 `.num` / `.is-num` 一字未动。 */
.sdo-sumtbl thead th.is-num,
.sdo-sumtbl tbody td.is-num,
.sdo-sumtbl tfoot td.is-num { white-space: nowrap; }
/* ⚠️ 这里**不需要**给表头单独写 nowrap —— 全站 `.table thead th` 本来就是 `nowrap`（table.css:27）。
   曾误加过 `.sdo-sumtbl thead th { white-space: nowrap }`，既冗余、配套断言也恒真（假绿），已一并删除。 */

/* 订单号：该列的主信息 → 与其它主信息列同为正常字号（原 12px 与全表 14px 不齐）。
   19 位数字 + 等宽字体，配合加宽后的列宽（190px）刚好一行放下（乐哥 2026-09-13）。 */
.sdo-no { font-size: var(--font-size-sm); }
.sdo-rf { color: var(--color-danger); }
.sdo-ses {
  font-size: var(--font-size-xs); white-space: nowrap;
  background: var(--color-accent-bg); color: var(--color-accent-text);
  padding: 1px 7px; border-radius: var(--radius-lg);
}
.sdo-st {
  font-size: var(--font-size-xs); white-space: nowrap;
  background: var(--color-surface-2); color: var(--color-fg-2);
  padding: 1px 7px; border-radius: var(--radius-sm);
}
.sdo-st--cancel { background: var(--color-danger-bg); color: var(--color-danger); }

/* 对账自检条：通过=绿框（让财务一眼确认对得上）；异常=红框（口径被破坏，必须显眼） */
.sdo-chk {
  margin-top: var(--space-3); padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-border-soft);
  border-left: 3px solid var(--color-success);
  background: var(--color-success-bg);
  border-radius: var(--radius-sm);
  font-size: var(--font-size-xs); line-height: 1.95; color: var(--color-fg-2);
}
.sdo-chk b { color: var(--color-fg); }
.sdo-chk--bad { background: var(--color-danger-bg); border-color: var(--color-danger); }

/* 口径说明里的「当前范围」（全部场次 / PQ000009-1）——用底色胶囊，与正文区分开 */
.sdo-scope {
  display: inline-block; margin-left: 8px;
  font-size: var(--font-size-xs); font-weight: var(--font-weight-regular);
  background: var(--color-accent); color: var(--color-accent-on);
  padding: 1px 8px; border-radius: var(--radius-lg);
}

/* ==========================================================================
   KPI 卡内的可点提示（乐哥 2026-09-13）
   --------------------------------------------------------------------------
   用于「N 张单数据有变化，重新计算」这类"看到问题就能顺手解决"的提示。
   刻意做成**文字链**而不是按钮：4 张卡里通常只有 1 张会出现它，
   做成按钮会显得突兀、也抢了卡片主数字的注意力。
   ========================================================================== */
.kpi-card__act {
  display: block; width: 100%; margin-top: var(--space-1); padding: 0;
  border: 0; background: none; text-align: left; cursor: pointer;
  font-size: var(--font-size-xs); line-height: 1.5;
  color: var(--color-warning-text);
}
.kpi-card__act:hover { text-decoration: underline; }
.kpi-card__act-go { font-weight: var(--font-weight-medium); text-decoration: underline; }
.kpi-card__act:disabled { opacity: 0.6; cursor: default; text-decoration: none; }

/* ==========================================================================
   口径说明条：「完整口径」点击展开（乐哥 2026-09-13）
   --------------------------------------------------------------------------
   改前是 hover 气泡装一整段长文（华大那页 1191 字）—— 读长文别扭、读到一半会消失、
   也没法复制。改成按钮展开：默认只占一行，展开后按主题分小节（小标题 + 要点列表）。
   ⚠️ 与原 .st-rule__more（气泡版）并存：三页改造是分批做的，另一页改造完可一并清掉。
   ========================================================================== */
.st-rule__toggle {
  margin-left: var(--space-2); padding: 0; border: 0; background: none; cursor: pointer;
  font-size: inherit; font-family: inherit; line-height: inherit;
  color: var(--color-primary-500); border-bottom: 1px dashed currentColor;
}
.st-rule__toggle:hover { color: var(--color-primary-600); }
.st-rule__full {
  margin-top: var(--space-2); padding: var(--space-3) var(--space-4);
  background: var(--color-surface-2); border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-sm);
  font-size: var(--font-size-xs); line-height: 1.9; color: var(--color-fg-2);
}
.st-rule__sec + .st-rule__sec {
  margin-top: var(--space-3); padding-top: var(--space-3);
  border-top: 1px dashed var(--color-border-soft);
}
.st-rule__sec-t { font-weight: var(--font-weight-medium); color: var(--color-fg); margin-bottom: 2px; }
.st-rule__list { margin: 0; padding-left: 18px; }
.st-rule__list li { margin: 0; }

/* ==========================================================================
   结算单展开区 · 「未满 35 天」分段（乐哥 2026-09-13）
   --------------------------------------------------------------------------
   背景：8 月 13 张结算单里 11 张是「全未满」（最大 ¥1,212,806.22 / 2094 单），
   而展开区此前直接返回一句「无业绩明细」—— 未满的钱、单数、场次全都看不到。
   现在把未满部分也**按场次列进同一张表**，用一条分段标题行与已满部分隔开。
   设计要求：既要在视觉上明确"这批还没到期"，又不能让它比"现在能付的钱"还抢眼
   → 用浅灰底 + 左侧虚线，不用红/黄警告色（那是异常的颜色，未满是正常业务状态）。
   ========================================================================== */

/* 分段标题行：跨全表宽的一条小标题 */
.st-drill .table--sub tbody tr.st-drill-segrow td {
  background: var(--color-surface-2);
  border-top: 2px solid var(--color-border);
  border-bottom: 1px solid var(--color-border-soft);
  padding-top: var(--space-2);
  padding-bottom: var(--space-2);
  white-space: nowrap;
}
.st-drill-segrow__t {
  font-weight: var(--font-weight-semibold);
  color: var(--color-fg-2);
  margin-right: var(--space-3);
}
.st-drill-segrow__d { color: var(--color-meta); font-size: var(--font-size-xs); }

/* 未满行：浅灰底 + 首列左侧虚线，表明"这一段的性质与上面不同"（还没到期） */
.st-drill .table--sub tbody tr.st-drill-pendrow td {
  background: var(--color-surface);
  color: var(--color-fg-2);
}
.st-drill .table--sub tbody tr.st-drill-pendrow td:first-child {
  border-left: 2px dashed var(--color-border);
}
.st-drill .table--sub tbody tr.st-drill-pendrow:hover td { background: var(--color-surface-2); }

/* 未满合计行：比正常合计更弱（它是"还没到期的预估"，不是现在能付的钱） */
.st-drill .table--sub tbody tr.st-drill-sumrow--pend td {
  border-top: 1px dashed var(--color-border);
  color: var(--color-meta);
}
.st-drill .table--sub tbody tr.st-drill-sumrow--pend td:first-child { color: var(--color-meta); }

/* ==========================================================================
   「本期佣金构成」—— 达人结算展开区（乐哥 2026-09-13）
   原话：「线上佣金/线下·机构/年框/线下·返点/激励佣金 怎么都是空的，我都不知道每个项目
         佣金多少；展开明细只有应结佣金，但结算的是哪个项目的佣金、什么点数结算，我都看不到」
   实测根因：某达人**配了年框 3%** 却没计入（未签合同），页面只有静默的 ¥0.00 + 「无适用维度」，
   一个字都不解释。本区块把五个项目逐项摊开（费率 / 基数 / 金额 / **为什么是这个数**）。
   放在业绩构成表**之前**：先给结论（该付多少、什么点数），再给依据（哪几场算出来的）。
   ========================================================================== */
.st-comp { margin-bottom: var(--space-4); }
/* 与「业绩构成」标题条同款，但用分隔线把两个区块分开，避免看成一整块 */
.st-comp__head {
  display: flex; align-items: center; gap: var(--space-2);
  margin-bottom: var(--space-3);
  padding-bottom: var(--space-2);
  border-bottom: 1px solid var(--color-border-soft);
}
.st-comp__head svg { flex: none; color: var(--color-accent); }
/* ⭐ 2026-10-15 区段标题 14 → 16px（乐哥：「这个表的 ui 整体按照设计规范重做下」）
   ──────────────────────────────────────────────────────────────────────────
   规范 DESIGN-SPEC §3 字号档位明文：
     `xs 12` 辅助/徽章 · `sm 14` **表格正文** · `lg 16` **卡片标题** · `xl 20` 模块标题 …
   ⇒ 「本期佣金构成」「业绩构成」是**卡片内的区段标题**，属 `lg 16` 档；
     写 14px 就与它下辖的表格正文**同号**，扫读时标题被正文淹没（看不见层级）。
   同板块的「提成明细」页用的 `.detail-section__title` 已按此改为 16px/600
   （那是 09-28 特意从 14 提上来的，理由一模一样），本页对齐它 —— 两页同层级标题
   此前一个 14 一个 16，看着不像同一个系统。
   ⚠️ 本类同时被**列表页行内展开区**使用（同一个 `drillHtml`）⇒ 展开区标题同步变大，
      这是**有意的**：同一组件同一种标题只该有一个字号档，且展开区宽度不受影响。 */
.st-comp__t { font-size: var(--font-size-lg); font-weight: var(--font-weight-semibold); color: var(--color-fg); }
.st-comp__meta { margin-left: auto; font-size: var(--font-size-xs); color: var(--color-meta); }
.st-comp__empty {
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  background: var(--color-surface-2);
  font-size: var(--font-size-sm); color: var(--color-fg-2);
}
.st-comp__empty b { color: var(--color-fg); }

/* 表格：表头弱化（与业绩构成表保持一致，避免两块表头抢注意力） */
/* ⚠️ 2026-09-14 删除表头皮覆盖（乐哥「所有列表统一标准」）：
   原为 `.st-comp__tbl thead th { background: transparent; color: var(--color-meta);
   font-weight: medium; }` —— 这是**第三套表头皮**（项目里共三套：
   主表 `.table thead th` 浅灰底 44px / 子表 `.table--sub thead th` 透明 28px /
   本表 透明 34px），三者并存就是乐哥反馈的「表头高度和颜色很多表格不一致」。
   现全部并入主表那套，此处不再覆盖任何表头样式。
   注意：tbody 的 `vertical-align: top` 保留（那是内容对齐，与表头无关）。 */
.st-comp__tbl tbody td { vertical-align: top; }
/* ⭐⭐⭐ 2026-10-16 晚 压紧（乐哥：「我觉得这个页面没有那么多内容，就不需要占用这么大的板块」）
   ──────────────────────────────────────────────────────────────────────────────
   实测（线上真机）：这块总高 **510px** = 卡头 56 + 表头 44 + 5 行（63/63/63/63/63）+ 合计 43。
   63px 的行高来自**格内两行**（金额 + 下面一句小字口径）+ `.table tbody td` 的 `padding: 12px`。
   两刀：
     ① 行内小字口径 → 搬到列名的 ⓘ（JS 侧，见 influencer-settlement.js 的 `thBaseNote`）
        ⇒ 每行回到**单行**；
     ② 内距 12 → 6（只限本表，⛔ 不动 `.table` 的全局值）。
   实测效果：行高 63 → ~37；再叠加"只渲染已配置项"（这张单少了 3 行）⇒ 总高 510 → ~250。
   ⛔ 表头高度**不动** —— `.st-comp__tbl` 的表头已统一到主表那套（44px），
      那是"全站表头一致"的要求（乐哥曾反馈「表头高度和颜色很多表格不一致」），别为压这块破掉它。 */
.st-comp__tbl tbody td { padding: var(--space-2) var(--space-3); }
/* ⚠️⚠️ 上面那条**在明细页里不生效**（本轮实测踩到）：
   明细页有两处让数据行变高的规则，特异性都比它高 ——
     · `.st-comp__tbl tbody td`                     = (0,1,2)
     · `#dt-body .table--sub > tbody > tr > td`     = **(1,0,3)** ← 压过它，上下内距被钉回 12px
   实测行高 46px（12+12+21+1），怎么改上面那条都不动。
   ⇒ 必须在 `#dt-body` 作用域下再写一条（(1,0,3) 同级 + 排在后面）。
   ⚠️ 只压**这张表**（佣金构成是"摘要块"，3~5 行每行一个结论）；
      同页的「业绩构成」是明细流水，保持 `#dt-body` 那套 12px —— **两类不同密度是有意的**，
      ⛔ 别为了"统一"把明细流水的行距也压掉（那会让几十行变难读）。 */
#dt-body .st-comp__tbl > tbody > tr > td {
  padding-top: var(--space-2);
  padding-bottom: var(--space-2);
}
/* ⭐ 2026-10-15 列宽收敛（乐哥：「表格 ui 还有布局也优化下」，方案 B）：
   此前「计算基数」列**没写宽度** ⇒ 自动布局把全部剩余塞给它（真机实测 **489px**，
   其余列 82~247px），数字右对齐飘在最右、表头悬在半空 —— "空档"就是这么来的。
   `table-layout: fixed` + 表头显式宽度（140/88/210/130，见 influencer-settlement.js）
   ⇒ 状态列吃剩余，数字回到表头正下方。 */
/* ⭐⭐⭐ 2026-10-16 深夜 乐哥（截图把「状态」列右侧那一大块空白框起来）：「**右边太宽了**」
   ──────────────────────────────────────────────────────────────────────────────
   实测（线上真机，卡片宽 1485px）：
     佣金项目 150 · 费率 96 · 计算基数 240 · 金额 150 · **状态 849（占 57%）**
     而「状态」列的实际内容只有 **210px** ⇒ 白白多出 639px 的空白。
   根因：`table-layout: fixed` + `width: 100%` + 只有前四列给了死宽度 ⇒
      **剩余全部喂给最后一列**（这正是 2026-10-15 修过的那个老毛病，换了列又来一次）。
   修法：给表格一个**上限宽度**（`max-width`）——这块只有 5 列、内容短，
     本来就不该铺满整张卡片；窄屏时仍回到 `width: 100%` 自适应。
     900px 的分配（实测）：150 + 96 + 240 + 150 + **264**（状态）= 900，
     状态列 264 ≥ 内容 210 ⇒ 刚好读得开，右侧不再出现"半张表都空着"。
   ⛔ 别把 `max-width` 去掉（会退回 849px 的状态列）；
   ⛔ 也别把 `width: 100%` 去掉（窄屏会横向溢出）。两条一起才是完整判据。 */
.st-comp__tbl { table-layout: fixed; width: 100%; max-width: 900px; }

/* 项目名：这一列是"主角"，给主文字色 + 中等字重 */
.st-comp__nm { color: var(--color-fg); font-weight: var(--font-weight-medium); }
/* 「平台直付」小标签：说明这一项不是我方掏钱，避免被误算进应付 */
.st-comp__tag {
  display: inline-block; margin-left: 6px; padding: 0 5px;
  border-radius: var(--radius-sm);
  background: var(--color-surface-2); color: var(--color-meta);
  font-size: var(--font-size-xs); font-weight: var(--font-weight-regular);
}
/* 费率：乐哥要的"什么点数结算"，加粗；「—」表示未配置 */
.st-comp__rate { font-weight: var(--font-weight-semibold); color: var(--color-fg); }
.st-comp__na { color: var(--color-muted); font-weight: var(--font-weight-regular); }
/* 金额：有值时用主色（与「截止目前应结」同色系，一眼认出"这是我方要付的"）
   ⚠️⚠️ 但**「线上佣金」是唯一不该用这个蓝的一项** —— 乐哥 2026-10-11 原话：
      「所有线上的佣金…颜色都不要用强调的蓝色了，**因为这个钱不是我们结算，我只能看到就行**」。
      ⇒ 判据就是 `pay`（= 是否计入"我方应付"）：只有**我方要掏的钱**才配这个强调蓝。
        上面那句注释写的"一眼认出这是我方要付的"，而线上佣金恰恰**不是** —
        此前它跟着 `is-on` 一起被涂成了蓝，视觉语义是错的（乐哥抓的就是这条）。
      ⇒ `pay:false` 的项走 `is-passive`：次要色 + 常规字重，一眼看出"不是我们要付的"。 */
.st-comp__amt { font-weight: var(--font-weight-semibold); }
.st-comp__amt.is-on { color: var(--color-accent-text); }
.st-comp__amt.is-passive { color: var(--color-muted); }
/* 状态说明：三种语义各有颜色 —— 未配置=灰（无需行动）、已计入=**中性深灰**、未计=警示橙
   ⭐ 2026-10-15 颜色收敛（乐哥：「问题不是紧凑，是颜色」，方案甲）：
   原「已计入=绿（success）」与「未计=橙」「应结=蓝」并列 ⇒ 一张表三套语义字色。
   已计入是**常规态**（不需要动作）、未计才是要看的 ⇒ 常规态回中性，
   **颜色只留给"要注意的"**（未计橙）与"钱"（应结蓝）。 */
.st-comp__st { font-size: var(--font-size-xs); line-height: 1.5; }
.st-comp__st.is-off { color: var(--color-muted); }
.st-comp__st.is-on { color: var(--color-fg-2); }
.st-comp__st.is-warn { color: var(--color-warning-text); }
.st-comp__st b { font-weight: var(--font-weight-semibold); }
/* 合计行：与展开区其它合计行同款（上方虚线、次要底色） */
.st-comp__sumrow td {
  border-top: 1px dashed var(--color-border);
  color: var(--color-meta);
}
.st-comp__sumrow td:first-child { font-size: var(--font-size-xs); }
.st-comp__sumrow b { color: var(--color-fg); }

/* 「本表与列表金额不一致」提示条（生成草稿后费率/订单/合同又变过）
   —— 构成表用实时费率，主表用单上快照，两者不等时**必须显式说明**，
   否则用户看到两个不同的数会直接判定"算错了"。配色用警示（不是错误，是"需留意"）。 */
.st-comp__drift {
  display: flex; align-items: flex-start; gap: var(--space-2);
  padding: var(--space-3); margin-bottom: var(--space-3);
  border-radius: var(--radius-md);
  background: var(--color-warning-bg); color: var(--color-warning-text);
  font-size: var(--font-size-xs); line-height: 1.7;
}
/* 图标垂直对齐（首行行高 1.7em，本条字号是 xs=12px → 20.4px → 下移 (1.7em − 16px) / 2 = 2.2px，原手写 1px 偏上 1.2px）
   ⚠️ 用 em 而非 px：本条 font-size 是 --font-size-xs，与其它提示条不同档，px 会写错。 */
.st-comp__drift svg { flex: none; align-self: flex-start; margin-top: calc((1.7em - 16px) / 2); }
.st-comp__drift b { font-weight: var(--font-weight-semibold); }

/* ==========================================================================
   统一「明细页」—— 2026-09-13（乐哥：行展开统一改为下级页面）
   ---------------------------------------------------------------------------
   9 处「详情类」展开改为独立明细页（detail.js 承载），这些样式是它的统一外壳。
   与列表页的关系：点行进明细 → 点「返回」回列表（保留筛选与滚动位置）。
   ⚠️ 全部走设计 token，无硬编码色值；间距用 --space-* 保持全站节奏。
   ========================================================================== */

/* 页头：返回按钮 + 标题（+ 右侧操作区）
   ⭐⭐ 2026-10-06 深夜（乐哥：「这些里面的 ui 显示问题，**莫名多了个模块**，我看了下全局都这样」）
   ────────────────────────────────────────────────────────────────────────
   ▸ **背景**：9-28 乐哥拍板「方案 A」包的白底圆角卡（当时底盘是灰的，白卡有对比、好看）；
     10-06 壳层把底盘改成接近白（#F6F6F6）且内容区本身也是白卡 ⇒ **白上白**，
     层次读不出来，只剩一圈边框"钩"着一个空框 ⇒ 就是他说的"莫名多了个模块"。
   ▸ **改法（乐哥 2026-10-06 深夜拍板「去框，融进页面」）**：
     去掉白底 + 边框 + 圆角，**只留间距**；层级交给标题的字号/字重表达。
     ⇒ 与同文件下面 `.detail-head--bare`（19 个页面的"精简页头"，本来就不包白卡）
        **终于一致** —— 同一天定的两套形态，10-06 之前一直是打架的。
   ⛔⛔ **只改视觉层，布局一行不动**：
     · `padding` 保留（横向内距是内容对齐基准，去掉会让标题与下方字段左缘错位）；
     · `margin-bottom` 保留（那是与下方 section 的间距，去掉会粘连）；
     · ⛔ `--form-max-width` 与本类**同值**那条隐性依赖（layout.css 的
       「两源取小、必须同值」）指的是**宽度**，与背景无关 ⇒ 不受影响。
   ⚠️ 为什么不能只留阴影：阴影是"浮起"，而这一层**本来就不该浮** ——
      它下面就是内容区白卡，上面浮一个白卡等于凭空多一层。 */
.detail-head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-5);
  background: transparent;
  border: none;
  border-radius: 0;
  /* ⚠️ 纵向内距由 12 收到 4：无框之后原来那圈"卡内留白"会读成无意义的空隙。
     横向（--space-6 = 24）保持不变 —— 那是标题与下方字段左缘的对齐基准，不能动。 */
  padding: var(--space-1) var(--space-6);
}

/* ⭐ 2026-09-28「精简页头」（乐哥拍板 A 方案）——**无返回、无标题**时用的形态：
   19 个页面（表单 12 + 明细 6 + 机制比价 1）只保留说明行/操作行，不再包白卡
   （单行灰字包一张白卡很怪；白卡是"页头"的壳，页头没了壳也就不需要）。
   ⚠️ 触发：`headNav` 收到「无 back 且无 title」时自动加本类（ui.js）。 */
.detail-head--bare {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
  background: transparent;
  border: 0;
  border-radius: 0;
  padding: 0;
}
/* 返回：做成明确的"按钮"形态（不是裸文字）—— 技能 §9 要求返回可预测且显眼 */
.detail-back {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  flex: none;
  height: var(--control-height-sm);   /* 两档制：32 → 34（紧凑档，返回按钮） */
  padding: 0 var(--space-3) 0 var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  color: var(--color-fg-2);
  font-family: inherit;
  font-size: var(--font-size-sm);
  cursor: pointer;
  transition: background-color var(--motion-fast) var(--motion-ease-standard),
    color var(--motion-fast) var(--motion-ease-standard),
    border-color var(--motion-fast) var(--motion-ease-standard);
}
.detail-back:hover { background: var(--color-surface-2); color: var(--color-fg); border-color: var(--color-gray-400); }
.detail-back:active { background: var(--color-gray-100); }
.detail-back:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.detail-head__main { flex: 1; min-width: 0; }
.detail-head__act { flex: none; }

/* ⭐ 2026-09-28 页头操作按钮与「页面头部」同规（乐哥：编辑按钮颜色得统一）。
   .detail-head__act 里的 ghost 按钮与 .page-actions 的加强版一致：
   描边 gray-400（可辨）+ 主文字色 + medium 字重 + hover 转主色。 */
.detail-head__act .btn--ghost {
  background: var(--color-surface);
  border-color: var(--color-gray-400);
  color: var(--color-fg);
  font-weight: var(--font-weight-medium);
}
.detail-head__act .btn--ghost:hover:not(:disabled) {
  border-color: var(--color-accent-text);
  color: var(--color-accent-text);
  background: var(--color-primary-50);
}

/* 内容堆叠：多张卡片之间统一间距 */
.detail-stack > * + * { margin-top: var(--space-4); }

/* 键值网格（成本明细）：**按内容自适应列数**，长值横跨整行避免被截断。
   ⭐⭐ 2026-10-11 乐哥两条反馈（同一处）：
     ①「**费用期间结束日这一行字怎么还断行了**」——
        🔍 根因：`.dt-kv__k` 宽度写死 **88px**，而最长的字段名是
        「费用类型（原始）」**8 个汉字**（13px 字号 ≈ 104px）⇒ 必然折成两行。
        ▸ 实测这份清单里 7 字以上的一共 4 个（费用类型（原始）/ 费用期间结束日 /
          发票附件（多）/ 支付凭证（多）），所以按**最长 8 字**定宽。
        ⚠️ 宽度必须**写死**、不能自适应：label 是 flex 子项，用 `max-content` 会变成
          "每行各自算宽" ⇒ 各行的值起始位置参差，比断行更难看。
        ⚠️ 加 `white-space: nowrap` 兜底 —— 将来再加更长的字段，宁可挤到值那一侧
          （值有 `min-width:0` 会缩），也**不许**再把字段名折行（折行最难读）。
     ②「**我看两边还有很多空位置的**」——
        🔍 原布局固定 2 列、每列平分（内容区 1088px 时每列 630px），而值普遍很短
        ⇒ 每个字段右边空一大片。
        ▸ 改成 `auto-fill`：宽屏自动排 3 列（每列 ≈364px），窄屏自动降回 2 列 / 1 列。
        ⚠️ 用 `1fr` 而非固定宽 ⇒ 列会均分剩余空间，不会在右侧留空轨道。 */
.dt-kv { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: var(--space-2) var(--space-5); }
.dt-kv__row { display: flex; gap: var(--space-2); min-width: 0; font-size: var(--font-size-sm); line-height: 1.7; }
.dt-kv__row--wide { grid-column: 1 / -1; }
.dt-kv__k { flex: none; width: 112px; white-space: nowrap; color: var(--color-muted); }
.dt-kv__v { min-width: 0; color: var(--color-fg); word-break: break-word; }
/* 窄屏收敛为单列，避免两列挤压 */
@media (max-width: 760px) {
  .dt-kv { grid-template-columns: minmax(0, 1fr); }
}

/* 明细页里的「展开区同款内容」（结算 / 华大对账）：
   这两块原本是列表内的展开区（自带 `.st-drill` / `.hs-drill` 的标题条与内边距），
   直接搬进明细页即可，只需去掉它作为"表格内嵌行"时的多余外边距。 */
.detail-drill .st-drill,
.detail-drill .hs-drill { margin-bottom: 0; }

/* ==========================================================================
   工作台首页改造（乐哥 2026-09-13）
   --------------------------------------------------------------------------
   新增：.chart-grid--three（投流消耗 / 今日排期 / 待办 三列）、.adcost（投流消耗展示）
   ========================================================================== */

/* 三列网格：底栏三块（投流消耗 / 今日排期 / 待办提醒）
   ⚠️ 窄屏必须降级为单列，否则每列 ~380px 会把「今日排期」的表格挤到换行。 */
.chart-grid--three { grid-template-columns: repeat(3, minmax(0, 1fr)); }
@media (max-width: 1279px) {
  .chart-grid--three { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 899px) {
  .chart-grid--three { grid-template-columns: minmax(0, 1fr); }
}

/* 投流消耗：主金额大号 + 占 GSV 比的细进度条
   ⚠️ 2026-09-20 删掉「投产比 ROI」那一格（乐哥：「roi 这个整体都不要了」）后，
      以下四条变成死样式、一并移除：
        .adcost__grid（两列 grid）/ .adcost__cell（格子）/ .adcost__k / .adcost__v
      原布局是「两个格子（投产比 / 投流占 GSV）+ 进度条」，现在只剩
      「进度条 + 一行占比说明」—— 占比不再重复呈现两次。
      若将来要再加指标格，从 git 历史里取回这四条即可（当时就是两列布局）。 */
.adcost__main {
  font-size: 26px;
  font-weight: var(--font-weight-semibold);
  color: var(--color-fg);
  font-variant-numeric: tabular-nums;
  line-height: 1.2;
}
.adcost__label { margin-top: 2px; font-size: var(--font-size-xs); color: var(--color-muted); }
/* 占比条：投流 ÷ 净成交 —— 用主色填充，直观表达"投流占多少" */
.adcost__bar {
  margin-top: var(--space-4);
  height: 6px;
  border-radius: var(--radius-pill);
  background: var(--color-surface-2);
  overflow: hidden;
}
.adcost__bar > span {
  display: block; height: 100%;
  background: var(--color-accent);
  border-radius: var(--radius-pill);
  transition: width var(--motion-base) var(--motion-ease-standard);
}
.adcost__hint { margin-top: var(--space-2); font-size: var(--font-size-xs); color: var(--color-muted); }

/* 「待维护」行的区分标识（2026-09-14 乐哥选：给页面加区分标识）
   ----------------------------------------------------------------------------
   待维护行有两类，成因与优先级完全不同，但长相相似：
     · 有归属订单 → 订单已成交、只是没人维护业绩（编号要维护后才生成）→ **该尽快补维护**
     · 无归属订单 → 多半是订单还没导入（实测订单库只到 8-29，9 月为 0）→ 暂时不用管
   所以有订单的那类用**警示色**把它挑出来（"这是要处理的"），
   无订单的保持 `.cell-sub` 默认弱化灰（"不用管"）。

   ⭐ 2026-09-25：文案从「已归属 N 单 · 待补维护」（132px）缩到「N 单」（约 35px）——
      状态列可用宽只有 52px，原文案会折成 **3 行**、把行高从 42px 撑到 54px。
      完整解释移到表头 ⓘ（见 performance.js 的 状态 列）。
   ⛔ **`color` 是"该处理 vs 不用管"的主信号，不许去掉**（去掉两类又长得一样了）。
   ✅ `white-space: nowrap`：防止更大单数（如 1243 单）在窄列里被折成「1243 / 单」。
      ⚠️ 实测边界：本列可用 52px，`N 单` 在 3 位数以内（43px）稳落一行；
         4 位数约 50px 仍在界内。真出现 5 位以上才可能溢出（本表单场单数远达不到）。 */
.perf-pend-note {
  color: var(--color-warning-text);
  white-space: nowrap;
}

/* ==========================================================================
   月度利润明细表（乐哥 2026-09-14 需求）
   --------------------------------------------------------------------------
   结构：一个月一行 × 收入(2 列) + 支出(三大组动态 N 列) + 总成本/盈亏/盈亏率
   ⚠️ 全部用 `.pl-` 前缀新开作用域类，**不动** .table / .is-num 等公共类
      （项目铁律：改公共类前先查复用范围；新页面新开类最稳）。
   ========================================================================== */
.pl-table { font-variant-numeric: tabular-nums; }
/* 分组表头（"成本 · 达人相关"这一行）：浅底 + 加粗 + 居中，
   与下面一行的细项列名形成层级 —— 不加底色时两级表头看着是一整片，分不清哪个管哪几列。 */
/* ⛔⛔ 这里原来有一条 `.pl-grouphd { text-align:center; background:--color-surface-2;
   font-weight:semibold; color:fg-2; border-bottom }` —— **整条都是白写的**：
   只写类名是 (0,1,0)，打不过 `.table thead th`（0,1,2）⇒ 底色/字重/颜色**全被表头默认样式吃掉**
   （实测：底色是白的、字重是 500，与写的内容完全不符，而**不报错**）。
   ⇒ 2026-10-16 夜已并入 `.table thead th.pl-grouphd` 那条**多页共用规则**（同文件上方 4448 行附近）。
   ⛔ 别在页级重新写一条 —— 要么并进共用规则，要么显式提权到 (0,2,2) 以上。 */
/* 细项表头 —— ⭐ 2026-10-16 夜改：`regular(400)` → `medium(500)`（= 全站 `.table thead th` 的默认值）。
   原注释写的是"比分组头弱一档，层级才立得住"，但实测**同板块的华大对账表**叶子头是 **500**，
   两张表并排看就是"一套系统两种表头字重"（乐哥 2026-10-16：「这个表也需要按华大那张表改造」）。
   层级仍然立得住：分组头是 `semibold(600)` > 叶子头 `medium(500)`，差一档。
   ⛔ 别再改回 400 —— 那会让本表与全站（含华大）不一致。 */
.pl-table thead tr:last-child th {
  font-weight: var(--font-weight-medium);
  color: var(--color-fg-2);
}

/* ⭐ 2026-10-16 夜：「月度利润明细」的表格卡头与表头**紧贴**。
   华大对账那张表实测就是这个间距（卡头底 → 表头 = **0px**），卡头 + 表头读成**一个头部区块**；
   本表原来卡头之外的表体有 `padding: 24px` 上内距 ⇒ 卡头与表头之间会空出 24px 的一条白带。
   ⚠️ 只去**上**内距：左右（24）与下（24）保留 —— 表格两侧留白与其它卡片一致，底部的展开明细也不贴边。
   ⛔ 用法与华大同源（`.card__body.hs-cardbody--flush-top { padding-top: 0 }`），
      这里因为「只作用于本页这一处」才新开一个页级类（项目铁律：改公共类前先查复用范围）。 */
.card__body.pl-cardbody--flush-top { padding-top: 0; }

/* ⭐⭐ 2026-10-17 乐哥：「上面筛选栏留月份筛选，不是可以和下面表格的工具栏整合一起吗」
   ⇒ 「月度利润明细」原来**单独一行 toolbar**（只有一个「近 12 个月」下拉 + 「共 N 个月」），
      下面再一行卡头 —— 两个各占一行、中间还夹着橙色警示条，页面被切成四段。
   ⇒ 把 **月份筛选 / 结果数 / 展开项目公共** 并进卡头右侧一行。
   ⚠️ 控件**不能沿用 36px+**：`.card__header` 的纵向内距是 `--space-4`(16)，控件越高卡头越厚。
      现行取**紧凑档 34**（`--control-height-sm` ⇒ 卡头 66），仍低于表头 78，层次不倒挂。
      （2026-10-17 曾压到 30，属两档制外的第三档，同一天按全站统一收敛回 34。）
   ⛔ 别把这组挪回页面级 toolbar —— 「筛选」与「它作用的表格」必须在同一行才读得出从属关系。 */
.pl-head-right {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

/* ⭐⭐ 2026-10-17 导入页：三个步骤各成一张 `.module-card`，页面级只剩「预览 / 导入结果」两张运行时卡。
   ▸ 它们由 JS 按需填充：**没内容时必须是"不存在"**，否则页面上会白摆两张空白卡
     （`.module-card` 有底 + 边框 + 阴影，空的看起来像渲染坏了）。
   ▸ 用 `:empty` 判 —— 注意 **空白文本节点也算内容**，所以 `renderPreview()` 清空时写的
     必须是 `box.innerHTML = ''`（现状如此），⛔ 别改成 `innerHTML = ' '`。
   ▸ 有内容时才给上间距，与上方模块卡对齐到同一档（16px）。 */
#page-root .imp-runtime-card:empty { display: none; }
#page-root .imp-runtime-card:not(:empty) { margin-top: var(--space-4); }
/* ⭐⭐ 2026-10-17 全站 UI 统一（乐哥：「利润台账卡头那两个 30px 控件」要改）：
   裸值 `30px` → **紧凑档令牌 `--control-height-sm`（34）**。
   ▸ 30 是两档制（34/44）之外的**第三档** —— 卡头里这个下拉和旁边那个按钮互相同高，
     但比全站所有工具栏/卡内控件都矮 4px（65 页实测只剩这一处）。
   ▸ 卡头会从 62 → 66（padding 16×2 + 34）：仍是"卡头 66 < 表头 78"的关系，
     不会出现"卡头比表头还高"那种失衡（原注释担心的 68 是 36px 那版）。
   ⚠️ `padding-top/bottom: 0` 保留 —— 控件高度由 height 定，纵向内距归零是为了
      文字在 34px 里垂直居中而不是被 padding 顶偏。 */
.pl-head-right .select {
  height: var(--control-height-sm);
  padding-top: 0;
  padding-bottom: 0;
  font-size: var(--font-size-sm);
}
.pl-head-right .btn {
  height: var(--control-height-sm);
  padding-top: 0;
  padding-bottom: 0;
  font-size: var(--font-size-sm);
}

/* ⭐⭐⭐ 2026-10-17 乐哥：「成本明细，达人相关的没标出来啊，需要和我们项目上的拆开」
   ⇒ 「月度利润明细」表头从两级变**三级**，最上一层是**归属**：
       收入 | 达人侧成本（合并了 达人相关+差旅招待+人力成本） | 项目侧成本（项目公共） | 合计列
   ▸ 这一层的职责是**一眼分清"花在达人身上" vs "花在我们项目上"** ——
     之前只有"成本 · 达人相关 / 差旅招待 / 人力成本 / 项目公共"四个并列组，
     看不出前三个其实同属"达人侧"（乐哥的读法：佣金+招待+人力都是为达人发生的）。
   ⚠️⚠️ 选择器必须写到 `.table thead th.pl-owner`（0,2,2）——
      `.pl-owner` 单独写（0,1,0）打不过 `.table thead th`（0,1,2），
      背景色会被表头默认样式吃掉（**等于白写、且不报错**）——本项目反复踩这个，见 .hs-grouphd 注释。
   ▸ 高度 34px：与下面「分组层」同高。三级表头合计 34+34+44 = **112px**
     （两级时是 78px；多出 34px 是为了这一层归属，乐哥明确要）。
   ▸ 配色：达人侧用主色淡底（--color-primary-50，与"选中/激活"同族，暗示"这是我们的主力成本"）；
           项目侧用中性浅灰（--color-surface-2，与"公共/辅助"语义一致）。 */
.table thead th.pl-owner {
  height: 34px;
  text-align: center;
  font-weight: var(--font-weight-semibold);
  font-size: var(--font-size-xs);
  letter-spacing: 0.02em;
  border-bottom: 1px solid var(--color-border);
}
.table thead th.pl-owner--talent {
  background: var(--color-primary-50);
  color: var(--color-primary-700);
}
.table thead th.pl-owner--project {
  background: var(--color-surface-2);
  color: var(--color-fg-2);
}
/* 可下钻单元格：虚线下划线（不用实线，避免与链接/正文混淆）+ hover 变色。
   可点但看着不像按钮，是刻意的 —— 整表几十个可点格，全做成按钮会非常吵。 */
.pl-link {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px dashed var(--color-border);
  cursor: pointer;
  transition: color var(--motion-fast) var(--motion-ease-standard),
              border-color var(--motion-fast) var(--motion-ease-standard);
}
.pl-link:hover,
.pl-link:focus-visible {
  color: var(--color-accent-text);
  border-bottom-color: var(--color-accent-text);
}
.pl-link:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; border-radius: 2px; }
/* 总成本 / 盈亏：整表最该被一眼看到的两列，加重 */
.pl-strong { font-weight: var(--font-weight-semibold); }

/* 大类小计列（2026-10-21）
   ----------------------------------------------------------------------------
   演进：灰底 → 品牌蓝底 → **去底色**（乐哥：「这个蓝底我不想要」）。
   最终做法：**不给整列上底色**（大面积色块在长表格里太重、且和他惯用的白底表冲突），
   只靠文字本身区分：
     文字用主色深阶（--color-primary-700）+ 加粗 —— 数字本身是蓝的，扫一眼就分得清。
     ② 左侧一道 1px 主色细分隔（inset shadow），与左边明细列断开。
   ⚠️ 用 `inset box-shadow` 而不是 `border-left`：表格是 border-collapse，
      加真边框会让该列宽 +1px，多列累积后与表头的 colspan 视觉对不齐。 */
.table th.pl-sub,
.table td.pl-sub {
  font-weight: var(--font-weight-semibold);
  color: var(--color-primary-700);
}
/* 月份列：不折行（"2026-08" 断成两行会让每行高度不齐） */
.pl-table tbody td:first-child { white-space: nowrap; }
/* 月份格：可点进「当月达人明细」二级页（乐哥 2026-09-15）
   ──────────────────────────────────────────────────────────────────────────
   乐哥原话：「不要虚线，改成蓝色（就是可以跳转那种，这样更显眼）」
   改前走全站 .pl-link（继承文字色 + 虚线下划线），在整表一堆下钻格里不突出。
   改后 = 表格内可跳转链接的标准形态（`.cell-link` 那套：蓝 #0064E0、无下划线、hover 加下划线）。

   ⚠️ **必须保留 `pl-link` 类**（不是换成 `cell-link`）：本页的下钻事件是
      `box.querySelectorAll('.pl-link')` 统一绑定的，类名一换**点击就失效**（且不报错）。
      故这里用「双类 + 提高特异性」覆盖样式，不动 HTML 上的类名。
   ⚠️ 为什么月份要与其他下钻格（虚线条）不同：两者是**不同层级的入口** ——
      月份去的是**页面级二级页**，其他格是**就地弹层下钻**。
      用两种视觉区分层级，比整表清一色虚线更清楚。
   ⚠️ 特异性说明：`.pl-link.pl-month`(0,2,0) 胜过 `.pl-link`(0,1,0)；
      `.pl-link.pl-month:hover`(0,3,0) 胜过 `.pl-link:hover`(0,2,0) —— 不依赖书写顺序。 */
.pl-link.pl-month {
  color: var(--color-primary-500);
  border-bottom: none;
  font-weight: var(--font-weight-semibold);
}
.pl-link.pl-month:hover,
.pl-link.pl-month:focus-visible {
  color: var(--color-primary-600);
  border-bottom: none;
  text-decoration: underline;
}

/* ==========================================================================
   月度利润明细 · 点月份进的「当月达人明细」二级页（乐哥 2026-09-15）
   --------------------------------------------------------------------------
   结构：本月合计 KPI → 达人明细（**行内展开场次**）→ 项目公共成本 → 底部验算条。
   ⚠️ 展开用「行内子表」而不是弹窗/新页面 —— 乐哥原话「不要跳来跳去，其实不直观」，
      想看某位达人这个月播了哪几场，就地展开即可（数据后端已一并返回，无额外请求）。
   ========================================================================== */
.pmd-table { font-variant-numeric: tabular-nums; }
/* 主行可点展开：必须有 hover 反馈，否则用户不知道这一行能点 */
.pmd-table tbody tr.pmd-row { cursor: pointer; }
/* ⭐ 2026-09-25：**斑马纹已按乐哥要求去掉**（全站统一，详见 table.css 的汇总清单）。
   ▸ 原实现（现删除）：本表主行后紧跟**隐藏的展开行**，两者交替 ⇒
     全站斑马纹的 `tbody tr:nth-child(even)` 会全落在隐藏行上、主行一条纹都没有
     （2026-09-15 实测踩到）。故当时由页面按**主行序号**交替加 `.is-alt` 类、这里给底色。
   ▸ 现在全站都没有斑马纹了，这条"绕过隐藏行"的特殊实现自然一并去掉。
     ⚠️ JS 里加 `.is-alt` 的代码（`pages/profit-month-detail.js`）**刻意保留** ——
        它现在不影响渲染（无对应规则），留着可作线索；要恢复只需取消下面这行注释。
   ⛔ 原规则：`.pmd-table tbody tr.pmd-row.is-alt { background: var(--table-row-alt); }`
   ▸ 本表去掉斑马纹后靠什么区分行：`cursor:pointer` + hover 高亮 + `.is-open` 展开态底色（下方两条）。 */
.pmd-table tbody tr.pmd-row:hover > td { background: var(--color-surface-2); }
.pmd-table tbody tr.pmd-row.is-open > td { background: var(--color-surface-2); }
/* 展开箭头：默认朝右，展开后转 90°（用 transition，不用弹跳缓动——全站禁用弹性缓动） */
.pmd-caret {
  display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px; color: var(--color-muted);
  transition: transform var(--motion-fast) var(--motion-ease-standard);
}
.pmd-table tbody tr.pmd-row.is-open .pmd-caret { transform: rotate(90deg); }

/* 展开区 = **主表的兄弟行**（不再是嵌套子表）
   --------------------------------------------------------------------------
   ⚠️⚠️ 为什么展开行不能嵌一张子表（2026-09-17 乐哥第二次报同一条 bug）：
      子表是**另一张表**，列宽由它自己的内容独立计算，与外层表头列宽毫无关系 →
      逐格错开。实测（1900px 视口 · 2026-09 数据）外层 13 列宽
      = 50/150/82/132/132/140/138/130/119/117/131/134/99，
      而嵌套子表 13 格全都是 ≈116px（内容相近 → 浏览器平均分），
      「第 1 场」因此落在 **T+35 GSV** 表头下、GSV 值落在「未满 35 天」下，最多偏 92px。
      ⇒ 展开行做兄弟行后，列宽由同一张表统一分配，**对齐是布局的自然结果**，
        不再需要任何"手工同步宽度"（那种做法改一列就会漂，且改一处漏一处）。
   ⚠️ `tr[hidden]` 显式声明 display:none：UA 的 `[hidden]{display:none}` 优先级低于
      任何作者样式，一旦将来有人给 tbody tr 设 display，收起态就会静默失效 —— 多这一行是保险。 */
.pmd-table tbody tr.pmd-sub[hidden] { display: none; }
/* 子行视觉：浅底 + 小一号字 + 更紧凑的上下留白（替代原 `.table--sub` 的「紧凑子表」角色）
   ⚠️ 底色设在 **td** 而不是 tr：全站斑马纹/悬浮是设在 tr 上的，
      设 td 的特异性更高、稳定压过它们（与 table.css 里 `tr.is-sum > td` 同一套做法）。 */
.pmd-table tbody tr.pmd-sub > td {
  background: var(--color-surface-2);
  font-size: var(--font-size-sm);
  padding-top: var(--space-2); padding-bottom: var(--space-2);
}
.pmd-table tbody tr.pmd-sub:hover > td { background: var(--color-surface-2); }
/* 「从属关系」用 inset 阴影表达，**不用 border-left**：本项目 border-left 已被若干
   语义线占用（见 table.css 的 .col-sep 注释），同属性会互相覆盖且极难排查。
   只画在首格（它贴着表格左边缘），视觉上就是那条"这些行属于上面那位达人"的竖线。 */
.pmd-table tbody tr.pmd-sub > td:first-child { box-shadow: inset 3px 0 0 var(--color-border-soft); }

/* 「该场无订单数据」标记（2026-09-17）
   --------------------------------------------------------------------------
   起因：乐哥问「艺芯怎么少了第二场直播」—— 那场确实一笔订单都没有，
   引擎从订单枚举场次导致整行不出现，用户只看到"少了一场"却不知原因。
   现在后端补出这类场次（金额恒为 0），这里给它一个**克制但一眼可辨**的标记：
   不用红色（不是错误、不影响任何金额），用中性警示色，避免看到就以为账错了。 */
.pmd-noorder {
  display: inline-block; margin-left: 6px; padding: 0 6px; border-radius: var(--radius-sm);
  font-size: var(--font-size-xs); line-height: 1.6;
  background: var(--color-warning-bg); color: var(--color-warning-text);
}
.pmd-noorder-row > td { opacity: 0.85; }

/* 展开行下方的**附注行**（跨整行）：排期级成本未计入 / 无订单数据的原因
   --------------------------------------------------------------------------
   ⚠️ 为什么不放在表格最后一格（2026-09-17 实测数据说话）：
      放进「状态」列后，那格文字会参与列宽分配 → 展开一下整表列宽就跳：
        1440px 视口 · 2026-09：状态列 75→163px，其余 12 列各缩约 9%；
        2026-08 那条长说明更糟 —— 被挤在 97px 宽里折成 **199px 高**，竖着排一长条。
      跨整行（colspan=全列数）后，其最小宽度被摊到所有列上（每列十几像素），
      实测展开前后列宽几乎不变，文案也能用满整行。
   ⚠️ 附注行与场次行**同一组**显隐（都带 data-detail-of），由 bindRowToggle 按组切换；
      `tr[hidden]` 同样显式声明 display:none（理由同 .pmd-sub）。 */
.pmd-table tbody tr.pmd-note[hidden] { display: none; }
.pmd-table tbody tr.pmd-note > td {
  background: var(--color-surface-2);
  font-size: var(--font-size-xs);
  color: var(--color-meta);
  padding-top: 2px; padding-bottom: var(--space-2);
  /* 跨列格**不能**设 white-space:nowrap —— 那会让它的最小宽度无法被摊薄，
     整表会被撑宽（本项目"nowrap + 长文案把表格撑破"踩过一次，见 table.css 的注释）。 */
  white-space: normal;
}
/* 附注左侧的从属竖线：与场次行同一条（否则看着像两条线） */
.pmd-table tbody tr.pmd-note > td:first-child { box-shadow: inset 3px 0 0 var(--color-border-soft); }
/* 说明文案在附注行里可以用满整行，不再限制 360px（那个上限是为"塞在窄格里"设的） */
.pmd-table tbody tr.pmd-note .pmd-noorder-tip { display: inline; margin-top: 0; max-width: none; white-space: normal; }
/* ⚠️ 徽标现在放在附注行**句首**（2026-09-17 从"场次"格移过来）：
   那一列实测只有 91px 宽，徽标"无订单数据"会被挤成两行（"无订单 / 数据"）。
   改到跨整行的附注行后一行放得下，也顺了"徽标 + 解释"挨着读的直觉。
   ⇒ 原来的 `margin-left: 6px`（跟在"第 N 场"后面的场景）改成只留右侧间距。 */
.pmd-table tbody tr.pmd-note .pmd-noorder { margin-left: 0; margin-right: var(--space-2); }
/* 徽标与「排期级成本未计入」两段说明之间留出间距（都是 inline，需要显式 margin） */
.pmd-table tbody tr.pmd-note .cell-sub + .cell-sub { margin-top: 0; }
.pmd-table tbody tr.pmd-note .pmd-noorder-tip + .cell-sub { margin-left: var(--space-3); }

/* ==========================================================================
   达人结算单 · 卡片底部口径块（乐哥 2026-09-15：「列表里面少了个底块」）
   --------------------------------------------------------------------------
   参照华大对账那张卡：它在表格下方挂一块口径说明，而达人结算表格下面直接就是分页、没有这块。
   ⚠️ 视觉规格**用全站统一的提示条 token（--notice-*）**：浅底 + 8px 圆角 + info 图标。
      不复用 `.st-rule__inner` 类名（那是"页面级规则条"的语义），但共用同一组 token，
      保证同一屏里两个说明块视觉完全一致；`probe-notice-standard.js` 也会一并覆盖到。
   ⚠️ 左右 margin 用 --space-5：与 .card__header / .pagination 的水平内边距对齐，
      否则这个块会比卡片里的表格"缩进不齐"。
   ========================================================================== */
/* ⛔ 2026-10-11 删掉了这里的 `.st-foot`（表格底部那块 244 字的「这张表怎么读」）——
   乐哥：「很多备注很啰嗦，有的可以不要，有的可以放？里」⇒ 整块收进卡片标题旁的 ⓘ
   （见 `influencer-settlement.js` 的 `.card__header`）。内容一字没删，只是换了地方。
   ⚠️ 同类如果还要用「表格底部提示条」，走全站提示条 token（`--notice-*`）那套，
      别把这组类名复刻回来（已经没有任何调用方了）。 */

/* 「待付 ¥X」—— 结算状态格下方的小注（渲染见 `influencer-settlement.js` 的 `pendNote()`）。
   2026-10-11 乐哥：「我还想看每个达人的[还要结算多少]」⇒ 徽章说明"到哪一步"，
   小注说明"还差多少钱"。
   ⚠️ 用警示色：这是"还要掏钱"的信号，必须比上面的状态徽章更抓眼（他要的"直观"）；
      但比徽章小一档（xs）—— 它是补充，不是主体。
   ⚠️ nowrap：金额折行会把这个格子撑成两行、整行变高（本项目在窄列小字上踩过）。 */
.st-pend {
  margin-top: var(--space-1);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-medium);
  color: var(--color-warning-text);
  white-space: nowrap;
}

/* ==========================================================================
   角色权限页 · 权限目录（2026-09-17 重构）
   --------------------------------------------------------------------------
   改造前：44 个复选框平铺在 8 个**旧业务域**分组里，与侧栏菜单对不上号 ——
   用户看到「commission:rebate:read」只有一个光秃秃的中文名，不知道：
     ① 它属于侧栏哪个菜单   ② 它是"看不见菜单"还是"看不到某个金额"
   现在：分组 = 侧栏一级分组；组头一行小字列出该组的二级菜单；
        每个权限带**性质角标**（菜单可见 / 页面内操作 / 字段脱敏）+「管什么」说明。
   ⚠️ 角标三色语义必须稳定（与图例逐字对应），改色会让图例变成假的：
         menu   → 主色（蓝）：控制"能不能看到入口"，最重
         action → 中性灰：页面内的按钮级操作
         field  → 橙色：只影响某些金额显示为「—」，最容易被误解，故用警示色
   ========================================================================== */
.perm-group { padding: var(--space-4) 0; border-bottom: 1px solid var(--color-border-soft); }
.perm-group:last-child { border-bottom: 0; }
.perm-group__head { display: flex; align-items: baseline; gap: var(--space-3); flex-wrap: wrap; margin-bottom: var(--space-3); }
/* ⚠️ 用 --font-size-lg（16px）而不是 --font-size-md —— 令牌表里**没有 md**（只有 xs/sm/base/lg/xl…），
   写错的表现是**整条声明被浏览器丢弃**且不报错（本项目 check-css-tokens.js 抓到的）。 */
.perm-group__title { font-size: var(--font-size-lg); font-weight: var(--font-weight-semibold); color: var(--color-fg); }
/* 组头那行菜单摘要：它是"权限 ↔ 菜单"的锚点，不能省 —— 省了就退回改造前那种"对不上号"的状态 */
.perm-group__menus { font-size: var(--font-size-xs); color: var(--color-muted); }

.perm-list { display: flex; flex-direction: column; gap: var(--space-1); }
/* 每个权限一行：整行可点（label 包住 checkbox），避免只能点 12px 的小方块 */
.perm-item { display: flex; align-items: flex-start; gap: var(--space-2); padding: var(--space-2) var(--space-2);
  border-radius: var(--radius-sm); cursor: pointer; font-size: var(--font-size-sm); }
.perm-item:hover { background: var(--color-surface-2); }
.perm-item input[type="checkbox"] { margin-top: 2px; flex: none; }
.perm-item__body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.perm-item__top { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
.perm-item__label { color: var(--color-fg); }
/* 权限码本身也给出来（等宽字体、弱化）：排查"某个权限没生效"时要能直接对上后端日志 */
.perm-item__code { font-size: var(--font-size-xs); color: var(--color-muted); font-family: var(--font-family-mono);  /* 2026-09-25 修：--font-mono 是拼错的令牌名（一直走 monospace 兜底），正确名 --font-family-mono */ }
.perm-item__scope { font-size: var(--font-size-xs); color: var(--color-muted); line-height: 1.5; }

/* 性质角标（三色，与图例一一对应） */
.perm-kind { display: inline-flex; align-items: center; height: 18px; padding: 0 var(--space-2);
  border-radius: var(--radius-pill); font-size: var(--font-size-xs); font-weight: var(--font-weight-medium);
  white-space: nowrap; }
.perm-kind--menu { color: var(--color-accent-text); background: var(--color-accent-bg); }
.perm-kind--action { color: var(--color-muted); background: var(--color-surface-2); }
.perm-kind--field { color: var(--color-warning-text); background: var(--color-warning-bg); }

/* 图例：常驻在「全选/清空」同一行右侧（不折叠），否则角标成了没人懂的符号 */
.perm-legend { display: flex; align-items: center; gap: var(--space-2); margin-left: auto; flex-wrap: wrap; }
.perm-legend__t { font-size: var(--font-size-xs); color: var(--color-muted); margin-right: var(--space-2); }

/* ==========================================================================
   经营看板 · 本期洞察（2026-09-17）
   --------------------------------------------------------------------------
   放在 #an-body 最顶部，KPI 卡之上 —— 一进页面先看到"这个月怎么样"。
   设计约束（来自乐哥：「不用很啰嗦，但是很关键」）：
     · 一行一条结论，左标签 + 中结论 + 右行动提示，**扫读三列对齐**
     · 只给"需要注意"的行加底色（risk/warn），正常的行不加 —— 全是底色等于没有重点
     · 数字用页面统一的「万」格式，盈利红/亏损绿与全站一致
   ⚠️ `.an-ins-label` 用固定 56px 宽度：三条以上时标签列必须对齐，
      否则「规模 / 亏损达人 / 口径」长短不一会让结论列参差，扫读时眼睛要来回找。
   ========================================================================== */
.an-ins { margin-bottom: var(--space-6); }
.an-ins-headline {
  font-size: var(--font-size-base);
  color: var(--color-fg);
  line-height: 1.7;
  padding: var(--space-3) var(--space-4);
  background: var(--color-surface-2);
  border-radius: var(--radius-md);
  margin-bottom: var(--space-3);
}
.an-ins-list { display: flex; flex-direction: column; }
.an-ins-row {
  display: grid;
  grid-template-columns: 56px 1fr auto;
  gap: var(--space-3);
  align-items: baseline;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  font-size: var(--font-size-sm);
  line-height: 1.7;
}
.an-ins-row + .an-ins-row { border-top: 1px solid var(--color-border-soft); }
/* 需处理：整行浅警示底（比只染标签更能"跳出来"，但不至于像报错） */
.an-ins-row.is-risk { background: var(--color-warning-bg-soft); }
.an-ins-row.is-warn { background: var(--color-warning-bg-soft); }
.an-ins-label { font-size: var(--font-size-sm); color: var(--color-muted); white-space: nowrap; }
.an-ins-row.is-risk .an-ins-label { color: var(--color-warning-text); font-weight: var(--font-weight-medium); }
.an-ins-row.is-warn .an-ins-label { color: var(--color-warning-text); }
.an-ins-text { color: var(--color-fg); min-width: 0; }
.an-ins-text b { font-weight: var(--font-weight-semibold); }
/* 数字：等宽数字（tabular）保证多行数字右边缘对齐，扫读时不会左右跳 */
.an-ins-num { font-variant-numeric: tabular-nums; font-feature-settings: "tnum"; font-weight: var(--font-weight-medium); }
.an-ins-note { font-size: var(--font-size-xs); color: var(--color-muted); white-space: nowrap; }
.an-ins-row.is-risk .an-ins-note,
.an-ins-row.is-warn .an-ins-note { color: var(--color-warning-text); }

/* AI 追问（2026-09-17）—— 默认收起，点「追问 AI」才展开。
   ⚠️ 输入行用 flex 而不是 grid：窄屏时按钮要能跟着输入框一起收缩，
      grid 的固定列在 375px 下会把"提问"按钮挤出容器。 */
.an-ask { margin-top: var(--space-4); padding-top: var(--space-4); border-top: 1px solid var(--color-border-soft); }
.an-ask__row { display: flex; gap: var(--space-2); align-items: center; }
.an-ask__row .input { flex: 1; min-width: 0; }
.an-ask__row .btn { flex: none; }
.an-ask__sug { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; margin-top: var(--space-2); }
/* 建议问题做成一排小胶囊：比空白输入框更好上手，也引导用户问"数据里有的东西" */
.an-ask__chip {
  border: 1px solid var(--color-border-soft);
  background: var(--color-surface);
  color: var(--color-fg);
  border-radius: var(--radius-pill);
  padding: 2px var(--space-3);
  font-size: var(--font-size-xs);
  cursor: pointer;
}
.an-ask__chip:hover { border-color: var(--color-accent); color: var(--color-accent-text); }
.an-ask__hint { font-size: var(--font-size-xs); color: var(--color-muted); }
.an-ask__out:not(:empty) { margin-top: var(--space-3); }
.an-ask__loading { font-size: var(--font-size-sm); color: var(--color-muted); display: flex; align-items: center; gap: 6px; }
.an-ask__q { font-size: var(--font-size-sm); color: var(--color-muted); margin-bottom: var(--space-2); }
.an-ask__a {
  font-size: var(--font-size-sm);
  line-height: 1.8;
  color: var(--color-fg);
  background: var(--color-surface-2);
  border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-4);
}
/* 来源标注：**必须可见** —— 用户要知道这是 AI 解释，权威数字在上方洞察卡（规则算的、可对账） */
.an-ask__src { font-size: var(--font-size-xs); color: var(--color-muted); margin-top: var(--space-2); }
.an-ask__err { font-size: var(--font-size-sm); color: var(--color-danger); }

/* ============================================================================
   权限配置页：按左侧菜单树布局（2026-09-18 乐哥拍板重构）
   四个区块：菜单名 | 页面可见 | 操作权限 | 金额可见
   ⚠️ 金额 chip 是**全局**的（同一个码在多个菜单下出现，勾一处联动其它处），
      因为打码按字段名判断、与页面无关 —— UI 上以「全局生效」提示。
   ============================================================================ */
.perm-toolbar { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; padding-bottom: var(--space-3); margin-bottom: var(--space-3); border-bottom: 1px solid var(--color-border-soft); }
.perm-head-row, .perm-menu-row { display: grid; grid-template-columns: minmax(150px, 1fr) 104px 210px 200px; gap: var(--space-2); align-items: center; }
.perm-head-row { padding: var(--space-2) var(--space-3); font-size: var(--font-size-xs); color: var(--color-muted); border-bottom: 1px solid var(--color-border-soft); }
.perm-menu-row { padding: var(--space-2) var(--space-3); border-bottom: 1px solid var(--color-border-soft); }
.perm-menu-row:last-child { border-bottom: 0; }
.perm-menu-row:hover { background: var(--color-surface-2); }
.perm-menu-row__name { color: var(--color-fg); display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.perm-menu-row__note { font-size: var(--font-size-xs); color: var(--color-muted); }
/* ⭐ 2026-10-08：note 经 `UI.inlineMd` 渲染，会产出 `<strong>` / `<code>` 两种行内件 ——
   它们必须有样式，否则加粗没有视觉（等于白加）、行内码与正文糊在一起。
   ⚠️ `code` 的字号用 `em` 相对单位（相对 note 自己的 xs）：写成 `--font-size-xs` 会和 note 同号、
      看不出是"码"；而写死 px 会在换字号时脱节。 */
.perm-menu-row__note strong,
.perm-item__scope strong { font-weight: var(--font-weight-semibold); color: var(--color-fg-2); }
.perm-menu-row__note code,
.perm-item__scope code {
  padding: 0 4px; border-radius: var(--radius-sm);
  background: var(--color-surface-2); color: var(--color-fg-2);
  font-family: var(--font-family-mono); font-size: 0.94em;
}
.perm-menu-row__read, .perm-menu-row__act, .perm-menu-row__money { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; }
.perm-read { display: inline-flex; align-items: center; gap: 4px; font-size: var(--font-size-sm); color: var(--color-fg); cursor: pointer; }
.perm-muted { font-size: var(--font-size-xs); color: var(--color-muted); }
.perm-chip { display: inline-block; padding: 1px 7px; border-radius: var(--radius-sm); font-size: var(--font-size-xs); cursor: pointer;
  border: 1px solid var(--color-border-soft); color: var(--color-muted); background: transparent; user-select: none; }
.perm-chip:hover { border-color: var(--color-border); color: var(--color-fg); }
.perm-chip.is-on { background: var(--color-accent-bg); border-color: var(--color-accent-border); color: var(--color-accent-text); }
.perm-chip--money.is-on { background: var(--color-warning-bg); border-color: var(--color-warning-border); color: var(--color-warning-text); }
.perm-group__head--fold { cursor: pointer; user-select: none; align-items: center; }
.perm-fold { font-size: 11px; color: var(--color-muted); width: 12px; }
.perm-group.is-folded .perm-group__body { display: none; }
.perm-global { display: flex; flex-direction: column; gap: var(--space-2); padding: var(--space-2) 0; }
.perm-item--inline { align-items: center; }

/* ==========================================================================
   「我的报销」页（2026-09-19）
   新增：替票额度条 / 筛选行（tab + 月份 + 日期口径 + 状态）/ 汇总行 / 驳回原因
   ⚠️ 只用既有 design-tokens 变量，不新造颜色 —— 与全站观感一致（本项目已因"孤例颜色"返工过）
   ========================================================================== */

/* 替票额度条：报销的**前置条件**（选「没有发票」要够额度），故放页面顶部一眼可见。
   2026-09-19 改：原来它是个「灰底 + 30px 按钮」的独立条，与周围两套语言都不搭 ——
     ① 底色用 --color-surface-2（=表头灰），视觉重量接近工具栏，像"第二个工具栏"；
     ② 按钮 30px，而页头按钮 44px、工具栏控件 34px，三套尺寸。
   改成：白底 + 1px 边框（与卡片同一语言），按钮升到 34px（= 工具栏控件高度）。
   ⚠️ 不能把按钮写成 `.btn--sm` 就完事 —— 那样它永远 30px；要在自己的类里显式给高。 */
.tt-bar {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-4);
  padding: var(--space-3) var(--space-5);
  margin-bottom: var(--space-6);
  background: var(--color-surface);
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-lg);
  flex-wrap: wrap;
}
.tt-bar__l { display: flex; align-items: baseline; gap: var(--space-3); flex-wrap: wrap; }
.tt-bar__k { font-size: var(--font-size-sm); color: var(--color-fg-2); }
.tt-bar__v { font-size: 20px; font-weight: var(--font-weight-medium); color: var(--color-accent-text); }
.tt-bar__hint { font-size: var(--font-size-sm); color: var(--color-muted); }
.tt-bar__r { display: flex; gap: var(--space-2); flex-shrink: 0; }
.tt-bar__r .btn { height: var(--control-height-sm); min-height: var(--control-height-sm); }

/* 状态笔数（工具栏右侧）：与 `.table-toolbar__total` 并排，故沿用同一套字号与颜色。
   2026-09-19 改：原来是一个占整行的自造 `.me-sum`（独立第三行、21px 高），
   现在并入工具栏右侧 —— 规范里统计属于工具栏，不该另占一行。 */
.me-counts { display: inline-flex; align-items: center; gap: var(--space-4); flex-wrap: wrap; }
.me-counts__item {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: var(--font-size-xs); color: var(--color-meta); white-space: nowrap;
}
.me-counts__item b { color: var(--color-fg); font-variant-numeric: tabular-nums; }
.me-counts__dot { width: 6px; height: 6px; border-radius: 50%; background: var(--color-muted); }
.me-counts__dot--warning { background: var(--color-warning); }
.me-counts__dot--success { background: var(--color-success); }
.me-counts__dot--danger  { background: var(--color-danger); }

/* 驳回原因（要让人知道改什么才能重提，否则只能干等） */
.me-reject { color: var(--color-danger); }

/* ---------- 手机端适配 ---------- */
@media (max-width: 768px) {
  .tt-bar { flex-direction: column; align-items: stretch; gap: var(--space-3); }
  .tt-bar__r { justify-content: stretch; }
  .tt-bar__r .btn { flex: 1; min-height: var(--control-height-touch); }
  .tt-bar__hint { display: none; }          /* 手机上省一行（额度本身已说明一切） */
  /* ⚠️ 手机上工具栏右侧必须能换行（2026-09-19 实测踩到）：
     统计并入工具栏后，`.table-toolbar__right` 是 `display:flex` **无 flex-wrap**、
     且其中 `.table-toolbar__total` 带 `white-space: nowrap`（公共类的防折行，不能动），
     于是 360px 下两者挤在同一行、总额被压掉尾数（截图实测：「合计 ¥2,705.0」被"审批中"盖住）。
     ⇒ 补 wrap + 让状态笔数独占一行。作用域限定在本页的工具栏上，不动公共类。 */
  .toolbar--join-card > .table-toolbar__right {
    flex-wrap: wrap;
    width: 100%;
    margin-left: 0;
    justify-content: flex-start;
  }
  .me-counts { width: 100%; gap: var(--space-3); }
}

/* ==========================================================================
   报销表单 · 分区单列布局（2026-09-19）
   乐哥：「报销提交那个页面，ui 整体调整下，现在做的一点都不直观」
   实测三个问题 → 三个改法：
     ① 因果链被打断（两列布局把「大类→类型→成本类目」拆散）→ **单列**
     ② 长说明占 2-3 行 → 收进每区一句「这一区在干什么」，长说明移进提示
     ③ 字段平铺无分组 → **3 个分区卡片**（带序号）
   ⚠️ 只用既有 design-tokens 变量；配色复用 module-card（全站模块卡的既有语言）
   ========================================================================== */

/* 报销人条：默认本人、不需要输入；点「帮他人报销」才展开下拉 */
.rf-who {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  margin-bottom: var(--space-4);
  background: var(--color-surface-2);
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-lg);
}
.rf-who__l { display: flex; align-items: baseline; gap: var(--space-2); flex-wrap: wrap; }
.rf-who__k { font-size: var(--font-size-sm); color: var(--color-fg-2); }
.rf-who__me { font-size: var(--font-size-base); font-weight: var(--font-weight-medium); }
.rf-who__tag {
  font-size: 11px; color: var(--color-fg-2);
  background: var(--color-surface); border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-pill); padding: 1px 8px;
}
/* 「代报」态（2026-10-10）：编辑一张别人代报的单时，这条要一眼看出"不是给我报的"。
   配色沿用报销单页「代报」徽章那一档（user 侧的 *.eb-chip--tag 代报态），保持全站一套语言。 */
.rf-who__tag--proxy {
  color: var(--color-warning-text);
  background: var(--color-warning-bg-soft);
  border-color: var(--color-warning-bg-soft);
}
.rf-who__other { margin-bottom: var(--space-4); }

.rf-req { color: var(--color-danger); }

/* 分区卡片：序号圆点 + 标题 + 一句说明 */
.rf-sec { margin-bottom: var(--space-4); }
.rf-sec:last-child { margin-bottom: 0; }
/* ⚠️ 2026-10-09 表单统一（第一批）：分区头外观已并入 overlays.css 的 `.form-sec` 别名组。
   ▸ gap 3 → **2**：与 `.form-sec__title` 的"徽标 ↔ 字"间距对齐（这属于"外观统一"）。
   ▸ 标题文字（`.rf-sec .module-card__title`）的字号/字重/字色也由那条别名给
     （限定在 `.rf-sec` 作用域内，不动全站其它卡片标题）。
   ⛔ 不许再把外观值加回来（门禁 `tests/form-unify.test.js ①`）。 */
.rf-sec__head { display: flex; gap: var(--space-2); align-items: flex-start; }
/* .rf-sec__no：外观见 overlays.css 的 `.form-sec__no`
   （22px 正圆 + 实心蓝 + 白字 → 20×20 · r6 · 浅蓝底 + 主色字，与另两套一致） */
.rf-sec__sub { margin-top: 3px; line-height: 1.5; }   /* 字号/字色由 `.form-sec__hint` 别名给 */

/* 分区内字段：单列、纵向间距均匀 */
.rf-sec .module-card__body > div { margin-bottom: var(--space-4); }
.rf-sec .module-card__body > div:last-child { margin-bottom: 0; }

/* 成本类目：从"第 3 行右侧的一个只读框 + 一行说明"改成**紧跟类型的实时反馈条** ——
   它是「大类 + 类型」算出来的**结果**，必须贴着它的两个输入项，否则读不出因果关系。
   ⚠️ 容器 .rf-alloc-cell 里现在只放一个 hidden input（回传用）+ 这条反馈条。 */
.rf-alloc-cell { margin-top: calc(var(--space-2) * -1); }
.rf-alloc-cell > .field-label { display: none; }      /* 反馈条自带语义，不需要再顶一行标签 */
.rf-alloc {
  display: flex; align-items: center; gap: var(--space-2);
  padding: 9px 12px;
  border: 1px dashed var(--color-border-soft);
  border-radius: var(--radius-md);
  /* ⭐ 2026-10-09 常态不铺状态色（全局检查：与报销单 `.eb-schedbox` 同一个毛病）。
     改前是 `--color-accent-bg`，而那是"选中/激活"的色 ⇒ 这条**只读结论**读起来像
     "这个字段被选中了"。它本来就靠**灰虚线**表达"自动算出来的，别手改"，
     底色去掉后语义不变、也不再和同页那些白底输入框抢注意力。
     ⚠️ 保留虚线边框（它是"自动"的信号），别改成实线。 */
  background: transparent;
}
.rf-alloc__k { font-size: var(--font-size-sm); color: var(--color-fg-2); }
.rf-alloc__v { font-size: var(--font-size-base); font-weight: var(--font-weight-medium); color: var(--color-accent-text); }

/* ---------- 手机端（乐哥：「得适配 pc 和手机页面」）---------- */
@media (max-width: 768px) {
  /* 页头「返回 + 标题」在手机上占两行、且标题被挤成小字 —— 改成单行紧凑 */
  .rf-who { padding: var(--space-2) var(--space-3); }
  .rf-who__me { font-size: var(--font-size-sm); }
  /* 手机上分区之间留白收一半：两屏能看完整个表单 */
  .rf-sec { margin-bottom: var(--space-3); }
  .rf-sec .module-card__body > div { margin-bottom: var(--space-3); }
  .rf-sec__sub { display: none; }      /* 手机上一屏寸土寸金：分区说明省掉（标题已表意） */
}

/* ==========================================================================
   手机端独立页（/h5/expense 等）—— 从飞书工作台点进来时，整屏只放表单
   ⚠️ 用「隐藏」而不是「不渲染」：DOM 结构不动，"退出独立模式"只需去掉类；
      也避免为独立页另建一套骨架（那会带来两套要同步维护的布局）。
   ========================================================================== */
.app-shell.is-standalone .sidebar,
.app-shell.is-standalone .page-tabs-bar {
  display: none !important;
}
/* ⭐⭐ `.sidebar-backdrop` **不在此列** —— 2026-09-29 从上面那条隐藏清单里移除。
   ──────────────────────────────────────────────────────────────────────────
   🐞 它原先是跟着 `.sidebar` 一起被藏掉的，后果很隐蔽：
      财务 / 人事行政 两个板块**恢复了侧栏和标签栏**（下面 is-finance / pages-kq.css），
      却**没有一家把遮罩恢复**（4 个板块全漏）⇒ 移动端抽屉打开后：
        · 点汉堡按钮关不掉（被抽屉盖住，见 layout.css 的 `--drawer-top` 注释）
        · **点空白也关不掉**（遮罩 display:none，那一层根本不存在）
      ⇒ 两条退路全断 = 乐哥报的「菜单栏打开收不回去，卡死了」。
   ⚠️ 为什么敢**通用**移除、而不是逐板块补一条 `display:block !important`：
      逐板块补正是这次漏掉的原因（4 处都忘了写），补第 5 个板块时还会漏。
      而通用移除是**安全的**：遮罩移动端基础态就是 `opacity:0; pointer-events:none`
      （见 layout.css），只有 `.sidebar-open` 时才变得可见可点；
      而 `sidebar-open` 只有"点得到汉堡按钮"的板块才加得上 ⇒
      纯独立页（整屏只有一个表单、没有按钮）永远不会有这个类，
      遮罩对它们**始终透明且不拦事件**，行为与改前一致。
   ⚠️ 门禁：`tests/mobile-drawer.test.js` 会断言"凡恢复了 `.sidebar` 的板块，
      都必须同时恢复 `.sidebar-backdrop`"——从根上防止再漏。
   ⚠️ 附加（同一次报障的第二半）：这两个板块还要把 `--drawer-top` 设成标签栏高度，
      抽屉和遮罩才不会盖住标签栏里的汉堡按钮（见 layout.css 的详细说明）。 */
.app-shell.is-hr,
.app-shell.is-finance,
/* ⚠️ 项目管理（2026-09-30）必须一起加：这三个板块都是"独立外壳 + 自带侧栏 + 显示标签栏"，
   抽屉与遮罩的起点都要落在标签栏下方。漏了它的后果就是乐哥报过的那个
   「菜单栏打开收不回去，卡死了」—— 汉堡按钮被抽屉盖住、点不到。 */
.app-shell.is-project,
/* ⚠️ 店铺运营中心（2026-10-23）同款：它也是"独立外壳 + 自带侧栏 + 显示标签栏"。
   漏了它的症状与上面一字不差 —— 手机端侧栏抽屉打开后收不回去。 */
.app-shell.is-shop {
  --drawer-top: var(--tabs-bar-height);
}
.app-shell.is-standalone .app-main {
  margin-left: 0;          /* 没有侧栏了，内容列从左边开始 */
  width: 100%;
}
/* 独立页没有标签栏，页面顶部即是内容 —— 把 .page 的上内边距收掉，
   否则会出现一条"本应是标签栏"的空白（实测 24px）。 */
.app-shell.is-standalone .page { padding-top: var(--space-4); }

/* ⭐⭐ 财务模块（**2026-09-21 起 = 与达播同构**：有侧栏 + 有标签栏）
   ──────────────────────────────────────────────────────────────────────────
   这里是**第三次**改它的形态，别把某一版当成唯一答案（演进有价值，记下来）：
     · 2026-09-20 之前：无侧栏 + 无标签栏 —— "独立的一层"，自带模块头（`.fin-top`）
     · 2026-09-20：无侧栏 + **有**标签栏（乐哥「上面的标签该保留的也保留」）
       —— 彼时跨板块入口靠标签栏里那个「公司门户 / 达播工作台 / 星盟财务中心」切换器
     · **2026-09-21（现状）**：**有侧栏 + 有标签栏**
       —— 乐哥「不要这个」（删掉标签栏的切换器）+「财务的左上角也做的和现在达播台一样」
       ⇒ 侧栏回归：顶部品牌区 + 中部财务导航 + 底部「返回门户网站 / 我的」；
         跨板块入口改由**侧栏底部的「返回门户网站」**承担（进财务则走门户页的卡片）。
   ⚠️⚠️ 侧栏必须**显式加 `!important` 覆盖**：上面那条
      `.app-shell.is-standalone .sidebar { display: none !important }` 带 `!important`，
      不写就盖不过它。这是本轮最容易漏的一处 —— 漏了的现象是
      **"侧栏内容都渲染对了，屏幕上却什么也看不见"**（DOM 里有、样式上是 none）。
   ⚠️ 同时 `.app-main` 的左边距必须**还回去**：上面那条 is-standalone 规则把它设成
      `margin-left: 0`（前提是"没有侧栏"）。侧栏回来后要让出 `--sidebar-width`，
      否则内容列会被压在侧栏底下；且 `width: 100%` 要一并改成 `auto`，
      不然"240px 外边距 + 100% 宽"会横向撑出滚动条。
   ⚠️ 用 `.is-finance` 这个**显式标记类**（由 showAppShell 加），不用 `:has(.fin)`：
      后者靠"页面内容恰好带某个类"推断，隔了一层，后来者不易发现。
   ⚠️⚠️⚠️ 2026-10-09（乐哥：「财务中心系统ui，要按照设计规范做，当前的这个不对，
      包括tab，还有页面底色等」）：`margin-left` **必须补 `--shell-inset`**
      ────────────────────────────────────────────────────────────────────────────
      壳层 2026-10-07 定稿是「**侧栏贴边通高 = 底色，内容屏浮在底色上，四条缝都露出底色**」
      ⇒ `layout.css` 的 `.app-main` 写的是 `calc(var(--sidebar-width) + var(--shell-inset))`
      （侧栏右缘 = sidebar-width，内容左缘 = sidebar-width + inset ⇒ **中缝恰好 = inset**）。
      ⚠️⛔ 而本条（当时 09-21 写的）只写 `var(--sidebar-width)` ⇒ **把中缝吃成 0**、
         `width` 也退成 `auto` 而非达播台的显式值 ⇒ **两栏贴死**。
      ▸ 实测（1920×950 真机，本轮修前）：
         达播台 `margin-left: 232px`（224 + 8）**中缝 8** ✔
         财务中心 `margin-left: 224px` ⇒ **中缝 0** ✘
         ⇒ 后果正是乐哥说的「页面底色不对」：**侧栏不再是"包着内容屏的底色"，
            而成了"内容屏左边接着的另一块板"** —— 底色本身（#E8EEF8）是对的，
            错的是**缝没露出来**，包裹结构读不出来。
      ⛔ 别改回 `var(--sidebar-width)`：那是"贴死"的写法，与壳层定稿直接冲突。
      ⚠️ **同一条老写法在另外 4 个独立外壳板块也各有一份**（本轮一并修，见下）：
         `.is-admin` / `.is-portal`（本文件）、`.is-project`（pages-project.css）、
         `.is-hr`（pages-kq.css）—— 全部实测缝 = 0。 */
.app-shell.is-standalone.is-finance .sidebar {
  display: flex !important;
}
.app-shell.is-standalone.is-finance .app-main {
  margin-left: calc(var(--sidebar-width) + var(--shell-inset));
  width: auto;
}
/* ⚠️⚠️ 移动端必须**显式收回**上面那条 `margin-left`。
   ──────────────────────────────────────────────────────────────────────────
   为什么：`@media` 查询**不改变选择器特异性**。上面那条是 3 个类的选择器，
   而移动端的基础规则只有 1 个类 —— 特异性能赢，于是**桌面的 240px 左边距在手机上照样生效**。
   实测（390×844 视口，本轮真踩）：`.app-main` 的 margin-left 被算成 240px
   ⇒ 财务内容只剩 **150px 宽**（屏宽 390），几乎不可用。
   ⚠️⚠️ 而且这个 bug **不报错、也不产生横向滚动条**（内容自适应收窄了）
      —— 只能靠"在窄视口下量宽度"才看得出来。不要只测桌面就宣布完成。
   ⚠️ 反过来 `.sidebar` 那条**不需要**在移动端收回：
      移动端侧栏抽屉是靠 `transform: translateX(-100%)` 移出屏幕的（见 layout.css），
      `display` 本来就是 flex ⇒ 我那条 `display:flex !important` 与它不冲突。
      （若哪天移动端改成用 `display:none` 隐藏侧栏，这里就必须同步补一条覆盖。） */
@media (max-width: 768px) {
  .app-shell.is-standalone.is-finance .app-main {
    margin-left: 0;
    width: auto;
  }
}
.app-shell.is-standalone.is-finance .page-tabs-bar {
  display: flex !important;
}
/* ==========================================================================
   系统配置板块（`.is-admin`，2026-09-22 第三轮）
   --------------------------------------------------------------------------
   与财务中心（`.is-finance`）**逐条同构**：都是"独立外壳 + 自己的侧栏 + 显示标签栏"。
   ⚠️ 为什么要重复一遍而不是写成 `.is-finance, .is-admin` 的合并选择器：
      两边将来会各自演进（如系统配置不需要标签栏），合并会让"改一边动两边"。
      本项目的偏好是**显式重复**，代价可控、意图清楚。
   ⚠️ 与财务的唯一差别：**系统配置不需要页面标签栏**（它是配置页，不存在"同时开几个
      配置页来回切"的用法）⇒ 这里**不加** `.page-tabs-bar` 那条。
      加了的后果不是报错，而是标签栏空着一条（因为没有可切换的页），看着像坏了。
   ⚠️⚠️ 移动端那条 `margin-left: 0` 的收回复制理由与上面财务那段**完全一样**
      （`@media` 不改变特异性 ⇒ 桌面规则会在手机上继续生效 ⇒ 内容只剩 ~150px 宽）。
   ========================================================================== */
.app-shell.is-standalone.is-admin .sidebar {
  display: flex !important;
}
/* ⭐⭐ 2026-10-09：同一条"贴死"老写法的第二处（系统配置），一并补 `--shell-inset`
   —— 理由与理由见上面 `.is-finance` 那段（壳层定稿要中缝 = inset）。 */
.app-shell.is-standalone.is-admin .app-main {
  margin-left: calc(var(--sidebar-width) + var(--shell-inset));
  width: auto;
}
@media (max-width: 768px) {
  .app-shell.is-standalone.is-admin .app-main {
    margin-left: 0;
    width: auto;
  }
}

/* ==========================================================================
   公司门户（`.is-portal`，2026-09-22 深夜新增）
   --------------------------------------------------------------------------
   ⚠️ 门户原来是**没有侧栏**的独立外壳（只有顶部品牌栏 + 卡片网格）。
      现在它自己带了设置页（权限管理 / 用户管理 / 操作日志 / 导出记录）⇒ 需要侧栏导航，
      形态与财务中心/系统配置**逐条同构**，这样"三个板块看起来是一套系统"。
   ⚠️ 与 `.is-admin` 的唯一差别：门户**不要**页面标签栏（同 `.is-admin` 的判断 ——
      它的"页"是极少数几个设置页，没有"同时开几个来回切"的用法，加了会空着一条）。
      ⛔ 反过来 `.is-finance` 那条标签栏是**乐哥明确要求保留**的，别照抄这里去删它。
   ⚠️⚠️ 移动端那条 `margin-left: 0` 的收回复制理由与上面两段**完全一样**：
      `@media` 不改变特异性 ⇒ 桌面那条 240px 左边距会在手机上继续生效，
      内容只剩 ~150px 宽，而且**不报错、也不产生横向滚动条**（内容自适应收窄了）。
   ========================================================================== */
.app-shell.is-standalone.is-portal .sidebar {
  display: flex !important;
}
/* ⭐⭐ 2026-10-09：同一条"贴死"老写法的第三处（门户），一并补 `--shell-inset`
   —— 理由见上面 `.is-finance` 那段（壳层定稿要中缝 = inset）。
   ⚠️ 门户的侧栏同样是 `.is-portal .sidebar{display:flex!important}` 恢复出来的，
      所以它与财务是**同一个病**，不是一个巧合。 */
.app-shell.is-standalone.is-portal .app-main {
  margin-left: calc(var(--sidebar-width) + var(--shell-inset));
  width: auto;
}
@media (max-width: 768px) {
  .app-shell.is-standalone.is-portal .app-main {
    margin-left: 0;
    width: auto;
  }
}

/* 手机安全区：底部按钮不被 iPhone 的小黑条压住 */
body.is-standalone .sf-actions { padding-bottom: env(safe-area-inset-bottom, 0); }

/* 分区布局里的控件要撑满单列宽度。
   ⚠️ 必须显式写：`.form-grid .input{width:100%}` 那条只对 form-grid 生效，
      分区布局不是 form-grid → 金额这类原生 input 会退回默认宽度（实测 ~120px，
      与旁边的日期/下拉宽度不一致，看着像坏了）。 */
.rf-sec .input,
.rf-sec .select,
.rf-sec .textarea {
  width: 100%;
}

/* ==========================================================================
   个人替票页 —— 列表件 + 上传替票弹窗（2026-10-08 按规范改造）
   依据：乐哥「上传替票这个页面，ui也整体优化下」（原截图：弹窗 880px 宽、4 个字段排两列、
        输入框被拉到 412px 宽右侧全空、票面金额填了会怎样没说、底部留白；
        余额卡只有「¥0.00 + 一行 14 字规则」，看不到额度是怎么来的）。
   铁律：容器一律走既有 .card / .module-card 体系（不新造模块容器）；
        颜色全走 CSS 变量（零裸 hex）；类名 .tu- / .tt- 前缀，不改任何公共类。
   ⚠️ 列表的**列宽不在这里给** —— 走 JS 的 `<colgroup>`（见 t-tickets.js 的 COLS），
      这样"哪一列多宽/为什么"与内容需要写在同一处，CSS 只保管视觉。
   ========================================================================== */

/* —— 列表件（2026-10-08 按规范改造后新增）——
   ⚠️ 余额展示已改用全站 `.kpi-row` + `.kpi-card`（标签在上、大数字在下），
      原来那套自造的 `.tu-bal`（卡内左右两栏）**已删除** —— 乐哥「按照规范改造下」。
      ⛔ 别再把它加回来：同一页里自造一套 KPI，与全站列表页的骨架就不一致了。
      唯一保留的自定义是「余额数字用红」：`--color-price` 是全站结论性金额的语言
      （门禁 `invoice-ocr.test.js` ⑤-9 锁着），而 `.kpi-card__value` 的默认色不是红，
      所以这里补一条覆盖面很小的类。 */
.tt-kpi-price { color: var(--color-price); }

/* 操作列 / 附件列里的按钮不许折行 —— 折行会让**行高不一致**（本项目 2026-10-08 实测：
   170px 的操作列把「修正 + 删除」挤到第二行，第一行行高比第二行多 30px，整张表看着是散的）。
   ⚠️ 列宽要按"最全那一行"给（见 t-tickets.js 里操作列的注释），本规则只兜住"绝不折行"。 */
.tt-row > td:last-child,
.tt-row > td:nth-last-child(2) { white-space: nowrap; }

/* 编号（TP20261008001）：等宽数字 —— 扫列时 TP 号能对齐，肉眼比大小/找号快得多。
   ⚠️ 加 `nowrap`：编号列只有 138px（首列内边距 20/12 + 号码 105），
      「TP20261008001」没有断点、本来也不会断，但 `nowrap` 把这条**写死**，
      将来有人把编号格式改成带空格/连字符的，也不会在列里折成两行。 */
.tt-no {
  font-variant-numeric: tabular-nums;
  font-weight: var(--font-weight-medium);
  color: var(--color-fg);
  white-space: nowrap;
}

/* 「识别来源」格（2026-10-08 乐哥：「自动识别单独一列」）——
   原来它是「发票信息」格的第二行小字，现在独立成列 ⇒ 财务扫这一列就能挑出要人工核的票。
   ⚠️ `nowrap`：这一列只放 2~7 个字，断行只会让行高参差。
   ⚠️ 「识别失败」的醒目色走**类名**（`--color-warning`），不写内联 `style` ——
      内联会盖过 CSS，表现成"CSS 改了它却不变"（本项目操作列踩过同一个坑）。 */
.tt-src { white-space: nowrap; }
.tt-src--warn { color: var(--color-warning); }

/* 校验状态下面那行「原因」小字：**最多两行**，超出打点（全文在 title 里）。
   ⚠️ 必须有这条：乐哥的窗口下列宽只有 130px，
      实测一段真实文案「查验接口返回 HTTP 401（APIG.0303 认证失败，请检查 AppCode 是否过期）」
      会折成 **8 行**、把这一行的行高从 66px 撑到 200+px —— 整张表看着像卡住了。
      截两行后高度恒定，财务想读全文鼠标一悬停就有（title 一直带着）。
   ⚠️ 用 `-webkit-box` 而非 `max-height`：按**行数**截断，换字号不会失效
      （项目既有写法，见 `.s-prod-summary`）。 */
.tt-note {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* 附件缩略图（2026-10-08 乐哥：「附件列改成点开弹窗看」——
   改前是一个「查看附件」文字按钮，点了**跳新标签页**）。
   ⚠️ 尺寸**写死** 56×40，不靠原图比例：发票有横有竖，不固定会让每行高度参差
      （本项目在"行高参差"上踩过：操作列按钮折行那次）。
   ⚠️ `object-fit: cover` 而不是 contain：这里要的是"一眼看出是张票"，
      contain 会让细长比例的票缩成一小条，看不出是不是发票。
   ⚠️ 白底：发票是白纸，透明底会显成"缺了一块"。 */
.tt-att-thumb {
  display: block;
  width: 56px;
  height: 40px;
  object-fit: cover;
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-sm);
  background: #fff;
  cursor: zoom-in;          /* 光标要说得出"能点" */
  transition: border-color var(--motion-fast) var(--motion-ease-standard);
}
.tt-att-thumb:hover { border-color: var(--color-accent); }

/* ⭐⭐ 2026-10-21 PDF 缩略图（乐哥：「替票审核…现在看不到照片」）
   ▸ 为什么现在才做：生产 7 张替票里 **6 张是 PDF**，而原来 PDF 只给一个「PDF」小按钮
     ⇒ 财务在列表里一张票面都看不到。现在用项目自带的 pdf.js 把**首页**渲进 canvas。
   ▸ 尺寸与图片缩略图**完全一致**（复用 `.tt-att-thumb` 的 56×40），
     所以同一列里图片票和 PDF 票高度一样，行高不会参差。
   ▸ 高度裁切而不是压扁：发票的抬头/号码都在上半截，露出上半截就够认出是哪张票；
     压扁（object-fit:fill）会把字挤成一团，反而认不出。
   ▸ `is-fallback`（pdf.js 没加载/文件损坏）：退回「PDF」角标居中显示 ——
     ⛔ 不能留个空白框，那比给个按钮更让人困惑。 */
.tt-att-pdf {
  position: relative; padding: 0; overflow: hidden;
  background: var(--color-surface-2); cursor: zoom-in;
}
.tt-att-pdf__cv { display: block; width: 100%; height: auto; }
.tt-att-pdf__badge {
  position: absolute; right: 2px; bottom: 2px;
  padding: 0 3px; border-radius: 2px;
  background: rgba(0, 0, 0, .55); color: #fff;
  font-size: 10px; line-height: 14px; letter-spacing: .3px;
}
/* 渲染成功 ⇒ 角标消失（画面上已经有票面了，再压个 PDF 字样是噪音） */
.tt-att-pdf.is-ready .tt-att-pdf__badge { display: none; }
/* 降级形态：居中一个 PDF 字样，占位与图片缩略图同尺寸 */
.tt-att-pdf.is-fallback { display: inline-flex; align-items: center; justify-content: center; }
.tt-att-pdf.is-fallback .tt-att-pdf__cv { display: none; }
.tt-att-pdf.is-fallback .tt-att-pdf__badge {
  position: static; background: transparent;
  color: var(--color-fg-3); font-size: var(--font-size-xs);
}

/* ⭐ 2026-10-21 替票面金额标红加粗（乐哥截图：「¥118.00」红字加粗）
   ⚠️ 走 `--color-price`（全站"结论性金额"的语言，与替票余额/报销单合计同源）——
     ⛔ 不自造颜色，也别用 `--color-danger`（那是"出错/驳回"的红，语义不同）。 */
.fin-tk-amt { color: var(--color-price); font-weight: var(--font-weight-semibold); }
/* ⚠️ 上面单类选择器（0,1,0）在**表格单元格**里会被 `.fin-fixtbl td` 之类的
   （0,1,1）盖掉 ⇒ 实测颜色仍是正文黑。提一档特异性，别用 !important。 */
.fin-fixtbl .fin-tk-amt { color: var(--color-price); font-weight: var(--font-weight-semibold); }

/* 非图片附件（PDF / OFD）：带**格式名**的小按钮 —— 缩略图渲染不出（塞 <img> 是裂图），
   而只写"查看"又看不出传的是什么格式。
   ⚠️ 高度与操作列的 `btn--sm` **同档**（--control-height-sm = 34）：
      同一行里两种高的按钮会明显错位（"控件两档制"的既有教训）。
   ⚠️ `font-family: inherit` 不能省：`<button>` 默认**不继承**字体，
      漏了会变成系统默认字体（和旁边文字不是一套）。 */
.tt-att-doc {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  height: var(--control-height-sm);
  padding: 0 var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: none;
  color: var(--color-accent-text);
  font-family: inherit;
  font-size: var(--font-size-sm);
  white-space: nowrap;
  cursor: pointer;
  transition: border-color var(--motion-fast) var(--motion-ease-standard),
    background var(--motion-fast) var(--motion-ease-standard);
}
.tt-att-doc:hover { border-color: var(--color-accent); background: var(--color-accent-bg); }

/* ⭐⭐ 附件预览（2026-10-08 乐哥：「这个发票预览，内容显示不全，要显示全，然后那些上面的工具，
   是不是可以隐藏了，我们自己做一个放大缩小移动组件放下面就好了」）
   ──────────────────────────────────────────────────────────────────────────
   改前是 `<iframe src="x.pdf#view=FitH">`，交给**浏览器自带**的 PDF 阅读器渲染 ⇒ 三件事失控：
     ① 顶部那排工具（文件名/页码/缩放/旋转/下载/打印）是浏览器的 UI，CSS 够不着；
     ② `#view=FitH` 是 **Adobe** 的参数，Chrome 只认 `#page/#zoom/#toolbar/#navpanes`
        ⇒ 发票右侧「发票号码」与底部「备注」就是这么被裁掉的（乐哥截图即此）；
     ③ 企业微信内置浏览器没有 PDF 阅读器 ⇒ iframe 一片空白。
   ⇒ 现在**自己渲染**（pdf.js，见 `public/js/attachment-viewer.js`），工具条也是我们自己的。
   ⚠️ 舞台高度 **66vh**：弹窗上限 90vh，减掉头(≈57)+身留白(40)+脚(≈66) 后还留得住余量；
      写 70vh 起，在大字号或小屏上会把 footer 挤出弹窗、整窗出滚动条。
   ⚠️ 底色用 `--color-surface-2`（浅灰）：白底 PDF 页面压在白底舞台上**看不出边界**，
      会误以为"页面没渲染出来"。 */
.av-stage {
  position: relative;
  height: 66vh;
  overflow: auto;
  padding: var(--space-3);
  background: var(--color-surface-2);
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-md);
  cursor: grab;
}
.av-stage.is-drag { cursor: grabbing; }
/* 拖拽期间禁掉子元素的原生拖拽/选中，否则会变成"拖着半张图"或"拖出一片蓝字" */
.av-stage.is-drag, .av-stage.is-drag * { user-select: none; }
/* ⚠️⚠️ 这里**不能用 flex 居中**（`justify-content: center`）——
   flex 容器里内容比容器宽时，居中会把左半边推到滚动区之外、
   且 `scrollLeft` 到不了 0 ⇒ **放大后左边的发票号码永远看不到**
   （经典的 flex + overflow 居中坑）。用"块级 + `width: fit-content` + `margin: auto`"：
   不溢出时居中、溢出时 margin 自动退回 0、四个方向都能滚到。 */
.av-inner { display: block; width: fit-content; margin: 0 auto; }
/* ⚠️ `max-width: none` 是**必须**的：base.css 有全局 `img { max-width: 100% }`，
   它会把"放大后的图片"重新压回舞台宽 —— 表现就是"点放大没反应"。
   （这也是"改公共样式前先查依赖"那条的正面用法：这里的全局规则是刻意留的，
     不能被这里改掉，只能在本组件内局部覆盖。） */
.av-img { display: block; max-width: none; height: auto; }
/* PDF 每页一张 canvas。白底由 CSS 给（canvas 本身透明），
   否则页面会透出舞台的浅灰、看着像"印在灰纸上"。 */
.av-page { display: block; background: var(--color-surface); margin: 0 auto var(--space-3); }
.av-page:last-child { margin-bottom: 0; }
.av-status {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  padding: 0 var(--space-6);
  color: var(--color-muted); font-size: var(--font-size-sm);
  text-align: center;
  /* 只是提示层：不能挡住底下的拖拽/滚动 */
  pointer-events: none;
}
.av-status--err { color: var(--color-warning-text); }

/* 底部工具条（放 `.modal__footer` 里，撑满后左右两端对齐） */
.av-bar { display: flex; align-items: center; gap: var(--space-3); flex: 1; min-width: 0; }
/* ⚠️ 左侧全是按钮/数字 ⇒ **不许收缩**（被压扁比溢出更难看，且 `−/＋` 压扁后点不准）。 */
.av-bar__l { display: flex; align-items: center; gap: var(--space-2); flex-shrink: 0; }
/* ⚠️⚠️ `.av-bar__r` **必须 `min-width: 0`** ——
   flex 子项的默认值是 `min-width: auto`（= **内容的最小宽度**），不加这一条时，
   右边那串「文件名 · 这一笔说明 · 操作提示」会把 `__r` 撑到比可用空间还宽，
   于是**溢出到「下载 / 关闭」上面**（2026-10-10 乐哥截图报的显示 bug）。
   加 `min-width: 0` 后子项才真的能收缩，配合下面三样的 `overflow: hidden` 出省略号。 */
.av-bar__r { display: flex; align-items: center; gap: var(--space-3); margin-left: auto; min-width: 0; }
/* 右侧按钮**永远完整显示** —— 可压缩的只有文字那三样（文件名 / 这一笔 / 操作提示）。 */
.av-bar__r > .btn { flex: 0 0 auto; }
/* 百分比固定宽：不固定时 100% ↔ 83% 会让左右两个按钮来回跳（本项目对这类位移一贯要求消除） */
.av-bar__pct { min-width: 48px; text-align: center; font-size: var(--font-size-sm); color: var(--color-fg-2); font-variant-numeric: tabular-nums; flex: 0 0 auto; }
.av-bar__sep { width: 1px; height: 18px; background: var(--color-border); flex: 0 0 auto; }
/* ⚠️ 操作提示也要能截断（宽屏下它 ≈180px，和"文件名 + 这一笔"叠起来仍可能超宽） */
.av-bar__hint { font-size: var(--font-size-xs); color: var(--color-muted); white-space: nowrap; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
@media (max-width: 768px) {
  /* 窄屏优先保控件，操作提示让位 */
  .av-bar__hint { display: none; }
  .av-stage { height: 56vh; }
}

/* ══════════════════════════════════════════════════════════════════════════
   多张模式（2026-10-10）：两侧箭头 + 空态说明 + 底部条的文件名/这一笔
   ──────────────────────────────────────────────────────────────────────────
   乐哥：「@截图 这个 pdf 的**不能左右键切换快速查看**，要处理；然后**关闭的时候，
          还会闪一下点击预览的那个窗口**」⇒ 把左右切换做进阅读器、**删掉自造画廊**
          （`.ea-view*` 整段删除），本组件成为附件查看的唯一窗口。
   ⚠️ 箭头挂在 `.av`（**不是 `.av-stage`**）上：`.av-stage` 是滚动容器，
      绝对定位的子元素会**跟着内容滚走**（放大后箭头就跑出可视区了）。
   ⚠️ 箭头是"键盘 ←→ 的鼠标等价入口"，键盘才是主路径。
   ⚠️ 到边界**禁用而不是隐藏**：灰色箭头表达"到头了"，
      比"按钮消失"更让人确信"这一笔的附件都看过了"（财务核对怕漏看）。
   ⚠️ 尺寸只在这里定（JS 只给语义）—— 与 `.ea-thumb` 同一条纪律。
   ══════════════════════════════════════════════════════════════════════════ */
.av { position: relative; }
.av-nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 36px;
  height: 36px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-full);
  color: var(--color-fg-2);
  cursor: pointer;
  box-shadow: var(--shadow-sm);
  /* ⚠️ `<button>` 自带字体 ⇒ 不 reset 图标会与别处的 icon-btn 不一致 */
  font: inherit;
}
.av-nav--prev { left: var(--space-2); }
.av-nav--next { right: var(--space-2); }
.av-nav:hover:not(:disabled) {
  border-color: var(--color-accent);
  color: var(--color-accent-text);
}
.av-nav:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 1px; }
.av-nav:disabled { opacity: .35; cursor: default; box-shadow: none; }

/* 没有"能画的东西"时的说明块。⛔ 两种绝不合并 —— 本项目最忌讳的误导是
   "让用户以为附件丢了"：`只有飞书码` 与 `浏览器渲染不了的版式` 后果完全不同。 */
.av-none {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: 320px;
  padding: var(--space-6);
  font-size: var(--font-size-sm);
  color: var(--color-fg-2);
  text-align: center;
}
.av-none__sub { font-size: var(--font-size-xs); color: var(--color-muted); }
/* 「只有缩略图（原件未留存）」的注明 —— 放在图下方，不遮图 */
.av-note { margin-top: var(--space-2); font-size: var(--font-size-xs); color: var(--color-muted); }

/* 底部条右侧：文件名（只在多张模式填）+ "这一笔"的说明。
   ⚠️ 都要能截断：文件名可能很长（"微信支付截图-20260917-北京.jpg"），
      不截断会把「下载 / 关闭」挤出去。 */
.av-bar__name,
.av-bar__scope {
  max-width: 200px;
  /* ⚠️ `min-width: 0` + `flex: 0 1 auto` 缺一不可 —— 只写 `max-width` 时，
     flex 子项的 `min-width: auto`（默认 = **内容宽度**）会让它无视上限、把整条底部栏撑破
     （乐哥 2026-10-10 截图那个"文字压到下载/关闭上"的 bug 就是这个）。 */
  min-width: 0;
  flex: 0 1 auto;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--font-size-xs);
  color: var(--color-muted);
}
.av-bar__scope { max-width: 240px; }
.av-bar__name:empty { display: none; }
/* 「下载」在没有本地件（原件在飞书）时是禁用态。
   ⚠️ 用 `pointer-events: none` 而不只是视觉置灰 —— 假链接点了有反应比没反应更糟。 */
.btn.is-disabled { opacity: .5; pointer-events: none; }

/* 窄屏：两侧箭头会压到图上（PDF 适应宽度后占满整个舞台）⇒ 排到舞台下方一行。 */
@media (max-width: 768px) {
  .av { display: flex; flex-wrap: wrap; align-items: center; }
  .av-stage { width: 100%; }
  .av-nav { position: static; transform: none; margin: var(--space-2) 0 0 var(--space-2); }
}

.tt-pv__doc { padding: var(--space-6) var(--space-4); text-align: center; }
.tt-pv__doc-t { margin-bottom: var(--space-4); color: var(--color-fg-2); font-size: var(--font-size-sm); }
/* ⛔ `.tt-pv__img` / `.tt-pv__frame` 已删（2026-10-08）：
   `__frame` 是 iframe 方案的产物（不再用 iframe）；
   `__img` 被 `.av-img` 取代（新组件要给图片单独限宽/居中/放大，与 PDF 同一套舞台）。
   删而不留：本项目对"定义了没人用的类名"一贯删除（留着会让下次改版照它改）。 */

/* —— 明细（2026-10-08 起住在**小弹窗**里，乐哥：「查看做成弹小窗口的样子，不要展开了」）——
   改前是"就地展开一行"（`.tt-detail > td`），现在由操作列的「查看」按钮弹 `UI.openModal`。
   ⚠️ 底色/单元格内边距那两条**随展开行一起删了**：弹窗自带背景与内边距，
      留着会变成"弹窗里再套一层灰底"（看着像内容没对齐）。
   ⚠️ `.tt-detail__box` 的 padding 归零 —— 内边距由 `.modal__body` 负责，
      两个都加会叠成双倍留白。 */
.tt-detail__box {
  padding: 0;
  /* ⭐⭐ 2026-10-08 二改（乐哥：「点查看后这个窗口优化下，一个是宽度…排版也整体看下」）：
     从"字段在上、附件在下"改成**左字段 / 右附件**两栏。
     ▸ 为什么：发票是**竖版**的，上下堆叠时那张图只拿得到 240~360px（被 max-height 卡住），
       而弹窗右侧整片空着。挪到右列后图更大、弹窗反而**更矮**（实测 775 → 495）。
     ▸ 档位：宽度用规范的 md 档 **880**（原来 620 是自造值）。
     ⚠️ `align-items: start`：右列必须贴顶，不能跟着左列的高度拉伸。 */
  display: grid;
  grid-template-columns: minmax(0, 1fr) 300px;
  gap: var(--space-6);
  align-items: start;
}
/* 没有附件时**不立右列** —— 否则那句"无附件"的说明文字会被压进 300px 里折三行，
   右列还立着一根空柱子 */
.tt-detail__box--noatt { grid-template-columns: minmax(0, 1fr); }
.tt-detail__grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-3) var(--space-5);
}
.tt-detail__item { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
/* 校验说明 / 备注可能很长 ⇒ 整行铺开，不挤在一格里（挤了会折成七八行） */
.tt-detail__item--wide { grid-column: 1 / -1; }
.tt-detail__k { font-size: var(--font-size-xs); color: var(--color-muted); }
.tt-detail__v {
  font-size: var(--font-size-sm);
  color: var(--color-fg);
  overflow-wrap: anywhere;
}
/* ⚠️ 不用上边框分隔：分栏布局里它在**右列顶部**画一条短线，反而像"少了半条线"。
   窄屏退回上下堆叠时再加回来（见下面的 1023 断点）。 */
.tt-detail__att { min-width: 0; }
/* `.tt-detail__attbox` 已删除（2026-10-08）：明细里的图片改成**直接显示 + 点击弹窗**，
   不再需要那个 `<a>` 包裹层（它原来包着图片去 `target="_blank"` 跳新页）。 */
/* 发票是白纸黑字：容器给白底（--color-surface），透明底会让它看着像"缺了一块" */
.tt-detail__img {
  display: block;
  /* ⭐ 2026-10-08：340/240 → **100% / 440**。右列宽 300，图按列宽铺满；
     高度给到 440（竖版发票能显示得比原来大一倍），再高就顶出弹窗了。
     ⚠️ 改这两个值要连带想右列宽（`.tt-detail__box` 的 300px）——
        三处是同一件事（能放多大），只改一处会变成"图被拉扁"或"右列撑高"。 */
  max-width: 100%;
  max-height: 440px;
  object-fit: contain;
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  /* 明细里的发票图现在**可点**（点开弹窗看大图），光标要说明这一点 */
  cursor: zoom-in;
}
.tt-detail__doc {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  color: var(--color-fg);
  /* ⚠️ 2026-10-08 起这里是 `<button>`（不再是 `<a target="_blank">`）：
     `<button>` 默认**不继承**字体、且光标是箭头 ⇒ 这两条不能省，否则
     字体会变成系统默认、鼠标移上去也看不出能点。 */
  font-family: inherit;
  cursor: pointer;
  text-align: left;
  text-decoration: none;
}
.tt-detail__doc:hover { border-color: var(--color-accent); }
.tt-detail__doc > span { display: flex; flex-direction: column; gap: 2px; font-size: var(--font-size-sm); }
.tt-detail__doc em { font-style: normal; font-size: var(--font-size-xs); color: var(--color-muted); }

/* —— 上传替票弹窗 —— */
/* ⚠️ 2026-10-08 乐哥：「改成红色字不要底框」——
   原来这里是「淡蓝底 + 虚线框」的提示条（.tu-note / .tu-alloc），
   两处同时出现在一个弹窗里、样式还不一样 ⇒ 视觉噪音，且"这是提醒还是说明"分不清。
   ⇒ 统一收敛成：**纯红字、无底色、无边框、无内边距**。
   ⚠️ 颜色走 --color-price（全站结论性金额/提醒色，= --color-danger 同源），
      禁裸 hex（设计规范铁律⑤）。 */
/* 顶部提示条：进弹窗第一眼要知道「这张票会变成什么」，而不是先看到一串要填的框 */
.tu-note {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-1) var(--space-3);
  margin-bottom: var(--space-4);
}
.tu-note__k { font-size: var(--font-size-sm); font-weight: var(--font-weight-medium); color: var(--color-price); }
.tu-note__v { font-size: var(--font-size-sm); color: var(--color-price); }

/* 分区复用 .rf-sec 的结构（数字圆标 + 标题 + 一段内容），与报销提交页同一套视觉语言。
   ⚠️ 2026-10-08：本类**只在上传弹窗里用**（`.tu-sec` 这个类名只有本弹窗挂）。
      修正弹窗（openFix）不用它 —— 别被上面那句旧注释误导。
   ⭐⭐ 2026-10-08 乐哥：「上传替票，这个整体的间距都不行，都要按照规范统一调整」
      ⇒ 三段间距**全部对齐同族基线**（报销提交页的 `.rf-sec` 与
         `.rf-sec .module-card__body > div`），不新造档位：
           · 分区之间          16px（= `.rf-sec` 的 margin-bottom）
           · 分区标题 → 内容    16px（= `.module-card__head` 的 margin-bottom，见 components.css）
           · 字段之间          16px（= `.rf-sec .module-card__body > div`，**由它提供、这里不重复给**）
       改前实测：字段之间 32px（既有规则的 16 与 `#tt-fields` 的 `gap` 叠加）、
       分区标题与内容 0px（`.module-card--bare` 的 padding-top 是 0）⇒ 一紧一松，就是"间距不行"。 */
.tu-sec { margin-bottom: var(--space-4); }
.tu-sec--last { margin-bottom: 0; }
/* ⚠️ padding-top 必须显式给：`.module-card--bare` 的基类是 `padding: 0 0 var(--space-6)`
   ⇒ 上内边距为 0，分区标题会**紧贴**下方内容（实测 0px）。 */
.tu-sec .module-card--bare { padding: var(--space-4) 0 0; border-bottom: none; }

.tu-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-4) var(--space-5);
}
.tu-span2 { grid-column: 1 / -1; }
/* 字段说明可能是两行，行高按正文给，否则两行会挤在一起 */
.tu-grid .field-hint { line-height: var(--font-line-height-body); }

/* 单列字段组（2026-10-08 上传弹窗改版）：删掉两列后中间会空半屏，改竖排一条线。
   ⚠️ 选择器是 **id** 不是类：`#tt-fields` 就是那个容器（它同时是 JS 的取值锚点，
      renderFields 往里填 `.tu-field`），并没有一个额外的 `.tu-fields` 包裹层
      —— 早先按类名写过一版，容器一恢复成 module-card__body 就不生效了。
   ⚠️⚠️ **这里一个间距都不要给**（2026-10-08 实测踩到）：
      `.rf-sec .module-card__body > div { margin-bottom: var(--space-4) }`（pages.css:5308）
      本来就在给字段之间 16px。我先前又加了个 `gap: var(--space-4)` ⇒
      **两条叠加成 32px**，比弹窗里其他所有间距都大一倍 —— 就是乐哥说的"间距不行"。
      ⇒ 字段间距只由那条既有规则提供（与报销提交页**逐字同源**），容器这里只管排列与满宽。 */
#tt-fields { display: block; }
.tu-field { display: block; }
/* ⚠️ 必须显式撑满（2026-10-08 真机截图抓到的）：
   改单列之前字段在 `.tu-grid` 里，grid 项默认 stretch；换成普通块级容器后
   `<input class="input">` **退回内容宽度** ⇒ 三个框只有一半宽、右边一大片空。
   静态截图/门禁都看不出来（判据只查 display 与列数），必须真机量宽度。 */
.tu-field > .input { width: 100%; }

/* 金额 → 额度 反馈：**纯红字，无底框**（2026-10-08 乐哥明确要求）。
   ⚠️ 与报销提交页的 .rf-alloc（虚线+淡底）刻意不同：
      那个是"系统算出来给你看"的联动条（在长表单里需要视觉锚点），
      这个是"你这张票能报多少"的一句结论 —— 加框会把它读成"一个待填的输入区"。 */
.tu-alloc {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  margin-top: var(--space-1);
}
.tu-alloc__k { font-size: var(--font-size-sm); color: var(--color-price); }
.tu-alloc__v {
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  color: var(--color-price);
  font-variant-numeric: tabular-nums;
}

/* ==========================================================================
   上传替票 · 「拍发票」大上传框（2026-09-19 第二次改版）
   ──────────────────────────────────────────────────────────────────────────
   改版前是一个 160×160 的小虚线框、文案只写「点击上传图片」，而且它在最下面
   （照片是"选填、收尾补一下"的定位）。现在照片成了**动线的第一步**
   （拍完自动识别 → 后面几项就不用打了），所以它必须：
     · 大（整宽、够高），一眼看出"这里是起点"
     · 文案说清拍了会发生什么（"会自动读出票面信息"）
     · 传完直接显示预览 —— 让人确认"拍的就是这张票"，而不是只留个文件名
   ⚠️ 颜色全走既有令牌：主色虚线框（与报销单的必传凭证同一语言 = "要你做动作"），
      完成后转成功色（"这一步做完了"）。
   ========================================================================== */
.tu-shot {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  width: 100%;
  min-height: 132px;
  padding: var(--space-4);
  /* ⚠️ 虚线 **2px**（不是 1.5 —— Chrome 会把边框宽度取整到 1px，写 1.5 等于写 1），
     与报销单上传格（`.eb-files`）**逐项对齐**。 */
  border: 2px dashed var(--color-primary-200);
  border-radius: var(--radius-md);
  /* ⭐⭐ 2026-10-09 常态**不铺底色**（全局检查：与报销单的上传格是同一个毛病）。
     ──────────────────────────────────────────────────────────────────────────
     ▸ 改前 `background: var(--color-accent-bg)`（= `--color-primary-50` 浅蓝）——
       而那个令牌在全站的语义是**"选中 / 拖拽中 / 激活"**（对照标准件 `.upload-drop`：
       常态无底 → `:hover` `surface-2` → `.is-dragover` `accent-bg`）。
       ⇒ 这一格一直看着像"被选中"。
     ▸ 顺带的一个哑点：拖拽高亮的**底色**与常态是同一个值（见下面
       `#tt-shot.is-dragover .tu-shot`），所以"拖进来"原本只剩边框在变、
       底色那一维等于没有反馈；现在常态透明 ⇒ 那一维也真的有变化了。
     ▸ 现在：常态透出弹窗白底 ⇒ hover 给浅灰、**拖进来才变浅蓝**。 */
  background: transparent;
  color: var(--color-accent-text);
  font-family: inherit;
  cursor: pointer;
  transition: border-color var(--motion-fast) var(--motion-ease-standard),
    background var(--motion-fast) var(--motion-ease-standard);
}
.tu-shot:hover { border-color: var(--color-accent); background: var(--color-surface-2); }
/* ⚠️ 拖拽高亮**已有既有实现**（别在这里另加一条 `.tu-shot.is-dragover`）：
   类挂在**容器** `#tt-shot` 上（见下面 `#tt-shot.is-dragover .tu-shot` 那段长注释），
   因为已上传态下按钮被预览图/文件卡片替换 —— 挂在按钮上就"传完再拖没有反馈"。
   ⛔ 2026-10-09 我一度以为这个钩子不存在（grep 只查了 `.tu-shot` 前缀、漏了 `#tt-shot …`
   这条），还补了一条重复规则 —— 已删。**否定式结论先回头查判据漏没漏主角。** */
.tu-shot__ico { display: flex; }
.tu-shot__t { font-size: var(--font-size-base); font-weight: var(--font-weight-medium); }
.tu-shot__s { font-size: var(--font-size-xs); color: var(--color-muted); }
/* 识别中：与普通框同高，避免"点完框突然变矮、下面的字段往上跳" */
.tu-shot--busy {
  flex-direction: row;
  gap: var(--space-2);
  cursor: default;
  border-style: solid;
  border-color: var(--color-border);
  background: var(--color-surface-2);
  color: var(--color-fg-2);
  font-size: var(--font-size-sm);
}
.tu-shot__spin {
  width: 14px; height: 14px; flex: 0 0 auto;
  border: 2px solid var(--color-border);
  border-top-color: var(--color-accent);
  border-radius: 50%;
  animation: tu-spin .8s linear infinite;
}
@keyframes tu-spin { to { transform: rotate(360deg); } }
/* 已上传：预览图 + 状态行 */
.tu-shot__wrap { width: 100%; }
/* ⭐ 预览区：图片 / PDF / OFD **三种形态共用同一个高度**，且**整区可点**（点开看大图）。
   ⚠️ 它现在是 `<button>`（不是 div）：键盘可达、自带 pointer 光标。
      `<button>` 有几条默认样式**必须显式盖掉**，漏一条就会"看着不像我们的组件"：
        · `background` 默认按钮灰 → 给 `#fff`（发票是白纸黑字，透明底会像"缺了一块"）；
        · `border` 默认 `2px outset` → 给 `1px solid`；
        · `padding` 默认有几 px → 归零（否则图片被内缩，与其他卡片不是一套边距）；
        · `font-family` **不继承** → `inherit`（否则卡片里的字变系统默认字体）。
   ⚠️ 高度（2026-10-08 乐哥：「内容不用这么大，这个页面支持点击预览就行」）：
      **200px → 120px**。预览只负责"一眼看出是张票"，细节交给点击后的弹窗。
   ⛔ 别让 img 自己定高、也别再把 PDF 塞成内嵌 iframe —— 前两轮各踩过一次：
      前者让"换个文件格式整块内容上下跳 130px"，后者在 120px 的框里根本看不见。
   ⚠️ 光标放在**容器**上（真正可点的是它），不在 `img` 上 —— 单一来源，别写两遍。 */
.tu-shot__view {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 120px;
  padding: 0;
  overflow: hidden;
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-md);
  background: #fff;
  font-family: inherit;
  cursor: zoom-in;          /* 光标要说得出"能点开看" */
  transition: border-color var(--motion-fast) var(--motion-ease-standard);
}
.tu-shot__view:hover { border-color: var(--color-accent); }
.tu-shot__img {
  display: block;
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
}
.tu-shot__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-top: var(--space-2);
  font-size: var(--font-size-sm);
  color: var(--color-success-text);
}
.tu-shot__bar > span { flex: 1; }

/* 删除已传附件（2026-10-08 乐哥：「传上去后没法直接删除，不合理」）。
   ⚠️ 红字与列表里的删除操作同一套语言（那里也是 ghost + 红字）。
   ⚠️ 走 `--color-danger` 而不是内联 `style="color:red"`：内联色会盖过 CSS，
      表现为"CSS 改了但它不变"（本项目在列表操作列踩过这个坑）。 */
.tt-del { color: var(--color-danger); }

/* —— 拖拽上传（2026-10-08 乐哥：「上传方式不支持直接拖入」）—— */
/* ⛔⛔ 复用全站既有的拖拽语言，**不新造**（2026-10-08 上线前比对生产时发现第一版是自造的）：
      · 类名：全站统一 `.is-dragover`（见 components.css 的 `.upload-drop.is-dragover`、
        本文件 `.eb-files.is-dragover`）—— 第一版写成 `is-drag`，等于同一件事两套名字；
      · 手法：**换边框色 + 换底色**（虚线框转实线），不是 `outline` 描边；
      · 配色：`--color-accent` 边 + `--color-primary-50` 底（与 `.eb-files.is-dragover` 逐字同款）。
   ⚠️ 类挂在**容器** `#tt-shot` 上（不是那个虚线框按钮）：已上传态下按钮已被
      预览图/文件卡片替换，挂按钮则"传完再拖"没有任何反馈。
   ⚠️ 已上传件只换边框色、**不动底色** —— `.tu-shot__view` 的底色是白色（发票是白纸），
      换成淡蓝会在图片/PDF 不满宽时露出一圈蓝边，看起来像"没传全"。 */
#tt-shot.is-dragover .tu-shot {
  border-style: solid;
  border-color: var(--color-accent);
  background: var(--color-primary-50);
}
/* ⚠️ 已上传态的边框长在 `.tu-shot__view` 上（它是三种形态共用的外框），
   所以拖拽高亮要标它 —— 标 `.tu-shot` 在已上传态是空的（那个类只在空态按钮上）。 */
#tt-shot.is-dragover .tu-shot__view { border-color: var(--color-accent); }

/* 文件卡片（**只给 OFD 及浏览器认不出的格式**用；PDF 已改走内嵌 iframe）。
   ⚠️ 浏览器没有任何原生方式渲染 OFD（国产版式文件），塞 iframe 只会一片空白 —
      给一张"能点开下载"的卡片，比让人对着空白框猜"是不是没传上"要好。
   ⚠️ 这里**不设高度**：高度由外层的 `.tu-shot__view` 统一给（同高规则见那里）。 */
/* 文件卡片（PDF / OFD —— 浏览器对这两种都渲染不出缩略图）。
   ⚠️ 它现在是 `<span>`（原来是 `<a target="_blank">`）：住在
      `<button class="tu-shot__view">` 里 —— 按钮内不能再嵌交互元素
      （既不合规，点击也会和按钮互相打架）。
   ⚠️ 不设高度：由外层 `.tu-shot__view` 统一给（同高规则见那里）。 */
.tu-shot__doc {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  max-width: calc(100% - var(--space-6));
  padding: var(--space-2) var(--space-4);
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-md);
  background: var(--color-surface-2);
  color: var(--color-fg-2);
  text-align: left;
}
.tu-shot__doc > span {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  font-size: var(--font-size-base);
  color: var(--color-fg);
}
.tu-shot__doc em {
  font-style: normal;
  font-size: var(--font-size-xs);
  color: var(--color-muted);
}
.tu-shot__doc:hover { border-color: var(--color-primary-500); }

/* 「自动识别」小标：挂在被识别出来的字段标签后面。
   ⚠️ 它的作用不是装饰，而是**告诉用户"这几项是机器填的，重点核对它们"** ——
      全填完却不说哪些是机器填的，用户会把整张表都当成"自己填的、肯定没错"。 */
.tu-auto {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 6px;
  border-radius: var(--radius-full);
  background: var(--color-success-bg);
  color: var(--color-success-text);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-regular);
  vertical-align: 1px;
}

/* ⛔ `.tt-viewer`（本页自造的图片灯箱）已删除（2026-10-08）：
   统一改用全站 `UI.openModal`（`previewAttachment`）—— 与列表附件、
   以及「我的报销」的凭证预览**同一套**。项目本来就有这条约定，
   my-expenses.js 里的原话是：「复用系统现有弹窗，**不另造 lightbox**」。
   ⛔ 别再把它加回来：多一套看图 UI，代价是三处各自修各自的 bug
      （它当时就踩过"Esc 会连带关掉底层弹窗"的坑）。 */

/* —— 明细小窗：宽度放不下两栏时退回"字段在上、附件在下" ——
   ⚠️ 断点取 **1023**（不是 768）：分栏要的是"弹窗里能放下 左字段 + 300 右附件"，
      弹窗宽 880 时对应的视口下限就是 ~1024。用 768 的话，
      在 800~1023 这段视口里弹窗被 `max-width:100%` 压窄，
      左右两栏会挤成"字段列只剩 300px、附件列还是 300px"的难看比例。 */
@media (max-width: 1023px) {
  .tt-detail__box, .tt-detail__box--noatt { grid-template-columns: minmax(0, 1fr); }
  /* 退回上下堆叠时，把"附件区"的分隔线加回来（分栏时它画在右列顶部、像少了半条线） */
  .tt-detail__att {
    margin-top: var(--space-4);
    padding-top: var(--space-4);
    border-top: 1px solid var(--color-border-soft);
  }
}

/* —— 手机端（乐哥：「得适配 pc 和手机页面」）—— */
@media (max-width: 768px) {
  /* 明细小窗：两列在窄屏也放不下 ⇒ 单列（标签在上、值在下，与 .tt-detail__item 同构） */
  .tt-detail__grid { grid-template-columns: 1fr; }
  .tt-detail__img { max-width: 100%; max-height: 320px; }
  .tu-grid { grid-template-columns: 1fr; }
  .tu-shot { min-height: 116px; }
  /* 预览区在窄屏矮一档（同高规则不变：三种形态一起变） */
  .tu-shot__view { height: 96px; }
}

/* ==========================================================================
   多明细报销表单（2026-09-19 乐哥需求 2+3）
   「抬头 + 多笔明细」——填一遍提交多笔，不用一笔一笔分开报。
   铁律：容器走既有 .card / .dt 体系，颜色全走 token（零裸 hex）。

   ▸ 2026-09-19 第二次改版（乐哥：「ui优化下，看要不要参考新建达人那种（只是不要 tab），
     然后部分框加带颜色的框」）：
     骨架从「一整张大 .card」换成 **新建达人页同款** ——
       UI.headNav（返回 + 标题）+ .sf-shell--modules + .sf-body__content（1120 限宽）
       + 一列 .module-card（分区卡）+ 底部 .form-actions 操作条。
     与新建达人的唯一差别就是**没有 .sf-tabs**（报销单是一条纵向动线，不需要切分组）。
     ⚠️ 彩色框只在 4 处（语义化，不是装饰），全部走既有语义令牌：
       汇总条=主色 / 关联排期=主色 / 代报条=警示 / 必传凭证=主色虚线。
       依据 docs/UIUX设计-达播工作台.md 的「绝对禁令 #1」——
       彩色描边只能用于**语义块**（如 --color-warning-border 注释里写的"待结算卡片外框"），
       页面级卡片一律白底 + 中性描边。
   ========================================================================== */
/* 分区卡标题栏右侧那句补充说明（跟着标题走，不另起一行）
   ⚠️ 原来还有一条 `.eb-head__t`（大标题）—— 标题已改由 `.module-card__title` 承担，
      该条已无引用，本轮删掉（本项目踩过"用了不存在的类名"，反向的"定义了没人用的类名"
      同样是死代码，会让下次改版误以为它还有用）。 */
.eb-head__s { font-size: var(--font-size-sm); color: var(--color-muted); }
.eb-head__s--end { margin-left: auto; }

/* 报销人条 —— **卡头右端的一组**（2026-10-09 第三次改；乐哥：「这个排版乱糟糟的」）
   ──────────────────────────────────────────────────────────────────────────
   一天内三改，全记下来免得下次推错方向：
     ① 原是「浅灰底 + 圆角 + 整圈边框」的一块 ⇒ 乐哥：「不要那个底了」
     ② 改成「无底无框 + 一条下边线」、左右内距归 0（与下方字段同一基线）
     ③ ⭐ 本次：**整组移进卡头右端**。因为②之后它仍**独占一整行**，
        而内容只有「报销人 + 姓名 + 本人」三个小元素 —— 实测徽章右沿到
        「帮他人报销」按钮之间是 **652px 空白**（左一小坨、中一大片空、右一按钮）。
        ⇒ 报销人是"这张单的属性"，与分区标题同源，归到卡头最自然：
          省掉一整行（区块高 200 → 152），也没有了两端撑着的大空。
   ⚠️ 靠右用 `margin-left: auto` —— 与卡头内开关**同一范式**
      （components.css 的 `.module-card__head .switch`），⛔ 不要再用内联 style。
   ⚠️ 那条下边线**不需要了**：卡头自带一条与正文分隔的线（② 它是为"独立成行"服务的）。
   ⚠️ 窄屏：卡头本身是 `flex-wrap: wrap`，这组会自动换行；下面媒体查询里再显式让它独占一行。 */
.eb-who {
  /* ⚠️⚠️ 2026-10-09 乐哥：「标签位置要和报销人名字对齐，现在**偏高**」——
     病根是**行高不统一**，不是 `align-items`：
       · 兄弟文字用默认行高（14px × 1.5 = 21px 行盒）
       · 而 `.eb-chip--tag` 当时写死 `line-height: 1`（12px 行盒 = 12px）＋ `height: 20px`
     ⇒ 两者的 em 盒在各自容器里的**占比不同**，`align-items: center` 只让"盒子中心"齐，
        墨迹中心就齐不了。像素级实测（3x 截图逐列统计墨迹）：
        「张乐」108.5 /「本人」**100.5** /「帮他人报销」102.5 ⇒ 徽章高 2.7px、按钮高 2px。
     ⛔ 试过 `align-items: baseline`：修过头（12px 字的重心本就比 14px 的低，
        变成徽章反而**低** 1.2px）。**基线对齐解决不了字号差**。
     ✅ 正解 = **统一行高**：这里声明 `--font-line-height-body`(1.5) 让整组继承，
        再把徽章的高度交还给行高（见 `.eb-chip--tag` 的 padding 注释）⇒
        三处文字的行盒比例一致 ⇒ 盒子中心对齐即墨迹对齐（残差 <1px，肉眼不可辨）。
     ⚠️ 为什么不微调 1~3px 了事：那是 magic number，字号/字体一换就失效；
        统一行高是"由同一个来源决定"。 */
  display: flex; align-items: center; gap: var(--space-2);
  margin-left: auto;
  line-height: var(--font-line-height-body);
}
/* ⚠️ 塞进这一组后卡头会被**撑高**：组里的「帮他人报销」按钮比标题行高，
   实测卡头 42 → 50px —— 而下面「费用明细」卡的头仍是 42 ⇒ 两个卡的头**差 8px**、
   竖着看是不齐的（那正是乐哥说的"乱"的一种）。
   ⇒ 用 `:has()` **只收"含报销人组的那个卡头"**的上下内距，把高度拉回 ~42。
   ⚠️ 为什么不去缩按钮：按钮是**可点控件**，压到 18px 以内就难点中了；
      收容器内距不影响任何控件的尺寸（规范里控件高度那两档一个字都不用动）。
   ⚠️ `:has()` 本项目已有先例（layout.css 的 `:has(.page-help-btn)`）。 */
.module-card__head:has(.eb-who) { padding-top: 6px; padding-bottom: 6px; }
/* 「帮他人报销」展开后：这一组变成"你在替别人报"的提醒。
   ⚠️ 提醒**只由徽章承担**（橙色「代报」二字）—— 原先还给整行/下边线染橙，
      那两个载体都已随"去底"消失，而且**也不该再涂一整行**（那正是乐哥要去掉的东西）。 */
.eb-who--proxy .eb-chip--tag {
  background: var(--color-warning-bg-soft);
  color: var(--color-warning-text);
}
.eb-who__k { font-size: var(--font-size-sm); color: var(--color-fg-2); }
/* 报销人**姓名**：上主色（乐哥 2026-10-09：「报销人名称也用彩色吧，看用蓝色咋样」）。
   ⚠️ 用 `--color-accent-text`（= `--color-primary-500` 主色），而**不是**随便一个蓝：
      它与「本人」徽章的浅蓝底同族、与卡头标题的深灰也能拉开层次。
   ⚠️ 语义提醒：主色在全站是"链接/可点"色 —— 这里的人名**不可点**。
      若日后觉得像链接，换 `--color-primary-700`（深蓝、更像"上色的文字"）即可。 */
.eb-who__me {
  font-size: var(--font-size-base); font-weight: var(--font-weight-medium);
  color: var(--color-accent-text);
}
.eb-who-other { margin-bottom: var(--space-5); }

/* 抬头字段网格：两列，长字段（排期）占满整行。
   ⭐ 2026-10-08 间距 16 → 20（`--space-4` → `--space-5`）：对齐规范 §7.2
      「标签在上的双列网格，行距/列距 20px」（2026-09-29 乐哥：「不要太紧凑，要有呼吸感」）。
      ⚠️ 这个值**必须与 `.form-grid`（弹窗表单网格）同值** —— 两个网格同屏出现的机会不多，
         但它们是"同一件事的两套实现"，各写一个数就会慢慢分叉（本项目对分叉的容忍度是 0）。 */
.eb-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-5); }
.eb-fld { display: block; }
/* 「票面金额 / 发票项目 / 票面要素」那一行（2026-09-20，2026-10-09 调列宽）。
   ⚠️ 它**曾经是 3 个手填输入框**（号码/代码/开票日期），现已改成只读汇总 ——
      但仍用 auto-fit 而不是写死列数：窄屏时自动堆成一列，
      否则这几格挤在 375px 里会看不全（票号有 20 位）。
   ⭐⭐ 2026-10-09 晚两次调整（同一天，记在一起免得下次又推错方向）：
     ① 乐哥：「发票项目，和金额放同一行吧，因为距离够的」⇒ 列宽下限 150 → 260px，
        让「票面金额 | 发票项目」并排（260px 放得下 `*生产生活服务*代订住宿费` 这种 14 字项目名）。
     ② 乐哥：「票面要素那个位置感觉不对」⇒ 那个入口**移出了网格**
        （改成清单自己的标题条，见 `.eb-invhd`）⇒ 常态只剩「票面金额 + 发票项目」**两格**。
        ⚠️ 此时 auto-fit 在 930px 下会算出 **3 列**：两格占两列、**右边空一列** ——
           又变成了本页刚修掉的那种"右侧留白"。所以这里改成**明确两列**。
     ③ 出现「对不上」警告时是第三格，它用 `.eb-fld--full` **占整行**（见 renderItem）——
        警告本来就该独占一行、更醒目，也不需要挤进两列里去。
     ④ 窄屏（≤768px，与 `.eb-grid` 同一个断点）退成一列。 */
.eb-grid--inv { grid-template-columns: 1fr 1fr; margin-top: var(--space-6); }
@media (max-width: 768px) {
  .eb-grid--inv { grid-template-columns: 1fr; }
}
/* 票面金额那一行（2026-09-20）—— 原来这里是三个手填输入框，
   改成"只读的票面金额汇总 + 可展开的要素清单"（乐哥：「发票代码、发票号这些
   是不是可以不用填了？…用逗号隔开我觉得没意义」）。
   ⚠️ 只读值必须**看起来就不能填**（无边框、无输入框外观）——
      做成 input[readonly] 会让人以为要填、点了没反应。
   ⚠️ 高度与输入框**对齐**（min-height 44px）：它与「费用类型」等字段同处一个网格，
      矮一截会让整行歪掉（本项目对这 1~4px 错位一贯要求对齐）。 */
.eb-ro {
  display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap;
  min-height: var(--control-height); padding: 0 var(--space-3);
  border: 1px dashed var(--color-border-soft); border-radius: var(--radius-md);
  background: var(--color-surface-2);
  font-size: var(--font-size-sm); color: var(--color-fg-2);
}
.eb-ro__v { font-size: var(--font-size-base); font-weight: var(--font-weight-semibold); color: var(--color-fg); }
/* ⭐ 票面金额的数值用**价格色**（2026-10-09 乐哥：「票面金额，突出下，用红色字体」）。
   ⚠️ 用 `--color-price` 而不是 `--color-danger`：两者同源（同一档红，见 design-tokens 的说明），
      但 `--price` 的语义就是"金额"，不会让人读成"这笔出错了"——
      这一格的值本身没有错，只是**要一眼看得见**（它是全卡最该被核对的那个数）。
   ⚠️ 只加在金额这一个值上（修饰类），**不要改 `.eb-ro__v` 基类**：
      那个类名是"只读值"的通用形态，将来别处复用时不该默认变红。 */
.eb-ro__v--amt { color: var(--color-price); }
/* 识别到的「发票项目」名称（2026-10-09）—— 票面的货物/服务名称，如
   `*生产生活服务*代订住宿费`。
   ⚠️ 最多两行（`-webkit-line-clamp`）：项目名可以很长、一笔还可能有多张票，
      不夹住会把明细卡撑高、把下面「说明」等要填的字段挤出首屏。
      完整内容在「票面要素」展开清单里（那里是给财务逐字核对用的）。
   ⚠️ 必须 `flex: 1 1 auto` + `min-width: 0`：`.eb-ro` 是 flex 容器，
      不给收缩余量时长文本会溢出到框外（flex 子项默认 min-width:auto 不收缩）。 */
.eb-ro__items {
  flex: 1 1 auto; min-width: 0;
  color: var(--color-fg-2); line-height: 1.5; overflow-wrap: anywhere;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;
}
.eb-ro__n { font-size: var(--font-size-xs); color: var(--color-meta); }
.eb-ro__empty { color: var(--color-meta); }
.eb-ro__why { font-size: var(--font-size-xs); color: var(--color-meta); }
/* 报销金额 > 票面金额的提醒（只提醒不拦）—— 用警告色，但要能一眼看出是"待核对"不是"报错" */
.eb-ro--warn {
  border-style: solid; border-color: var(--color-warning-text);
  background: var(--color-warning-bg); color: var(--color-fg-2);
}
.eb-ro--warn .eb-ro__why { color: var(--color-warning-text); }
/* ⚠️ 这里曾有 `.eb-ro__lnk`（只读框里的文字按钮）—— 2026-10-09 晚随"票面要素入口
   移出网格"一起删掉了：它是那条入口用的，入口改成标题条（`.eb-invhd__act`）后
   全站再无引用。按规范"定义了没人用的类名"要清掉 ——
   留着会让下次改版误以为那个按钮还在、照着它改。 */

/* 票面要素清单（默认 hidden，点「点开查看」才展开）——
   三栏从界面上拿掉了，但要素必须**有地方能看**（财务核对要用）。
   ⚠️ 默认收起而不是展开：它是"给财务核对用"的明细，
      填单的人不需要天天看到 20 位的票号（那是纯噪音）。 */
/* 票面要素 —— **清单自己的标题条**（2026-10-09 晚。乐哥：「票面要素那个位置感觉不对，
   你看下有没有其他方案」）。
   改前它占网格里的一格，与「票面金额」「发票项目」两个只读值并排 ——
   但那三个格里只有它不是"值"，而是**操作**；而且入口在网格里、
   它控制的内容在网格外面（点上面、内容在下面），中间还隔着别的元素。
   现在：入口贴着清单（折叠面板的惯例），网格里只留两个真正的值。
   ⚠️ 用 `<button>` 而不是 `div` + click：要能 Tab 聚焦、回车触发
      （与全站可点元素同一做法）。
   ⚠️ 底色/描边沿用 `.eb-invpanel` 那一套（surface-2 + border-soft）——
      两者视觉上是一组（标题 + 内容），不要各写一套。 */
.eb-invhd {
  display: flex; align-items: center; gap: var(--space-3);
  width: 100%; margin-top: var(--space-4);
  padding: 9px var(--space-3);
  border: 1px solid var(--color-border-soft); border-radius: var(--radius-md);
  background: var(--color-surface-2);
  font: inherit; font-size: var(--font-size-sm); color: var(--color-fg-2);
  cursor: pointer; text-align: left;
  transition: border-color var(--motion-fast) var(--motion-ease-standard);
}
.eb-invhd:hover { border-color: var(--color-border); }
.eb-invhd__t { font-weight: var(--font-weight-medium); color: var(--color-fg); }
.eb-invhd__n { font-size: var(--font-size-xs); color: var(--color-meta); }
/* 右侧动作字（点开查看 ▾ / 收起 ▴）—— 用 accent 让它一眼是"可点" */
.eb-invhd__act { margin-left: auto; color: var(--color-accent-text); }

/* 展开后：标题条与清单**接成一块**（2026-10-09 晚）。
   ⭐ 原来两者之间留着 8px 空隙、各带一圈描边和圆角 ⇒ 看着是"两个不相干的盒子"，
      乐哥说的「很奇怪」有一半来自这里。现在标题条下边与清单上边**对切成一条线**
      （各自的那条边设 transparent，并互相去掉相邻圆角）。
   ⚠️ 判据用 `[aria-expanded="true"]` —— 它由 JS 在展开/收起时维护（见 bindInvToggles），
      ⛔ 不要另加一个 `--open` 类：那会出现"两份状态"（历史上这类重复状态已出错多次）。 */
.eb-invhd[aria-expanded="true"] {
  border-bottom-left-radius: 0; border-bottom-right-radius: 0;
  border-bottom-color: transparent;
}

.eb-invpanel {
  margin-top: var(--space-2); padding: var(--space-2) var(--space-4);
  border: 1px solid var(--color-border-soft); border-radius: var(--radius-md);
  background: var(--color-surface);
}
/* ⭐⭐ 2026-10-09 晚美化（乐哥：「展开的这个 ui 美化下，现在这个看着不直观，也很奇怪，灰蒙蒙的」）
   ──────────────────────────────────────────────────────────────────────────
   改前实值（真机量，见 scripts/frontend-debug/cmp-invpanel-look-20261009.js）：
     · 面板底 = `--color-surface-2`（#F1F5F9 灰）**贴在白卡上** ⇒ 整块是一块暗斑 = 「灰蒙蒙」
     · 行内**全是灰阶**：文件名 `--color-meta`(124,141,163)、标签也是 meta、
       值只深一档（63,71,83）⇒ 没有视觉锚点，"没有主角"
     · 文件名（"这是哪张票"）反而是**最浅**的那个
   所以这一版做的三件事，按重要性排序（对应下面 ①②③…）：
     ① 底色：灰 → **白**（靠描边与卡区分，不再是一块暗斑）
     ② 层次：给每一层**不同的色阶**，而不是一律灰 ——
        文件名 → `--color-fg` + medium；标签 → meta；值 → fg-2 + medium；
        金额 → `--color-fg` + semibold + 等宽数字
     ③ 归属：序号改成**主色浅底**（每张票的第一个彩色锚点），
        号码/开票/项目整组**缩进到文件名左沿**（见 `.eb-invpanel__sub`）⇒ 一眼看出属于哪张票
   ⚠️ 一律不新造色值：全部走既有语义令牌（本批只用了 primary-50/700 与 fg / fg-2 / meta）。
   ⚠️ 与标题条的连接见上一条（`.eb-invhd[aria-expanded="true"]`）。
   ⚠️ 上面那条 `.eb-invpanel` **是原地改的**（不是文件末尾追加一条同名规则）——
      同特异性规则"后写生效"，追加会反向盖掉别处依赖顺序的覆盖（本项目已有专条规定）。 */
.eb-invhd[aria-expanded="true"] + .eb-invpanel {
  margin-top: 0;
  border-top-left-radius: 0; border-top-right-radius: 0; border-top-color: transparent;
}
.eb-invpanel__row {
  display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap;
  padding: 12px 0; border-bottom: 1px solid var(--color-border-soft);
  font-size: var(--font-size-xs);
}
.eb-invpanel__row:last-of-type { border-bottom: none; }
/* 序号：白底灰圈 → **主色浅底 + 主色字**。
   ⭐ 为什么值得上色：它是每张票的**第一个锚点**，一屏里重复 3~5 次 ——
      按"出现 10+ 次必须做弱"的反面，这个频次可以做强一点（浅底 + 深字，不是实心块）。 */
.eb-invpanel__n {
  flex: 0 0 auto; width: 20px; height: 20px; line-height: 20px; text-align: center;
  border-radius: 50%; background: var(--color-primary-50); color: var(--color-primary-700);
  font-size: 11px; font-weight: var(--font-weight-medium);
}
/* 文件名 = "这是哪张票"，必须是**第一眼**：主文字色 + medium（改前它是最浅的 meta）。
   ⚠️ `max-width` 由 180 放到 260：180px 下"订单1128151505889754-电子发票.pdf"会被截成
      "订单1128151505889754-电子…"，而截掉的那截正是"哪个平台/哪一笔"的区分位。 */
.eb-invpanel__src {
  flex: 0 0 auto; max-width: 260px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: var(--color-fg); font-weight: var(--font-weight-medium);
}
/* 标签与值**拉开两档**（改前两者几乎同深，所以整行糊成一片） */
.eb-invpanel__f { color: var(--color-meta); }
.eb-invpanel__f b { color: var(--color-fg-2); font-weight: var(--font-weight-medium); font-variant-numeric: tabular-nums; }
/* 金额 = 每张票的右端锚点：加深 + 半粗 + 等宽数字（三张票的金额能上下对齐） */
.eb-invpanel__amt {
  margin-left: auto; color: var(--color-fg); font-weight: var(--font-weight-semibold);
  font-variant-numeric: tabular-nums;
}
/* ⭐ 副内容行（号码 / 代码 / 开票 / 项目）—— 2026-10-09 晚新增。
   整组换行独占一行，并**缩进 32px = 序号圈宽 20 + 间隙 12** ⇒ 左沿与文件名严格对齐，
   读起来就是"文件名是标题、这行是它的要素"。
   ⚠️ 缩进值不许随手写：它必须等于"序号 + gap"，否则会与文件名错开几像素
      （本项目对"两个元素要对齐就由同一个来源决定"有专门规定）。
   ⚠️ 内部 `flex-wrap: wrap`：项目名放得下就与号码/开票同行（3 张票的清单能少 1/3 高度），
      放不下自动折到下一行，不会把号码挤到看不清。 */
.eb-invpanel__sub {
  flex: 1 1 100%; display: flex; flex-wrap: wrap;
  gap: 2px var(--space-4); padding-left: 32px;
}
/* 票面上的「项目名称」——
   ⚠️ 这里**不夹行数**（与明细卡上那一格相反）：这块是财务逐字核对的原始值，
      截断会让"票面和系统里的不一样"这种误会无从解释。
   ⚠️ 值色与"号码/开票"**同一档**（fg-2 + medium），不给它更深的色：
      它是最长的一段文字，再加深就成了"全行最抢眼的是最长的那个" ⇒ 反而更糊。
      要突出它靠的是**标签**和它自己在副内容行里的位置，不靠颜色。 */
.eb-invpanel__items { flex: 0 1 auto; color: var(--color-meta); overflow-wrap: anywhere; }
.eb-invpanel__items b { color: var(--color-fg-2); font-weight: var(--font-weight-medium); }
/* 底部免责声明：与上面三张票拉开（一条浅分隔线），它是**说明**不是数据 */
.eb-invpanel__tip {
  margin-top: var(--space-2); padding-top: var(--space-3);
  border-top: 1px solid var(--color-border-soft);
  font-size: var(--font-size-xs); color: var(--color-meta); line-height: 1.6;
}

/* 「正在修改报销单」提示条（2026-10-09，提单页的 `?claim=` 编辑模式）
   ⚠️ 为什么必须有：编辑模式与"新提单"的界面**长得几乎一样**，不标出来就会出现
      "我以为在提新单、其实在改旧单"（或者反过来）—— 那两种情况的后果完全不同
      （一个新增一张单，一个改掉已提交的单）。所以这条横幅是**必需信息**，不是装饰。
   ⚠️ 被驳回的单额外给出**驳回原因**（提报人打开页面第一眼要看到"要改什么"）。 */
.eb-editbar {
  margin-bottom: var(--space-6); padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-accent-border); border-radius: var(--radius-md);
  background: var(--color-accent-bg);
  font-size: var(--font-size-sm); color: var(--color-fg-2);
}
/* ⭐⭐ 2026-10-10 乐哥：「这种被驳回的，应该红色显示吧，红色底红色文字」
   ──────────────────────────────────────────────────────────────────────────
   ▸ 改前用的是**告警黄**（`--color-warning-border` / `-bg-soft` / `-text`）。
     黄色表达的是"**提醒/注意**"（待处理、快到期、口径说明），而"这张单**被否了、
     必须改完重提**"是**危险/失败**语义 ⇒ 黄条读起来像"提示一下"，与后果不符。
   ▸ 全站既有的正例是审批页的 `.ap-reject`（驳回原因）—— 它一直是
     `--color-danger` 字 + `--color-danger-bg` 底。本条**照抄那一对**，
     从此"驳回原因"在全站**只有一种红**（⛔ 不要再新增第三套配色）。
   ▸ ⛔ 令牌成对使用（深字 + 浅底，DESIGN-SPEC §2.4）：
     底 `--color-danger-bg`(#FDE8EB) / 字 `--color-danger`(#E41E3F) / 描边 `--color-danger-border`。
     不要用实心 `--color-danger` 铺底 + 白字 —— 那是 `.btn--danger`（按钮）的规格，
     横幅面积大，实心红会压过页面全部内容。 */
.eb-editbar--reject {
  border-color: var(--color-danger-border);
  background: var(--color-danger-bg);
}
.eb-editbar__t { font-weight: var(--font-weight-medium); color: var(--color-fg); }
.eb-editbar__hint { margin-top: var(--space-1); color: var(--color-muted); }
.eb-editbar__r { margin-top: var(--space-1); color: var(--color-danger); }
/* 「正在修改报销单 BX…」这行是**结论行**（不是标题行）—— 驳回态下它也要跟着红：
   整条横幅只有两种颜色（红=被驳回 / 蓝=撤销重提），标题仍是黑的会读成"半截红"。 */
.eb-editbar--reject .eb-editbar__t { color: var(--color-danger); }
.eb-editbar--reject .eb-editbar__t .mono { color: inherit; }

/* ⭐ 2026-10-10 「**哪几张票没过**」—— 驳回横幅里单独一块（号码 + 财务写的原因）。
   乐哥：「重新提交的时候系统会告诉他那个发票审核没通过，要重新提交」
   ⚠️ 这块用**白底 + 红字红边**（而不是再铺一层红底）：它在红底横幅里面，
      再叠一层同色底会糊成一片、号码读不清。白底把列表"抠"出来，扫读最快。 */
.eb-editbar__fails {
  margin-top: var(--space-2); padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-danger-border); border-radius: var(--radius-sm);
  background: var(--color-surface);
}
.eb-editbar__ft { font-weight: var(--font-weight-medium); color: var(--color-danger-strong); }
.eb-editbar__fl { margin: var(--space-1) 0 0; padding-left: 18px; list-style: disc; }
.eb-editbar__fl li { margin-top: 2px; color: var(--color-danger); }
.eb-editbar__fl code { font-family: var(--font-family-mono); font-size: var(--font-size-sm); }
/* 原因用中性色：号码是要照抄的（红），原因是给人读的（灰）—— 一红一灰才分得开 */
.eb-editbar__fr { margin-left: var(--space-2); color: var(--color-fg-2); }

.eb-fld .input, .eb-fld .select { width: 100%; }
.eb-fld--full { grid-column: 1 / -1; }
/* ⭐⭐ 2026-10-10 傍晚（乐哥：「**出行方式和往返地点放一行**」）——
   「怎么走」的那几格（交通方式 / 出发地 / 到达地）收进一个**整宽的子网格**：
   ▸ 改前它们直接落在 `.eb-grid`（2 列）里 ⇒ 费用类型占了第 1 行左格之后，
     「交通方式」只能去第 1 行右格，「出发地 / 到达地」被挤到第 2 行 ——
     一次行程的三个字段散在两行上，看着像三件不相干的事。
   ▸ 列数用 **auto-fit**（不是写死 3）：打车只有「交通方式」一格时它会**自动占满一行**，
     不会剩两个空坑（写死 3 列就会出现"右边两大片空白"，比原来还难看）。
   ⚠️ 用 `minmax(180px, 1fr)` 而不是固定宽度：窄屏（≤768px 时 `.eb-grid` 已退成一列）
      这里会跟着自动折行，不会把输入框压成一条缝。
   ⚠️ 日期区间**不进这个组**：往返机票的「去程→返程」本身就是两个日期控件，
      塞进来会变成一行五个控件（本项目对"一行塞太满"有明确纪律）。 */
.eb-grid--trip {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--space-5);
}

/* 关联排期块：整块套一层主色浅底框。
   为什么单独上色：它是**只有「达人溯源直播」才出现**的必填项，也是这张单
   "钱算到哪场"的唯一依据 —— 漏选会把达人成本错记成项目成本（不报错、只错账）。
   给它一圈主色，是为了让它在一屏字段里一眼被看见。 */
.eb-schedbox {
  border: 1.5px solid var(--color-primary-200);
  border-radius: var(--radius-md);
  /* ⭐⭐ 2026-10-09 去掉常态浅蓝底（乐哥：「这几个框还有里面的颜色……不直观」）。
     ▸ 改前 `--color-accent-bg` 是**选中/激活态**的色 ⇒ 整块看着像"这个字段被选中了"，
       而它其实只是"必填、值得被看见"。
     ▸ 现在透出卡片白底、**留着主色描边** ⇒ 与上传格（白底 + 主色虚线）统一成
       「**白底 + 蓝框 = 需要你处理的地方**」，浅蓝只留给 hover / 拖拽 / 选中。
     ⚠️ 描边必须是主色令牌（`--color-primary-200`）—— 门禁 ⑥ 锁着这条，
        它表达"漏选排期会把达人成本错记成项目成本"，不是装饰。 */
  background: transparent;
  padding: var(--space-3) var(--space-4);
}

/* 「关联排期选了多个达人」的费用处理提醒（乐哥 2026-10-15 12:44/12:49：
   「提醒规则感觉也不够清晰，提醒的颜色样式也得改一下」→ 拍板 **a1 文案 + s1 样式**）。
   ▸ s1 = **浅蓝信息块**：浅蓝底 + 左侧 3px 主色条 + ⓘ 图标 + 三行（首行加粗）。
   ▸ ⛔ 刻意**不是警示**（不用 `--color-warning-*` 的黄/橙）：这块在**用户自己填的表单**里，
     是把规则讲清楚 —— 是"引导"不是"判错"。黄条（`.eb-warn`）留给"金额 > 票面"那种
     "你得停下来看一眼"的事。语义分家也写进了门禁（⑧）。
   ▸ 蓝底 + 蓝条 + 深蓝标题 = 全站「信息/说明」的统一语言，与排期选择框（白底蓝框）同族不打架。 */
.eb-star-tip {
  display: flex;
  gap: var(--space-2);
  margin-top: var(--space-2);
  padding: var(--space-3) var(--space-4);
  background: var(--color-info-bg);
  border-left: 3px solid var(--color-primary-500);
  border-radius: var(--radius-sm);
  font-size: var(--font-size-sm);
  line-height: 1.6;
  color: var(--color-fg-2);
}
/* ⚠️ 图标颜色走 `currentColor`（`Icons.icon` 出的 SVG 是 stroke="currentColor"）——
   在这里定色，⛔ 不在 JS 里写死十六进制（否则改主题要改两处）。 */
.eb-star-tip__ico { flex: 0 0 auto; display: inline-flex; margin-top: 2px; color: var(--color-primary-500); }
.eb-star-tip__body { flex: 1 1 auto; min-width: 0; }
.eb-star-tip__l--lead { font-weight: var(--font-weight-semibold); color: var(--color-primary-700); }
.eb-star-tip__l + .eb-star-tip__l { margin-top: 1px; }
/* 两条规则缩进一档 —— 首行是判断动作（不缩），规则缩进 ⇒ "一句结论 + 两条"一眼看得出层级 */
.eb-star-tip__l:not(.eb-star-tip__l--lead) { padding-left: var(--space-2); }


/* ⚠️ 已删除（2026-09-19 晚，改版后无引用，属于"定义了没人用的类名"）：
   · `.eb-sum` / `.eb-sum__k` / `.eb-sum__v` / `.eb-sum__auto`
     —— 汇总条从卡片内移到了底部固定条，见下面的 `.eb-total*`
   · `.eb-chips` / `.eb-chip[data-sid]` / `.eb-chip__d` / `.eb-chip__x` / `.eb-chip--add`
     —— 排期从"自绘胶囊 + 弹窗"换成了 `UI.schedulePicker`（它自带胶囊样式 `.fp-chip`）
   删而不留的理由：本项目踩过"用了不存在的类名"（卡片头挤成一坨），
   反向的"定义了没人用的类名"同样是负债 —— 下次改版会以为它还有用、照着改。 */

/* 报销人条上的「本人 / 代报」小标签 —— **仍在用**，所以保留。
   ⚠️⚠️ 2026-10-09 补全造型（乐哥：「这个本人的标签排版不对，然后标签颜色也很奇怪」）：
   它此前**只吃到了 height / 字号 / 颜色**，而 `.eb-chip` 这个"基础类"**全站根本没有定义**
   （上面那段清理注释里，排期胶囊的 `.eb-chip*` 早被删除，可它仍在本页被引用）⇒
   实测：`padding: 0`、`border-radius: 0`、`display: block`，
   **框宽只有 20px，而"本人"两个字要 23.8px ⇒ 文字被挤/溢出** —— 这就是"排版不对"。
   颜色也不对：白底 `--color-surface` 贴在行底 `--color-surface-2` 上 ⇒ 像一块"白补丁"。
   ⇒ 改成**浅灰底 + 正文色**的中性胶囊：「本人」是**正常状态**（既不是警示也不是成功），
      所以**不用语义色**（红=出错、绿=成功都会误导）。
   ⭐ 造型**写在本类自己的规则里**、不去新增 `.eb-chip` 基础类：那会影响将来任何
      一次"顺手用一下 eb-chip"的地方，而本类的唯一使用者就是「本人 / 代报」
      （grep 全站：只有 expense-batch-form.js 一处）。
   ⚠️ `line-height: 1` + `white-space: nowrap`：徽章高度靠行高定死，
      不然两字与四字（"代报"）高度会不同。 */
.eb-chip--tag {
  display: inline-flex; align-items: center;
  /* ⚠️ 高度**交给行高**（不写死 height）：`line-height` 由 `.eb-who` 统一给到 1.5 ⇒
     12px × 1.5 = 18px 行盒，上下各 1px 内距 ⇒ 总高 **20px**（与原来一致、两字/四字仍等高）。
     ⛔ 不要再写 `line-height: 1` + `height: 20px` —— 那正是"本人比名字偏高 2.7px"的成因
        （小字号的 em 盒在容器里占比不同，盒子中心对齐了、墨迹中心仍差）。 */
  padding: 1px var(--space-2);
  border-radius: var(--radius-pill);
  font-size: var(--font-size-xs); white-space: nowrap;
  /* 底/字色 2026-10-09 晚：**浅灰 → 主色浅蓝**（乐哥：「那个本人标签用彩色」）。
     灰底那版读起来像"一块补丁"；换主色系则：① 与旁边蓝色的姓名呼应、
     ② 「本人」是**状态标记**、本就该比正文显眼一点、
     ③ 代报态仍是橙色（警示），两者不会混淆。 */
  color: var(--color-primary-700); background: var(--color-primary-50);
}
/* 分区标题旁的「?」（乐哥：「整单信息、费用明细得注释，放在？里面」）
   —— 与页头「标题 + ?」、字段标签旁的 `?` 是**同一手法**，只是字号档次不同
   （这里是 16px 的卡片标题，页头那个是 24px）。
   ⚠️ 图标 15px：比字段标签那个（13px）大一档、比页头那个（17px）小一档。 */
.module-card__title .th-note { margin-left: var(--space-2); color: var(--color-muted); vertical-align: 1px; }
.module-card__title .th-note svg { width: 15px; height: 15px; }

/* ==========================================================================
   底部固定操作条（合计 + 取消/提交）（2026-09-19 乐哥：
   「累计的这个报销费用，要不要一直放在页面右下角」）
   ─────────────────────────────────────────────────────────────────────────
   ⭐ 依据（调研 Expensify / Ramp / 飞书审批后定的）：
      Ramp 的官方说明原话是 "one row per purchase, then subtotals by category
      and a grand total" —— **总计在最后**；三家成熟系统都把合计与"提交"放在同一区。
      我们原来把合计夹在「整单信息」和「费用明细」之间 ⇒ 滚到明细区就完全看不见，
      而这恰恰是用户最需要看到合计的时刻（正在一笔一笔加钱）。

   ⚠️ 用 `position: sticky; bottom: 0`，**不是** `fixed`：
      fixed 脱离文档流，页面短时会浮在半空、窄屏会压住内容；
      sticky 只在"它自己那一行可见"时才粘住，页尾自然落位。全站首次用这个形态，
      已单独验证过（不遮内容、不盖侧栏、手机端正常）。
   ⚠️ `z-index` 要低于侧栏遮罩与弹层（它们用 --z-modal），
      否则日历弹窗会被这条压住。 */
/* ⭐⭐⭐ 2026-10-08 乐哥：「右下角取消保存按钮，没按照系统规范设计」
   —— 「合计条」从**整宽白底条**改成与全站同一套的**右下角玻璃浮岛**。
   ─────────────────────────────────────────────────────────────────────────
   改前的形态（实测）：1131×59 的整宽白条、`border-radius: 0`、`backdrop-filter: none`、
   按钮高 **37px** —— 而全站其余 9 个表单页早已是「sticky 内容列右下 + 玻璃 + 圆角 16 +
   按钮 34（--control-height-sm）」。同一系统里两种底部操作条，就是乐哥说的"没按规范"。

   ⚠️⚠️ 为什么**不**直接把 `--total` 并进上面那条公共浮岛选择器：
      · 公共规则**只**处理"岛"本身，按钮是**直接子元素**（`.sf-actions > .btn`）；
        而本页岛内还有「费用汇总 + 金额 + 笔数」一层 `.eb-total__inner`，
        按钮挂在它下面 ⇒ 公共规则里的 `> .btn` 选不中，按钮档位会漏掉。
      · 所以这里**独立成块**（岛体 + 内层重置 + 按钮档位三件套），
        与公共浮岛**逐项同值**（同令牌、同一处 sticky 定位），只是多带一层内容。
      ⇒ `tests/bottom-action-island.test.js` 仍要求公共规则**排除** `--total`（避免两份岛体打架），
        另加一条断言：`--total` 必须自己带全套浮岛参数（同批次已补）。

   ⚠️ 合计**留在岛上**（乐哥 2026-09-19：「累计的这个报销费用，要不要一直放在页面右下角」）——
      本轮只改形态，不把合计搬回页内；岛因此比"只有两个按钮"的那种宽一些。
   ⚠️ `bottom: 24px`（与公共浮岛同值，那处是硬编码 24，这里也硬编码以保持**逐字一致**，
      便于门禁用同一条正则锁两处；它等于 `--space-6`）。

   ⚠️⚠️ 选择器写 `.form-actions.form-actions--total`（两个类，特异性 0-3-0）**不是啰嗦**：
      `pages-influencer.css` 里有一条姐妹规则 `.sf-shell > .form-actions`
      （`display:block / padding:0 / border-top / 白底`，特异性 0-2-0），而该文件在
      `pages.css` **之后**加载 ⇒ 同特异性时光靠源码顺序**压不过它**。
      第一版实测就是被它覆盖的：岛高只剩 31px（padding 被清成 0）、白底、上边线变 --color-border-soft。
      ▸ 提特异度是正解；**不要**去改那条公共规则（达人表单在用），
        也**不要**把本块搬到 pages-influencer.css 里（那是"多会话共用文件"，
        项目约定：只推自己的文件，见 index.html 里那段说明）。 */
.sf-shell > .form-actions.form-actions--total {
  display: flex;
  align-items: center;
  position: sticky;
  bottom: 24px;
  z-index: 90;
  width: fit-content;
  margin: var(--space-4) var(--space-6) 0 auto;
  padding: 12px 14px;
  border: 1px solid var(--color-frame-border);
  border-radius: var(--radius-lg);
  background: var(--glass-bg);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  box-shadow: var(--glass-lit), var(--glass-lift);
}
/* 内层：清掉旧的"整宽 + 限宽 + 内边距"三件套（岛自己提供内边距，
   再留一层就是叠成"又高又窄"——这正是 2026-09-28 浮岛第一版的踩坑）。 */
.sf-shell > .form-actions.form-actions--total .eb-total__inner {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  width: auto;
  max-width: none;
  margin: 0;
  padding: 0;
}
/* 按钮降档到与 tab 胶囊同高（公共浮岛同款；不走 `#page-root` 是因为本块要能覆盖
   `.sf-shell > .form-actions` 那条公共规则的自带高度） */
.sf-shell > .form-actions.form-actions--total .btn {
  height: var(--control-height-sm);
  min-height: var(--control-height-sm);
  min-width: 88px;
}
/* 窄屏：与公共浮岛同样退化成贴底全宽条（两键等分），避免悬浮岛压住表单 */
@media (max-width: 760px) {
  .sf-shell > .form-actions.form-actions--total {
    width: auto;
    margin: var(--space-3) 0 0;
  }
  .sf-shell > .form-actions.form-actions--total .btn { flex: 1; }
}
.eb-total__k { font-size: var(--font-size-sm); color: var(--color-fg-2); }
.eb-total__v {
  font-size: var(--font-size-xl);
  font-weight: var(--font-weight-semibold);
  font-variant-numeric: tabular-nums;
  /* ⭐ 红色（乐哥定的）：--color-price 是本项目全站既有的"金额语言"
     （货品价格 .price、备注里的金额 .rmk-hl--money 都用它），
     与主营业务里"金额用红"的习惯一致。它不是"错误色"——
     --color-price 在令牌里就是 --color-danger 的语义别名，注释写着"价格：与危险色同源"。 */
  color: var(--color-price);
}
.eb-total__n { font-size: var(--font-size-xs); color: var(--color-muted); }
/* 按钮组推到最右（与"合计在最左"形成两端对齐） */
.eb-total__btns { display: flex; gap: var(--space-3); margin-left: auto; }

/* --------------------------------------------------------------------------
   住宿的「入住 → 离店」：**一个字段分区内**并排两个日期（乐哥：
   「住宿的开始入住和离店，要不要也放同一行，这样填起来更直观」）
   ⚠️ 关键：合成一格（而不是让两个独立单元格"凑"到同一行）。
      在 CSS Grid 里，两个单元格能否同行取决于它前面有几格 ——
      住宿时前面只有"费用类型"1 格 ⇒ 两个日期各占 1 格正好被推到两行；
      以后往前加一个字段又会被打散。合成一格就与"前面有几个字段"无关。
   -------------------------------------------------------------------------- */
.eb-range { display: flex; align-items: center; gap: var(--space-2); }
.eb-range__item { flex: 1 1 0; min-width: 0; }
.eb-range__sep { flex: 0 0 auto; color: var(--color-muted); font-size: var(--font-size-sm); }

/* ⚠️⚠️ 日期控件高度必须对齐标准输入框（44px）。
   `DateTimePicker` 的触发按钮默认 40px（见 datetime-picker.css 的 .dtp-trigger），
   而全站 `.input` / `.select` 是 44px ⇒ 不补这条，日期框会比旁边的输入框**矮 4px**，
   一行里两个框高低不齐（本项目对这类 1~4px 错位一贯要求对齐）。 */
.eb-fld .dtp-trigger { height: var(--control-height); }

/* 明细区：明细卡直接坐在分区卡里，不再需要独立的分区标题行
   （标题已由 .module-card__head 承担） */
.eb-dt .dt__bd { padding: var(--space-4); }
/* ⚠️ 这里曾有一条 `.eb-dt .eb-grid { gap: var(--space-4) var(--space-4) }`
   （明细卡里单独"更紧凑"）—— 2026-10-08 网格统一到 §7.2 的 20px 后，
   它与 `.eb-grid` 同名同值 ⇒ 删掉（同一事实不留两处实现）。 */

/* 「再加一笔」——放在明细分区卡的底部，整宽虚线按钮 */
.eb-add {
  width: 100%; margin-top: var(--space-3); height: var(--control-height);
  border: 1px dashed var(--color-border); border-radius: var(--radius-lg);
  background: transparent; color: var(--color-accent-text);
  font-size: var(--font-size-base); font-family: inherit; cursor: pointer;
}
.eb-add:hover { background: var(--color-accent-bg); border-color: var(--color-accent); }

/* 凭证上传格
   ⚠️ 必传的两格（支付凭证 / 发票）用**主色虚线**而不是原来的灰虚线 ——
      灰虚线看着像"选填的装饰位"，而这两格不传就提交不了。
      已传的仍是绿色实线（.eb-up--done），"传没传"一眼可辨。 */
/* ⭐ 2026-10-08：`--up`（凭证/发票区）与 `--tail`（说明）都是"网格之后的整行区"，
   间距一律 **24px**（`--space-6`），对齐规范 §7.3。
   ⚠️ 改前是 16 / **12** —— `--tail` 的 12px **小于网格行距本身（16）**，
      正是规范铁律 #3 点名的"倒挂"：局部比全局还紧，读起来像"被压住"。
      §7.3 还要求取 24 而不是 20："大一档才有独立一组的分组感"。 */
.eb-grid--up { margin-top: var(--space-6); }
.eb-up {
  display: flex; align-items: center; gap: var(--space-2);
  min-height: var(--control-height); padding: var(--space-2) var(--space-3);
  border: 1px dashed var(--color-primary-200); border-radius: var(--radius-md);
  /* ⭐ 2026-10-09 与 `.eb-files` 同款：常态不铺底色（浅蓝是"拖拽中/选中"的色，见那边的长注释） */
  background: transparent; color: var(--color-accent-text);
  font-size: var(--font-size-sm); cursor: pointer;
}
.eb-up:hover { border-color: var(--color-accent); background: var(--color-surface-2); }
/* 走替票时发票那格是"不用传"的说明 —— 退回中性灰，别让它看着还能点 */
.eb-up--na {
  border-style: solid; border-color: var(--color-border-soft);
  background: var(--color-surface-2); color: var(--color-muted);
  cursor: default; font-size: var(--font-size-sm);
}
.eb-up--done { border-style: solid; border-color: var(--color-success); background: var(--color-success-bg); color: var(--color-success-text); cursor: default; }
.eb-up__img { width: 32px; height: 32px; object-fit: cover; border-radius: var(--radius-sm); flex: 0 0 auto; }
.eb-up__pdf {
  flex: 0 0 auto; padding: 2px 6px; border-radius: var(--radius-sm);
  background: var(--color-danger-bg); color: var(--color-danger); font-size: var(--font-size-xs);
}
.eb-up__name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--font-size-xs); }
.eb-up__del { border: none; background: transparent; color: var(--color-danger); font-size: var(--font-size-xs); cursor: pointer; }
/* ⚠️ `.eb-up` / `.eb-up--done` 等**单文件时代**的样式已无引用（2026-09-19 深夜改多文件）——
   被下面 `.eb-files` 系列取代。这里**不删**这两个已无引用的类：
   `.eb-up--na`（走替票时发票那格的"不用传"说明）**仍在用**，它与 `.eb-up` 同族、
   删掉基类会让下一条规则无从参照。留着只占几行、不产生视觉效果。 */
.eb-opt { color: var(--color-muted); font-weight: var(--font-weight-regular); font-size: var(--font-size-xs); }

/* ⭐⭐ 走替票那格：**可用余额 + 上传入口 + 说明**（2026-10-08 乐哥两条）
   ──────────────────────────────────────────────────────────────────────────
   改前这里只有一句灰字「这笔走替票额度抵扣，不用传发票」——
   员工看不到自己有多少额度，也没地方补票（补票要去「我的替票」菜单，
   一离开正在填的这张单就没了）。
   改后：左「可用替票余额 ¥xx」 · 右「＋上传替票」 · 下「这笔走替票额度抵扣，不用传发票」。
   ⚠️ 选择器用**两个类**（`.eb-up--na.eb-tt`，特异性 0,2,0）而不是单类 `.eb-tt`：
      这条要覆盖 `.eb-up` / `.eb-up--na` 的**横排 flex 与 min-height 44**，
      单类写法与它们同特异性、只能靠源顺序决胜 —— 哪天这段被挪到上面就静默失效
      （本项目对这种"注入 !important 都改不动"的层叠坑栽过）。
   ⚠️ `min-height: 68px` 与左列「支付凭证」上传格（`.eb-files`）**同值**：
      两格在同一行，一高一矮会让明细卡底部参差；这两格的高度一直是对齐的。
   ⚠️ 金额色走 `--color-price`（全站结论性金额的红）：与替票页余额卡
      （`t-tickets.js` 的 `.tt-kpi-price`）**同源**，两处看到的是同一个颜色。 */
.eb-up--na.eb-tt {
  flex-direction: column; align-items: stretch; justify-content: center;
  gap: var(--space-1); min-height: 68px;
  /* ⚠️ 上下内边距必须**跟着内容收**（4 而不是 `.eb-up` 的 8）：
     内容是「34px 按钮行 + 4 间距 + 18px 小字」= 56 ⇒ 8+56+8 = 72，
     比左列上传格（68）高 4px、底部就参差了。收到 4 后是 64 < 68 ⇒
     `min-height` 生效、**实测正好等于 68**（与 `.eb-files` 同高）。
     ⛔ 别靠"改 min-height 迁就内容"—— 那是让左列去追右列，左列高度是它自己的契约。 */
  padding: var(--space-1) var(--space-3);
}
.eb-tt__row { display: flex; align-items: center; gap: var(--space-2); min-width: 0; }
.eb-tt__k { font-size: var(--font-size-sm); color: var(--color-muted); }
.eb-tt__v { font-size: var(--font-size-lg); color: var(--color-price); font-weight: var(--font-weight-semibold); }
/* 按钮顶到右端（`margin-left:auto`，与 `.toolbar__right` 同一手法）——
   余额在左、动作在右，是这格固定的两端，别改成"跟着余额走"（余额位数一变就跑位）。 */
.eb-tt__btn { margin-left: auto; flex: 0 0 auto; }
.eb-tt__note { font-size: var(--font-size-xs); color: var(--color-muted); }
.eb-tt__empty { color: var(--color-muted); font-weight: var(--font-weight-regular); }

/* ==========================================================================
   多文件上传区（2026-09-19 深夜，乐哥：「需要支持多个文件上传」「支持拖入、粘贴」）
   ──────────────────────────────────────────────────────────────────────────
   形态：已传的文件排成一行 54px 方块（缩略图），末尾一个「＋」追加格。
   ⭐ 为什么横排而不是"一行一个"（实测数据支撑）：
      这块可用宽 **474px** ⇒ 一排能放 **7 个**（54 格 + 8 间距 = 62px/格，474÷62=7）。
      而"一行一个"3 个文件就 185px（比旁边「发票」格高 84px），明细卡跟着变高。
      放不下时会自动折行（flex-wrap 的自然行为）。
      ⚠️⚠️ 实测数据（2026-09-20 逐档探针，**方块的尺寸改过，数据要跟着重测**）：
          · 方块 44px 时：每格占 52px ⇒ 一排 8 个；0/3/6 个 = 46px，8/10 个 = 98px（两行）
          · 方块 **54px 时（当前）**：每格占 62px ⇒ 一排 **7 个**；
            实测 9 个文件 = **118px**（7 + 3 两行）
          ⚠️ 两次都踩过同一件事：**把估算值当实测值写进注释**（44 那版写成"10 个也只占 44px"）。
             ⇒ 凡"能放几个 / 多高"的结论，一律**改完后用探针逐档重量一遍**再写。
   ⚠️ 还有一个"乘数效应"要记住：**方块尺寸 → 每行容量 → 折行阈值 → 上传格高度 → 明细卡高度**。
      所以以后调方块大小时，上面这串数据全都要重测（不是只改一个数就完事）。

   ⚠️ 高度锁在一档固定值（`min-height`），**两格必须等高、且各状态下不变**：
      它要和右列「发票」那格永远顶部对齐 —— 两格高度不同会让整个明细卡的网格歪掉
      （本项目对这 1~4px 错位一贯要求对齐，见 .eb-range 上方那段）。
      ⚠️ 2026-09-20 起空态引导（"点击选择、拖入或粘贴截图"）放进了框**里面**、
         与「＋」**同一行** —— 是行内元素、不新起一行，所以不影响高度。
         （曾经的做法是把这句并进框下面的 field-hint 那一行；那样「发票」那格
          底下空着、用的人完全不知道能拖能粘，见 .eb-guide 上方那段。）
   ⚠️⚠️ 2026-09-20 **再次调高**（乐哥：「上传支付凭证，还有发票的框框要不要高一点，
       现在太矮了」）。量化了"矮"在哪：原来是方块 44px 塞进 46px 容器，
       **上下各只剩 1px** —— 几乎顶到虚线边框，所以显局促
       （对比其他输入框也是 44px，但里面是文字、居中后显得饱满）。
       ⇒ 改为容器 **68px** + 方块 **54px**（上下各留 7px），空态和已传文件都宽松，
         缩略图同时放大约 23%（看发票照片更容易分辨）。
       ⚠️ 代价是每笔明细卡 +22px（5 笔多 110px）—— 这是当时对比三档后乐哥选的档位，
          将来若要再调，别只改容器高度而不改方块（那就退回"只有留白变多"的 A 档观感）。
   ⚠️ 颜色全部走既有 token：主色虚线 + 主色浅底（与旧的单文件上传格同一套语言），
      拖入高亮复用全站既有的 `.upload-drop.is-dragover` 配色（主色实边）。
   ========================================================================== */
.eb-files {
  display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap;
  /* 68px（2026-09-20 调高，乐哥「框框要不要高一点，现在太矮了」）——
     原先 44px 时方块 44px 上下只剩 1px，顶着边框显局促。 */
  min-height: 68px; padding: 0 var(--space-3);
  /* ⚠️ 虚线 **2px**（2026-10-09 随底色一起调）：底色去掉后，1px 的浅蓝虚线在白卡上太淡，
     "这一格能传东西"的暗示几乎看不见。
     ⚠️ 别写 1.5px —— **Chrome 会把边框宽度取整到 1px**（实测 `getComputedStyle` 返回 1px、
        截图与 1px 逐像素相同），写了等于没写。
     2px 与全站标准上传件 `.upload-drop` 一致（`border: 2px dashed`）。 */
  border: 2px dashed var(--color-primary-200); border-radius: var(--radius-md);
  /* ⭐⭐ 2026-10-09 常态**不铺底色**（乐哥：「这几个框还有里面的颜色……当前的感觉不直观」）。
     ──────────────────────────────────────────────────────────────────────────────
     ▸ 改前 `background: var(--color-accent-bg)`（= `--color-primary-50`，浅蓝）——
       而那个令牌在全站是**"选中 / 拖拽中 / 激活"的状态色**
       （对照全站标准上传件：`.upload-drop` 常态**无底**、`:hover` 才 `surface-2`、
        `.is-dragover` 才 `accent-bg`；`.img-upload` 同理）。
     ▸ 后果有两个：① 上传格**一直看着像"被选中"**；
       ② **它自己 dragover 时的底色与常态是同一个值** ⇒ 拖进来**底色毫无变化**（反馈为零）。
     ▸ 现在：常态透出卡片白底 ⇒ hover 给浅灰、**dragover 才变浅蓝**（反馈回来了）。
     ⚠️ **主色虚线保留**：它表达"这两格不传就提交不了"（选填才是灰虚线）——
        有门禁锁这条（`expense-batch-form-ui.test.js` ⑥），别改成灰的。 */
  background: transparent;
  cursor: pointer;
  transition: border-color var(--motion-fast) var(--motion-ease-standard),
              background var(--motion-fast) var(--motion-ease-standard);
}
.eb-files:hover { border-color: var(--color-accent); background: var(--color-surface-2); }
.eb-files:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; }
/* 拖入高亮（复用全站 .is-dragover 语言：主色实边 + 稍深底） */
.eb-files.is-dragover { border-style: solid; border-color: var(--color-accent); background: var(--color-primary-50); }
/* 粘贴目标：点过这一格之后才标出来 —— 让"Ctrl+V 会粘到哪"一眼可见。
   ⚠️ 用**实线 + 主色**与"拖入中"区分不开也没关系：两者都是"这一格是当前目标"，
      而且拖入时用户手上有鼠标、粘贴时看的是边框，不会同时发生。 */
.eb-files.is-paste-target { border-style: solid; border-color: var(--color-accent); }
/* 上传/识别中：整格变淡 + 不可点，另有一个**转圈 + 进度文案**（`.up-wait`，
   由 JS 追加进这一格，见 expense-batch-form.js 的 flashBox）。
   ⚠️ 2026-10-12 推翻此前"不转圈（这一格太矮，圈会挤掉方块）"的判断 —— 前提变了：
      那一版假设"传 1 张、一两秒就好"，而实际一笔可传多张、**逐个串行**等服务端
      识别，攒起来十几秒（乐哥：「发票上传，识别的时候发票一多就会卡一下」）⇒
      没有可见的"在动"指示，用户会以为卡死。
      现在也不会挤掉方块：转圈是**追加在末尾**的行内小件（14px），不动已有缩略图与
      「＋」；放不下时 flex-wrap 自然折行。 */
.eb-files.is-uploading { opacity: .6; pointer-events: none; }
/* 上传中不显示"点击选择／拖入／粘贴"引导 —— 此刻这几个动作都不可用
   （`pointer-events: none`），留着会与等待文案打架、也像"还能点"。 */
.eb-files.is-uploading .eb-guide { display: none; }

/* ── 空态引导（2026-09-20 乐哥「UI 升级下」）────────────────────────────────
   改前：框里空态只有一个「＋」，右边一大片空白 —— 看不出这格能放几个、
        更看不出可以拖入或粘贴；说明还躲在框**下面**的小字里，且只有「支付凭证」
        那格有。改后引导语进框、与「＋」同一行。
   ⚠️ 是**行内元素、不新起一行** ⇒ 高度仍是 44px（+边框=46），
      与「发票」那格永远对齐 —— 这是这块最不能破的不变量。 */
.eb-guide { font-size: var(--font-size-xs); color: var(--color-meta); white-space: nowrap; }
.eb-guide b { color: var(--color-accent-text); font-weight: var(--font-weight-medium); }

/* 三种状态同一时刻只显示一条；默认只留 idle。
   ⚠️ 用 CSS 切而不是在回调里改 textContent：dragover 每秒触发几十次，
      重建文本节点既浪费、文案还会闪；切类名则由浏览器合成，零抖动。 */
.eb-guide__drop, .eb-guide__paste { display: none; }
.eb-files.is-dragover .eb-guide__idle { display: none; }
.eb-files.is-dragover .eb-guide__drop { display: inline; }
.eb-files.is-paste-target:not(.is-dragover) .eb-guide__idle { display: none; }
.eb-files.is-paste-target:not(.is-dragover) .eb-guide__paste { display: inline; }
/* 已有文件时**不显示任何引导文字**（连拖入/粘贴的实时提示也不显示）：
   ⚠️⚠️ 这里踩过一个边缘问题：文件多到快占满一行时（例如 8 个 ⇒ 已占 424px、
      只剩 47px），那条提示文字（约 112~144px）放不下就会**折行**，
      于是"点一下这一格"会让整格从 46px 跳到 92px、点别处又跳回来 —— 明显的抖动。
   ⭐ 取舍：有文件时不靠文字确认，改靠**边框变实线 + 主色**（空间反馈仍然明确：
      换另一格会立刻看到实线边框移过去）。用户此时已经用过这一格，
      不需要再读一遍"点击选择、拖入或粘贴"；而**高度恒定**比多一句提示更值。
   ⚠️ 隐藏的是**整条 `.eb-guide`**、不是里面的子元素：子元素 display:none 后
      外层 span 仍是 flex item、仍占一个 8px 的 gap，会在「＋」后留出像 bug 的空隙。 */
.eb-files.has-files .eb-guide { display: none; }

/* 单个文件方块（54×54）—— ⚠️ 比上传格（68px）矮 14px，上下各留 7px 的呼吸；
   ⚠️ 这两个数要**成对**改：只改容器高度会让方块重新贴边（退回"只有留白变多"的观感）。 */
.eb-file {
  position: relative; width: 54px; height: 54px; flex: 0 0 auto;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--color-border); border-radius: var(--radius-md);
  background: var(--color-surface); overflow: visible;
}
.eb-file__img { width: 100%; height: 100%; object-fit: cover; border-radius: var(--radius-md); display: block; }
/* PDF 没有缩略图 → 显示 PDF 标签（不要留空白，否则看着像没传成功） */
.eb-file__t {
  /* 方块 44→54 后，只靠原来的 xs 字号会显得空 —— 提到 sm */
  font-size: var(--font-size-sm); font-weight: var(--font-weight-semibold);
  color: var(--color-danger);
}
.eb-file--pdf { background: var(--color-danger-bg); border-color: var(--color-border); }
/* 已满上限时末尾那个数字格 */
.eb-file--full { font-size: var(--font-size-xs); color: var(--color-muted); background: var(--color-surface-2); }

/* 移除按钮：贴在方块右上角（负偏移）—— 方块只有 44px，放里面会盖住缩略图 */
.eb-file__x {
  /* 方块变大后按钮也略放大（16→18），否则在一个 54px 的方块上显得过小、不好点 */
  position: absolute; top: -6px; right: -6px; width: 18px; height: 18px; padding: 0;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--color-border); border-radius: 50%;
  background: var(--color-surface); color: var(--color-muted);
  cursor: pointer; line-height: 1;
  transition: background var(--motion-fast) var(--motion-ease-standard),
              color var(--motion-fast) var(--motion-ease-standard);
}
/* ⚠️ 白字用 `var(--color-surface)`（= #FFFFFF）而不是裸 `#fff` ——
   本页有门禁"报销表单样式里不许出现裸十六进制颜色"，且全站既有惯例也是这么写的
   （见 components.css 的 .link-btn 区域）。 */
.eb-file__x:hover { background: var(--color-danger); border-color: var(--color-danger); color: var(--color-surface); }

/* ⭐ 2026-10-10 财务已「审核通过」的发票 ⇒ **锁定**：右上角锁形标记替代「×」移除按钮，
   方块本身给一层成功色，与旁边的可移除文件一眼可分。
   乐哥：「上传的发票，如果是财务已经审核通过的…（这样能减少财务工作量）」
   ⚠️ 锁形标记与 `.eb-file__x` **同尺寸同定位**（18px / -6px 负偏移）——
      换掉按钮后不能改尺寸，否则整排缩略图的右上角会出现忽大忽小的"角标毛刺"。
   ⚠️ 无 `--color-success-border` 令牌 ⇒ 描边直接用 `--color-success`（实体绿），
      ⛔ 不许在本页写裸十六进制（本页有门禁）。 */
.eb-file--locked { border-color: var(--color-success); background: var(--color-success-bg); }
.eb-file__lock {
  position: absolute; top: -6px; right: -6px; width: 18px; height: 18px; padding: 0;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--color-success); border-radius: 50%;
  background: var(--color-success-bg); color: var(--color-success-text);
  line-height: 1;
}

/* 「＋」追加格：与文件方块同尺寸，虚线 → 一眼看出"这里还能加" */
.eb-file-add {
  width: 54px; height: 54px; flex: 0 0 auto; padding: 0;
  display: flex; align-items: center; justify-content: center;
  border: 1px dashed var(--color-primary-200); border-radius: var(--radius-md);
  background: var(--color-surface); color: var(--color-accent-text);
  cursor: pointer;
}
.eb-file-add:hover { border-color: var(--color-accent); background: var(--color-accent-bg); }

/* 手机端：方块收到 40px（一行能多放一个），移除按钮稍微往内收一点防出界 */
@media (max-width: 768px) {
  .eb-file, .eb-file-add { width: 40px; height: 40px; }
  .eb-file__x { top: -4px; right: -4px; }
}

/* ⛔ 已删除（2026-10-08）：排期选择**弹窗**的样式整组（`.eb-ps-list` / `.eb-ps-row`
   / `.eb-ps-inf` / `.eb-ps-d` / `.eb-ps-empty`）。
   那个弹窗早在 2026-09-19 晚就换成了内嵌的 `UI.schedulePicker`（见 expense-batch-form.js
   bindHead 里的挂载点），JS 那侧删得干干净净、**CSS 忘了删** —— 属于规范里点名的
   "定义了没人用的类名"：留着会让下次改版误以为弹窗还在、照着它改。
   同批清理：手机端媒体查询里的 `.eb-sum`（汇总条已搬去底部 `.eb-total*`）。 */

/* 手机端：单列 */
@media (max-width: 768px) {
  .eb-grid { grid-template-columns: 1fr; }
  /* 卡头一行放不下「标题 + 说明 + 报销人组」⇒ 让报销人组**独占一行**（卡头是 flex-wrap，
     不写这条它会挤在标题右边、把标题压窄）。 */
  .module-card__head .eb-who { margin-left: 0; flex-basis: 100%; }
  /* ⚠️ 2026-10-09 删掉这里原有的 `.eb-chip { height: 32px }` —— 它是**死规则**：
     本文件上面那段清理注释（"排期从自绘胶囊 + 弹窗换成 UI.schedulePicker"）
     已经把 `.eb-chips` 一族删干净，却**漏了手机端这一条**；
     而 `.eb-chip` 全站唯一的使用者是「本人 / 代报」标签（它自己有 20px 高），
     留着会在手机上把那个小标签撑到 32px、与桌面端不一致。 */
}

/* 明细卡片头（.dt__*）—— 2026-09-19 补：
   ⚠️ 这几个类名我先在原型里用了、也在产品代码里直接用了，但**产品 CSS 里从没定义过**
      （原型是独立文件、自带样式，所以原型看着正常）。实测截图发现卡片头变成
      「1交通出行¥860.00删除」全挤成一坨 —— 典型的"用了不存在的类名却不报错"。 */
.eb-dt .dt__hd {
  display: flex; align-items: center; gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  background: var(--color-surface-2);
  border-bottom: 1px solid var(--color-border-soft);
  /* ⭐ 2026-10-08（乐哥：「这个圆角处理的不干净」）：
     头部自己**也**带一份上圆角，与父级 `overflow: hidden` 的裁切**叠加**。
     ▸ 只靠父级裁切时，圆角的抗锯齿是由"父级把子元素切掉"算出来的 ——
       缩放屏（本项目 body 有 zoom）下那条弧线的边缘会读作"毛"。
       子元素自带半径后，弧线由它自己画，边缘更锐利（实测 9× 放大边缘过渡从
       "255→254→250→边框" 收成更干净的一档）。
     ⚠️ 半径**不是另写一个数**，而是 `calc(var(--radius-lg) - 1px)` ——
        减 1px 是扣掉父级那 1px 边框（子元素在 border-box 内侧）；
        它引用的是**同一个令牌** ⇒ 父级一改、这里自动跟随，不存在"两处实现会漂移"的问题
        （这正是上一版只肯用 `overflow:hidden` 的原因，现在改判据为"必须同源"）。
     ⚠️ 只给**上**两角：头部下面还接着表单区，下两角必须保持直角。 */
  border-radius: calc(var(--radius-lg) - 1px) calc(var(--radius-lg) - 1px) 0 0;
}

/* ══════════════════════════════════════════════════════════════════════════
   每笔明细卡一色（2026-10-08 乐哥）
   「每一笔报销的框一个颜色，每一笔一个颜色，这样就很直观了，
     框的颜色和表头的一致，注意不要用很艳的颜色，不同笔数的颜色你自己做一个
     自定义就行，固定下来，可以循环重复」
   ──────────────────────────────────────────────────────────────────────────
   ▸ **色值**：`--color-entry-tone-1..5`（定义在 `design-tokens.css` 里，逐支验算过）——
     每组都是"感知明度与表头灰（--color-surface-2）差 ≤0.024、RGB 极差 12~18"，
     读起来就是同一个档次的浅色、只是色相不同。
   ▸ **试过但没采用的配方**：拿 `--color-tone-*-bg`（KPI 卡的底）按 32% 混进表头灰。
     实测两支不合格：橙色混完 **极差只剩 5**（等于没颜色，违背"这样就很直观了"）、
     紫色混完把明度**拉低 0.065**（那一笔明显比别的笔重，违背"和表头的一致"）。
     ⇒ 记在这里，免得下一个人再试一遍这个配方。
   ▸ 色相顺序按"相邻差异最大"排：**蓝 → 青 → 橙 → 绿 → 紫**（把冷暖交替开，
     否则相邻两支看着像同一个色）。
   ▸ **循环**：第 6 笔回到蓝色（JS 侧 `idx % 5`，见 expense-batch-form.js 的说明）。
   ▸ 用到 `--color-surface-2` 与 `--color-tone-*-bg` 都是**既有令牌**，不新造色值。
   ⚠️ 选择器写成 `.eb-dt.eb-dt--c1`（两个类）而不是 `.eb-dt--c1`：
     上面 `.eb-dt .dt__hd` 也是 0-2-0，同特异性只能靠"谁写在后面"决胜 ——
     那种脆弱写法本项目批过。加一个类直接提到 0-3-0，明确盖过。 */
.eb-dt.eb-dt--c1 .dt__hd { background: var(--color-entry-tone-1); }
.eb-dt.eb-dt--c2 .dt__hd { background: var(--color-entry-tone-2); }
.eb-dt.eb-dt--c3 .dt__hd { background: var(--color-entry-tone-3); }
.eb-dt.eb-dt--c4 .dt__hd { background: var(--color-entry-tone-4); }
.eb-dt.eb-dt--c5 .dt__hd { background: var(--color-entry-tone-5); }
/* ⭐⭐ 2026-10-09 每笔一色 · 最终版
   （乐哥：「刚刚做的这个，可以做为系统级的设计规范，就是**大模块标题前标数字，
            小模块就是这个竖条**」⇒ 已升为**系统级规范**，见 DESIGN-SPEC §7.11）
   ──────────────────────────────────────────────────────────────────────────
   ⚠️ **竖条的外观规格不在这里**（规格全站只有一处定义）：已收进 `components.css` 的
      `.sub-mark::before, .eb-dt .dt__kind::before` 那一组多选择器 ——
      与 `.form-sec` 收编三个旧分区头是**同一个做法**（别名映射，改规格只改一处）。
      本文件只负责**报销单的配色**：按笔循环 5 档，覆盖 `--sub-mark-color`。
   ▸ 颜色用 `--color-tone-*-fg`（**饱和档**）：头部那条浅底（L≈0.95）上，
     极浅色的小竖条**根本看不见** —— **浅色只能做"面"、做不了"线"**。
     色相与头部浅底逐一对应（蓝-蓝/青-青/橙-橙/绿-绿/紫-紫）。
   ⛔ 别再在这里写 `::before` 的 width/height/display（那会让规格出现第二个来源）。
   ⛔ 也别改回"贯穿整卡的长条"（`.eb-dt::before` 那种，乐哥明确否掉过）。 */
.eb-dt.eb-dt--c1 { --sub-mark-color: var(--color-tone-blue-fg); }
.eb-dt.eb-dt--c2 { --sub-mark-color: var(--color-tone-teal-fg); }
.eb-dt.eb-dt--c3 { --sub-mark-color: var(--color-tone-orange-fg); }
.eb-dt.eb-dt--c4 { --sub-mark-color: var(--color-tone-green-fg); }
.eb-dt.eb-dt--c5 { --sub-mark-color: var(--color-tone-purple-fg); }
/* ⭐ 2026-10-10 有票被财务判「校验未通过」的那一笔 ⇒ 小竖条转**危险色**。
   乐哥的场景是"只改有问题的这一笔"，长表单里得让他**一眼扫到是哪一笔**
   （不必逐笔展开看缩略图）。
   ⚠️⚠️ 变量必须是 **`--sub-mark-color`** —— 真正生效的那个
      （规格在 `components.css` 的 `.sub-mark::before, .eb-dt .dt__kind::before`）。
      ⛔ 原来紧跟在上面的 5 行 `--eb-entry-bar` 是 2026-10-09「规格升系统级」时的
         **改名残留**：全站没有任何地方消费它 ⇒ 写它等于**静默无效**。
         （本轮第一版就照抄了它，出图量到竖条 `rgba(0,0,0,0)` 才发现 —— 已删。）
   ⛔ 不动 `.dt__hd` 的底色 —— 底色是"每笔一色"的识别语言（乐哥 2026-10-08 定），
      改掉会让"第几笔"的配色规律乱掉。竖条转红已经足够醒目。 */
.eb-dt.eb-dt--rej { --sub-mark-color: var(--color-danger); }

/* ⭐ 2026-10-09 报销单的两个分区头（① 整单信息 / ② 费用明细）——
   标题里嵌了编号徽标（`.form-sec__no`），需要内联对齐；
   字重从"卡片标题"的 500 提到"分区头"的 600（与全站分区头一致，见 DESIGN-SPEC §7.5）。
   ⚠️ 选择器要写到 0-3-0 才盖得过 `.module-card__head--plain .module-card__title`（0-2-0）。 */
.module-card__head--plain .module-card__title.eb-sec-title {
  display: inline-flex; align-items: center; gap: var(--space-2);
  font-weight: var(--font-weight-semibold);
}
.eb-dt .dt__kind { font-size: var(--font-size-sm); font-weight: var(--font-weight-medium); }
.eb-dt .dt__amt {
  margin-left: auto; padding-left: var(--space-3);
  font-size: var(--font-size-sm); font-weight: var(--font-weight-medium);
  font-variant-numeric: tabular-nums;
}
.eb-dt .dt__del {
  flex: 0 0 auto; border: none; background: transparent;
  color: var(--color-danger); font-size: var(--font-size-sm);
  cursor: pointer; padding: 0 var(--space-1);
}
/* ⭐⭐ 2026-10-08 乐哥：「费用明细里面这每一笔的这个 ui 也不对，比如最上面没有圆角，
   然后每笔的分隔看着不清楚」。两个症状、两个根因：
   ─────────────────────────────────────────────────────────────────────────
   ① **最上面没有圆角** —— 圆角在 `.eb-dt` 上（16px），但子元素 `.dt__hd` 自带浅灰底
      （--color-surface-2）。**子元素的方角不会自动跟随父级圆角**，灰底四角正好盖在
      16px 圆角上 ⇒ 视觉上"卡片顶部是直角"（实测 `.dt__hd` 的 computed border-radius = 0px）。
      ▸ 修法：父级 `overflow: hidden`（让灰底被圆角裁掉）。
        ⚠️ 不给 `.dt__hd` 另写 `border-radius: calc(16px - 1px) 0 0` ——
           那样半径值有两处实现，父级一改就漂移（本项目"同一事实不留两处实现"的纪律）。
        ⚠️ 卡内不需要溢出的东西（头部 + 表单区），且 `.dt__bd` 有 16px 内边距，
           控件的聚焦环（--shadow-ring）离边缘还有余量，裁不到。
   ② **每笔分隔看不清** —— 卡片白底、模块卡也白底，只靠 1px 的 `--color-border-soft`
      （#EDF0F4，几乎看不见）+ 12px 缝 ⇒ 两笔之间读成"一整块里的几条线"。
      而本卡**连阴影都没有**（实测 box-shadow: none）。
      ▸ 按规范 §3 / §8.8「卡片只靠 **1px 线 + 柔和阴影**，不加多余框」：
        补上卡片族的 `--shadow-card`（与 .card/.module-card/.kpi-card 同一套浮起阴影），
        并把相邻间距 12 → 16（`--space-4`，仍在同一分区的"紧邻"档内）。
      ⚠️ 不要用"加边框颜色/加深底色"来解决 —— 规范明确写了加框是最后手段，
         而且加深底色会让明细区看着比整单信息区更重，层级倒挂。 */
.eb-dt {
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-lg);
  background: var(--color-surface);
  overflow: hidden;
  box-shadow: var(--shadow-card);
}
.eb-dt + .eb-dt { margin-top: var(--space-4); }

/* ==========================================================================
   「我的报销」列表：一行一张单（2026-10-09 乐哥拍板）
   ─────────────────────────────────────────────────────────────────────────────
   ⛔ 改前这里是"按单折叠"那一套（`.me-sheet` 汇总行 + `tr.me-item--child` 子明细行，
      点开才看得到明细）。乐哥 2026-10-09：「我提的一笔报销，怎么还要这样展开？
      这个和我们之前在做财务系统时沟通的不一样」⇒ 拍板改成**与「报销单总览」同形**：
      一行一张单（带笔数列），明细进「详情」。
   ⛔ **不要再把这套折叠样式加回来**：没有折叠行可用了，留着就是死代码，
      而且下次改版会误以为"列表还能折叠"照着它改。
   ========================================================================== */

/* 操作列（详情 / 撤回 / 修改后重新提交）—— 三个按钮在一格里的排布 */
.me-ops { white-space: nowrap; }
.me-ops .btn + .btn { margin-left: var(--space-2); }
/* 单级金额：等宽数字，纵向能对齐比较（多笔单的金额是合计，更要能竖着比） */
.me-amt { font-weight: var(--font-weight-medium); font-variant-numeric: tabular-nums; }

/* 提报人详情弹窗（`GET /my-expenses/claims/:id` 的内容）
   ⚠️ 块名用 `me-detail`（本页自己的前缀），别复用审批台的 `.ap-detail` ——
      两页的字段与操作都不一样，共用一个块名会让"改一处动两页"。 */
/* ⭐⭐ 2026-10-10 单据头重排（乐哥：「报销单号可以加粗显示，然后报销时间啥的是不是
   可以和单号同一行」「报销金额是不是可以换个合适位置，加红加粗」）
   ──────────────────────────────────────────────────────────────────────────────
   ▸ 左：**单号（加粗·等宽·一档放大）** + 状态标签 + 一行元信息
     —— 元信息（报销人/部门/提交时间/笔数）与单号**同一行**（改前压在第二行、
        与单号脱节，白占一行高度）。
   ▸ 右：**报销金额**独立成块、红加粗 —— 整页唯一的"结论性数字"。
     改前它混在灰色元信息里当小字（`合计 ¥10,905.00（2 笔）`），扫视时抓不到。 */
.me-dhead {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--space-4) var(--space-5); flex-wrap: wrap;
}
.me-dhead__main {
  display: flex; align-items: baseline; flex-wrap: wrap;
  gap: var(--space-1) var(--space-2); min-width: 0;
}
.me-dhead__no {
  font-family: var(--font-family-mono);
  font-size: var(--font-size-lg);
  font-weight: var(--font-weight-semibold);
  color: var(--color-fg);
}
.me-dhead__meta { font-size: var(--font-size-sm); color: var(--color-muted); }
.me-dhead__meta b { color: var(--color-fg); font-weight: var(--font-weight-medium); }
.me-dhead__amt { display: flex; align-items: baseline; gap: var(--space-2); flex: none; }
.me-dhead__amt-k { font-size: var(--font-size-xs); color: var(--color-muted); }
/* 金额走 `--color-price`（全站"结论性金额"那一档红，与 --color-danger 同源）——
   ⛔ 别用 --color-danger 另起一套，两者在令牌里就是同一个值。 */
.me-dhead__amt-v {
  color: var(--color-price);
  font-size: var(--font-size-xl);
  font-weight: var(--font-weight-semibold);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
/* 整单信息：**一行平铺**（乐哥：「费用信息是不是可以一行显示」）。
   改前是两列网格 ⇒ 字段一多就折行、左一列右一列来回跳，读起来不是"一句话"。
   `inline-flex + wrap`：宽屏一行装下，窄屏自然折行（不设固定列数）。 */
.me-dinfo {
  display: flex; flex-wrap: wrap; align-items: baseline;
  gap: var(--space-1) var(--space-5);
  margin-top: var(--space-3); padding-top: var(--space-3);
  border-top: 1px solid var(--color-border-soft);
  font-size: var(--font-size-sm);
}
.me-dinfo__i { display: inline-flex; align-items: baseline; gap: var(--space-2); min-width: 0; }
.me-dinfo__k { flex: none; color: var(--color-muted); }
.me-dinfo__v { color: var(--color-fg); word-break: break-all; }
/* 驳回原因：提报人打开详情第一眼要看的就是它 ⇒ 警示色整条（不是小字） */
.me-detail__reject {
  margin-top: var(--space-3); padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-danger-border); border-radius: var(--radius-md);
  background: var(--color-danger-bg); color: var(--color-danger);
  font-size: var(--font-size-sm);
}
/* ⭐ 2026-10-10 「哪几张票没过」—— 与驳回原因**同一套红**（全站"驳回"只许一种红）。
   ⚠️ 紧接在驳回原因下方、同一视觉块 ⇒ 提报人读到的是一句话："为什么退回 + 要换哪几张"。 */
.me-detail__reject--inv { margin-top: var(--space-2); }
.me-rj-inv {
  display: flex; flex-wrap: wrap; gap: var(--space-1) var(--space-3); margin-top: var(--space-1);
}
.me-rj-inv code {
  font-family: var(--font-family-mono); color: var(--color-danger-strong);
  font-weight: var(--font-weight-medium);
}
/* 原因用中性色：号码是"要照着换的"（红），原因是"给人读的"（灰） */
.me-rj-inv i { font-style: normal; margin-left: 4px; color: var(--color-fg-2); }
.me-detail__note { margin-top: var(--space-2); font-size: var(--font-size-sm); color: var(--color-muted); }
.me-detail .table-wrap { margin-top: var(--space-3); }
.me-detail__sec {
  margin-top: var(--space-4); font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium); color: var(--color-fg);
}
.me-detail__logs { margin: var(--space-2) 0 0; padding-left: 18px; font-size: var(--font-size-sm); color: var(--color-muted); }
.me-detail__logs li { margin-bottom: var(--space-1); }
.me-detail__acts { margin-top: var(--space-4); display: flex; gap: var(--space-2); justify-content: flex-end; }
.me-detail__err { padding: var(--space-4); color: var(--color-danger); font-size: var(--font-size-sm); }

/* 费用明细表（2026-10-10 从"卡片"改成"表格"——乐哥：
   「费用明细预览的时候，明细能不能做成表格样式」）。
   ⚠️ 缩略图样式**不在本页写** —— 走全站公共件 `.ea-thumb`（expense-attach.js：
      发票/凭证分组、图片/PDF 分流、可点开原件）。这里只给它一个对齐的容器。
   ⚠️ 单元格一律 `vertical-align: top`：凭证那格会因缩略图多而成两行，
      不顶部对齐会让"金额/日期"看着浮在中间。 */
.me-itbl td { vertical-align: top; font-size: var(--font-size-sm); }
.me-itbl__no { width: 34px; color: var(--color-muted); }
.me-itbl__files .ea-wrap { flex-wrap: wrap; gap: 4px; }
/* ⭐⭐ 2026-10-10 附件拆成**两列**（发票 / 付款凭证）后，每列各自给一个下限宽度：
   一张缩略图 32px + 组内间距 ⇒ 两列各自 < 76px 时会被压成一条线（表格自动布局
   会优先挤"没有最小宽度"的列）。给 84px ⇒ 一行至少能放两张，装不下再换行。 */
.me-itbl__files { min-width: 84px; }
/* 合计行（2026-10-10 加）：整单层面的"对不对得上"落点 ——
   逐笔都对得上、合计却对不上，也只可能是分摊/录入的错，必须一眼看见。
   ⚠️ 底色用 `--color-surface-2`（表头同款"次级底"）——它是**表的一部分**，
      不是另一张卡；不要加边框/阴影（会读成"两个表格接在一起"）。 */
.me-itbl__sum td { background: var(--color-surface-2); font-weight: var(--font-weight-medium); }
/* ⭐⭐ 「发票金额 vs 报销金额 对不上」的现场标记（乐哥：「避免发票金额和报销金额对不上」）
   —— 挂在**发票金额**那一格下方，红字写明差多少。
   为什么必须写差额而不是只标红：财务看到红点还得自己去减，写出来他才知道是 100 还是 10 万。 */
.me-diff {
  display: block; margin-top: 2px;
  font-size: var(--font-size-xs); font-weight: var(--font-weight-medium);
  color: var(--color-danger);
}
.me-sum__ok { color: var(--color-muted); }
/* 发票号码：**块级**逐个成行（财务要一个字符一个字符对着税局核，横排会被折得断断续续） */
.me-itbl .me-d-code { display: block; margin-top: 2px; width: fit-content; }
/* 发票号码：等宽 + 浅底，方便对着税局页面核对（财务/提报人都要一个字符一个字符看） */
.me-d-code {
  display: inline-block; margin-right: var(--space-2); padding: 0 4px;
  border-radius: var(--radius-sm); background: var(--color-surface-2);
  font-size: var(--font-size-xs); color: var(--color-fg);
}

/* 明细卡片「说明（备注）」尾行（2026-09-19 乐哥指定字段顺序）
   顺序：费用类型 → 类型相关（交通方式/出发到达/日期）→ 金额 → 发票类型
        → 支付凭证 → 发票 → **说明**（这里）
   ⚠️ 独立成一段而不是塞进上面的两列网格：备注可能写得较长（"汉庭王府井店 3 晚"），
      挤在半列里看不全；也与"钱的字段"分开，扫一眼不会混。 */
.eb-grid--tail { margin-top: var(--space-6); }
.eb-fld--full { grid-column: 1 / -1; }

/* ==========================================================================
   登录页 · 三套门面（2026-09-22 定稿）
   ⭐ 分的是**门面**，不是账号：门户 / 财务 / 达播各看各的门面，
      底层 users / 权限 / 会话完全共用。
      （机制说明见 public/js/pages/login.js 顶部的 entryKind / FACADE）

   ⚠️ 背景图不在这里 —— 在文件前部 `.login-root--hub/--fin/--main .login-hero`
      那三条（包在 `@media (min-width:769px)` 里，为了让小屏不下载图片）。
      这里只定义**门面强调色变量**，它们影响控件（焦点框 / 悬停 / 登录按钮底）。
   ⚠️⚠️ `--login-brand-deep` 是**按钮底色**专用，比 `--color-accent` 深一档：
      `--color-accent` 是品牌标准色（用于焦点框/悬停/图标），拿它当按钮底压白字
      达不到 4.5:1（实测财务金 4.06 / 达播橙 3.67 / 微信绿 2.38）。
      ⇒ **两者不要合成一个**（详见 .sso 的注释）。
   ========================================================================== */
.login-root--hub { --login-brand-deep: #0b5fd9; --color-accent: #0b5fd9; }
/* ⚠️ 是 `--finance` 不是 `--fin`（与 login.js 的 entryKind() 返回值逐字对齐，见上） */
.login-root--finance { --login-brand-deep: #8f6111; --color-accent: #a8741a; }
/* ⭐ 2026-10-10：人事行政门面强调色（**深钢蓝灰**，与它的背景图同色系）。
   ⚠️ 此前 hr **没有**这条规则 ⇒ 强调色回落到全局默认蓝 `#0064E0`
     （与门户的 `#0b5fd9` 几乎看不出区别）。补上后五个门面各有各的色温。
   ⚠️ 白字压在 `#4a6b8a` 上对比度 ≈ 5.1:1（按钮/焦点态用），在标准之上。 */
.login-root--hr { --login-brand-deep: #3f5670; --color-accent: #4a6b8a; }
/* ⭐ 2026-10-09 新增：**项目管理**门面（青绿）—— 与门户的蓝明确区分，
   背景图 `project.jpg` 同为青绿系（层叠玻璃模块，呼应"模块化/项目管理"）。
   ⚠️ `--login-brand-deep` 是**按钮底**专用，必须比 `--color-accent` 深一档：
      accent #0e8f76 压白字只有 **4.03:1**（< 4.5），deep #0b6e5c 才是 **6.18:1** ✅
      —— 与上面 finance/main 同一个理由，别把两个变量合成一个。 */
.login-root--project { --login-brand-deep: #0b6e5c; --color-accent: #0e8f76; }
.login-root--main { --login-brand-deep: #a85505; --color-accent: #c96e08; }
/* ⚠️ 这里原本是一条 `.login-root--fin .login-hero` 的**金色渐变**覆盖规则
   （2026-09-20 财务门面 = 金币¥ + 金色调）—— 2026-09-22 换成背景图后**已删除**：
   留着它是无害的死代码，但会让下一个人以为财务门面还是金色渐变。
   门面的区分现在完全由**背景图 + 上面的强调色变量**承担。
   另有一条"把财务 logo 底衬提亮一档"的覆盖也早已删除：logo 底衬已整体改成**透明**
   （理由见 .login-hero__logo 的注释），不存在底衬可调。 */

/* ==========================================================================
   报销审批（2026-09-22 ·《方案-报销流程重构》）
   三个页面共用一套：审批台（.ap-*）｜报销后台（.xb-*）｜审批流配置（.ef-*）
   ⚠️ 前缀选择：`.eb-*` **已被费用明细的编辑表单占用**（见上面的 eb-grid 那一大段），
      故本次全部另起前缀，避免样式串味（串味不会报错，只会"某个按钮忽然变样"）。
   ========================================================================== */

/* ── 审批台 ───────────────────────────────────────────────────────────── */
.ap-tabs { display: flex; gap: var(--space-2); align-items: center; flex-wrap: wrap; margin-bottom: var(--space-3); }
.ap-detail__head { display: flex; flex-direction: column; gap: 4px; margin-bottom: var(--space-3); }
.ap-no { font-size: var(--font-size-lg); font-weight: 600; }
.ap-meta { color: var(--color-muted); font-size: var(--font-size-sm); }
.ap-meta b { color: var(--color-fg); }

/* 四道进度条 —— 让"卡在哪一道"一眼可见（乐哥要求"审批流程要看得见"） */
/* ⭐ 2026-10-16 乐哥：「**间距要不要大一点点**」——指的是进度条与下方第一个卡片之间。
   上间距不动（它上面是页头 meta，本来就够）；只把**下方**从 12 提到 20。 */
.ap-steps { display: flex; align-items: center; flex-wrap: wrap; gap: 6px;
  margin: var(--space-3) 0 var(--space-5); }
.ap-step { display: inline-flex; align-items: center; gap: 5px; padding: 3px 10px 3px 4px;
  border: 1px solid var(--color-border); border-radius: var(--radius-pill); font-size: var(--font-size-sm);
  color: var(--color-meta); background: var(--color-surface); }
.ap-step i { display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px;
  border-radius: 50%; background: var(--color-surface-2); font-style: normal; font-size: 11px; }
/* ⭐⭐ 2026-10-16 乐哥：「上面状态那个，**用实心的颜色**吧」
   ▸ 改前：胶囊是**白底 + 描边**，只有里面那个 16px 小圆是实心色 —— 整体读起来还是"空心标签"。
   ▸ 改后：**整个胶囊用实心色**（已完成=绿、当前=蓝）+ 文字反白；
     小圆反过来用**白底 + 彩色勾**（⛔ 不用半透明白 —— 那要写裸 rgba，
     本项目禁裸色值；白底彩勾同样干净、且全程走令牌）。 */
.ap-step--done { background: var(--color-success); border-color: var(--color-success); color: #fff; }
.ap-step--done i { background: var(--color-surface); color: var(--color-success); }
/* ⭐⭐⭐ 2026-10-17 乐哥（截图红框）：「这个环节**不要用蓝色实底**了吧，换个，你觉得呢」
   → 三方案里他选 **A 空心描边**
   ──────────────────────────────────────────────────────────────────────────
   ▸ 原样式是**蓝色实底**，与前面完成的**绿色实底**并列 ⇒ 两个实心色块抢眼，
     而且蓝色在本站是**链接色**，拿它当大色块用显得重。
   ▸ 改成**白底 + 蓝描边 + 蓝字**（空心）⇒ 和绿色实心形成
     「**实心 = 已完成 / 空心 = 进行中 / 灰 = 未到**」三档，状态一眼分得出，整体也轻。
   ⚠️ 序号圆圈 `> i` 仍是**蓝实心**（它是"当前"的锚点，需要一点实心强调）；
     分隔竖线改中性色（白底上不能用半透明白）；头像底改浅蓝（白底上看得出来）。 */
.ap-step--current { background: var(--color-surface); border: 1px solid var(--color-accent);
  color: var(--color-accent-text); font-weight: 600; }
.ap-step--current > i { background: var(--color-accent); color: #fff;
  border-color: var(--color-accent); }
.ap-step--current .ap-step__who { border-left-color: var(--color-border); }
.ap-step--current .ap-step__who .cell-person__avatar { background: var(--color-accent-bg); }
.ap-step--current i { background: var(--color-surface); color: var(--color-accent); }
/* ⭐ 2026-10-17 「当前环节」挂上处理人（借鉴飞书审批流程图：环节与"谁"同框）
   ▸ 只在**当前**那道胶囊里出现 ⇒ 已完成的"谁"在流水里已有，不在胶囊上重复。
   ▸ 用**竖线分隔**（不是括号），与胶囊内其它元素保持同一视觉语言；透明度略降，
     让"环节名"仍是主体、"人"是补充。 */
/* ⭐⭐⭐ 2026-10-17 乐哥（截图红框）：「这个**标题离正文的间距不协调，感觉太大了**；
   然后每个标题前应该有那个**竖的短彩色条**」
   ──────────────────────────────────────────────────────────────────────────
   ▸ 实测间距：标题栏高 48px，**标题文字 → 正文 = 24px**（内下边距 12 + 外下边距 12）
     ⇒ 收成 **16px**（各 8）—— 标题与内容是一组，不该拉得比段间距还开。
   ▸ 竖条：**规格照抄全站标准件 `.sub-mark`（小模块标记）**：
     3px × 14px · 圆角 2px · `--color-primary-500` · 文字前留 8px。
     ⇒ 它在该文件的定义处写明是**乐哥 2026-10-09 定的系统级规范**：
       「大模块标题前标数字，**小模块就是这个竖条**」。
     ⚠️ 那按同一份规范，`费用明细/改动记录/审批流水` 属于**大模块（有序分区）**
        ⇒ 规范里对应的是**数字**（`.form-sec__no`）、不是竖条 —— 这条已跟乐哥确认过再定。
   ⚠️ **作用域限死在审批弹窗 `.ap-detail` 内**：`.module-card__head` 与
     `.module-card__title` 是**全站公共件**，直接改会把别的页面一起动了。
     若确认全站都要这样，再提到 `components.css` 里统一改。 */
.ap-detail .module-card__head { margin-bottom: var(--space-2); padding-bottom: var(--space-2); }
/* ✅ 2026-10-17 竖条已**提成全站默认**（`.module-card__title::before`，见 components.css）——
   原来那条 `.ap-detail .module-card__title::before` 的临时作用域**已删**，
   否则会叠出两条规则（外观相同，但改规格时要改两处，容易漂移）。 */


/* ⭐ 2026-10-17 当前环节的处理人：**头像 + 名字**（乐哥：「应该有王紫蕾头像的」）
   ▸ 进度条是**实心蓝胶囊** ⇒ 头像要**小一号（16px）**、并去掉描边，
     否则在彩色底上会显得笨重；名字保持 xs。
   ▸ 分隔用半透明白竖线（在实心底上能看见，用 `--color-border` 会糊掉）。 */
.ap-step__who { display: inline-flex; align-items: center; gap: 4px;
  margin-left: 6px; padding-left: 6px; font-size: var(--font-size-xs);
  font-weight: var(--font-weight-regular);
  border-left: 1px solid rgba(255, 255, 255, .55); }
.ap-step__who .cell-person__avatar { width: 16px; height: 16px; font-size: 9px;
  border: 0; background: rgba(255, 255, 255, .9); color: var(--color-accent-text); }
.ap-step--done .ap-step__who { border-left-color: rgba(255, 255, 255, .45); }

.ap-step__line { width: 14px; height: 1px; background: var(--color-border); }

/* ⭐⭐ 2026-10-16 审批弹窗的**分模块卡片**（乐哥：「分模块展示，类似这种」——
   给的是「提交报销」页截图：整单信息 / 费用明细 各自一张 `.module-card`）
   ──────────────────────────────────────────────────────────────────────────
   ▸ 复用**公共件** `.module-card`（白底 + 1px 描边 + 圆角 16 + `--shadow-card` + 内边距 24），
     ⛔ 不在这里重写一套卡片样式 —— 那正是"同一个系统两种卡"的来源。
   ▸ `.ap-mod` 只做**弹窗内的节奏微调**：卡片间距从页面级的 16 收到 12
     （弹窗只有 ~800px 宽、纵向更紧；页面级 16 是给整页模块用的）。
   ⚠️ `.ap-section` / `.ap-section__title` **保留** —— 「审批票据」弹窗里的
     发票/付款凭证分组（`.ap-vch-grp`）还在用，⛔ 别顺手删。 */
.ap-mod + .ap-mod { margin-top: var(--space-3); }
/* ⭐⭐ 2026-10-16 乐哥：「**通过驳回那些按钮，我觉得和审批意见在同一个模块里面吧**」
   ＋「审批意见和上面模块的**间距没有了**」
   ──────────────────────────────────────────────────────────────────────────
   ▸ 间距为什么没了：意见卡是**动态插进 `#ap-cmts` 这个壳**里的，
     于是它与上一张卡之间**隔着壳** ⇒ `.ap-mod + .ap-mod`（相邻兄弟）**匹配不到**
     ⇒ 一点间距都没有。✅ 给壳补上同样的间距。
   ▸ 按钮怎么"在同一模块里"：意见区**每次重绘都会清空容器**
     （`container.innerHTML = html(...)`）⇒ ⛔ **不能把按钮真的移进卡里**（监听器会被销毁，
     表现为"按钮点了没反应"）。✅ 改用项目**既有手法**——**DOM 上分开、外观拼成一张卡**
     （同 `.toolbar--join-card` / `--join-target` 那一对）：上方去下圆角/下边框，下方去上圆角/上边框。
     这样事件绑定一个字都不用动，视觉上就是**一张卡**。 */
.ap-detail > #ap-cmts { margin-top: var(--space-3); }
/* ⭐⭐⭐ 2026-10-16 修：意见卡"去掉底边"**必须有条件**（乐哥当天报的 UI 问题：
   「我刚发了个"对"的消息，点了个回复，然后就出现现在这个样子…下面那个单独一个框…感觉很奇怪」）
   ──────────────────────────────────────────────────────────────────────────
   ▸ 病根：我为了"审批按钮与意见卡拼成一张卡"，**无条件**把意见卡的底边框与下圆角去掉了。
     而**只有"轮到我审"时**才会渲染出按钮行（`.ap-actions`）——
     不是我在审的单，下半张卡**根本不存在** ⇒ 意见卡没有底 ⇒ 里面的输入框"裸露"在外，
     看起来像一个**独立的框**（他截图里红框那块）。
   ▸ 正解：用 `:has()` 从 `.ap-detail` 那一层看 —— **只有后面真的紧跟着 `.ap-actions`**
     才把意见卡做成"上半卡"。没有按钮时，它就是一个**完整独立的白卡**（四边都有、四角都圆）。
   ⚠️ 为什么不能直接写 `.ap-cmt:has(+ .ap-actions)`：`.ap-actions` 是 `#ap-cmts` 的兄弟，
     不是 `.ap-cmt` 的兄弟（中间隔着 `#ap-cmts` 这层壳）⇒ 那个相对选择器**永远不成立**。 */
.ap-detail:has(> .ap-actions) > #ap-cmts > .ap-mod.ap-cmt {
  border-bottom: 0; border-radius: var(--radius-lg) var(--radius-lg) 0 0; box-shadow: none;
}
/* 审批按钮行：作为"下半个卡"，与意见卡同底同边、去掉上圆角与上边框 */
.ap-actions.ap-foot {
  margin-top: 0; padding: var(--space-4) var(--space-6);
  background: var(--color-surface);
  border: 1px solid var(--color-frame-border); border-top: 0;
  border-radius: 0 0 var(--radius-lg) var(--radius-lg);
}
/* ⚠️ 两者拼成一张时，**上半个卡的外阴影**会在接缝处投一条淡影（box-shadow 是整体的）⇒
   拼接态下一律不外投影，由**边框**表达边界（与卡的其他部分同色，接缝看不出来）。
   ⚠️ 同样**只在拼接态**才去阴影（上面那条 `:has()` 里已经带了 `box-shadow: none`）。 */
.ap-actions.ap-foot { box-shadow: none; }
/* ⭐⭐ 2026-10-16 乐哥：「**标题文字应该是黑色，不是绿色**」
   ▸ 根因（全站令牌）：`.module-card__title` 取 `--color-head-off-fg`，而它 =
     `var(--color-success-text)`（**深绿**，`design-tokens.css:1290`，是"卡头开关关闭态"那轮定的）。
   ▸ 处理：**只在本弹窗内**覆盖成正文色 —— 全站有几十处模块卡，擅自改令牌会波及整站外观。
     ⚠️ 若乐哥确认"全站都该黑"，再改令牌那一处（并同步检查用它的其它组件，如 `.rf-sec`）。 */
.ap-mod .module-card__title { color: var(--color-fg); }
/* ⭐⭐ 2026-10-16 乐哥：「**报销备注那个，要显眼，看颜色背景+文字**」
   ▸ 事由原来是普通黑字（14/400、透明底），夹在一张白卡里确实"不显眼"。
   ▸ 改成**浅色底块 + 深色字**（主色系）：色值全走令牌，无裸色。
   ⚠️ 只给背景与文字上色，**不加边框/阴影** —— 它仍在模块卡内，规范「卡内子块保持平」。 */
.ap-reason { font-size: var(--font-size-sm); line-height: 1.6; color: var(--color-fg);
  background: var(--color-accent-bg); border-radius: var(--radius-md);
  padding: var(--space-3); }
.ap-section { margin-top: var(--space-3); }
.ap-section__title { font-size: var(--font-size-sm); font-weight: 600; margin-bottom: 6px; }
/* ⭐⭐ 2026-10-16 「事由」模块（乐哥：「这个事由看着不显眼…要不要通过模块这样展示？」）
   ▸ 由一行灰底提示条（`.ap-hint`）改为**正式分区**：标题 + 正文，与费用明细/流水/意见同语言。
   ▸ 正文**不加卡片阴影、不加底色** —— 规范「卡内子块保持平」（避免卡中卡）；
     醒目靠的是"有标题"和正文的深浅，不是再加一层框。 */

.ap-files { display: flex; flex-wrap: wrap; gap: 8px; }
.ap-files img { width: 88px; height: 88px; object-fit: cover; border: 1px solid var(--color-border);
  border-radius: var(--radius-seg); display: block; }
/* ⭐⭐ 2026-10-10 发票号码**逐行校验表**（凭证弹窗里，缩略图下方）
   ──────────────────────────────────────────────────────────────────────────
   乐哥：「发票号码这个做成表格，列为：发票号码、开票日期、发票金额，
         然后最后操作的按钮是：校验通过（蓝色的），校验未通过（红色的）」
   ⚠️ 它取代了 2026-09-26 的「号码 + 复制」清单（`.ap-nolist*` 已整段删除）：
      财务的活从"复制号码去税局查"变成"就在本页下结论"，复制不再必需；
      但号码仍保留 `user-select: all`（他还会手选号码去税局核对，那是既有工作方式）。
   ⚠️ 行底色只做**极淡**一层：主角是两个操作按钮，底色抢注意力就本末倒置了。 */
.ap-invtbl__bar {
  display: flex; align-items: center; gap: var(--space-3);
  margin: var(--space-2) 0 6px;
  font-size: var(--font-size-xs); color: var(--color-muted);
}
.ap-invtbl__prog { margin-left: auto; }
.ap-invtbl__bar .is-bad { color: var(--color-danger-strong); font-weight: 500; }
.ap-invtbl {
  width: 100%; border-collapse: separate; border-spacing: 0;
  border: 1px solid var(--color-border-soft); border-radius: var(--radius-md);
  overflow: hidden; font-size: var(--font-size-sm);
}
.ap-invtbl th {
  text-align: left; font-weight: 500; color: var(--color-muted);
  background: var(--color-surface-2); padding: 6px 10px; font-size: var(--font-size-xs);
  border-bottom: 1px solid var(--color-border-soft);
}
.ap-invtbl td { padding: 6px 10px; border-bottom: 1px solid var(--color-border-soft); vertical-align: middle; }
.ap-invtbl tbody tr:last-child td { border-bottom: 0; }
.ap-invtbl__th-ops { width: 1%; }
/* ⭐ 2026-10-10 乐哥（截图指正）：「**序号放在最前面**，当前那个**费用大类不需要，去掉**」
   ⇒ 列改成：序号 | 发票号码 | 项目类型 | 开票日期 | 发票金额 | 操作。
   ⛔ 原来的 `.ap-invtbl__th-scope` / `.ap-invtbl__scope`（费用大类那一列）**已删** ——
      留着就是给"把那一列加回来"备地基（本项目对死规则的既有处置：删而不留）。 */
.ap-invtbl__th-i { width: 1%; white-space: nowrap; }
.ap-invtbl__i {
  text-align: right; white-space: nowrap; color: var(--color-muted);
  font-size: var(--font-size-xs); font-variant-numeric: tabular-nums;
}
.ap-invtbl__no { white-space: nowrap; }
.ap-invtbl__no code {
  font-family: var(--font-family-mono); font-size: var(--font-size-sm);
  color: var(--color-fg); letter-spacing: .02em; user-select: all;
}
.ap-invtbl__date { white-space: nowrap; color: var(--color-fg-2); }
/* 项目类型（票面「货物或应税劳务、服务名称」）—— 可能很长（`*生产生活服务*代订住宿费`），
   一行一张票，全串直出会把这一列撑到半个屏 ⇒ **单元格里截断 + 悬停看全**。
   ⚠️ `max-width` 必须给（固定布局下 td 会自动分配，但内容过宽仍会把列撑变形）；
   ⚠️ `text-overflow` 要生效 ⇒ 配 `overflow:hidden` + `white-space:nowrap`。 */
.ap-invtbl__items {
  display: inline-block; max-width: 260px; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; vertical-align: bottom;
  color: var(--color-fg-2);
}
/* ⭐ 2026-10-10 乐哥：「操作的两个按钮**挨着太紧了**，改下」
   ▸ 用 `inline-flex + gap`（而不是给第二个按钮加 `margin-left`）：
     已通过那行左边还多一个「已审核通过」标记，用 gap 才是"这一格里所有元素等距"。 */
.ap-invtbl__ops { white-space: nowrap; display: inline-flex; align-items: center; gap: var(--space-2); }
.ap-invtbl tbody tr.is-ok td { background: var(--color-success-bg); }
.ap-invtbl tbody tr.is-bad td { background: var(--color-danger-bg); }
/* 未通过的原因（跨整行）—— ⚠️ 2026-10-10 晚起**只在老数据有原因时**才画这一行
   （乐哥：「发票审核没通过，不要写原因了」⇒ 新标的都没有原因）。
   规范保留：它是驳回/换票的依据，一旦有就必须看得见。 */
.ap-invtbl__why td { padding-top: 0; font-size: var(--font-size-xs); color: var(--color-danger-strong); }
/* ⛔ `.ap-invtbl__reason-row` / `.ap-invtbl__reason` / `.ap-invtbl__why-in`
   （就地展开的"必填原因"输入框）**已于 2026-10-10 晚随该交互一并删除** ——
   JS 里再也没有地方产出它们，留着就是死规则（本项目对死规则的既有处置：删而不留，
   ⛔ 别因为"看着像功能"就留着，那会给后来人"还能填原因"的错觉）。 */
/* 「定没定」靠**实心 / 淡底**区分（⛔ 不靠颜色深浅 —— 蓝与红本身都是语义色） */
.ap-invbtn { height: var(--control-height-sm); padding: 0 10px; font-size: var(--font-size-xs); }
.ap-invbtn--pass { background: var(--color-primary-50); color: var(--color-primary-500); border-color: var(--color-primary-200); }
.ap-invbtn--pass:hover:not(:disabled) { background: var(--color-primary-100); }
.ap-invbtn--pass.is-on { background: var(--color-primary-500); color: #fff; border-color: transparent; }
.ap-invbtn--fail { background: var(--color-danger-bg); color: var(--color-danger-strong); border-color: var(--color-danger-border); }
.ap-invbtn--fail:hover:not(:disabled) { background: var(--color-danger-border); }
.ap-invbtn--fail.is-on { background: var(--color-danger); color: #fff; border-color: transparent; }
/* ⭐⭐ 2026-10-10 财务已「审核通过」的行：**两个按钮置灰禁用** + 一个「已审核通过」标记。
   ──────────────────────────────────────────────────────────────────────────────
   乐哥：「有要标记，**该笔发票已审核通过**（**两个审核按钮直接灰色**），
         这样能减少财务工作量」
   ▸ 口径：**完全锁死、无撤回入口** ⇒ 按钮可见但点不动；服务端 `assertNotPassedYet` 同款守卫。
   ⚠️ 禁用态必须**显式写颜色**：浏览器对 `disabled` 的默认处理只是降不透明度，
      而 `.is-on` 那层实心蓝（`--color-primary-500`）叠上不透明度**仍然是亮蓝**，
      看着像"还能点"—— 那就完全没达到"一眼看出已经过了"的目的。
   ⚠️ 「通过」的禁用态用**成功绿**（而不是灰色）：它表达的是"这张**已经验好了**"，
      与「未通过」按钮的灰（`--color-surface-2`）区分开 —— 两处都灰会读成"整行都不可用"。 */
.ap-invbtn:disabled { cursor: default; opacity: 1; }
.ap-invbtn--pass:disabled,
.ap-invbtn--pass.is-on:disabled {
  background: var(--color-success-bg); color: var(--color-success-text);
  border-color: var(--color-success);
}
.ap-invbtn--fail:disabled {
  background: var(--color-surface-2); color: var(--color-muted);
  border-color: var(--color-border-soft);
}
/* 「已审核通过」标记：贴在两个灰按钮**前面**，是这一行唯一"活的"信息 */
.ap-invok {
  display: inline-flex; align-items: center; gap: 4px; margin-right: var(--space-2);
  font-size: var(--font-size-xs); font-weight: var(--font-weight-medium);
  color: var(--color-success-text); white-space: nowrap;
}
.ap-attach { display: flex; align-items: flex-start; gap: 8px; margin-bottom: 8px; }

.ap-actions { display: flex; justify-content: flex-end; gap: var(--space-2); margin-top: var(--space-4);
  padding-top: var(--space-3); border-top: 1px solid var(--color-border); }
/* ⭐⭐ 2026-10-09 审批弹窗的动作区（乐哥：「这3个按钮的尺寸要和设计标准来，现在这个明显不对」）
   ──────────────────────────────────────────────────────────────────────────
   实测：三个按钮**高度**本来就是规范档 44px（`--control-height`，达播入口与财务入口
   逐值一致，见 probe-approval-modal.js），不对的是**宽度参差**：
     驳回 53 / 修改并同意 89 / 通过 53 ⇒ 一排里三个方块大小不一，看着零碎。
   ⇒ 统一 `min-width` 成一条等宽动作栏（`min-width` 而不是固定 `width`：
     「通过并入账」这种 5 字文案要能自己撑开，不能被截断）。
   ⚠️ 用 `> .ap-actions__row .btn` 而不是 `.ap-actions .btn`：`financeBlocked` 时
      `.ap-actions` 里还会多一条提示条（`.ap-approve-hint`），
      限定到行容器才不会被以后新增的元素蹭到。
   ⚠️ 行容器从**内联 style** 提成类：内联的 `display:flex` 会让"间距/对齐"
     散在 JS 字符串里，改一次要翻页面代码（本项目反复吃过这个亏）。 */
.ap-actions > .ap-actions__row {
  display: flex;
  gap: var(--space-2);
  justify-content: flex-end;
  flex-wrap: wrap;
}
.ap-actions > .ap-actions__row .btn { min-width: 88px; }
.ap-hint { color: var(--color-fg-3); font-size: var(--font-size-sm);
  /* ⚠️ 原为 `--color-bg-subtle` —— **该变量全项目没有定义** ⇒ 底色静默丢失
     （提示条看着就是一段裸文字，没有"这是一个提示块"的边界）。
     改用真实存在的 `--color-surface-2`（全站 hover/表头底，浅灰）。 */
  background: var(--color-surface-2); border-radius: var(--radius-seg); padding: 8px 12px; margin: var(--space-3) 0; }
.ap-reject { color: var(--color-danger); background: var(--color-danger-bg); border-radius: var(--radius-seg);
  padding: 8px 12px; margin: var(--space-3) 0; font-size: var(--font-size-sm); }
/* ⭐ 2026-10-21 替票「未审核」警示块（审批台详情；渲染见 pages/expense-approvals.js 的 tTicketNotice）
   ▸ 为什么要单独一个类，不复用现成的两条：
     · `.ap-hint`（灰条）= **说明**，"读一次就够"的信息；
     · `.ap-reject`（红底）= **已驳回/错误**，事情已经坏了。
     本块是第三种：**还没出错，但要审批人先确认再放行** ⇒ 橙底 + 左侧粗边，
     比灰条显眼、比红条克制。混用会让"要警惕的"和"随便看看的"长得一样。
   ⚠️ 变量全部用 design-tokens 里**真实存在**的三个（`--color-warning` / `-bg` / `-border`）——
     本项目有先例：`.ap-hint` 曾引用未定义的 `--color-bg-subtle`，底色**静默丢失**，
     提示条变成一段裸文字。⛔ 别再写不存在的变量名。 */
.ap-tt-warn { color: var(--color-fg-2); background: var(--color-warning-bg);
  border-left: 3px solid var(--color-warning); border-radius: var(--radius-seg);
  padding: 10px 12px; margin: var(--space-3) 0;
  font-size: var(--font-size-sm); line-height: 1.65; }
.ap-tt-warn b { color: var(--color-warning-text); font-weight: 600; }

/* ═══ 2026-10-21 无票报销：提示 / 重复金额确认 / 审批台提醒 ══════════════════
   三处同一条业务线（乐哥：「为了防止有人重复使用无发票的替票提报销」）：
     · .eb-noinv-tip  提报表单里选「没有发票」时的行内提示
     · .eb-dup*       提交前"同金额历史报销"确认弹窗
     · .ap-dup*       审批台详情里的提醒块（单号可点开原单）
   配色统一用 warning 语义色（橙），与"未审核替票"那条 `.ap-tt-warn` 同一族 ——
   ⛔ 不要用 danger 红：这些是"要你确认一下"，不是"出错了"。 */

/* 提报表单：选「没有发票」时的行内提示（占满整行，跟在发票类型下拉下面） */
.eb-noinv-tip {
  grid-column: 1 / -1;
  margin-top: var(--space-2);
  padding: 8px 12px;
  background: var(--color-warning-bg);
  border-left: 3px solid var(--color-warning);
  border-radius: var(--radius-seg);
  color: var(--color-fg-2);
  font-size: var(--font-size-sm);
  line-height: 1.6;
}
.eb-noinv-tip b { color: var(--color-warning-text); }

/* 提交前"同金额历史报销"确认弹窗 */
.eb-dup__lead { margin: 0 0 var(--space-3); font-size: var(--font-size-sm); color: var(--color-fg-2); }
.eb-dup__row {
  display: flex; align-items: baseline; gap: var(--space-3);
  padding: 7px 10px; margin-bottom: 4px;
  background: var(--color-surface-2); border-radius: var(--radius-seg);
  font-size: var(--font-size-sm);
}
.eb-dup__amt { color: var(--color-price); min-width: 76px; }
.eb-dup__no { font-family: var(--font-family-mono); color: var(--color-fg); }
.eb-dup__meta { color: var(--color-fg-3); }
.eb-dup__ask { margin: var(--space-3) 0 0; font-size: var(--font-size-sm); color: var(--color-fg-2); }
.eb-dup__ask b { color: var(--color-warning-text); }

/* 审批台：同金额历史报销提醒块（在费用明细表正下方） */
.ap-dup {
  margin: var(--space-3) 0;
  padding: 10px 12px;
  background: var(--color-warning-bg);
  border-left: 3px solid var(--color-warning);
  border-radius: var(--radius-seg);
}
.ap-dup__hd { display: block; color: var(--color-warning-text); font-size: var(--font-size-sm); font-weight: 600; }
.ap-dup__hint { margin: 4px 0 var(--space-2); color: var(--color-fg-2); font-size: var(--font-size-sm); line-height: 1.6; }
.ap-dup__row {
  display: flex; align-items: baseline; gap: var(--space-3);
  padding: 5px 0; border-top: 1px dashed var(--color-border-soft);
  font-size: var(--font-size-sm);
}
.ap-dup__amt { color: var(--color-price); min-width: 76px; }
.ap-dup__no { font-family: var(--font-family-mono); color: var(--color-accent); text-decoration: underline; cursor: pointer; }
.ap-dup__no--plain { color: var(--color-fg-2); text-decoration: none; cursor: default; }
.ap-dup__meta { color: var(--color-fg-3); }
/* ⭐⭐ 2026-10-16 审批流水改**时间线**（乐哥：「审批流水…ui 好丑，优化下」）
   ▸ 改前是 `<ul>` 默认圆点 + 一行纯文本，条条等重、且"通过/流转到下一道"成对重复。
   ▸ 改后：左侧一条竖线串起所有节点，**动作按语义着色**（通过=绿、驳回=红、提交=蓝…），
     人加粗、环节与时间弱化 —— 一眼扫得出"谁在什么时候把单推到哪一道"。 */
.ap-logs { margin: 0; padding: 0; list-style: none; font-size: var(--font-size-sm); }
/* ⭐⭐ 2026-10-16 乐哥：「**内容两边的缩进**，看下要不要调整」
   ▸ 改前：`.ap-log` 自带 `padding-left:16` ⇒ 圆点落在「卡片内边距 24 + 16 = 40」处，
     而**卡片标题在 24** ⇒ 右边时间贴着 24、左边却缩到 40 ⇒ **左右不对称**、内容整体偏右。
   ▸ 改后：去掉额外缩进 ⇒ **圆点与卡片标题同一左基线**（都在卡片内边距上），
     文字用 `margin-left` 让开圆点，右边时间照旧 ⇒ 左右两侧都落在同一个内边距上。 */
/* ⭐⭐⭐ 2026-10-17 修 UI bug（乐哥：「ui不对」+ 截图）：
   **有「驳回原因 / 修改说明」那两行整体乱掉** —— 时间跑到正文里、原因被截成"重新提"。
   ▸ 病根：`.ap-log` 是 `display: flex` 但**没开 `flex-wrap`** ⇒ 下面
     `.ap-log__reason { flex: 1 1 100% }` 这个"独占一行"的意图**在 nowrap 容器里根本不生效**
     ⇒ 原因被当成普通 flex item 塞在同一行 ⇒ 被压缩截断，并把 `margin-left: auto` 的
     时间也挤得不再靠右。
   ▸ 修：容器开 **`flex-wrap: wrap`** ⇒ 原因（`flex-basis: 100%`）才会真的换到下一行，
     时间（`margin-left: auto`）也能回到第一行末尾。
   ⚠️ 原因要**缩进对齐**到人名（不是贴着圆点）⇒ 用 padding-left 兜住圆点与竖线的宽度。 */
/* ⚠️⚠️ 2026-10-17 乐哥：「**头像和左边的那个环节圆点重叠了**」
   ▸ 病根：`.ap-log` 原来 `padding-left: 0`，而圆点/竖线是**绝对定位在 left:0** ⇒
     内容从 0 开始 ⇒ 头像正好压在圆点上。
   ▸ 修：左侧留出 **18px** 给圆点(0~7px)+间隙；圆点/竖线位置不用动。 */
/* ⚠️⚠️ 2026-10-17 乐哥：「**人名和后面其他字行没对齐**」
   ▸ 病根：整行是 `align-items: baseline` —— 而**人名胶囊是 inline-flex**，
     它的基线取的是**内部第一个元素（头像图片）的底边** ⇒ 胶囊整体比右侧文字**偏低**，
     看起来"名字沉下去一截"。
   ▸ 修：改成 `align-items: center` ⇒ 各元素按**垂直中心**对齐，胶囊与文字自然一条线。
     （原因那一行同理受益：不会再出现"文字顶对齐、胶囊沉底"。） */
.ap-log { position: relative; display: flex; align-items: center; flex-wrap: wrap;
  gap: var(--space-2); padding: 3px 0 3px 18px; color: var(--color-fg); }
/* 竖线：每条的左侧，最后一条不画（避免尾巴悬空） */
/* ⭐ 2026-10-17 乐哥：「那个**竖向引导线看不清**」—— 原来是 `1px` 的 `--color-border-soft`
   （最淡那一档），在白色卡面上几乎断掉。改成 **2px + `--color-border`**（深一档）：
   既看得见，又不会抢主体内容的注意力。
   ⚠️ 圆点的 `box-shadow: 0 0 0 2px surface`（白圈）是故意留的 —— 它让线在圆点处"断开"，
     视觉上更像时间线；改线宽时别把它删了。 */
.ap-log::before { content: ''; position: absolute; left: 2px; top: 0; bottom: 0;
  width: 2px; background: var(--color-border); }
.ap-log:first-child::before { top: 10px; }
.ap-log:last-child::before { bottom: auto; height: 10px; }
/* 节点圆点 */
.ap-log__dot { position: absolute; left: 0; top: 8px; width: 7px; height: 7px;
  border-radius: 50%; background: var(--color-border); box-shadow: 0 0 0 2px var(--color-surface); }
/* ⭐⭐ 2026-10-17 流水里的"人"改成**头像胶囊**（乐哥：「这俩都用，都是不同位置的」）
   ▸ 与「意见区的人名胶囊」同款、同尺寸（16px 头像 · 11px 字 · 高 20 · 白底蓝边）
     —— 同一屏里"人"只有一种长相，扫读时不用换脑子。
   ⚠️ 尺寸与 `.ap-cmt__quote-person` 对齐；改一处要同时看另一处（两处都是胶囊，别改出两种大小）。 */
/* ⭐⭐⭐ 2026-10-17 修**列不对齐**（乐哥：「展示还是不对，再看下」）
   ──────────────────────────────────────────────────────────────────────────
   ▸ 实测（生产 id=12，量各列左边界）：
       动作列 `294 / 283 / 294 / 283 / 294 / 294` ⇒ **跳 11px**
         ← 人胶囊宽度**跟着名字长短变**（3 字 65px、2 字 54px）
       环节列 `330 / 319 / **358** / 319 / 330 / 330` ⇒ **跳 39px**
         ← 动作词本身长短不一（"提交" vs "重新提交"）
   ▸ 修：给这两列**最小宽度** ⇒ 后面的列都从**同一条竖线**开始，整块能竖着扫。
     ⚠️ 用 `min-width` 而不是固定 `width`：万一出现 4 个字的名字（如"欧阳娜娜"），
        胶囊**自然变宽**也不会把名字压断（固定宽度会溢出/截断）。
   ▸ 为什么不直接换 `grid`：这行还有"原因"要跨列、竖线/圆点是绝对定位，
     改 grid 牵连太多；两处 `min-width` 就能把对齐修好，代价最小。 */
.ap-log__person { display: inline-flex; align-items: center; gap: 5px;
  /* ⚠️ 头像 16→**20px**（乐哥：「也太小了」）；胶囊高度随之 20→24，min-width 给到 88
     —— 头像变大后名字可用宽度不能缩，否则"重新提交"那列又会被推歪。 */
  height: 24px; min-width: 88px; padding: 2px 10px 2px 2px;
  border-radius: var(--radius-pill); background: var(--color-surface);
  /* ⚠️ 边框用**中性灰**（原来是主色蓝）—— 让步给"动作"这个主角；
     名字本身**保留主色蓝**：还能一眼看出"这是个人"，只是不再抢眼。 */
  border: 1px solid var(--color-border); }
.ap-log__person > .cell-person__avatar { width: 20px; height: 20px; font-size: 10px;
  background: var(--color-accent-bg); color: var(--color-accent-text); }
.ap-log__person .ap-log__who { font-size: 11px; color: var(--color-accent-text);
  font-weight: var(--font-weight-medium); }

/* 动作列：`min-width` 让"提交 / 驳回 / 重新提交"三种长度都从同一条竖线起
   （实测"重新提交"≈56px ⇒ 给 58px；短动作靠左、列不动） */
/* ⭐⭐⭐ 2026-10-17 乐哥：「（流水里）评论的**内容需要和人名一行，得有主次**」→ 选 **A 动作为主**
   ──────────────────────────────────────────────────────────────────────────
   ▸ 为什么"动作"该是主：**它是这一步的结论**（提交 / 通过 / 驳回 / 重新提交），
     审批人扫流水时找的就是"批没批、驳没驳"；人名是"谁"，环节是"哪道"，都属背景。
   ▸ 做法：动作 **加粗 + 升一档字号**（xs→sm），并**沿用已有的语义色**
     （通过=绿 / 驳回=红 / 提交=蓝，见下方 `.ap-log--approve` 那几条）。
   ⚠️ `min-width: 58px` 必须留着 —— 它保证"提交/驳回/重新提交"三种长度**列都对齐**；
     加粗后字变宽，"重新提交"仍放得下（实测 56→约 60，必要时同步调大）。 */
.ap-log__act { flex: 0 0 auto; min-width: 62px; margin-left: var(--space-3);
  font-size: var(--font-size-sm); font-weight: var(--font-weight-semibold); }
.ap-log__who { flex: 0 0 auto; font-weight: var(--font-weight-semibold); color: var(--color-fg); }
.ap-log__step { flex: 0 0 auto; font-size: var(--font-size-xs); color: var(--color-muted); }
.ap-log__t { flex: 0 0 auto; margin-left: auto; font-size: var(--font-size-xs);
  color: var(--color-meta); font-variant-numeric: tabular-nums; }
/* ⭐⭐ 2026-10-17 乐哥：「"重新提"是**这个环节的审批意见**，应该和这个审批环节**同一行**」
   ▸ 从"独占整行"改成**跟在动作后面、同行**（`flex: 0 1 auto` 让它按内容占位、可被压缩换行）。
   ▸ 太长时靠父级 `flex-wrap: wrap` 自然折行，**不会把时间挤掉**（时间有 `margin-left: auto`）。
   ⚠️ 不要省略号：这是"为什么驳回/改了什么"，截断等于把关键信息藏掉。 */
.ap-log__reason { flex: 0 1 auto; min-width: 0; font-size: var(--font-size-xs);
  color: var(--color-muted); white-space: normal; word-break: break-word;
  /* 与动作之间留一点气口，避免读成"驳回属于达人成本"一坨 */
  padding-left: var(--space-1); }
/* 动作语义色（圆点 + 文字同一个色，弱化文字只留圆点也行 —— 这里两者同色更好扫） */
.ap-log--submit .ap-log__dot, .ap-log--resubmit .ap-log__dot { background: var(--color-accent); }
/* ⭐⭐ 2026-10-17 乐哥：「**重新提交这个应该是分割线中间是重新提交的字**，这样就很清晰了」
   ▸ 语义：resubmit = 一轮审批被打回后**从头重走四道** ⇒ 它是**两轮之间的分界**，
     不该跟普通记录一样排一行；做成分隔条，一眼看出"下面那一轮是新的"。
   ▸ 做法：整行变成 `─── 重新提交 ───`（左右两条线用 `::before/::after` 画），
     关掉圆点与竖线（`::before` 已被用来画线）。 */
/* ⭐⭐⭐ 2026-10-17 「重新提交」分隔条 = **文字在分割线中间**
   ──────────────────────────────────────────────────────────────────────────
   ⛔ 前两版都失败（记录一下，别再走回头路）：
      ① flex 三段 + 伪元素线（两侧 `flex: 1 1 0`）⇒ **线没撑开**，字被挤到最右
         （实测 `leftGap 926 / rightGap 0`）；
      ② 改成"一条贯穿线 + 文字压在线上（`::before` + `background` 顶开）"
         ⇒ **线整条不显示**（伪元素在这行没画出来）。
   ✅ 第三版：**两条真实的 `<span>` 线 + 中间文字**，靠 flex 分配 ⇒ 稳定、可控、可测。
      · 线：`flex: 1 1 0`（两侧**严格等长**）
      · 字：`flex: 0 0 auto`（文字长短变化，两侧线自动重算 ⇒ 永远居中）
      · 原因：`flex: 1 1 100%` ⇒ 独占下一行、左对齐
   ⚠️ 别把线换回伪元素 —— 上面第 ② 条就是"看着更优雅、实际不渲染"。 */
.ap-log.is-divider { display: flex; align-items: center; flex-wrap: wrap;
  gap: var(--space-3); padding: 12px 0 8px; padding-left: 0; }
.ap-log__divider-line { flex: 1 1 0; height: 1px; background: var(--color-border);
  min-width: 12px; }
.ap-log.is-divider .ap-log__divider-center { flex: 0 0 auto; display: inline-flex;
  align-items: baseline; gap: var(--space-2); max-width: 70%; }
.ap-log.is-divider .ap-log__divider-txt { flex: 0 0 auto; font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold); color: var(--color-accent-text); white-space: nowrap; }
/* 原因跟在"重新提交"后面、同一行居中（乐哥：「应该放在中间重新提交那」）
   ⚠️ `max-width: 70%` 在 center 上：太长时它会自己折行，但仍留在中间区、两侧线照样等长。 */
.ap-log__divider-reason { flex: 0 1 auto; min-width: 0; font-size: var(--font-size-xs);
  color: var(--color-muted); white-space: normal; word-break: break-word; }
.ap-log.is-divider .ap-log__dot { display: none; }
.ap-log__divider-txt { flex: 0 0 auto; font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold); color: var(--color-accent-text); }

.ap-log--approve .ap-log__dot, .ap-log--approve_with_changes .ap-log__dot { background: var(--color-success); }
.ap-log--reject .ap-log__dot { background: var(--color-danger); }
.ap-log--cancel .ap-log__dot, .ap-log--skip .ap-log__dot { background: var(--color-border); }
.ap-log--approve .ap-log__act { color: var(--color-success-text); }
.ap-log--reject .ap-log__act { color: var(--color-danger); }

/* ⭐⭐ 2026-10-10 晚 乐哥：「这个改动记录，看着不直观」⇒ 改**方案 A：三列表格**
   ──────────────────────────────────────────────────────────────────────────────
   ▸ 改前的两条实测依据（不是感觉）：
     ① 「谁改的」用 14px、600 字重、深色，视觉重量**高于**改动内容本身 ⇒ 注意力被抢走；
     ② 旧值 `--color-meta`(#7C8DA3) 在黄底(#FEF5E7)上 **对比度 3.14:1**，低于正文 4.5:1；
        而旧值恰恰是判断"改了什么"的关键 —— 看不清等于没记。
   ▸ 改后：
     · **三列表格**（字段 / 改前 / 改后）—— 改 3 个字段就是 3 行**天然对齐**，扫一眼定位；
     · 元信息（谁 + 来路 + 时间）压成 **12px 次要色**一行，改动内容成为主角；
     · 旧值改用 `--color-muted`(#546579)，在白底上 **5.97:1** ⇒ 读得清、但仍弱于新值。
   ⚠️ 不再用整块黄底：一单改 5 个字段就是一大片黄，反而冲淡了「驳回原因」那块**真该抢眼**的
      地方；改成白底 + 细边框，颜色只留在**来路标签**上（审批人=橙、提报人=蓝）。
   ⚠️ 两种来路同框：`approve_with_changes`（审批人）与 `resubmit`（提报人）都写 changes。 */
.ap-change { border: 1px solid var(--color-border); border-radius: var(--radius-seg);
  padding: var(--space-3); margin-bottom: var(--space-2); background: var(--color-surface); }
.ap-change__top { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 6px; }
.ap-change__who { font-size: var(--font-size-xs); color: var(--color-muted); }
.ap-change__tag { font-size: var(--font-size-xs); padding: 1px 7px; border-radius: var(--radius-sm);
  background: var(--color-warning-bg); color: var(--color-warning-text); }
.ap-change__tag.is-reporter { background: var(--color-info-bg); color: var(--color-info); }
.ap-change__time { font-size: var(--font-size-xs); color: var(--color-meta); }
.ap-change__reason { font-size: var(--font-size-xs); color: var(--color-muted); margin-top: 6px; }
.ap-ctbl { width: 100%; border-collapse: collapse; font-size: var(--font-size-sm); table-layout: fixed; }
.ap-ctbl th { text-align: left; font-weight: 600; font-size: var(--font-size-xs);
  color: var(--color-muted); padding: 4px 8px; background: var(--color-surface-2); }
.ap-ctbl td { padding: 5px 8px; border-top: 1px solid var(--color-border); word-break: break-all; }
.ap-ctbl__k { width: 32%; color: var(--color-muted); }
.ap-ctbl__from { color: var(--color-muted); }
.ap-ctbl__to { font-weight: 600; }

/* 「修改并同意」弹窗里的逐笔字段网格 */
/* ── 「修改并同意」弹窗的字段区（2026-09-23 重做 · 乐哥：「选项选不了、显示的是代码」）
   ▸ 改前：`.ap-field` 是 `inline-flex` + 固定 168px 输入框 ⇒ 7 个纯文本框挤成一片。
   ▸ 改后：**网格 3 列**，控件宽度跟着格子走（下拉/日期组件都能撑满），
     与提报页（`.eb-grid`）的观感一致。
   ⚠️ 之前 `.ap-field > span` 用的是 `--color-text-secondary` ——
      **该变量全项目没有定义**（实测 grep design-tokens 为空）⇒ 颜色静默回落到继承色。
      统一改成真实存在的 `--color-fg-3`（次要文字色）。 */
.ap-modify-item { border: 1px solid var(--color-border); border-radius: var(--radius-seg); padding: var(--space-3);
  margin-bottom: var(--space-2); }
.ap-modify-item__head { font-size: var(--font-size-sm); font-weight: 600;
  margin-bottom: var(--space-3); display: flex; align-items: baseline; gap: var(--space-3); }
.ap-modify-item__tip { font-weight: 400; font-size: var(--font-size-xs); color: var(--color-fg-3); }
.ap-mgrid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-2) var(--space-3); }
.ap-field { display: flex; flex-direction: column; gap: 2px; font-size: var(--font-size-sm);
  min-width: 0; }
.ap-field > span { color: var(--color-fg-3); }
/* ⚠️ 用 `> *` 而不是 `> input`：日期组件的根是 `.dtp`（div），
   原来只写 `> input` 会让日期框宽度不受控。 */
.ap-field > .input, .ap-field > .select, .ap-field > .dtp { width: 100%; }
/* ⚠️ textarea 也要撑满（`.ap-field > .input` 那条选不中 textarea 里的行高，
   而宽度是能命中的 —— 这里显式写出来是为了防止将来有人把 `> .input` 收窄）。 */
.ap-field > textarea { width: 100%; resize: vertical; }
/* 整行（修改原因这种长文本）—— 不属于网格，直接占满弹窗宽度 */
.ap-field--full { width: 100%; }
/* 区间（入住→离店 / 去程→返程）占两格：两个日期 + 中间的箭头要放得下 */
.ap-field--wide { grid-column: span 2; }
.ap-range { display: flex; align-items: center; gap: var(--space-2); }
.ap-range__i { flex: 1 1 0; min-width: 0; }
.ap-range__sep { color: var(--color-fg-3); flex: 0 0 auto; }

/* ── 报销后台 ─────────────────────────────────────────────────────────── */
.xb-scope { margin-bottom: var(--space-2); }
.xb-filters { display: flex; gap: var(--space-2); align-items: center; flex-wrap: wrap; }
.xb-filters .input { width: 176px; }

/* ── 审批流配置 ─────────────────────────────────────────────────────────
   ⭐⭐ 2026-09-23 修（乐哥：「上面的时间轴是横向的，样式不对，没对上」
        +「点击某一个环节之后，下面的文字很密，里面排列的文字布局、间隔都有问题，
          包括输入框等等」）

   ▸ 时间轴「没对上」的**实测根因**（不是猜的）：
     上一版在每两个节点之间插一个 `.ef-node__line`（宽 32px）当连线，
     而节点自己宽 **239px** ⇒ 圆点右边缘到线头之间留了 **105px 空白**
     ⇒ 看上去是"三段悬空的短线"，根本没连到圆点上（几何上 cy 是对齐的 0 偏差，
        但**视觉上没连起来** —— 这正是「没对上」）。
     ⇒ 与达人排期详情（`.sch-tl-line`）**同一套做法**：改成一条**绝对定位的连续线**，
        从首节点圆心贯穿到末节点圆心、压在节点下层。

   ▸ 「文字很密」的**实测根因**（比间距更要命）：
     面板里 32 个元素有 **29 个是同一个颜色**（`rgb(28,30,33)` 主文字色）——
     标题 / 说明 / 小字 / 按钮文字全一个色 ⇒ 视觉上就是一坨黑字。
     根因：本片 12 处引用了 `--color-text-secondary` / `--color-text-primary` /
     `--color-text-tertiary` / `--color-bg-subtle`，而这**四个变量全项目都没有定义**
     （实测 grep design-tokens / base / components 均为 0）⇒ 颜色静默失效、回落成继承色。
     ⇒ 统一换成真实存在的变量（与 `.field-hint` 的项目标准一致：**12px + --color-muted**）。
   ⚠️ 色阶按项目语义表选（别自造）：
        --color-fg       主文字      --color-muted   次级 / 副标题
        --color-fg-3     三级 / 元信息（= --color-meta）
        --color-surface-2 浅底（全站 hover / 表头底）
   ══════════════════════════════════════════════════════════════════════ */
.ef-flag { border-radius: var(--radius-seg); padding: 10px 14px; margin-bottom: var(--space-3);
  font-size: var(--font-size-sm); color: var(--color-fg); }
.ef-flag--on { background: var(--color-success-bg); }
.ef-flag--off { background: var(--color-warning-bg); }

/* ── 4 道审批的横向时间轴 ⭐⭐ 2026-09-23 第二版（乐哥两轮反馈后定稿）────────────────
   乐哥第一轮：「时间轴是横向的，样式不对，没对上」   ⇒ 连线改连续轨道线（已修）
   乐哥第二轮：「主管审批环节的颜色怎么是灰色的？」 + 「人的展示方式得有头像、得有名字」
     +「选择人的方式应该按照我们之前定的那种去做」 + 「文字展示方式还是不直观」

   ▸ 颜色语义（三档，与全站状态色**同一套语言**）：
       · **绿** `--color-success` = 能审（人工道已配人 / 主管道能解析出主管）
       · **红** `--color-danger`  = 没人审（人工道一个人都没配 / 主管道解析不出任何人）
       · ⛔ **不再用灰色** —— 本项目里灰 = 未开始/无数据/环境问题，用在"系统自动生效"上
         会被读成"这一道被禁用了"（乐哥就是这么读的）。
   ▸ 轨道线：一条从首节点圆心贯穿到末节点圆心的连续线（`.ef-flow::before`），
     压在节点下层；⛔ 不要再输出节点间连线元素（会和伪元素叠成双线）。 */
.ef-flow {
  position: relative;
  display: flex; align-items: flex-start;
  /* ⚠️ 左右 padding 必须为 0：轨道线用百分比（12.5%/87.5%）对齐节点圆心，
     而绝对定位元素的百分比是相对**包含块的 padding box** ⇒ 有左右 padding 就会算偏。 */
  padding: 20px 0 16px;
  margin-bottom: var(--space-4);
  background: var(--color-surface-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
}
.ef-flow::before {
  content: '';
  position: absolute;
  left: 12.5%; right: 12.5%;
  /* top = 容器 padding-top(20) + 圆点半径(14) - 线高一半(1) = 33
     （圆点紧贴节点顶部，见 .ef-node 的 padding-top: 0） */
  top: 33px;
  height: 2px;
  background: var(--color-gray-300);
  z-index: 0;
}
.ef-node {
  position: relative; z-index: 1;     /* 压在轨道线之上 */
  flex: 1 1 0; min-width: 0;
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  padding: 0 8px 10px;                /* ⚠️ padding-top 必须是 0（轨道线 top 按此推算） */
  background: transparent; border: 0;
  border-radius: var(--radius-md); cursor: pointer;
  font: inherit; text-align: center;
}
.ef-node__dot {
  width: 28px; height: 28px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: var(--font-size-xs); font-weight: var(--font-weight-semibold);
  color: #fff; background: var(--color-muted);
  /* 描边配色与容器底同色 ⇒ 线从圆点"背后"穿过而不是贴上去（参照物同手法） */
  border: 2px solid var(--color-surface-2);
  box-sizing: border-box;
}
.ef-node--done .ef-node__dot { background: var(--color-success); }
.ef-node--warn .ef-node__dot { background: var(--color-danger); }
.ef-node__name {
  margin-top: var(--space-2);
  font-size: var(--font-size-sm); font-weight: var(--font-weight-medium); color: var(--color-fg);
}
.ef-node__who { font-size: var(--font-size-xs); color: var(--color-muted); }
.ef-node--warn .ef-node__who { color: var(--color-danger); font-weight: var(--font-weight-medium); }
/* ⭐ 「谁在审」= 头像 + 姓名（乐哥：「人的展示方式得有头像，得有名字」）。
   ⚠️ 头像用 `UI.avatar`（`.cell-person__avatar`，无图走姓名首字）⇒ 这里把它的尺寸
      缩到 18px：默认尺寸是给表格行用的（28px+），放在时间轴节点里会把节点撑高。
   ⚠️ 姓名超长要 ellipsis（节点只有 ~240px 宽）⇒ JS 侧已同时给了 title，悬停可看全。 */
.ef-node__who--names { display: inline-flex; align-items: center; gap: 4px; max-width: 100%; }
.ef-node__who--names .cell-person__avatar {
  width: 18px; height: 18px; font-size: 9px; flex: 0 0 auto;
}
.ef-node__names {
  font-size: var(--font-size-xs); color: var(--color-muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ef-node:hover .ef-node__dot { box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-muted) 18%, transparent); }
.ef-node.is-active .ef-node__dot {
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--color-accent) 20%, transparent);
}
.ef-node.is-active .ef-node__name { color: var(--color-accent); font-weight: var(--font-weight-semibold); }
.ef-node:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; }

/* 面板：4 个**都渲染在 DOM 里**，只显示当前那道 ——
   见 expense-approval-flow.js 里"设计取舍①"的说明（只渲染当前道会让保存静默清空其他道）。 */
.ef-panel { display: none; }
.ef-panel.is-active { display: block; }

/* ── 单道面板 ────────────────────────────────────────────────────────────
   ⭐ 2026-09-23 第二版：分组从"左侧竖线块"改成**分隔线式**（与项目「角色权限」页的
      `.perm-group` 同一套语言）；面板标题从 14px 升到 16px，与分组标题(14px)拉开一级
      —— 原来两者同字号，加上四段同规格的小灰字 ⇒ 整体读起来"很密"（乐哥原话）。 */
.ef-step {
  border: 1px solid var(--color-border); border-radius: 8px;
  padding: var(--space-4) var(--space-4) 0;
}
.ef-step__head { display: flex; align-items: center; gap: var(--space-2); margin-bottom: var(--space-2); }
.ef-step__head b { font-size: var(--font-size-lg); font-weight: var(--font-weight-semibold); color: var(--color-fg); }
/* 规则说明：项目标准的辅助文字规格（12px + --color-muted），行高放宽到 1.65。
   ⚠️ 每道**最多一行**（文案已在 JS 里精简）—— 这是"不密"的关键。 */
.ef-step__note {
  font-size: var(--font-size-xs); color: var(--color-muted); line-height: 1.65;
  margin-bottom: var(--space-2);
}
.ef-step__note b, .ef-group__h b, .ef-notice b, .ef-pick-hint b {
  font-weight: var(--font-weight-semibold); color: var(--color-fg);
}
/* 分组：标题 + 一行说明 + 内容，用分隔线分段（最后一个不画线） */
.ef-group { padding: var(--space-3) 0; border-bottom: 1px solid var(--color-border-soft); }
.ef-group:last-child { border-bottom: 0; }
.ef-group__t {
  display: flex; align-items: baseline; gap: var(--space-2); flex-wrap: wrap;
  font-size: var(--font-size-sm); font-weight: var(--font-weight-semibold); color: var(--color-fg);
  margin-bottom: var(--space-1);
}
.ef-group__n { font-weight: var(--font-weight-regular); font-size: var(--font-size-xs); color: var(--color-muted); }
/* 「改完立即生效」—— 必须显眼：否则管理员会一直等底部那个「保存配置」按钮 */
.ef-group__n--live { color: var(--color-accent); font-weight: var(--font-weight-medium); }
.ef-group__h { font-size: var(--font-size-xs); color: var(--color-muted); line-height: 1.65; margin-bottom: var(--space-2); }
/* 一行式提示（替代原来散落的 5 个琥珀块）：普通=中性说明，--warn=会影响提单/同步 */
.ef-notice { font-size: var(--font-size-xs); color: var(--color-muted); line-height: 1.65; margin-bottom: var(--space-2); }
.ef-notice--warn {
  background: var(--color-warning-bg); color: var(--color-warning-text);
  border-radius: var(--radius-seg); padding: 6px 10px;
}
/* 多行提示块（飞书权限诊断 / 孤儿部门）——
   ⚠️ 必须 `display:block` + 行高放宽：这类文案是**完整操作指引**（含权限码、去哪个页面），
      挤成一行会读不完。与上面单行式 `.ef-notice` 的区别就在这里。 */
.ef-notice--block { display: block; line-height: 1.75; padding: 8px 12px; }
.ef-mode { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; }
.ef-radio { display: inline-flex; align-items: center; gap: 5px; font-size: var(--font-size-sm); cursor: pointer; }
.ef-actions { display: flex; justify-content: flex-end; margin-top: var(--space-3); }

/* ⭐ 选人框下方那行状态（FeishuPicker 的配套说明）：
   普通 = 「已选 N 人 · 都能审批这一道」；--warn = 有人没权限（保存时会自动开通）/ 一个人都没选。
   这是"允许从整个通讯录选人"的必要说明 —— 不说清，用户管理里凭空多出账号会让人以为出 bug。 */
.ef-pick-hint { font-size: var(--font-size-xs); color: var(--color-muted); line-height: 1.65; margin-top: var(--space-2); }
.ef-pick-hint--warn { color: var(--color-warning-text); }

/* ── 第 1 道「逐人单独指定」的一行：`[头像 员工/部门] → [头像 审批人] [换人][移除]`
   ⭐ 加了头像（乐哥：「人的展示方式得有头像，得有名字」）——
      原来是一行纯文字「张乐（总经办 · ou_6968…3c9）的审批人 [下拉]」，读起来很费劲。
   ⚠️ 行内**不放 picker**（picker 每次 render 都挂 document 监听且无 destroy，
      N 行会累积 N 个）⇒ 行内只展示，换人走弹窗。 */
.ef-sup2 {
  display: grid;
  /* ⚠️ 四列（2026-09-23 改）：员工 | 箭头 | 审批人 | 操作。
     原来只有三列 ⇒ 操作按钮只能塞在"审批人"那格里 ⇒ 它们**紧贴在名字后面**，
     右边空掉大半个容器宽度，几行看下来像"没排完版"（乐哥：「这部分的按钮，你看下放哪」）。
     现在给操作单独一列 ⇒ 它被推到行尾，四行的按钮**左边缘对齐成一条竖线**。 */
  grid-template-columns: minmax(0, 260px) 22px minmax(0, 1fr) auto;
  align-items: center; gap: var(--space-2);
  /* 行悬停浅底要盖住整行 ⇒ 用负外边距把内边距"撑出去"，避免左移头像对齐线 */
  margin: 0 calc(-1 * var(--space-2));
  padding: var(--space-2);
  border-radius: var(--radius-md);
}
/* 行间细分隔线：几行叠在一起时不至于糊成一块（最后一个不画） */
.ef-sup2 + .ef-sup2 { border-top: 1px solid var(--color-border-soft); }
/* 悬停：整行浅底 —— 让人一眼看出"这行 + 这行的按钮"是一组 */
.ef-sup2:hover { background: var(--color-surface-2); }
.ef-sup2__who { display: inline-flex; align-items: center; gap: var(--space-2); min-width: 0; }
.ef-sup2__who-t {
  display: flex; flex-direction: column; min-width: 0;
  font-size: var(--font-size-sm); color: var(--color-fg);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* 主标题（姓名）与副标题（部门 · 用户名）两级 —— 副标题**不再重复姓名**（见 JS 的 personSub）。
   ⚠️ 主标题要给 line-height，否则两行文字贴在一起（中文字体上下留白小）。 */
.ef-sup2__who-n { line-height: 1.5; }
.ef-sup2__who-s { font-size: var(--font-size-xs); color: var(--color-muted); line-height: 1.5; }
.ef-sup2__arrow { color: var(--color-fg-3); display: inline-flex; justify-content: center; }
.ef-sup2__sup { display: inline-flex; align-items: center; gap: var(--space-2); min-width: 0; }
.ef-sup2__sup-t { display: inline-flex; align-items: center; gap: var(--space-2); min-width: 0; }
.ef-chip-name { font-size: var(--font-size-sm); }
/* 行尾操作组：靠右（即"操作"那一列的左沿），两按钮等距 */
.ef-sup2__ops { display: inline-flex; align-items: center; gap: var(--space-1); margin-left: auto; }
/* ── 行内操作按钮：**无边框文字按钮 + 按语义上色**
   ⚠️ 不用 `.btn--ghost`：它带灰边框，和这一屏里真正的输入框（选人组件的搜索框、
      两个录入按钮）长得一样 ⇒ 分不清"哪个能点、点了做什么"。沿用本项目
      `[data-role]`（accent）/ `[data-del]`（danger）那套既有语言。
   ⚠️ 「移除」平时**只红字不铺红底** —— 一行一个红色实心块会让整屏很扎眼；
      红色浅底留到 hover 才出现（破坏性操作"要动手时才提醒"即可）。 */
.ef-act {
  background: transparent; border-color: transparent; color: var(--color-accent-text);
  padding: 0 var(--space-2); gap: 4px;
}
.ef-act:hover:not(:disabled) { background: var(--color-primary-100); border-color: transparent; }
.ef-act--danger { color: var(--color-danger); }
.ef-act--danger:hover:not(:disabled) { background: var(--color-danger-bg); border-color: transparent; }
/* 窄屏（≤900px）：四列挤不下 ⇒ 拆成两行。
   第 1 行：员工  + 箭头            （占满宽度 ⇒ 姓名不会被挤到截断）
   第 2 行：审批人（跨前两列） + 操作按钮（第 3 列，右侧正好空着）
   ⚠️⚠️ 两个坑都实测过：
     ① 操作按钮**不能**跨两行（`grid-row: 1 / 3`）—— 它会在整个行高上垂直居中，
        与"员工""审批人"两行**都不对齐**，看着像浮在半空（390px 截图很明显）；
     ② 操作按钮也**不能留在第 1 行**（`grid-row: 1`）—— 它的宽度（约 132px）
        会挤压第 1 列，实测 390px 下「李功司」的可用宽度只剩 33px（需要 39px）
        ⇒ **姓名被截断**。挪到第 2 行右侧后第 1 行独占宽度，实测不再截断。 */
@media (max-width: 900px) {
  .ef-sup2 { grid-template-columns: minmax(0, 1fr) 22px auto; row-gap: var(--space-1); }
  .ef-sup2__sup { grid-column: 1 / 3; grid-row: 2; padding-left: calc(28px + var(--space-2)); }
  .ef-sup2__ops { grid-column: 3; grid-row: 2; gap: 0; }
  /* ⚠️ 极窄屏（手机）下**收起按钮图标**：实测 390px 视口里整行只剩 251px，
      而两个带图标的按钮要 128px ⇒ 留给姓名的只有 33px（「李功司」需 39px）⇒ **姓名被截断**。
      收起图标（每个省约 18px）并把内边距收到 6px，按钮组降到约 80px，姓名即完整显示。
      ⚠️ 只收图标、**不收文字** —— "换人/移除"这两个词是操作语义本身，不能省。 */
  .ef-act svg { display: none; }
  .ef-act { padding: 0 6px; }
}

/* 「我的报销」单据行的审批环节小字 —— 与状态徽章并排，说明卡在哪一道 */
.me-step { margin-left: 6px; font-size: var(--font-size-xs); color: var(--color-meta); }

/* ── 部门主管（可多个）⭐ 2026-09-23 重做 ──────────────────────────────────
   一行 = 一个部门：`[部门名 · N 人]  [主管 chips / 飞书兜底 / 未指定]  [指定主管]`
   ⚠️ 三列用 grid 且**操作列靠右**（与上面「逐人单独指定」同一套语言）——
      按钮紧贴内容时右边会空掉一大片，几行下来像没排完版（那个问题上一轮刚修过）。 */
.ef-dept { border-top: 1px solid var(--color-border-soft); }
.ef-dept__row {
  display: grid;
  /* ⚠️ 第一列 172px 是**量出来的**（2026-09-23 截图后发现换行才定的）：
     最长部门名「直播旗舰事业部」（7 个中文字 ≈ 98px）+ 「飞书」标签（≈34px）
     + 间距 6px + 两侧 padding 16px ≈ 154px。取 172px 留一点余量。
     原来 150px 不够 ⇒ 部门名被压成两行（"BEATEAM事/业部"），
     连带把「飞书」标签挤到第二行、整行高矮不一 —— 就是"看着没对齐"的根因。
     ⚠️ 第一列**不能**用 `auto`：各行部门名长度不同，auto 会让每行的第二列起点都不同，
        那比现在更乱（列与列必须对齐）。 */
  grid-template-columns: minmax(0, 172px) minmax(0, 1fr) auto;
  align-items: center; gap: var(--space-3);
  padding: var(--space-2) var(--space-2);
  margin: 0 calc(-1 * var(--space-2));
  border-bottom: 1px solid var(--color-border-soft);
  border-radius: var(--radius-md);
}
.ef-dept__row:hover { background: var(--color-surface-2); }
.ef-dept__name {
  display: flex; align-items: center; gap: 6px; min-width: 0;
  font-size: var(--font-size-sm); color: var(--color-fg); font-weight: var(--font-weight-medium);
}
/* ⛔ 部门名**绝不换行**（超长省略、`title` 里有全名）。
   换行会让每一行高度不一致 —— 几行下来整个列表看着就是歪的。 */
.ef-dept__name-t { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ef-dept__name .ef-dept__tag { flex: none; }
/* 「N 人」——放在第二列最前（紧邻主管信息）。
   `flex:none` + `nowrap`：绝不允许把"10 人"拆成"10"/"人"两行。 */
.ef-dept__n {
  flex: none; white-space: nowrap;
  font-size: var(--font-size-xs); color: var(--color-muted);
  font-weight: var(--font-weight-regular);
}
/* 「飞书」来源标签（灰）—— 与手工指定的蓝 chips 区分开，一眼看出这个部门是怎么来的 */
.ef-dept__tag {
  font-size: 11px; line-height: 1; padding: 2px 5px; border-radius: var(--radius-sm);
  background: var(--color-surface-2); color: var(--color-muted); font-weight: var(--font-weight-regular);
}
.ef-dept__who { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; min-width: 0; }
/* 主管 chip：头像 + 姓名 + 移除 ×（蓝底 = 手工指定的生效主管） */
.ef-dept__chip {
  display: inline-flex; align-items: center; gap: 5px; max-width: 220px;
  padding: 2px 4px 2px 3px; border-radius: var(--radius-pill);
  background: var(--color-primary-50); border: 1px solid var(--color-primary-100);
  /* ⭐ 2026-09-23 乐哥两轮要求调小后定稿：`--font-size-sm`(14px) → **12px**。
     ⚠️ 与下面的头像尺寸同属"部门行里的人和名字都收一号"这一次改动。 */
  font-size: var(--font-size-xs); color: var(--color-accent-text);
}
.ef-dept__chip-n { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* ⚠️ 停用的人：整块降透明度 + 姓名加删除线来源不明，用灰底区分（后端给了 active 标记） */
.ef-dept__chip.is-off {
  background: var(--color-surface-2); border-color: var(--color-border);
  color: var(--color-muted); opacity: .75;
}
/* 「已停用」小字 —— ⚠️ 必须与灰底同时出现：只有灰底时用户看不出原因，
   会以为"配了却没生效"是 bug（实际是那个人停用了 ⇒ 这一档回落到飞书负责人）。 */
.ef-dept__off {
  font-size: 11px; line-height: 1; padding: 1px 4px; border-radius: 3px;
  background: var(--color-danger-bg); color: var(--color-danger);
}
.ef-dept__x {
  display: inline-flex; align-items: center; justify-content: center;
  width: 16px; height: 16px; border-radius: 50%; border: 0; background: transparent;
  color: inherit; opacity: .55; cursor: pointer; padding: 0;
}
.ef-dept__x:hover { opacity: 1; background: var(--color-danger-bg); color: var(--color-danger); }
/* 飞书负责人：**灰底虚线**表达"它是兜底值、不是你手工指定的"（视觉上与蓝 chip 拉开） */
.ef-dept__chip--feishu {
  background: var(--color-surface-2); border: 1px dashed var(--color-gray-400); color: var(--color-fg-2);
}
.ef-dept__src { font-size: var(--font-size-xs); color: var(--color-muted); }
.ef-dept__multi { font-size: var(--font-size-xs); color: var(--color-muted); }
/* 「0 人 · 待分配员工」—— **手工建的**部门还没人：这是待办，用琥珀色提醒。 */
.ef-dept__zero {
  font-size: var(--font-size-xs); color: var(--color-warning-text);
  background: var(--color-warning-bg); border-radius: var(--radius-sm); padding: 1px 6px;
}
/* 「暂无本系统员工」—— **飞书来的**空部门：公司有、我们没跟踪到人，属正常现象。
   ⚠️ 必须与上面那个黄色待办**在视觉上分开**：实测 35 个部门里 28 个是这种，
      全打黄标 = 满屏黄色 = 该看的那个被淹掉（"总在报警"等于没报警）。 */
.ef-dept__zero--quiet {
  color: var(--color-muted); background: transparent; padding-left: 0;
}
/* 「未指定」= 要处理的（这个部门的人提不了单）⇒ 用 danger 色，不能是中性灰 */
.ef-dept__none { font-size: var(--font-size-xs); color: var(--color-danger); }
.ef-dept__none b { font-weight: var(--font-weight-semibold); }
.ef-dept__ops { display: inline-flex; align-items: center; gap: var(--space-1); }
.ef-dept__foot {
  display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap;
  padding-top: var(--space-2);
}
/* 窄屏：部门名与操作各占一行（三列挤不下会把主管姓名压成两三个字） */
@media (max-width: 900px) {
  .ef-dept__row { grid-template-columns: minmax(0, 1fr) auto; row-gap: var(--space-1); }
  .ef-dept__who { grid-column: 1 / -1; grid-row: 2; }
  .ef-dept__ops { grid-column: 2; grid-row: 1; }
}

/* ── 指定 / 换人弹窗（内部是两个 FeishuPicker 单选）────────────────────── */
.ef-add { padding-bottom: var(--space-2); }
.ef-add__r { display: flex; align-items: center; gap: var(--space-3); margin-bottom: var(--space-3); }
.ef-add__r > span { font-size: var(--font-size-sm); color: var(--color-muted); min-width: 84px; flex: 0 0 auto; }
/* ⚠️ 右侧容器要能撑满（FeishuPicker 根元素是 `.fp`，宽度默认按内容） */
.ef-add__r > div { flex: 1; min-width: 0; }

/* 「我的报销」列表的「报销人」列（2026-09-22 加）—— 窄一点，别抢"费用"列的宽度 */
.me-applicant { white-space: nowrap; }

/* 审批详情：明细表「凭证」列（2026-09-22）
   —— 眼睛图标 + 张数角标；点开看该笔的发票与付款凭证（替代原来独立的「发票附件」区块）。 */
.ap-vt { margin-top: 2px; }
.ap-vch { display: inline-flex; align-items: center; gap: 4px; }
.ap-vch__n {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 16px; height: 16px; padding: 0 4px;
  border-radius: var(--radius-pill); background: var(--color-surface-2);
  font-size: 11px; line-height: 1; color: var(--color-fg-2);
}
.ap-vch-none { color: var(--color-meta); }
.ap-legend { font-size: var(--font-size-xs); color: var(--color-muted); margin-bottom: var(--space-2); }
/* 凭证弹窗里「发票 / 付款凭证」两组 */
.ap-vch-grp { margin-top: var(--space-3); }
.ap-vch-cnt { margin-left: 6px; font-size: var(--font-size-xs); color: var(--color-muted); font-weight: 400; }

/* ⭐⭐ 发票验票区（2026-09-26 立；2026-10-10 改成**逐号**校验后的"整笔结论 + 一键"块）
   改版后这里**只放结论**（逐张操作移到了上面的 `.ap-invtbl` 表里）。三件事：
     ① 结论一句话说清（全通过 / N 张未通过 + 原因 / 已验 M / N）；
     ② 「全部校验通过」走主按钮（实心蓝）并**右对齐** —— 它是一次性把这一笔标完的快捷动作；
     ③ 整块底色跟着结论走（通过=浅绿 / 有未通过=浅红），一眼扫过去知道这一笔的成色。 */
.ap-vfy { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap;
  margin-top: var(--space-2); border: 1px solid var(--color-border-soft); border-radius: var(--radius-md); padding: 10px 12px; }
.ap-vfy--passed { border-color: var(--color-success-bg); background: var(--color-success-bg); }
.ap-vfy--failed { border-color: var(--color-danger-border); background: var(--color-danger-bg); }
.ap-vfy__ops { display: flex; gap: 6px; flex-shrink: 0; margin-left: auto; }
/* ⚠️ 高度用**令牌**（两档制纪律：控件高度一律 var(--control-height-sm)，
   不许硬编码 34px —— 见 design-tokens 说明与 tests/control-size-two-tier.test.js） */
.ap-vfy__ops .btn { height: var(--control-height-sm); padding: 0 12px; font-size: var(--font-size-sm); }
.ap-vfy__state { flex: 1 1 auto; min-width: 0; font-size: var(--font-size-xs); color: var(--color-muted); }
.ap-vfy__state.is-ok { color: var(--color-success); }
.ap-vfy__state.is-bad { color: var(--color-danger-strong); }
/* 「本单只能驳回」那句（2026-10-10 晚加）—— 它是**结论**不是注释：
   有未通过的票 ⇒ 财务点「通过」会被服务端拦住，不如在这里就说清后果。
   ⚠️ 与原因行同色但**更重**（`font-weight`），因为它是行动指令。 */
.ap-vfy__only-reject { font-weight: var(--font-weight-medium); }
/* 财务道「未验完不能通过」的提示（跟在 footer 按钮上方） */
.ap-approve-hint { font-size: var(--font-size-xs); color: var(--color-danger-strong); margin-bottom: 6px; }

/* 审批详情明细区（2026-09-22 五项优化）
   —— 列序按"审批判断顺序"：# 金额 费用类型+验真 凭证 日期 备注
   —— 限高内部滚动：底部三个按钮（驳回/修改并同意/通过）始终可见 */
.ap-items { max-height: 320px; overflow-y: auto; }
.ap-items thead th { position: sticky; top: 0; z-index: 1; background: var(--color-surface); }
.ap-seq { width: 34px; color: var(--color-meta); }
.ap-amt { font-weight: var(--font-weight-medium); white-space: nowrap; }
/* 验真标签提亮：它决定批不批，不该比备注还弱 */
.ap-vt .tag { font-size: var(--font-size-xs); font-weight: var(--font-weight-medium); }
/* 凭证按钮统一形态：无凭证时置灰但形状一致 */
.ap-vch--none { opacity: .55; }

/* ── 「归属排期」醒目条（2026-10-14 乐哥：「报销审批里面，如果选了达人直播溯源的，
   好像没有归属排期信息，这个得做，而且要显眼」）────────────────────────────────
   放在「费用明细」标题与明细表之间：审批人扫一眼就知道这笔钱算到哪场直播头上。
   ⚠️ 为什么要**醒目条**而不是表格里多一列：排期是**整单共用**的（提单时选的），
      加列会让 5 行显示 5 遍同样的内容、还把表格 8 列挤成 9 列。
   ⚠️ 配色只给两种：绿 = 有排期（正常）、红 = 该有却没有（**要拦下来**）。
      红色那档不是"提示"，是"这笔钱会落进项目成本、不进任何达人场次"的告警。 */
.ap-sched { display: flex; gap: 10px; align-items: flex-start; padding: 10px 12px;
  margin-bottom: 10px; border-radius: var(--radius-lg); background: var(--color-success-bg);
  border: 1px solid var(--color-success); }
.ap-sched--warn { background: var(--color-danger-bg); border-color: var(--color-danger-border); }
.ap-sched__ico { font-size: 15px; line-height: 1.4; flex: none; }
.ap-sched__body { flex: 1; min-width: 0; }
.ap-sched__k { font-size: var(--font-size-sm); font-weight: var(--font-weight-medium);
  color: var(--color-success-text); }
.ap-sched--warn .ap-sched__k { color: var(--color-danger-strong); }
.ap-sched__chip { display: inline-flex; align-items: baseline; gap: 6px; margin: 5px 6px 0 0;
  padding: 2px 9px; border-radius: var(--radius-pill); background: var(--color-surface);
  border: 1px solid var(--color-success); font-size: var(--font-size-sm); color: var(--color-fg); }
.ap-sched__chip b { font-family: var(--font-family-mono); color: var(--color-success-text); }
.ap-sched__chip i { font-style: normal; color: var(--color-muted); font-size: var(--font-size-xs); }
.ap-sched__hint { font-size: var(--font-size-sm); color: var(--color-danger-strong); margin-top: 5px; }

/* 审批「列表」行内的排期小标（不新增列：放在单号格内，与 `N 笔` 小标同一位置语义）
   ⚠️ 该表是 `table-layout:fixed` + colgroup 百分比 ⇒ 小标必须 `nowrap` + 省略，
      否则长达人名会把单号列撑到顶破格子（总览页同样处理，见 `.fin-code`）。
   ⚠️ 颜色一律走**令牌**（`--color-success-*` / `--color-danger-*`）：
      本项目有 CSS 规范门禁（`tests/ui-token-standards.test.js` 禁用裸 hex，
      `tests/radius-tokens.test.js` 禁写 `999px`），裸值会直接判红。 */
.ap-sched-badge { display: inline-flex; align-items: center; gap: 4px; margin-top: 4px; max-width: 100%;
  padding: 1px 7px; border-radius: var(--radius-pill); background: var(--color-success-bg);
  border: 1px solid var(--color-success); color: var(--color-success-text);
  font-size: var(--font-size-xs); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ap-sched-badge b { font-family: var(--font-family-mono); }
.ap-sched-badge--warn { background: var(--color-danger-bg);
  border-color: var(--color-danger-border); color: var(--color-danger-strong); }
.ap-sched-badge__more { color: var(--color-muted); }

/* 审批列表「报销人」格（2026-10-14 二改）：名字过长时**行内省略**，不折成两行。
   背景：加「费用大类」列后列宽变窄，真机实测 5 字名（`验收管理员`）被挤成两行 ——
   折行会把那一行的行高撑高，整张表看着参差不齐（与「部门」列 `.ap-ellip` 同一类问题）。
   ⚠️ 只作用在审批台那一格（`.ap-who`），⛔ 不动公共的 `.cell-person`
      —— 总览页 / 人员页也在用它，全局改成省略会改变那些页的长相。
   ⚠️ flex 链上**每一层都要 `min-width:0`**：少一层，ellipsis 就不生效
      （本项目在 flex 子项上踩过同一个坑）。 */
.ap-who .cell-person,
.ap-who .cell-person__body,
.ap-who .cell-person__line { min-width: 0; }
.ap-who .cell-person__line { flex-wrap: nowrap; }
.ap-who .cell-main { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* 审批列表里**长文本格**的通用省略处理（2026-10-14）：内容超出列宽时**截断**而不是折行 ——
   折行会把那一行的行高撑高、整张表看着参差不齐（真机实测 8 列后确实挤：
   13 字的部门名 `直播旗舰事业部（华南大区）` 需要 169px，列里只有 112px ⇒ 会折成两行）。
   用在：费用类型（`.ap-ellip` + 完整清单在 title）、部门名（同一处理）。
   ⚠️ 套一层 `inline-block` 而不是直接给 `td` 加 `overflow:hidden`：
      `table-layout:fixed` 下对 `td` 直接加省略号在某些浏览器里不生效（本项目踩过同类）。
   ⚠️ 完整值一律放 `title`（悬停可见），⛔ 不是"藏起来不给了"。 */
.ap-ellip { display: inline-block; max-width: 100%; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; vertical-align: bottom; }

/* ── 用户管理 · 按部门分组（2026-09-23，乐哥：「用户管理里，用户得分组」）──────
   组头是一行跨列的可点击行，点它折叠/展开该组成员。
   ⚠️ 组头**不复用** `thead th` 的样式：那些是全站表头（`white-space:nowrap`、
      居中等），而组头是"可点击的分区标题"，语义不同 —— 混用会让组头在窄屏把表格撑宽。
   ⚠️ 折叠用 `row.hidden`（JS 侧），这里只负责 caret 的旋转，不写 `display:none` ——
      两边都写会打架（JS 设 hidden=false 时 CSS 的 display:none 仍在）。 */
.u-group > td {
  /* ⭐ 2026-09-23 强化（乐哥：「用户管理里面，用户分组展示方式不行」）：
     原来只是 `--color-surface-2` 的浅灰底，与数据行**几乎同色** ⇒
     扫一眼看不出"这里开始换部门了"，分组等于没做。
     现在用更实的灰底 + 上下描边，让它明确是"一个分区标题"。 */
  background: var(--color-gray-50);
  padding: var(--space-2) var(--space-3);
  cursor: pointer;
  user-select: none;
  border-top: 1px solid var(--color-border);
  border-bottom: 1px solid var(--color-border-soft);
}

/* ⚠️⚠️ **分组头必须显式左对齐**，而且特异性要赢过 `table.css` 的"全表默认居中"——
   写 `.u-group > td { text-align: left }` 是**不够的**（这条我已经踩过一次）。

   ⛔ 乐哥 2026-09-23 报障原话：「用户管理里面，这个不对」（附截图，红框圈在
      「财务 1 人 / 商务 6 人 / 行政 5 人」三处）。实测数据：
        · 分组头 td: `colspan=7`、`text-align: center`、classList 为空
        · 分组头文字左边缘 = **距表格左 601px**
        · 「角色」列区间 = 距表格左 471~635px ⇒ **601 正好落在角色列里**
        ⇒ 观感就是"分组头跑到角色列下面去了"，不像分区标题。

   ⛔ 根因（两条叠加，缺一条都不会出这个现象）：
     ① `table.css` 有一条**全表默认居中**：
          `.table:not(:where(td .table)) > tbody > tr > td { text-align: center; }`  ← 特异性 (0,1,3)
        而我上一轮写的 `.u-group > td` 只有 **(0,1,1)** ⇒ **静默压不过**
        （同一类坑 `table.css` 里 `.is-num` 那段注释也记过：只写 `.table .is-num` 压不过它）。
     ② `app.js` 的 `fixColAlign()` **救不了它**：该机制按「表头文本」判定哪些列是长文本列，
        再给**同列**的 td 打 `.col-align-left`；而分组头是 `colspan=7` 的**单格**，
        实测拿不到这个类（classList 为空）⇒ 只能继承默认的居中。

   ⇒ 所以判据必须写成 `.table:not(:where(td .table)) > tbody > tr.u-group > td`（(0,2,3)，稳赢），
     并且**要放在 `.u-group > td` 之后**（同为分区标题的两条规则，离得近才好一起看）。 */
.table:not(:where(td .table)) > tbody > tr.u-group > td {
  text-align: left;
}

.u-group:hover > td { background: var(--color-surface-2); }
.u-group__i {
  display: inline-flex; align-items: center; vertical-align: middle;
  margin-right: var(--space-2); color: var(--color-muted);
  transition: transform var(--motion-fast) var(--motion-ease-standard);
}
.u-group.is-folded .u-group__i { transform: rotate(-90deg); }
.u-group__t {
  font-weight: var(--font-weight-semibold); color: var(--color-fg);
  font-size: var(--font-size-sm);
}
/* 人数做成**徽章**（原来只是一串小灰字，和部门名糊在一起、扫不出来）。
   ⚠️ 颜色一律走 design-tokens —— 写死色值在暗色主题下会变成看不清的浅底灰字。 */
.u-group__n {
  display: inline-flex; align-items: center; vertical-align: middle;
  margin-left: var(--space-2); padding: 1px 8px;
  border-radius: var(--radius-full);
  background: var(--color-surface); border: 1px solid var(--color-border);
  font-size: var(--font-size-xs); color: var(--color-muted);
}
/* ── 「未填部门」整组转橙色警示 ──────────────────────────────────────────────
   它不是普通部门，是**待处理项**：未配部门 ⇒ 财务模块按部门统计时归不到人，
   报销单的部门列也是空的（详见 userRowHtml 里「未配」的说明）。
   所以它必须在"全部"视图里就有颜色，而不是等人自己点进去发现。 */
.u-group--warn > td { background: var(--color-warning-bg-soft); }
.u-group--warn:hover > td { background: var(--color-warning-bg); }
.u-group--warn .u-group__t { color: var(--color-warning-text); }
.u-group--warn .u-group__n {
  background: var(--color-warning-bg); border-color: var(--color-warning);
  color: var(--color-warning-text); font-weight: var(--font-weight-medium);
}
.u-group__warn {
  margin-left: var(--space-3); vertical-align: middle;
  font-size: var(--font-size-xs); color: var(--color-warning-text);
}

/* ── 用户管理 · 部门 Tab（2026-09-23 立；2026-10-09 去数字）────────────────────
   ⭐⭐ 2026-10-09 **删掉「贴文字右侧的人数小字」**（原 `.u-dept-tab__n` /
   `.u-dept-tab__n--warn` 三条规则）—— 乐哥截图：「这个页面的 tab 没按照标准做」。
   ▸ 标准 = **tab 只表示"有哪些分类"，不表示"各有多少"**（人数由表格上方
     「共 N 条」承担）：达人管理 `#inf-status` / 排期列表 `#sch-status` 的 tab
     都是纯文字；依据是 2026-10-08 19:38 乐哥「去掉 tab 旁边的数字显示吧，感觉多余」
     （完整三步演进见 `influencer-list.js` 的 `updateStatusCounts`）。
   ▸ 本页是 09-23 做的、之后没跟着走 ⇒ 全站最后一个还在 tab 上显示人数的页面。
   ⛔ 别再回填数字（**包括** `UI.setTabLabel` 的浅底徽标 —— 那版乐哥看过，说"多余"）。 */
/* ✅ 只保留「未填部门」的**待处理项警示**（09-23 定）：它标的是"这个分类要处理"
   （该部门的人提交报销时部门列为空、财务按部门筛不到），与"有多少人"无关
   ⇒ 从"标在数字上"改挂到**标签文字**上。
   ⚠️⚠️ **特异性是这里唯一的坑，两轮才定下来（别简化）**：
   · 第 1 版写裸 `.u-dept-tab--warn`（0,1,0）⇒ 被 `table.css` 的
     `.table-tabs .seg__btn { color: var(--color-muted) }`（0,2,0）压住 ⇒
     达播台量出来警示色**根本没生效**（验收脚本 ③a 抓到）；
   · 第 2 版写 `.table-tabs .seg__btn.u-dept-tab--warn:not(.is-active)`（0,4,0）⇒
     达播台对了，但**财务中心（embedded 分支）仍不生效** —— 那个分支的容器是
     `seg table-tabs`（**没有** `--in-header`），吃到了 `components.css` 的
     `.seg.table-tabs:not(.table-tabs--in-header) .seg__btn:not(.is-active)`
     = **5 个类 (0,5,0)**，把 (0,4,0) 压住 ⇒ 未填部门的字仍是 `--color-fg-2`（实测 rgb(63,71,83)）。
   ⇒ 最终写成 **6 个类 (0,6,0)**（`.seg` 与 `.table-tabs` 同元素、`[data-udept]` 是真属性）
     —— 比两边都高，**不依赖文件加载顺序**（靠源顺序赢是脆的：别人给 components 那条
     多加一个类就静默失效）。
   ⚠️ 用 `:not(.is-active)` 把选中态排除 ⇒ 选中时走标准的白字规则（蓝底要可读）。
   ⛔ 别再写一条 `is-active` 覆盖 —— 它与 `.table-tabs--in-header .seg__btn.is-active`
   （同为 0,3,0）抢源顺序，`color: inherit` 会从托盘继承成**深色**（实测 rgb(28,30,33)）。 */
.table-tabs.seg .seg__btn.u-dept-tab--warn[data-udept]:not(.is-active) { color: var(--color-warning-text); }

/* ── 用户管理 · 表格列宽（2026-09-23 立；2026-10-09 因"删掉用户名列"重排）────────
   · 历史（留着是为了别把老方案当尺子量新表）：这一列曾装飞书 36 字符 open_id
     （`ou_a9581258e7b2ff3eec0df1f9331e534f`）——**一串没有空格** ⇒ 它的 `min-content`
     就是全长 **319px**，`table-layout:auto` 压不动任何低于 min-content 的列 ⇒
     整表最小宽被钉在 **1133px**：视口 1440（容器 1134）刚好塞下，
     1300（容器 ~1000）溢出 133px ⇒ 最右侧「删除」被切（乐哥 2026-09-23 报障）。
     当年的解法是"给这一列限宽 + 其余列 nowrap"，即上面注释里那两条。
   ⚠️ 用 `#u-table` 作用域，**新开范围**不动全站 `.table` / `.is-num`（几十张表在用）。
   ⭐⭐ 2026-10-09 乐哥「用户名这一列隐藏掉」⇒ 当年那个根因本身**消失了**：
      改回 explicit 列宽 —— 模板给 `<colgroup>`（百分比合计 100%），
      这里只负责 `table-layout: fixed` + 一个"窄到不能看"的下限 `min-width`。
      行为差别（为什么这样更稳）：auto 布局下列宽由**内容争夺**决定，某一列一长就
      偷偷吃掉别人的空间（旧表格的 1133px 就是这么来的）；fixed 布局下 colgroup 说了算。
   ⚠️ 用户名列**不许加回来**：它要传达的两件事都搬走了 ——
      「内部/外部」= 姓名后的标签（`.u-acct`）；「重名怎么区分」= 姓名下的副标题。 */
/* ⚠️ `min-width` 不是随便给的：**操作列是定宽内容**（「改角色+改部门+⋯」实测要 184px，
   它不会像文字那样被省略号吃掉）⇒ 必须保证 `18% × min-width ≥ 184`
   → 1060 × 0.18 = 191 ✓。给 900 的话操作列只有 162px ⇒ 内容溢出单元格、
   整张表被顶出横向滚动条（第一版就是这么被实测抓到的）。
   ⚠️ 达播内容列宽 ≈ `视口 − 322`（1440 视口 → 1118 ≥ 1060 ⇒ 不滚）；
      更窄的窗口才会出现横向滚动（`.table-wrap` 自带 `overflow-x:auto`，属既有形态）。 */
.u-tbl { table-layout: fixed; min-width: 1060px; }
/* 角色列（**第 3 列**）是唯一**允许换行**的列（多个徽章自动折行）；
   ⚠️ 2026-10-09：前面加了「勾选」列 ⇒ 角色列从第 2 列变成**第 3 列**，这里必须跟着改
      （不改的话 nowrap 会落到角色列上 ⇒ 徽章不再折行、整表被顶宽；而**它不报错**，纯视觉塌）。
   其余列锁单行 —— 否则「2026-10-08 12:14」这类文本在窄列里会折成两行、行高参差。 */
#u-table .u-tbl tbody td:not(:nth-child(3)) { white-space: nowrap; }
#u-table .u-tbl .badge { white-space: nowrap; }

/* ── 「框选人」勾选条（2026-10-09 乐哥：「框选那个组织架构下的人」）──────────────
   勾了人才出现（`hidden`），避免常态白占一行；样式与底部操作岛同族但更轻。 */
.u-selbar {
  display: flex; align-items: center; gap: var(--space-2);
  margin-bottom: var(--space-2);
  padding: var(--space-2) var(--space-3);
  background: var(--color-surface-2);
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-md);
  font-size: var(--font-size-sm);
}
/* ⚠️⚠️ 必须写这一条：上面的 `display: flex` 是**作者样式**，会盖掉 UA 的
   `[hidden] { display: none }`（UA 特异性最低）⇒ 按钮条明明声明了 `hidden`，
   却**一直显示**成一个 1238×18 的空条子（2026-10-09 乐哥截图问「这个条子干嘛的」）。
   本项目为此已踩坑 6 次（components / cost-chart / datetime-picker / layout /
   pages-finance 都补过同样的兜底）—— **凡是「用 hidden 属性切显隐」的类，必须自带这条**。
   门禁：`tests/hidden-attr-guard.test.js`。 */
.u-selbar[hidden] { display: none; }
.u-selbar__n b { color: var(--color-fg); }

/* ── 部门管理（弹窗内的列表）──────────────────────────────────────────────── */
.u-dm { display: flex; flex-direction: column; }
.u-dm__row {
  display: flex; align-items: center; gap: var(--space-2);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--color-border-soft);
}
.u-dm__row:last-child { border-bottom: 0; }
.u-dm__name { font-weight: var(--font-weight-medium); color: var(--color-fg); }
/* ── 「选人」弹窗里的名单（2026-10-09 乐哥选定「方案 A · 列表」）──────────────
   一行一人：勾选框 + 姓名 + 所属部门（+ 通讯录成员的小标记）。
   为什么不用 `FeishuPicker` 的 chips：62 个人会堆成一坨、还把搜索框挤到下面
   （乐哥：「你这样看着不直观啊」）。名单是"多也能读"的形态。
   ⚠️ `.dm-pk__name` 必须 `min-width:0` —— flex 子项默认 `min-width:auto`，
      长名字会把后面的「部门」挤出可视区（本项目在 `.role-item__name` 上踩过同一个坑）。 */
.dm-pk { border: 1px solid var(--color-border-soft); border-radius: var(--radius-md); max-height: 320px; overflow-y: auto; }
.dm-pk__row {
  display: flex; align-items: center; gap: var(--space-2);
  padding: 7px var(--space-3); cursor: pointer;
  font-size: var(--font-size-sm);
  border-bottom: 1px solid var(--color-border-soft);
}
.dm-pk__row:last-child { border-bottom: 0; }
.dm-pk__row:hover { background: var(--color-surface-2); }
.dm-pk__name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dm-pk__dept { flex: 0 0 auto; font-size: var(--font-size-xs); color: var(--color-muted); }
.dm-pk__tag {
  flex: 0 0 auto; font-size: var(--font-size-xs); color: var(--color-muted);
  border: 1px solid var(--color-border-soft); border-radius: var(--radius-sm); padding: 0 4px;
}
/* 「不参与报销」= 提醒它**不会出现在报销的部门筛选里**（生产 10 个部门里 9 个是关的）。
   ⚠️ 用 warning 语义色（不是主色/状态色）—— 与 `.u-acct--ext` 同一套口径。 */
.u-dm__off {
  flex: 0 0 auto;
  height: 20px; padding: 0 var(--space-2);
  display: inline-flex; align-items: center;
  border: 1px solid transparent; border-radius: var(--radius-seg);
  font-size: var(--font-size-xs); line-height: 1;
  color: var(--badge-warning-fg); background: var(--badge-warning-bg);
}
/* 角色徽章容器：折行 + 行间距（只有列间距的话，折行后的两排会贴在一起） */
.u-roles { display: flex; flex-wrap: wrap; gap: 4px 6px; align-items: center; }

/* 「内部 / 外部」账号标签（2026-10-09 乐哥：「外部还是内部在姓名后面做个标签展示就好了」）
   ⚠️ 比 `.badge` **更小更轻**（20px、描边、不用实心底）—— 它与姓名挤在同一格，
      用 `.badge` 的底色会让整个姓名列变重（同族先例：`.pending-approval`）。
   ⭐⭐ 2026-10-09 乐哥二次：「**内外部标签也做点颜色**」⇒ 从"中性灰两档"改成
      **语义色两档**（用 `.badge` 家族同一套已做对比度校准的令牌）：
        · 内部（飞书通讯录同步，多数）→ **蓝**（info）
        · 外部（手工创建的本地账号，少数、需留意）→ **琥珀**（warning）
      ⚠️ 为什么用 `--badge-*-fg` 而不是 `--color-info` / `--color-warning` 裸值：
        前者是**对比度校准过**的文字色（warning 实测 1.86:1 → 5.23:1），
        后者在这种浅底上对比度不够（同族先例：`.badge--warning` 的注释）。
      ⛔ **不许用主色**（`--color-accent` / `--color-primary`）—— 它是"提示账号来源"的标记，
        不该比姓名/角色更显眼（门禁 `tests/user-table-layout.test.js` ② 逐条守着）。
      ⛔ 也**不是**状态色：别拿 `--color-accent-bg`（选中/拖拽态）来铺它 ——
        那是"某一刻的状态"，而内外是**账号的固有属性**（2026-10-09 全站查过一遍状态色当常态底色）。
   ⚠️ `border-color: transparent`：底色已经界定了形状，再留一圈灰边框会显脏；
      但**不能删掉 `border`**（外层 20px 高是算进 1px 边框的，删了会与别的胶囊差 2px）。 */
.u-acct {
  display: inline-flex; align-items: center; flex: 0 0 auto;
  height: 20px; padding: 0 var(--space-2);
  border: 1px solid var(--color-border); border-radius: var(--radius-seg);
  font-size: var(--font-size-xs); line-height: 1;
  white-space: nowrap;
}
.u-acct--int {
  color: var(--badge-info-fg); background: var(--badge-info-bg); border-color: transparent;
}
.u-acct--ext {
  color: var(--badge-warning-fg); background: var(--badge-warning-bg); border-color: transparent;
  font-weight: var(--font-weight-medium);
}
/* 姓名与标签同行时，名字要能被压缩（flex 项默认 `min-width:auto` ⇒ 名字长时整个行溢出、
   把标签挤出单元格）。`.cell-person__name` 自带省略号，配上这条即可优雅截断。 */
#u-table .cell-person__line { flex-wrap: nowrap; }
#u-table .cell-person__name { min-width: 0; }

/* 权限管理 · 角色列表行内的「⋯」（改名 / 删除）—— 2026-10-09
   乐哥：「权限那个，新增的角色需要支持删除，更名」⇒ 这两个动作不再只藏在
   右下角操作区，列表每行右端直接给入口（与操作日志页的操作列同一套 `UI.rowMenu`）。
   ⚠️ 行布局是 inline style 的 `flex + space-between`，所以这里只补"名字能被压缩"与
      按钮本身 —— 名字不加 `min-width:0` 时，长角色名会把右侧「⋯」挤出可视区
      （flex 项默认 `min-width:auto`，与上面姓名列同一个坑）。
   ⚠️ 「⋯」用**中性灰**：它是次要动作入口，不该比角色名更显眼；hover 才加深。 */
.role-item__name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.role-item__side { display: inline-flex; align-items: center; gap: var(--space-2); flex: 0 0 auto; }
.role-item__more {
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; padding: 0; flex: 0 0 auto;
  border: 0; border-radius: var(--radius-sm);
  background: transparent; color: var(--color-muted); cursor: pointer;
}
.role-item__more:hover { background: var(--color-surface-2); color: var(--color-fg); }
.role-item.is-active .role-item__more { color: var(--color-accent-text); }

/* 「本地账号」标签（2026-09-23 加）—— 标在"不是飞书组织架构、而是本地账号自填的
   职能标签"（实测 8 个 `local:xxx` 占位账号填着「商务/行政/运营」）的部门行上。
   ⚠️ 用**中性灰**而不是主色：它是"提示你别在这配"的标记，不该比真实部门更显眼。 */
.ef-dept__tag--local { background: var(--color-surface-2); color: var(--color-fg-2); }
/* 「飞书配了负责人但他没系统账号 ⇒ 需手工指定」—— 这是**要处理的**，
   必须与中性的"飞书部门负责人 · 兜底"在视觉上区分开（实测 10 个一级部门里 3 个是这种）。
   ⚠️ `nowrap + ellipsis`：这条文案比"兜底"那行长，若不限宽会**顶到右侧按钮**
      （实测截图看到"手工指定"直接贴在按钮边上，很难看）。
      完整原因在元素的 `title` 里，悬停可见 —— 行内只留结论。 */
.ef-dept__src--warn {
  color: var(--color-warning-text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
}
.ef-dept__src--warn b { font-weight: var(--font-weight-semibold); }

/* 别名 chips + 「归入部门」下拉（2026-09-23）——
   用途：把外部账号自填的职能标签（商务/行政/运营）归入真实部门。
   ⚠️ 别名 chip 用**虚线描边**：它与主管 chip（蓝、实线、带头像）是两种东西——
      主管是"人"、别名是"一个名字"，不能长得像。 */
.ef-dept__chip--alias {
  background: transparent;
  border: 1px dashed var(--color-border);  /* 2026-09-25 修：--color-border-strong 不存在（一直走兜底），需要强边框时再到 design-tokens 新增 */
  color: var(--color-fg-2);
  /* ⚠️ 右侧留 6px：它里面也有一个 ×（移除别名）。
     上一版写的 3px 实测**挤在一起**（截图看到 × 贴着"人"字）。 */
  padding-right: 6px;
}
/* 「归入部门…」下拉：紧贴主操作按钮左侧、等高。
   ⚠️ 宽度必须限死：部门名（如"直播旗舰事业部"）很长，不限宽会把整行撑开、
      把中间那列的主管信息挤到换行（上一轮刚修过同类问题）。 */
.ef-dept__merge {
  flex: none; width: 116px; height: 28px;
  padding: 0 6px; font-size: var(--font-size-xs);
}

/* ═══ 部门屏蔽 + 外部账号（2026-09-23 乐哥）═══════════════════════════════ */

/* 「显示已屏蔽」开关 —— 标题行右侧的弱化控件。
   ⚠️ 只在**有已屏蔽部门时**才渲染（没屏蔽过就不出现，减少噪音）。 */
.ef-dept__showtoggle {
  display: inline-flex; align-items: center; gap: 4px;
  margin-left: var(--space-3);
  font-size: var(--font-size-xs); font-weight: var(--font-weight-regular);
  color: var(--color-muted); cursor: pointer;
}
.ef-dept__showtoggle input { margin: 0; }

/* 已屏蔽的行 —— 整行降透明 + 弱化，一眼看出"这行现在不生效"。
   ⚠️ 不做 `display:none`：屏蔽后默认不显示是在**渲染前过滤**掉的，
      这里处理的只是"用户主动勾了显示已屏蔽"的情况 ⇒ 要**能看见但明显不同**。 */
.ef-dept__row.is-blocked { opacity: .55; }
.ef-dept__row.is-blocked .ef-dept__name { text-decoration: line-through; }

/* 屏蔽/恢复的图标按钮：`is-on` = 当前处于"已屏蔽"态（图标是睁眼，提示可恢复）。
   ⚠️ 用描边色而不是主色：它是**次要操作**（绝大多数时候不会点），
      主色会让每行都有一处抢眼的东西、整页发花。 */
.ef-dept__ops .icon-btn.is-on { color: var(--color-warning-text); }

/* ── 外部账号区块 ─────────────────────────────────────────────────────
   一行 = 一个标签：`[标签 · N 人] [人名列表] [归入部门 ▾]`
   ⚠️ 第一列宽度与 `.ef-dept__row` **保持一致**（172px）：两块紧挨着，
      列不对齐会显得很乱（本项目"看着没对齐"的常见成因）。 */
.ef-ext { border-top: 1px solid var(--color-border-soft); }
.ef-ext__row {
  display: grid;
  grid-template-columns: minmax(0, 172px) minmax(0, 1fr) auto;
  align-items: center; gap: var(--space-3);
  padding: var(--space-2) var(--space-2);
  margin: 0 calc(-1 * var(--space-2));
  border-bottom: 1px solid var(--color-border-soft);
  border-radius: var(--radius-md);
}
.ef-ext__row:hover { background: var(--color-surface-2); }
.ef-ext__tag {
  display: flex; align-items: center; gap: 6px; min-width: 0;
  font-size: var(--font-size-sm); color: var(--color-fg); font-weight: var(--font-weight-medium);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ef-ext__n { font-size: var(--font-size-xs); color: var(--color-muted); font-weight: var(--font-weight-regular); flex: none; }
/* 「待归入」= 这些人现在提不了单，是待办 ⇒ 琥珀色（与"未指定主管"同一种语气）。 */
.ef-ext__todo {
  flex: none; font-size: 11px; line-height: 1; padding: 1px 5px; border-radius: 3px;
  background: var(--color-warning-bg); color: var(--color-warning-text);
}
.ef-ext__who {
  font-size: var(--font-size-xs); color: var(--color-fg-2); min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ef-ext__ops { display: inline-flex; align-items: center; gap: var(--space-1); }
/* 归入部门的下拉：与部门行里的「归入部门」同宽同高（视觉一致） */
.ef-ext__merge { flex: none; width: 148px; height: 28px; padding: 0 6px; font-size: var(--font-size-xs); }
/* ⚠️ 「所归入的部门已屏蔽 ⇒ 仍提不了单」—— 必须跨列显示在下一行，
   否则会被挤到第一列里把标签压变形（grid 三列已满）。 */
.ef-ext__bad {
  grid-column: 1 / -1; font-size: var(--font-size-xs); color: var(--color-warning-text);
}
@media (max-width: 900px) {
  .ef-ext__row { grid-template-columns: minmax(0, 1fr) auto; row-gap: var(--space-1); }
  .ef-ext__who { grid-column: 1 / -1; grid-row: 2; }
  .ef-ext__ops { grid-column: 2; grid-row: 1; }
}

/* ═══ 报销审批流 · 部门主管区块（2026-09-23 乐哥四条反馈）═══════════════════════ */

/* ① 卡片内的呼吸空间：给 `#ef-box` 留 8px。
   ⚠️⚠️ **必须是 `#ef-box` 这一层、且必须是 `var(--space-2)`（8px）** ——
      部门行 `.ef-dept__row` 用了 `margin: 0 -8px` + `padding: 0 8px`（"内容对齐父级、
      hover 背景外扩 8px"）。在这层加 8px padding，恰好让两件事同时成立：
        · 普通文字（部门标题/说明）左边距 = 卡片 24px + 8px = **32px**（不再贴边）
        · 部门行因 -8px 负边距回到 24px，其内部 padding 8px ⇒ 内容仍在 **32px** ✓ 对齐不变
      若改成 12px 或加在更外层，正文与部门名就会**差 4px**（看着就是"没对齐"）。 */
#ef-box { padding: var(--space-2); }

/* ② 标题行右侧的两个控件（「显示已屏蔽」+「从飞书同步部门」）——
   乐哥：「从飞书同步部门这个按钮移到右上角，然后显示已屏蔽和这个同步部门按钮放到同一排」。
   ⚠️ `margin-left: auto` 把它们推到最右（不跟着"N 个部门"的字数左右跳）。
   ⚠️ `align-self: center` 覆盖标题行的 `align-items: baseline` ——
      按钮没有文字基线，跟着 baseline 对齐会往下沉半行。 */
.ef-group__tools {
  margin-left: auto;
  display: inline-flex; align-items: center; gap: var(--space-3);
  align-self: center;
}

/* ③ 主管/负责人卡片里的人像（2026-09-23 乐哥**两轮**反馈后定稿）——
   第一轮「人名和头像可以适当调小一点」：28px/14px → 22px/13px
   第二轮「名字还是大，要不再小一点？」：22px/13px → **20px/12px**
   ⚠️⚠️ 定稿值**只留这一条规则**。上一版把两轮的值**都留在文件里**
      （两条同名 `.ef-dept__chip .cell-person__avatar`，靠"后者覆盖前者"生效）——
      那是**坏的**：读代码的人（以及我自己的探针）会先看到 22px 那条，
      以为生效的是 22px（本轮真踩：探针误报"头像 22px"）。
      ⇒ **改样式时要替换旧值，不要追加**；确实需要保留过程，写在注释里、不要留第二条规则。
   ⚠️ 12px 就是本项目的 `--font-size-xs`，是**可读下限**，不要再往下调
      （低于 12px 中文在人像旁会糊成一团，且与「已停用」标签同字重时难区分）。
   ⚠️ 头像基础尺寸定义在 `table.css` 的 `.cell-person__avatar`（28px），
      这里**只在本区块内覆盖**（`.ef-dept__chip` 前缀限定作用域）——
      不能改全局那个：表格人员列是另一套密度，动了会全站变样。
   ⚠️ 字号（12px）**已写在上面的 `.ef-dept__chip` 基础规则里**，这里不再重复覆盖 ——
      两处写同一个属性就是本轮踩过的"留下两条同名规则、读的人不知道哪条生效"。 */
.ef-dept__chip .cell-person__avatar { width: 20px; height: 20px; font-size: 11px; }


/* ═══ 付款批次台账 · 展开看明细（2026-09-23 乐哥：「我勾选完之后的记录在哪？」）═══
   用途：批次行只有批次号/张数/金额时，看不出"这批里是哪几张单"。
   ⚠️ 批次号做成**按钮**而不是给整行加 click —— 行里还有「标记已打款」按钮，
      整行可点会让"想打勾却展开了明细"（本项目在别的表格上踩过同类误触）。 */
.fin-batch-open {
  display: inline-flex; align-items: center; gap: 5px;
  background: transparent; border: 0; padding: 0; cursor: pointer;
  color: inherit; font: inherit;
}
.fin-batch-caret {
  display: inline-flex; color: var(--color-muted);
  transition: transform var(--motion-fast) var(--motion-ease-standard);
}
.fin-batch-open.is-open .fin-batch-caret { transform: rotate(90deg); }
/* ⚠️ `.fin-batch-n` 已**无使用者**（2026-09-23 统一到 BT 时改用 `.fin-badge`）——
   原来它在提报单号后面显示「N 张」，但它**只有字号与颜色、没有 `margin-left`**
   ⇒ 实测小标紧贴单号（看着像被粘住）。
   总览页的「N 笔」用的是 `.fin-badge`（自带 6px 间距 + 浅底）—— 同一种语义
   （单号后的数量小标）该长一样 ⇒ 复用 `.fin-badge`。
   ⚠️ 本行**保留不删**：本项目有并行会话在改同一个文件，删共用 CSS 有风险；
      新代码请不要使用本类，改用 `.fin-badge`。 */
.fin-batch-n { font-size: var(--font-size-xs); color: var(--color-muted); }
.fin-batch-open:hover .fin-code { text-decoration: underline; }

/* 明细行：浅底色 + 左侧缩进，一眼看出它从属于上面那个批次。
   ⚠️ `background` 用 `--color-surface-2`（比白卡片深一档），不能用主色浅底 ——
      后者在"选中行"上会被误读成被选中了。 */
.fin-bdet-row > td { background: var(--color-surface-2); padding: 8px 12px 10px 32px; }
.fin-bdet { display: flex; flex-direction: column; gap: 2px; }
.fin-bdet__item {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto auto;
  align-items: center; gap: var(--space-3);
  padding: 4px 6px; border-radius: var(--radius-sm);
  font-size: var(--font-size-sm);
}
.fin-bdet__item:hover { background: var(--color-surface); }
.fin-bdet__code { text-decoration: none; }
.fin-bdet__who { display: flex; align-items: center; gap: 6px; min-width: 0; }
.fin-bdet__amt { font-variant-numeric: tabular-nums; }
.fin-bdet__st { flex: none; }
@media (max-width: 900px) {
  .fin-bdet__item { grid-template-columns: minmax(0, 1fr) auto; row-gap: 2px; }
  .fin-bdet__who { grid-column: 1 / -1; }
}

/* ⭐ 2026-10-09 「人员成本」页 · 支援成本明细弹层（`openSupportDetail`）——
   行展开去掉后（乐哥：「下面的明细不要做展开了」），"逐条计入成本"的开关搬进弹层。
   为什么是弹层而不是塞回主行格子：一个排期可能挂 2~6 条支援成本（生产实测），
   塞进格子会把行撑高；弹层不占表格高度，开关仍是逐条粒度（语义不变）。
   ⚠️ 全部引令牌，无裸 hex（门禁 ui-token-standards 锁着）。 */
.ps-sup-list { display: flex; flex-direction: column; gap: var(--space-2); }
.ps-sup-row {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-md);
}
.ps-sup-row__main { flex: 1 1 auto; min-width: 0; }
.ps-sup-row__amt { flex: 0 0 auto; font-variant-numeric: tabular-nums; }


/* ── 报销单「审批意见」区（2026-10-12）────────────────────────────────────────
   审批台（财务板块）与「我的报销」（达播板块）**共用**同一块 UI ⇒ 样式必须放这里，
   ⛔ 别写进某一侧的页面 CSS（那样另一侧就没了）。
   视觉语言沿用审批台既有的 `.ap-section` / `.ap-section__title`（同一屏里的其它分区）。 */
/* ⭐⭐⭐ 2026-10-16 每条意见改成**浅蓝气泡**（乐哥：「**评论回复，参考我发的这个做**」
   —— 他给的是飞书那种气泡截图）
   ──────────────────────────────────────────────────────────────────────────────
   ▸ 形态（自上而下，照参照图）：
       ① 正文            ← 块内第一眼
       ② 头像 + 名字(蓝) + 环节标签 + 时间
       ③ 💬 回复 / 撤回
   ▸ 整块是**浅蓝底 + 圆角**（`--color-accent-bg`）—— 不再是"白底无框的两栏跟帖"。
   ▸ **提报人**那条用中性灰底（同一块里区分"谁在说"，比给头像上色更省、也不用多一个信号）。
   ⚠️ 头像尺寸在这里显式给：`.cell-person__avatar` 的基础尺寸按容器限定
     （`.fin td` / `.fin-person`），而这块 UI 两侧共用（审批台在 `.fin` 里、「我的报销」不在）
     ⇒ 不给尺寸会变成 0 宽（静默"头像不见了"）。 */
.ap-cmt__list { list-style: none; margin: 0; padding: 0; }
.ap-cmt__item { display: block; }
.ap-cmt__item + .ap-cmt__item { margin-top: var(--space-2); }
/* ⭐⭐⭐ 2026-10-16 **同一话题合成一个框**（乐哥：「同一个回复跟评论**不应该分两个框**。
   **外框带颜色，里面的框就不要有颜色了** —— 这样的话，文字也能看得清楚」）
   ──────────────────────────────────────────────────────────────────────────
   ▸ 结构：外框 = 原评论（`.ap-cmt__bubble`，**带浅蓝底**）；
           内框 = 它的回复们（`.ap-cmt__replies`，**完全不上色**，只靠一条细线分隔）。
   ▸ 为什么内框不能上色：两层浅蓝叠在一起 ⇒ 层次糊掉、字也发闷
     （这正是他说"文字也能看得清楚"要解决的）。⛔ 内框别再加任何背景。
   ⚠️ 内层的 `.ap-cmt__bubble` 一并**去掉底色与内边距**：它只是个语义容器，
     保持 `.ap-cmt__bubble` 这个钩子（结构一致、将来好改），但视觉上什么都不画。 */
/* ⭐⭐ 2026-10-16 乐哥：「**C 的这个颜色没问题**」（回复区极淡灰底）+「隔得很远」的修正：
   ▸ 原来 12(margin)+12(padding)+细线 = **24px 留白**，两条被"拉开"了 ⇒ 收到 8px 左右。
   ▸ 底色用 `--color-surface-2`（极淡灰）—— 与"外框浅蓝"形成**主（白蓝）/ 副（灰）**两档，
     但**不是第三种颜色**，所以文字照样清楚。
   ⛔ 别再用顶部的分隔细线：有了底色，线是多余的（两条线反而乱）。 */
.ap-cmt__replies { margin-top: var(--space-2); padding: var(--space-3);
  background: var(--color-surface-2); border-radius: var(--radius-md);
  display: flex; flex-direction: column; gap: var(--space-3); }
.ap-cmt__item.is-nested > .ap-cmt__bubble { background: transparent; padding: 0;
  border-radius: 0; }
/* 内层不再吃"条与条之间的间距"（间距由 `.ap-cmt__replies` 的 gap 给，否则会翻倍） */
.ap-cmt__replies > .ap-cmt__item + .ap-cmt__item { margin-top: 0; }

/* 回复那条**不缩进**（乐哥：「回复也做成平等的，不缩进」）。
   ⚠️ `is-reply` 这个类**保留**（语义还在，将来要用；只是样式不再区分）——
     ⛔ 别因为它"没样式"就删掉，删了以后想按回复关系做区隔就得重加。 */
.ap-cmt__item.is-reply { margin-left: 0; }
/* ── 气泡本体 ── */
/* ⭐⭐⭐ 2026-10-17 乐哥：「角色应该在人名后面：**胶囊人名、角色 冒号： 审批意见**」
   ──────────────────────────────────────────────────────────────────────────
   ▸ 一行读完：`[头像 张乐] 提报人： 对的`  ← 胶囊(次) + 角色冒号(次) + 正文(主)
   ▸ 实现：`.ap-cmt__bubble` 改 **flex-wrap** 容器 ——
        · `.ap-cmt__meta`（人胶囊 + 角色 + 环节 + 时间）与 `.ap-cmt__body`（正文）**同在第一行**
        · 引用 / 回复区 / 操作 各占整行（`flex-basis: 100%`）⇒ 落到后面的行
   ⚠️ 正文 `flex: 1 1 200px`：窄到放不下时**整体换行**（人名一行、正文下一行），
      而不是把正文压成竖条。
   ⛔ 别把 `.ap-cmt__meta` 的 `margin-bottom` 留回来 —— 那正是"两行"的来源。 */
.ap-cmt__bubble { display: flex; flex-wrap: wrap; align-items: baseline;
  column-gap: 6px; row-gap: 2px;
  background: var(--color-accent-bg); border-radius: var(--radius-lg);
  padding: var(--space-3) var(--space-4); }
/* 身份（审批人 / 提报人）**不用底色区分** —— 平等样式；身份由元信息行的**标签**表达。
   ⚠️ 只有「已撤回」才换灰底：那是**状态**，与"谁在说"无关，必须能一眼看出。 */
.ap-cmt__item.is-deleted .ap-cmt__bubble { background: var(--color-surface-2); }
/* 正文＝**主**：比人名/角色/时间大一档、颜色最深（乐哥：「得有主次」） */
.ap-cmt__body { flex: 1 1 200px; min-width: 0; font-size: var(--font-size-base);
  line-height: 1.5; color: var(--color-fg); word-break: break-word; }
/* ── 元信息行：头像 + 名字(蓝) + 环节标签 + 时间靠右 ── */
/* ⭐ 2026-10-16 人名行**移到正文上方**（乐哥：「发消息的人名应该在正文前面…并且人名也得带头像」）
   ⇒ 它成了块内第一行，所以是 `margin-bottom` 而不是 `margin-top`。 */
.ap-cmt__meta { display: inline-flex; align-items: center; gap: 6px;
  flex: 0 1 auto; min-width: 0; }
.ap-cmt__meta > .cell-person__avatar { flex: 0 0 auto; width: 22px; height: 22px;
  font-size: var(--font-size-xs); background: var(--color-surface); color: var(--color-muted); }
/* 名字用主色（参照图里名字是蓝的）—— 但它**不是加粗大号**，别抢正文 */
.ap-cmt__who { font-size: var(--font-size-xs); font-weight: var(--font-weight-medium);
  color: var(--color-accent-text); white-space: nowrap;
  /* ⚠️ 原来是 `display: block`（会被当成块级撑满/换行）⇒ 改成 inline-block，
     让名字与头像**同在一行**、宽度按内容算。 */
  display: inline-block; }
/* ⭐⭐ 2026-10-16 「头像 + 名字」做成**描边胶囊**（乐哥选 B；他原话：「人名及其对应的头像，
   都按照"打包工作台"那样做成胶囊形状」）
   ▸ 白底 + 1px 主色描边 + 全圆角 ⇒ **在浅蓝外框底和淡灰回复底上都清楚**
     （淡灰底胶囊会与那两种底糊在一起，这是他没选 A 的原因）。
   ▸ 头像收到 **20px**：22 在胶囊里会显得头重脚轻。
   ⚠️ 这个胶囊**只包"头像 + 名字"**，里面的身份/环节徽章、时间、回复片段仍在胶囊**外面**
     —— 它们是"这条的属性"，不是"这个人"的一部分。 */
.ap-cmt__person { display: inline-flex; align-items: center; gap: 6px;
  padding: 2px 10px 2px 3px; border-radius: var(--radius-pill);
  background: var(--color-surface); border: 1px solid var(--color-accent-border);
  /* ⚠️⚠️ 2026-10-17 实测：正文并到同一行后，一行里元素变多 ⇒ 胶囊被压窄
     ⇒ **名字被拆成竖排**（"张 / 乐"两行）。修：不许压缩、不许换行。
     ⭐ 关键是 **`width: max-content`** —— 只写 `flex: 0 0 auto` 不够：
        在**回复区**（`.ap-cmt__replies`，本来就窄）里它仍会被压到"恰好一个字的宽"，
        然后 `white-space: nowrap` 只能让字**溢出**，视觉上就是竖排。
        `max-content` 让胶囊**按内容撑开**，宽度不参与压缩。 */
  flex: 0 0 auto; white-space: nowrap; width: max-content; }
.ap-cmt__person > .cell-person__avatar { width: 20px; height: 20px; font-size: 11px;
  background: var(--color-accent-bg); color: var(--color-accent-text); }

/* ⭐ 2026-10-17 角色文字（跟在人胶囊后、带中文冒号）＝**次**：小字灰色，
   让正文（主）先被看到；冒号后面直接接正文，读起来是"谁·什么身份·说了什么"一句。 */
.ap-cmt__role-txt { font-size: var(--font-size-xs); color: var(--color-muted);
  white-space: nowrap; }

/* ⚠️ 2026-10-16 起「身份」改用徽章（`UI.badge(…, 'outline')`）⇒ 这条已不再使用；
   保留是为了别让别处引用它的地方突然没样式（本项目出现过"删了才发现还有人用"）。 */
.ap-cmt__role { font-size: var(--font-size-xs); color: var(--color-muted); }
.ap-cmt__t { margin-left: auto; font-size: var(--font-size-xs); color: var(--color-meta);
  white-space: nowrap; }
.ap-cmt__empty { font-size: var(--font-size-sm); color: var(--color-muted); padding: var(--space-2) 0;
  line-height: 1.6; }
/* 表单区纵向：上面是「正在回复谁」的引用条（有才显示），下面是输入框 + 发表 */
/* ⭐⭐⭐ 2026-10-16 乐哥：「（下面那个单独一个框）**这个输入框应该在那个"对"的下面灰色框里面吧**」
   —— 三方案里他选 **B：输入区加浅灰底**。
   ──────────────────────────────────────────────────────────────────────────
   ▸ 意图：让"写意见"这块**看起来属于上面那张卡**，而不是卡外面另起的一个人框。
   ▸ 做法：给整个输入区（**含引用条**）一档浅灰底（与上面的回复区**同色**）⇒ 视觉上连成一块。
   ▸ 输入框本身保持**白底 + 边框**，压在灰底上反而更清楚。
   ⚠️ 引用条**必须改白底**：它自带 `--color-surface-2`，与本区的灰底**同色** ⇒ 会直接"化掉"
     （左侧那条主色竖线也会糊在灰里）。 */
.ap-cmt__form { display: flex; flex-direction: column; gap: var(--space-2);
  margin-top: var(--space-3); padding: var(--space-3);
  background: var(--color-surface-2); border-radius: var(--radius-md); }
.ap-cmt__form .ap-cmt__replybar { background: var(--color-surface); }
.ap-cmt__row { display: flex; gap: var(--space-2); align-items: flex-end; }
.ap-cmt__box { flex: 1 1 auto; resize: vertical; min-height: 44px; }
.ap-cmt__send { flex: 0 0 auto; }

/* 引用条（点了某条的「回复」后悬在输入框上方）—— 左侧主色条沿用"审批人身份条"的视觉语言 */
/* ⚠️⚠️ 2026-10-16 修：`hidden` 属性在这里**不管用** —— 本规则写了 `display: flex`，
   而 `[hidden]` 的 `display: none` 来自 UA 样式表（特异性最低）⇒ **被压掉**
   ⇒ 「正在回复谁」的引用条**常驻**：输入框上方永远挂着一条空白灰条 + 一个「×」
   （乐哥 2026-10-16 截图里那条就是它；他说的"好丑"有它一份）。
   ✅ 显式声明 `[hidden]` 形态（同一选择器族，靠源顺序 + 属性选择器取胜）。 */
.ap-cmt__replybar[hidden] { display: none; }
.ap-cmt__replybar { display: flex; align-items: center; gap: var(--space-2);
  padding: var(--space-2) var(--space-3); border-radius: var(--radius-md);
  background: var(--color-surface-2); border-left: 3px solid var(--color-accent);
  font-size: var(--font-size-xs); color: var(--color-muted); }
.ap-cmt__replybar-txt { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ap-cmt__replybar-x { flex: 0 0 auto; border: 0; background: transparent; cursor: pointer;
  padding: 0 var(--space-1); font-size: var(--font-size-base); line-height: 1;
  color: var(--color-muted); border-radius: var(--radius-sm); }
.ap-cmt__replybar-x:hover { color: var(--color-fg); }

/* ⭐⭐ 2026-10-16 「回复 某某：原话」改成**行内片段**（乐哥：「这个是不是可以放在同一行？
   比如"张乐，提报人回复什么什么什么"，放在同一行展示会更直观一点」）
   ⇒ 它现在是**人名行里的一段**，不再是独立一行；所以只做"淡一级 + 不换行截断"，
     ⛔ 不要再给它块级的内边距/底纹（那会把人名行撑高）。
   ⚠️ 空间不够时整段**省略**（人名行本身 `flex-wrap: wrap`，会整体换行，不会把名字压断）。 */
.ap-cmt__quote-inline { flex: 1 1 100%; font-size: var(--font-size-xs); color: var(--color-muted);
  min-width: 0; white-space: normal; word-break: break-word; }
/* ⭐⭐ 2026-10-16 乐哥：「张乐回复黄沛鑫的那条消息里面，**黄沛鑫的名字也需要做成胶囊这样**」
   ▸ 与「发言人人名」用**同一款描边胶囊**（白底 + 1px 主色边），只是**小一号**
     （引用行整行是 12px，胶囊内文字 11px、高 20px）。
   ▸ **只包名字、不带头像** —— 引用是"提到某人"，不是"此人发言"；
     他的头像在上一条（被回复的那条）已经出现过，这里再放一次会让人误以为是另一条发言。
   ⚠️ 它仍是**行内**元素，别改成块级（会把人名行撑成两行）。 */
/* ⭐⭐ 2026-10-16 乐哥：「黄沛鑫的名字**前面也得加头像**，头像要拉飞书最新的」
   ⇒ 引用里的小胶囊从"只有名字"改成"**小头像 + 名字**"，与发言人人名的胶囊同款（小一号）：
     头像 16px、字号 11px、高 20px。
   ⚠️ 被回复人的头像 URL 由服务端补（`avatarMap`，取人事系统每天同步的飞书头像）；
     取不到时 `UI.avatar` 自动退回首字占位。 */
.ap-cmt__quote-person { display: inline-flex; align-items: center; gap: 4px;
  height: 20px; padding: 1px 8px 1px 2px; border-radius: var(--radius-pill);
  background: var(--color-surface); border: 1px solid var(--color-accent-border);
  vertical-align: middle; }
.ap-cmt__quote-person > .cell-person__avatar { width: 16px; height: 16px; font-size: 9px;
  background: var(--color-accent-bg); color: var(--color-accent-text); }
.ap-cmt__quote-who { color: var(--color-accent-text); font-size: 11px;
  font-weight: var(--font-weight-medium); }
.ap-cmt__quote-arrow { color: var(--color-meta); margin-right: 2px; }
.ap-cmt__quote-txt { color: var(--color-meta); }
.ap-cmt__quote-inline.is-gone .ap-cmt__quote-txt { font-style: italic; }

/* 已撤回的那条：正文换成「该条已撤回」，整条淡一点，但**位置留着**（时间线读得通） */
.ap-cmt__item.is-deleted .ap-cmt__body { color: var(--color-muted); }
.ap-cmt__gone { font-style: italic; color: var(--color-muted); }

/* 每条下面的操作（回复 / 撤回）—— 小字按钮，不抢正文注意力 */
.ap-cmt__ops { flex: 1 1 100%; display: flex; gap: var(--space-3); margin-top: 2px; }
/* 「💬 回复」带图标（照参照图）—— 图标与文字同色、基线对齐 */
.ap-cmt__btn { display: inline-flex; align-items: center; gap: 4px; }
.ap-cmt__btn svg { flex: 0 0 auto; }
.ap-cmt__btn { border: 0; background: transparent; cursor: pointer; padding: 0;
  font-size: var(--font-size-xs); color: var(--color-accent); }
.ap-cmt__btn:hover { text-decoration: underline; }
.ap-cmt__btn--danger { color: var(--color-danger); }

/* 华大对账：主表列多（15+），给表格一个最小宽度 ——
   否则列被浏览器压窄，永远不溢出，`.table-wrap` 的 overflow-x 就出不来滚动条
   （乐哥 2026-10-21：「想右滑动，表格少了滑动条」）。 */
#hs-body-inner .table-wrap > table.table { min-width: 1480px; }

/* ── 华大对账：页头按层级显隐（乐哥 2026-10-21）─────────────────────────────
   ① 月维度 / 附表：看的是"多个月"，**不显示月份筛选**（选了也没意义）
   ② 进到某月（达人×店铺 / 对账确认）：**不显示 tab 栏**（已经钻下去了，再切 tab 只会乱），
      但保留月份筛选 —— 在这一层换月份是常用动作
   `data-hs-view` 由 switchView() 写在 .page-header 上，已存在，无需改 JS。 */
.page-header[data-hs-view="month"] #hs-month,
.page-header[data-hs-view="annex"] #hs-month { display: none; }
.page-header[data-hs-view="influencer"] #hs-tabs,
.page-header[data-hs-view="statement"] #hs-tabs { display: none; }

/* 达人明细（月度利润点月进去）：金额列一律不换行 —— 早先「¥124.77 万」会在窄列里折成两行 */
#pmd-body td.is-num, #pmd-body th.is-num { white-space: nowrap; }

/* ═══ 2026-10-21 报销草稿箱（「我的报销」页头「草稿箱」弹层）══════════════════
   ▸ 一行一张草稿：金额 / 几笔 + 事由 / 保存时间 / 「继续填写」。
   ▸ 金额用 `--color-price`（与全站"结论性金额"同源），与列表里的金额列一致。
   ⛔ 不要给草稿做第四种状态色 —— 它不进审批流，不需要状态标签。 */
.me-drafts { display: flex; flex-direction: column; gap: 6px; }
.me-draft {
  display: flex; align-items: center; gap: var(--space-3);
  padding: 10px 12px; background: var(--color-surface-2);
  border-radius: var(--radius-seg);
}
.me-draft__amt { min-width: 88px; color: var(--color-price); font-weight: var(--font-weight-semibold); }
.me-draft__meta {
  flex: 1; min-width: 0; color: var(--color-fg-2); font-size: var(--font-size-sm);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.me-draft__time { color: var(--color-fg-3); font-size: var(--font-size-sm); white-space: nowrap; }
/* 草稿箱里"代 XX 报"的标识（2026-10-21）：代提人一眼分清这张是替谁填的 */
.me-draft__who { color: var(--color-accent); font-weight: var(--font-weight-semibold); }

/* 达人明细：表格铺满容器（列宽由 th 的 width + 浏览器摊分），避免右侧出现一大块空白 */
#pmd-body table.table { width: 100%; }

/* ══ 口径弹窗内的排版（2026-10-22）══════════════════════════════════════════
   口径说明从"页面正文横幅"搬进弹窗（乐哥：那两条注释很多余，要么放右上角）。
   弹窗里没有页面级字号继承，且内容较长 ⇒ 单独约束：去掉横幅的外边距、限制最高高度并可滚动。
   ⚠️ 只作用于 `.st-rule--modal`，不改 `.st-rule` 本体（正文其它地方还在用横幅形态）。 */
.st-rule--modal { margin-bottom: 0; }
.st-rule--modal .st-rule__inner { margin-bottom: var(--space-3); }
.st-rule--modal .st-rule__full { max-height: 52vh; overflow-y: auto; }
.st-rule--modal .st-rule__sec + .st-rule__sec { margin-top: var(--space-3); }

/* ══ 华大对账：按视图显隐页头控件（2026-10-22）════════════════════════════════
   乐哥（截图红框）：「哪个生成预览干嘛的，感觉多余，位置也不对」
   ──────────────────────────────────────────────────────────────────────────────
   根因：`.hs-annex-only` / `.hs-month-only` 这两个类**从来没写过 CSS**（只有 JS 在拼 class）
   ⇒ 「生成预览 / 生成 Word」在月维度也照常显示，与「导入结算单」挤成一排。
   现在补齐：附表专属动作只在附表出现，月维度专属动作（口径/导入/月份）只在月维度出现。
   ⚠️ 依赖 `.page-header[data-hs-view]`（由 render/switchView 写），与已有的
      `#hs-month` / `#hs-tabs` 那两条规则同一机制。 */
.page-header[data-hs-view="month"] .hs-annex-only { display: none !important; }
.page-header[data-hs-view="annex"] .hs-month-only { display: none !important; }


/* ==== /css/pages-influencer.css ==== */
/* ==========================================================================
   达播工作台 — pages-influencer.css
   达人列表 / 达人详情
   ========================================================================== */

/* ============================ 达人列表 ============================ */

.batch-bar {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-4);
  margin-bottom: var(--space-4);
  background: var(--color-primary-50);
  border: 1px solid var(--color-primary-100);
  border-radius: var(--radius-md);
  font-size: var(--font-size-sm);
}

.batch-bar__count {
  font-weight: var(--font-weight-medium);
  color: var(--color-accent-text);
}

.platform-badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  height: 20px;
  padding: 0 var(--space-2);
  border-radius: var(--radius-sm);
  border: 1px solid var(--color-border);
  color: var(--color-fg-2);
  font-size: var(--font-size-xs);
  background: var(--color-surface);
}

.platform-dot {
  width: 6px;
  height: 6px;
  border-radius: var(--radius-pill);
  background: var(--color-accent);
}

/* 达人列表：平台+机构竖排列表（上下排列，机构彩色徽章） */
.cell-agency-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-top: 4px;
}
.cell-agency-row {
  display: flex;
  align-items: center;
  gap: 5px;
  max-width: 100%;
  min-width: 0;
  font-size: var(--font-size-xs);
  color: var(--color-fg-2);
}
.cell-agency-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--color-accent);
  flex-shrink: 0;
}
.cell-agency-platform {
  flex-shrink: 0;
  color: var(--color-fg-2);
}
/* 机构标签（统一 .tag 风格）在列表/详情行内超长截断 */
.cell-agency-row .tag,
.platform-detail-row .tag {
  flex-shrink: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}

/* 展开卡片：平台账号/机构行 */
.platform-detail-row {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 4px 0;
  min-width: 0;
}
.platform-detail-platform {
  color: var(--color-fg);
  font-weight: var(--font-weight-medium);
  flex-shrink: 0;
}
.platform-detail-account {
  color: var(--color-muted);
  font-size: var(--font-size-xs);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ============================ 达人详情 ============================ */

/* 达人详情内容区与编辑页统一：整体包在 .sf-shell 单卡片内，居中限宽 */
.detail-shell {
  max-width: var(--form-max-width);
  margin-left: auto;
  margin-right: auto;
}

/* 通用：外壳不再是一整张大白卡，改为「Tab 栏 + 一列模块卡」
   达人详情页用 .detail-shell--modules，达人表单页用 .sf-shell--modules，共用同一套规则 */
.detail-shell--modules,
.sf-shell--modules {
  background: transparent;
  border: none;
  box-shadow: none;
  overflow: visible;
}
/* ⚠️⚠️ `overflow: visible` 必须**单独写一条高特异性规则**（2026-09-19 修 bug）。
   ─────────────────────────────────────────────────────────────────────────────
   上面那条 `.sf-shell--modules { ... overflow: visible }` 特异性是 (0,1,0)，
   而文件后面（371 行）的 `.sf-shell { overflow: hidden }` **特异性相同、位置更靠后**
   ⇒ 后者胜出 ⇒ **`--modules` 的 `overflow: visible` 从来没有生效过**。
   实测证据（浏览器 computed）：`<div class="sf-shell sf-shell--modules">` 的
   `overflow-y` 是 `hidden` 而不是 `visible`。

   为什么这次必须修（不只是"洁癖"）：`overflow: hidden` 会**创建新的滚动容器**，
   于是 `position: sticky` 元素的"粘附参照"从视口变成那个容器 ⇒
   报销单新加的底部固定汇总条**完全粘不住**（实测：粘附目标算错，元素被推到视口下方）。
   ⇒ sticky 的祖先链上**一个 overflow != visible 的祖先都不能有**（`clip` 除外，它不建滚动容器）。

   为什么这样修是安全的：`.sf-shell--modules` 的整个意图就是"去掉外壳"
   （background/border/box-shadow 全清），**没有圆角、没有需要裁切的东西**；
   而真正需要 `hidden`（为了让内容跟随圆角裁切）的是**有边框圆角**的 `.sf-shell` 本身。
   受影响的是 3 个用该变体的表单页（达人表单 / 签约公司表单 / 报销单），
   均为「Tab 或分区卡」结构、内部无通栏宽表 ⇒ 已逐一验证无横向溢出。
   用 `.detail-shell--modules` 的两个详情页不受影响（`.detail-shell` 本就没设 overflow）。 */
/* ⚠️⚠️ 这条必须**高特异性**（`.sf-shell.sf-shell--modules` = (0,2,0)），
   因为文件后面（496 行）的 `.sf-shell { background/border/overflow/... }` 特异性 (0,1,0)——
   看似"--modules 更具体"其实**同源单类、平手**，此时**位置靠后者胜**。
   ─────────────────────────────────────────────────────────────────────────────
   🐞 2026-09-19 只发现了 `overflow` 中招（见下方注释），修了它一条；
   🐞 2026-09-28 乐哥报「二级页卡片里面又套了一层卡片，2 层」（本项目的坑**第二次发作**）：
      同一条规则里的 **`background` / `border` / `box-shadow` 也全部没生效** ——
      实测 computed：`<div class="sf-shell sf-shell--modules">` 仍是
      `background: rgb(255,255,255)` + `border: 1px` + `border-radius: 16px`（1064×1324 的大白卡），
      里面再套 2 张 `.module-card` ⇒ **卡套卡（2 层背景）**。
   ⇒ 修法：把当年漏掉的 background / border / box-shadow 一起收进高特异性规则。
      修完该元素四个属性全部归零，内容区只剩模块卡（1 层）。 */
.sf-shell.sf-shell--modules,
.detail-shell.detail-shell--modules {
  background: transparent;
  border: none;
  box-shadow: none;
  overflow: visible;
}
.detail-shell--modules .sf-body,
.sf-shell--modules .sf-body {
  background: transparent;
}
/* hero 与 Tab 栏、Tab 栏与内容之间也要留足呼吸（原来分别是 12px / 16px，显挤） */
.detail-shell--modules .detail-hero {
  margin-bottom: var(--space-5);
}
.detail-shell--modules .sf-body__content,
.sf-shell--modules .sf-body__content {
  /* ⭐ 2026-09-28 上内边距 24 → **16**（乐哥：「二级页面 tab 栏和下面内容模块间距太大了，
     这些间距你全局都排查一遍，要一致」）——改前 tab 行 → 首个内容卡 24px，
     比卡片之间的 16px 大 8px、明显空；改后与全站卡片间距同档（16），节奏一致。
     ⚠️ 这是"唯一的间距来源"（详情页）；表单页的间距由外层 padding-bottom + 本值合成，
        see `.sf-tabs` 的 padding-bottom 归零说明。 */
  padding-top: var(--space-4);
  padding-bottom: var(--space-6);
  /* ⭐ 2026-09-28 左右 padding 24 → 0（乐哥：排期详情上面和下面宽度不一致）——
     改前：页头卡/hero 贴满 shell（宽 1063），而内容卡被这 24px 左右 padding 内缩成 1015，
     上下差 48px。该 padding 是「白色大卡外壳」时代的遗产（卡嵌壳内要留呼吸），
     2026-09-28 卡套卡修复后外壳透明，module-card 自己就是卡，不再需要内缩。
     去掉后内容卡与页头/hero **完全同宽**（1063），且卡内文字(x=332)与 Tab 栏左缘天然对齐。 */
  padding-left: 0;
  padding-right: 0;
}

/* 详情页 hero：头部信息区
   ⭐ 2026-09-28 包盒（乐哥：「排期详情还得优化」）——
      改前是 `background: transparent`（当年设计是"嵌在白色大卡内"），
      2026-09-28 修复卡套卡后外壳透明，hero 失去白底衬托 ⇒ 头像+信息裸摊在灰底上。
      现与 .detail-head 同规格包成白卡：白底 + 1px 边框 + 圆角 16。 */
.detail-hero {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  padding: var(--space-5) var(--space-6);
  background: var(--color-surface);
  border: 1px solid var(--color-frame-border);
  border-radius: var(--radius-lg);
  margin-bottom: var(--space-5);
}

/* 详情页 「内容分组 Tab」：与内容列左右对齐
   ⚠️⚠️ 2026-09-26 托盘化：本条有**两个**必须改的点 ——
     ① **不能再设 `background: transparent`**（原值）：托盘底色画在 `.tabs` 自己身上
        （`--tab-tray-bg`，见上文 `.tabs` 段），清掉就退化成"没有托盘的散排文字"，
        与一级 Tab 又分不开了。
     ② **不能再设 `padding`**（原值 `0 24px`）：padding 特异性高于 `.tabs` 自身的
        `padding: 4px`，会把托盘内边距一起清掉（白块紧贴托盘边、无呼吸）。
        缩进一律用 margin 表达。
   ⭐⭐ 2026-09-28 悬浮吸顶 + 胶囊化（乐哥拍板，三轮原型定稿）：
     · 通栏白底细边框（灰托盘浮在灰底上显脏 ⇒ 白底；贴左缘与卡片对齐，不再缩进 24）
     · 选中项 = 渐变蓝胶囊（白底上白指示块不可见 ⇒ ink 隐藏）
     · sticky 吸顶 top = 侧栏白岛同公式（tabs-bar-height + frame-gap + 4px）
     · 滚动后（.sf-shell.is-scrolled）半透明毛玻璃 + 阴影 =「悬浮」
     · 右侧 `.detail-head__act`（编辑等操作按钮）随 Tab 一起悬浮 */
.sf-shell .tabs {
  margin-left: 0;
  margin-right: 0;
  width: 100%;                        /* 半截托盘 → 通栏（乐哥：右边空太多） */
  justify-content: space-between;     /* 胶囊左、操作按钮右 */
  align-items: center;
  /* ⭐⭐⭐ 2026-10-08 11:36 灰 → **回白**（乐哥：「这个 tab 栏的高度和标准不一样，
     然后背景怎么不是白色的」）
     ────────────────────────────────────────────────────────────────────────────────
     ▸ 10-07 13:55 把本骨架改灰，是对"浅灰托盘"的**过度应用**：乐哥当时要的浅灰是
       **内层托盘**（`.tabs__tray`，装胶囊的那个小框）；而本层是**整行的骨架**
       （通栏、右侧还装操作按钮），它对应的是列表页的 `.page-header--has-tabs`
       **白卡** ⇒ 底色应该**回白**、灰留给内层托盘。
     ▸ 层次目标（与列表页逐值同源）：白内容卡 → **白行骨架** → 浅灰托盘 → 蓝胶囊。
     ▸ 高度：配套把 `.tabs__tray` 桌面内距 4 → 3（托盘 44 → **42**，与
       `.table-tabs--in-header` 同规格）⇒ 本行 = 10×2 + 42 + 2 = **64**，
       与 `.page-header--has-tabs` 白卡**逐像素一致**（乐哥说的"和标准不一样"即指此）。
     ⛔ 别再写回 `var(--tab-tray-bg)` —— 那会让整行变成一条灰带（就是这次报的现象）。 */
  background: var(--color-surface);
  /* ⭐ 2026-09-28 边框加深（乐哥：「达人详情页 tab 没有边框，我记得刚刚沟通过」）——
     原用 `--color-frame-border`(#EBECEF)——那是**卡片留白用的极浅灰**，在白底 tab 行上
     与白卡片几乎融成一片（Δ 仅 20 阶，截图里看不出框）。tab 行是**控件容器**
     （语义同输入框），边界应比卡片更明确 ⇒ 改用 `--color-gray-300`(#DDDDDD，Δ 34)。
     ⚠️ 只加深 Tab 行这一处，不动卡片族（卡片保持 frame-border 的弱边界）。 */
  border: 1px solid var(--color-gray-300);
  /* ⭐ 2026-09-28 二次修：左右内边距 4 → 24（乐哥：「tab 模块的位置当前几乎是完全贴着
     左边的，不合理」）—— 基类 padding:4px 是"灰托盘"时代的紧凑值（托盘是独立小控件）；
     现在是**白底卡**形态，首个胶囊距容器左缘只有 5px（4px + 1px 边框，实测），
     与下方内容卡的文字缩进（24px）差一大截，读起来"贴边不成体"。
     改 24px 后：胶囊距左缘 25px ≈ 内容卡文字（333）、hero 文字——三层对齐成一条竖线。
     ⚠️ 垂直保持 4px（选中胶囊上下贴容器内缘的紧凑感是刻意的，不许一起放大）。 */
  /* ⭐ 2026-09-28 三次修订（乐哥：「底框高度还是贴着胶囊」）—— 垂直 6 → **8px**
     （胶囊距框上下 9px，明显不贴）；水平保持 24（与 `.tabs` 基类同值，胶囊距框左统一 25px）。
     ⭐⭐ 2026-09-28 第五次修订（全站 tab 统一）：**改为 12px 上下** ——
     因为本层现在是"白卡"（里面还要装内层小框 44px），
     12 + 44 + 12 + 2(边框) = **70px**，与一级页白卡（`.page-header--has-tabs`）**逐像素一致**。 */
  /* ⭐⭐ 2026-10-07（乐哥：「那个框有些多余，就是**选项应该贴着骨架**」）9px → **14px**、
   水平 24 → **4px**
   ────────────────────────────────────────────────────────────────────────
   ▸ **为什么改**：选项在框里"飘"着 —— 实测（改动前）**左内距 30px、上下各 15px**，
     在 55px 高的框里四周都空 ⇒ 乐哥的观感是"框有些多余"。
     ⇒ 水平收到 **4px**（选项贴左缘）+ 垂直加到 **14px**（骨架更"撑"，上下有呼吸）。
   ▸ **⚠️ 骨架高度不是塌陷，是刻意调高**：
     高度算式 = 上下内距×2 + 内层托盘 44 + 边框 2
     · 改前 9×2 + 44 + 2 = **64**
     · 改后 14×2 + 44 + 2 = **74**
     ⛔ 曾把内距**归零**试过"完全贴边"，结果骨架塌成 36 —— **那是错的**：
       贴边（水平收窄）与骨架变矮（垂直归零）**根本不冲突**，不该一起做。
   ▸ ⛔ **圆角/边框一律没动**：骨架圆角 16、`.tabs__tray` 圆角 10 + 边框 1px 全部原样。
     曾试过连圆角一起删 —— 也错了：托盘的「padding 4 + 圆角 10 + 边框 1」是
     **44 = 4+34+4+2 的成套规格**，与右侧按钮齐平，门禁 `tab-unify.test.js`
     锁着它与 `.table-tabs--in-header` 同规格 ⛔不许漂移。
   ▸ ⛔ 托盘净高只有 27px 而选项本身 34px ⇒ 选项**本来就溢出填满**托盘，
     那套规格（选项 34 / 托盘 44）**不需要动**（曾误以为"选项没填满"，实测才发现已超）。
   ⚠️⚠️ **全站四处必须同改**（下面三处也是 9/24）：
     `.tabs` / `.sf-shell .tabs` / `.sf-tabs__track` / `.page-header--has-tabs`
     —— 只改一处会让"tab 栏高度"重新分叉（实测分叉后果：两页并排时一高一矮）。
     ⛔ 水平 24 → 4 这一步尤其要同改：它是**给下方内容文字对齐**用的
     （2026-09-28 乐哥要24 是为了让"胶囊左缘 ≈ 内容卡文字左缘"成一条竖线），
     改 4 后那条对齐关系会变，若不同步会看出胶囊与下方文字错位。 */
  /* ⭐⭐⭐ 2026-10-07 13:40 垂直 14 → **9px**（乐哥：「左右都是贴着边的（没问题），
     但是**高度上下余量太多了**」）—— 四处同改 ⛔勿单改。
     实测容器 84 → 74（省 10px）；9 是 09-28「矮个 10%」的实测值。 */
  /* ⭐⭐⭐ 2026-10-07 14:40 **9px 4px → 10px**（乐哥：「tab 上下和左边的间距也不一致」）
     —— 上下左右统一，理由与 `.page-header--has-tabs` 那处完全相同（三处同源）。
     ⛔ 别再写 9/4 这类"上下一个值、左右另一个值"的组合：它没有推导过程，
        是多轮调整叠出来的，每改一处另外几处就对不上。 */
  padding: 10px;
  position: sticky;
  /* ⭐⭐⭐ 2026-10-07「边框锁死 · 框内滚动」：`+ 4px` 删掉（与 layout.css:1656、
     components.css:878 **三处必须同值**，否则同屏出现时一处高一处低）。
     吸附参照从视口变成 `.app-main` 后，顶栏下沿 = 框顶 + tabs-bar-height。 */
  top: var(--tabs-bar-height);
  z-index: 60;                        /* 高于内容卡，低于顶栏(1100)/下拉(1000) */
  transition: box-shadow var(--motion-base) var(--motion-ease-standard),
              background-color var(--motion-base) var(--motion-ease-standard),
              border-color var(--motion-base) var(--motion-ease-standard);
}
.sf-shell .tabs .detail-head__act {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
/* ⭐ 2026-10-11：签约公司详情页的**页级 ⓘ**（挂在 Tab 行右端、编辑公司左边）——
   收纳原本占一层楼高的「挂载与口径是两件事」提示卡（乐哥：「注释进？」）。
   ⚠️ 图标放大到 17px：这一排是 44px 的 `btn--lg`，沿用 `UI.tip` 默认的 13px
      会明显偏小、看着像标签的一部分（13px 是"跟字段名"那一档，见 pages.css 的注释）。
   ⚠️ `flex: none`：`.detail-head__act` 是 flex，不写会被压缩。
   ⚠️ 颜色沿用 `.th-note` 的 `--color-muted`（hover 转主色），与全站 ⓘ 同一套。 */
.sf-shell .tabs .detail-head__act .th-note.co-hd-tip {
  flex: none;
  margin-left: 0;
  margin-right: var(--space-1);
}
.sf-shell .tabs .detail-head__act .th-note.co-hd-tip svg {
  width: 17px;
  height: 17px;
}
/* 悬浮态：**克制玻璃**（乐哥 2026-09-28 拍板：「玻璃用克制玻璃」）——
   透光渐变 + 饱和度提升 + 上缘高光：比原「白 82%」透（能看出底下内容在动），
   又比全液态玻璃克制（文字区仍清晰可读）。
   ⚠️ 边框**保留**（乐哥：「这个页面tab不需要边框吗？」）——基类的
      `border: 1px solid var(--color-frame-border)` 继续生效，玻璃只换底与阴影；
      ⛔ 不许把边框改成白色/透明（白边框在白卡上等于没有框，实测过）。 */
/* ⭐⭐⭐ 2026-09-28 第五次修订：**贴顶才变「液态玻璃」**（乐哥拍板两条）
   ──────────────────────────────────────────────────────────────────────────
   乐哥：「这个标签，我如果不滑动，刚进来的时候应该没那么重的阴影吧，
         是不是和其他内容叠加才有」+「这个透白毛玻璃不行，做能液态玻璃那种」
         + 三档里选了 **L3**。
   ⛔ 改前挂 `.is-scrolled`（滚 50px 就触发）——见 app.js `updateStuck` 的实测说明：
      各页 tab 的初始 y 不同（228 / 249 / 322），贴顶发生在滚 138~232px，
      固定 50px 会在"还没贴顶、下面还有 88px 空档"时就变玻璃 ⇒ 玻璃下面没有内容可透，
      只显得"莫名糊了一块"；等真贴顶时玻璃反而"用旧了"。
   ✅ 改挂 `.is-stuck`（js 用 `rect.top <= stickyTop` 判定，与页面/屏高无关）。 */
.sf-shell .tabs.is-stuck {
  background: var(--liquid-bg);
  backdrop-filter: var(--liquid-blur);
  -webkit-backdrop-filter: var(--liquid-blur);
  border-color: var(--liquid-edge);
  box-shadow: var(--liquid-shadow);
}
/* 内层小框**一起**做玻璃（乐哥：「内层小框要不要一起做玻璃」→ 做）——
   ⛔ 不做的后果（已实测）：白卡变成玻璃后，小框若仍是**不透明白底**，
      看上去就是"玻璃上贴了一张白纸"，层次全乱。
   ⭐ 2026-10-05：**边框色不再跟着变** —— 与 layout.css 里
      `.page-header--has-tabs.is-stuck .table-tabs--in-header` 同步改（同一套东西的两份实现）。
      原因：`--liquid-tray-edge` 是半透明白，叠在白卡上几乎不可见 ⇒ 吸顶时"边框消失"，
      与静态态的实色 `--color-border` 不一致（乐哥：「悬浮的时候就没了，这个得统一」）。
      ⚠️ 改这类"吸顶态外观"必须**两处一起改**，否则列表页与表单页的吸顶观感会分叉。 */
.sf-shell .tabs.is-stuck .tabs__tray {
  background: var(--liquid-tray-bg);
  border-color: var(--color-border);
}
/* 胶囊项尺寸（与 `.tab` 基类同值；选中态由基类统一定义 ——
   ⭐ 2026-09-28 全站胶囊化后，原 scoped 选中态规则（含压 transparent 的 :has 分支）已删：
   基类 `.tab.is-active` 现在直接给渐变 + 白字，无需在此重复/提权）。
   ⚠️ 本规则特异性(0,3,0)高于基类 `.tab`(0,1,0) ⇒ 基类改了这里**必须同步改**
      （本次实测：只改基类 34px，本页仍显 32px —— 就是被这条压住的）。 */
.sf-shell .tabs .tab {
  padding: 0 16px;
  border-radius: var(--radius-seg);
  height: 34px;
}
/* Tab 行里的操作按钮：浅蓝轻按钮（乐哥 2026-09-28 拍板）——比 ghost 醒目、
   不与选中胶囊抢注意力；主按钮（如「佣金配置」）保持原样 */
.sf-shell .tabs .detail-head__act .btn {
  /* ⭐ 2026-09-28 行内按钮降档（乐哥：「右边编辑、佣金配置按钮太大了，不协调」）——
     原为 btn--lg 的 44px（标准档），比同行 tab 项(32px)高一大截，且把整行撑到 54px。
     降到 **34px（紧凑档）**：与同站"列表工具栏控件"同档，行高随之回到 44px
     （34 + 内边距 4×2 + 边框 1×2），与胶囊 tab 的紧凑感一致。
     ⚠️ 只降尺寸、不动颜色/语义（浅蓝轻按钮 + 主按钮的层级保持）。 */
  height: var(--control-height-sm);
  min-height: var(--control-height-sm);
  padding: 0 var(--space-4);
}
/* ⭐ 2026-10-08 行内按钮升到「行标准」**42**（--control-height-row）（乐哥：
   「这个 tab 栏的高度和标准不一样」）——与托盘 42 同高、与列表页白卡的行内控件同档
   （10-07 19:50 乐哥拍板「其他控件升到 42」）。手机端保持 34（窄屏零回归）。
   ⛔⛔ **本块的源码位置必须在上面那条 `.detail-head__act .btn` 基础规则之后** ——
      两者选择器相同（0,4,0），层叠**后者胜**；写着 42 也在源码里"看得见"，
      但放前面时 computed 仍是 34（本次实测：加在前面 ⇒ 真机量到 34，白忙一轮）。 */
@media (min-width: 769px) {
  .sf-shell .tabs .detail-head__act .btn {
    height: var(--control-height-row);
    min-height: var(--control-height-row);
  }
}
.sf-shell .tabs .detail-head__act .btn--ghost {
  background: var(--color-primary-50);
  border-color: transparent;
  color: var(--color-accent-text);
  font-weight: var(--font-weight-semibold);
}
.sf-shell .tabs .detail-head__act .btn--ghost:hover:not(:disabled) {
  background: var(--color-primary-100);
  border-color: transparent;
  color: var(--color-accent-text);
}
/* 信息卡收缩（滚动后一行：头像 64→36，高度 ~127→60，过渡动画）
   ⚠️ 只动 height/padding（不用 transform + fill both —— 会静默创建层叠上下文遮下拉面板） */
.sf-shell .detail-hero {
  transition: height var(--motion-fast) var(--motion-ease-standard),
              padding var(--motion-fast) var(--motion-ease-standard);
  overflow: hidden;
}
.sf-shell .detail-hero .detail-avatar {
  transition: width var(--motion-fast) var(--motion-ease-standard),
              height var(--motion-fast) var(--motion-ease-standard);
  flex: none;
}
.sf-shell.is-scrolled .detail-hero {
  height: 60px;
  padding-top: var(--space-2);
  padding-bottom: var(--space-2);
  margin-bottom: var(--space-2);
}
.sf-shell.is-scrolled .detail-hero .detail-avatar {
  width: 36px;
  height: 36px;
}

.detail-avatar {
  width: 64px;
  height: 64px;
  border-radius: var(--radius-pill);
  background: var(--color-primary-100);
  color: var(--color-accent-text);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--font-size-xl);
  font-weight: var(--font-weight-semibold);
  flex-shrink: 0;
  overflow: hidden;
}

.detail-avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.detail-name {
  font-size: var(--font-size-xl);
  font-weight: var(--font-weight-semibold);
  color: var(--color-fg);
}

.detail-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-2);
}

/* 所属商务（关联人员）头像列表 */
.detail-bd-list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

/* 达人列表「所属商务」列：带头像 chip */
.bd-cell-list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
}
/* ⭐ 达人列表的「所属商务」单元格：**多个商务上下各占一行**（2026-10-13）
   ──────────────────────────────────────────────────────────────────────────────
   ▸ 改前：它靠"列放不下"才折行 —— 该列**没写死宽度**、被自适应压到 128px
     （< 两个 chip 的 138px）⇒ 目前看着是 2 行。但这是**偶然**：窗口再宽些、
     列被分到 ≥138px，就会自己变回横排一行 —— 与排期列表（已强制竖排）不一致。
   ▸ 改后：和排期列表用同一套规则**显式竖排**，两处行为一致、且与窗口宽度无关。
   ⚠️ 只挂在**本页这个单元格**（`.inf-bd-cell`）上，不动公共件 `.bd-cell-list`：
     详情页 / 货品明细 / 业绩佣金页的商务仍是横排（乐哥没提，不擅自扩大范围）。 */
.inf-bd-cell .bd-cell-list {
  flex-direction: column;
  align-items: flex-start;
}
.bd-cell-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 1px var(--space-1) 1px 1px;
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-pill);
  background: var(--color-surface-2);
  font-size: var(--font-size-xs);
  color: var(--color-fg);
}
.bd-cell-avatar {
  padding-top: 1px; /* 补偿中文字面偏上 */
  width: 20px;
  height: 20px;
  border-radius: var(--radius-pill);
  background: var(--color-primary-100);
  color: var(--color-accent-text);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-medium);
  flex-shrink: 0;
  overflow: hidden;
}
.bd-cell-avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.bd-cell-name {
  max-width: 140px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.detail-bd-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 1px var(--space-1) 1px 1px;
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-pill);
  background: var(--color-surface-2);
  font-size: var(--font-size-xs);
  color: var(--color-fg);
}
.detail-bd-avatar {
  width: 20px;
  height: 20px;
  border-radius: var(--radius-pill);
  background: var(--color-primary-100);
  color: var(--color-accent-text);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-medium);
  flex-shrink: 0;
  overflow: hidden;
}
.detail-bd-avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* 详情页字段：无线布局，靠字号、行间距、留白区分层级（label 上 + value 下） */
.info-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--space-5) var(--space-4);
  padding: var(--space-2) 0;
}

.info-item {
  padding: 0;
  background: transparent;
  border: none;
  border-radius: 0;
}

.info-item__label {
  font-size: var(--font-size-xs);
  color: var(--color-muted);
  line-height: 1.4;
  margin-bottom: var(--space-1);
}

.info-item__value {
  font-size: var(--font-size-base);
  color: var(--color-fg);
  line-height: 1.5;
  word-break: break-all;
}

/* 详情页区块小标题（佣金配置 / 开票豁免 / 平台账号 等） */
.detail-section__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  flex-wrap: wrap;
  margin-bottom: var(--space-3);
}

.detail-section__title {
  font-size: var(--font-size-lg);   /* ⭐ 2026-09-28：14(sm) → 16(lg) —— 板块标题与正文(14)同号会淹没，
                                       提到「卡片标题」档；字重同步 500 → 600（与 pages-schedule 对齐） */
  font-weight: var(--font-weight-semibold);
  color: var(--color-fg);
  margin-bottom: var(--space-3);
}

.detail-section__head .detail-section__title {
  margin-bottom: 0;
}

/* ══════════════════════════════════════════════════════════════════════════════
   「内容分组 Tab」—— 灰托盘 + 白块（2026-09-26 全站统一）
   ──────────────────────────────────────────────────────────────────────────────
   ⭐ 为什么改（乐哥 2026-09-26）：「部分页面的二级 tab，样子是不是得改改，
      当前名字下加下划线那种，和一级的冲突了」。
     冲突是真实的：业绩总表这一页，顶部「全部｜待维护｜已维护」（一级：表格视图切换）
     和卡内「直播业绩｜私域业绩」（二级：内容分组）**是同一种视觉语言** ——
     都是「文字 + 名字下一条主色下划线」，只差高度（32 / 44px）—— 用户得靠位置猜层级；
     而且二级还多一条横贯基线，比一级更"重"，反而更像主 tab。

   ✅ 解法：两级各用一套语言，靠**有没有容器**一眼区分 ——
     · 一级（表格视图切换，`.table-tabs--in-header`）＝ **散排文字 + 下划线**（页面级导航）
     · 二级（本段，`.tabs`）＝ **灰托盘 + 选中白块**（组内切换）
     这也是主流后台（TDesign / Ant Design 的 segmented）最通用的二级切换语言。

   ⚠️⚠️ 这是 `.tabs` / `.tab` 的**唯一定义处**（原来它也叫「一级 Tab（全局通用）」，
      注释已按 2026-09-26 的命名纠正：它承载的是"页面内内容分组"，不是页面级导航）。
      全站 7 处在用：业绩总表、达人详情、达人表单（新增/编辑）、签约公司详情/表单、
      新建排期/排期详情，外加两个弹窗内（成本下钻、新增用户）。
      ⇒ 将来改尺寸/配色**只改这里**。（本项目反复吃亏的一条：同一件事两套实现必然漂移。）

   ⚠️ 尺寸从 44px/16px 降到 **32px/14px**：托盘方案的项是"块"不是"栏"，
      44px 会让托盘高到 52px（44+8）盖过页面标题行；32px 是紧凑档（与表格内 TAB 项同高）。
      字号跟着降到 14px —— 且**一级反而更大（16px）**，层级方向变正确了
      （原来二级 16px 比一级的 16px 同级、只靠高度区分，本末倒置）。
   ══════════════════════════════════════════════════════════════════════════════ */
.tabs {
  display: flex;
  align-items: center;
  gap: 2px;                              /* 项间距 24 → 2（容器内项是紧邻的块，不是散排文字） */
  /* ⭐ 2026-09-28 三次修订（乐哥：「底框高度还是贴着胶囊」+「胶囊和底框左边间距没统一」）——
     实测改前：胶囊距框 左 25px(详情) vs 7px(表单/卡内) —— **左边距三处不一**；垂直 7px 仍偏贴。
     统一为 **8px 24px**：垂直 8（胶囊距框上下 9px，有明显呼吸）；
     水平 24（**三处同值** —— 胶囊距框左统一 25px，与详情页对齐）。
     ⚠️ 垂直 8 是"先给一档"的保守值；若乐哥仍觉贴可提到 10。
     ⚠️ 水平 24 是三处**统一口径**（`.tabs` 基类 / `.sf-shell .tabs` / `.sf-tabs__track` 同值）。 */
  /* ⭐⭐ 2026-09-28 垂直内边距 12 → **9px**（乐哥：「这个 tab…感觉有一点点高了，要不矮个 10%」）——
     容器高 = 9×2 + 内层框44 + 2(边框) = **64**（原 70，矮 8.6%）；
     无内层框的页面（编辑排期）60 → **54**（正好 −10%，命中乐哥给的幅度）。
     ⚠️ 12 → 9 是**全站四处同改**（`.tabs` / `.sf-shell .tabs` / `.sf-tabs__track` /
        `.page-header--has-tabs`）——只改一处会让"tab 栏高度"重新分叉。
     ⚠️ 9px 不是 `--space-*` 档位值（令牌里没有 9）—— 它由"矮 10%"倒推得到，
        不是随手写的；改它之前先看这句注释。
     ⚠️ 胶囊（34）**不动**：乐哥当天上午刚要求过"胶囊太矮了"提到 34（见该处注释），
        这次说的"高"是**容器**厚，不该再把胶囊压回去。 */
  /* ⭐⭐⭐ 2026-10-07 13:40 垂直 14 → **9px**（乐哥：「左右都是贴着边的（没问题），
     但是**高度上下余量太多了**」）—— 与 `.page-header--has-tabs` **同改** ⛔勿单改。
     实测：容器 84 = 14×2 + 托盘 54 + 边框 2 ⇒ 改 9 后容器 **74**，省 10px。
     9 是 09-28「矮个 10%」的实测值（当时 12→9，容器 70→64），不是随手写的。 */
  /* ⭐⭐⭐ 2026-10-07 14:40 **9px 4px → 10px**（乐哥：「tab 上下和左边的间距也不一致」）
     —— 与前两处同源，理由相同：9/4 是多轮叠出来的、上下不对称的值。 */
  padding: 10px;
  background: var(--tab-tray-bg);        /* ⚠️ 这是**灰**（#F1F3F5）——
                                            本基类同时服务两种角色，这里的灰是给**卡内托盘态**
                                            （卡内那行没有内层托盘，本层即托盘）；
                                            **骨架态**（详情页/表单页外层）的白由
                                            `.sf-shell .tabs` 覆盖。
                                            ⛔ 注释别再写"白底"：2026-10-08 令牌
                                            `--tab-tray-bg` 从白改成 Apple 灰时，
                                            所有引用它的**骨架**选择器都被静默刷灰
                                            （`.sf-shell .tabs` / `.sf-tabs__track`
                                             各犯过一次，见那两处注释）。 */
  /* ⭐ 2026-09-28 补外框（乐哥：「有的有外框，有的没有，需要补进去」）——
     白底容器必须有完整 1px 边框才是"一张卡"；缺框时它在白卡/页头内彻底隐形。
     ⚠️ 原 `border-bottom: 0`（当年为清掉继承来的下划线基线）已删 —— 现在本块自己
        定义了完整边框，再写 border-bottom:0 会把底边打掉（三边有框、底边缺口）。 */
  border: 1px solid var(--color-frame-border);
  border-radius: var(--tab-tray-radius);
  /* ⭐ 2026-09-26：指示块（.tabs__ink）的定位参照 —— 它必须 `position: relative`。 */
  position: relative;
  /* ⚠️ 托盘要**收缩到内容宽**（不是通栏）：
     通栏的灰底会变成一条"分区横条"，与页面标题行、下方工具栏撞语言。
     ⚠️ 用 `width: fit-content` 而不是 `display: inline-flex`：inline 级元素会被父级的
        `text-align: center` 影响（达人详情页 `.detail-shell` 内就有居中上下文），
        `fit-content` 在 block 容器里恒定左对齐，不受 text-align 干扰。 */
  width: fit-content;
  max-width: 100%;
  flex-wrap: wrap;                       /* 窄屏/项多时换行，不出横向滚动条
                                            （⚠️ 不用 overflow-x: auto —— 它会让 overflow-y
                                             也变成 auto，托盘高度恰好 40px 时可能长出竖滚动条，
                                             本项目 2026-09-14 已在 .table-tabs 踩过同一个坑） */
}

/* ⭐⭐ 2026-10-08 **卡内** tab 行（`wrapTabTrays()` 刻意跳过 `.card` ⇒ 没有内层托盘）
   ──────────────────────────────────────────────────────────────────────────────
   ▸ 上面那条 `.tabs` 基类是**外层骨架**规格（10px 内距 + 16 圆角 = 64 高），
     它靠内层 `.tabs__tray`（灰、3 内距、10 圆角 = 42）表达"托盘"。
     但卡内的 tab 行**没有内层托盘**（JS 见 `.card/.modal/...` 即跳过包装）
     ⇒ 本层自己就是托盘，却套着骨架的 10px 内距 ⇒ **56px 的厚灰带**
     （全站巡检抓到：`/performance` 的 `#pf-main-tabs`）。
   ▸ 按"卡内托盘"标准对齐：3 内距 + 10 圆角 = **42**，与 `.tabs__tray` /
     `.table-tabs` 同档同值。
   ⚠️ 用 `:not(:has(> .tabs__tray))` 兜住"万一将来卡内也包了托盘"的情况：
       那时本层仍是骨架、不该被压成托盘。 */
.card > .tabs:not(:has(> .tabs__tray)) {
  padding: 3px;
  border-radius: var(--radius-md);
}

/* ══════════════════════════════════════════════════════════════════════════════
   Tab 托盘的「指示块」—— 选中白块**滑过去**，不再"重新贴一次"
   ──────────────────────────────────────────────────────────────────────────────
   ⭐ 2026-09-26 动效（乐哥：「轻盈灵动」）—— 这是最"灵"的一处：
     改前：选中项自己长白底（`background` 直接切换），**换个 tab 就是"啪"地换位置**，
           用户看不出"我从哪个切到了哪个"。
     改后：托盘里只有**一个**白块，切换时它从旧位置滑到新位置 ⇒ 位移本身就是"从哪到哪"的说明。

   ⚠️⚠️ 白块由 JS 注入（app.js 的 paintTabInk），因为"哪个 tab 是选中的、它在什么位置"
      只有运行时才知道。**CSS 必须能优雅降级**：JS 没跑/跑失败时，回退成"选中项自己长白底"
      （见下面的 `:not(:has(.tabs__ink))` 那条）—— 没有这层兜底，JS 一挂就是"看不出哪个被选中"。
   ⚠️ 用 `:has()` 判"有没有装指示块"，而不是给 `.tabs` 加一个 `.has-ink` 类：
      类名要 JS 维护，而 JS 注入 ink 和加类**可能不同步**（本项目反复踩"两处状态"）。
      用结构判据 `:has()` 天然同步。
   ══════════════════════════════════════════════════════════════════════════════ */
.tabs__ink {
  position: absolute;
  /* ⭐⭐ 2026-10-08 14:1x **表格 tab 族（`.table-tabs`）也接 ink**（乐哥：「这个一级页面
     tab 切换的时候，tab 滑动动画还是没有」）——
     这一族此前**没有 ink**：`wrapTabTrays` 对 `--in-header` 直接跳过（当年它是
     "透明底 + 下划线"形态，选中靠 `::after` 表达），全站统一成实心胶囊后没人给它补 ink
     ⇒ 选中块只能"自己长蓝底" ⇒ 切换瞬间跳。
     ⚠️ `.table-tabs` 必须补 `position: relative`（它此前没有）—— ink 是绝对定位，
        缺参照会跑到外层祖先去（`.sf-tabs__track` 2026-09-26 就栽过一次，白块高 1757px）；
     ⚠️ 圆角用 `--radius-md`(10px) 与"选中胶囊"逐值同源（in-header 那族胶囊就是 10px；
        ink 默认的 8px 会有 2px 差，在纯色块上看得出来）。 */
  /* ⭐ 2026-09-26：位置与尺寸**全量由 JS 给**（width / height / translate(x, y)，
     见 app.js 的 paintTabInk）。原实现是 CSS 固定 `top:4px; bottom:4px; left:0` + JS 只给 x ——
     那要求"托盘永远单行、且每个 tab 都恰好 托盘高−8"；托盘是 `flex-wrap: wrap`，
     窄屏换行后第二行的 tab 会拿到第一行的 y（x 对、y 错）。
     改成 JS 量 active 的 offset* 后：单行结果与原来**逐像素一致**
     （4px 内缩 = offsetTop 4 / 高度 32），换行也正确。
     ⚠️ 这条与三个宿主的 `position: relative` 是**配对**的：一个负责"参照对了"，
        一个负责"位置对了"，缺任一条白块都会跑偏（乐哥 09-26 实报过一次）。 */
  top: 0;
  left: 0;
  border-radius: var(--tab-tray-item-radius);
  /* ⭐⭐⭐ 2026-10-08 12:5x **选中框改由 ink 承担 + 恢复滑动**（乐哥：「tab 选中那个框，
     能左右丝滑地滑到下一个选中框」）
     ──────────────────────────────────────────────────────────────────────────
     ▸ 改前：ink 是**白块**，且 2026-09-28「全站胶囊化」后被 `display:none` 全站隐藏
       （当时理由：白块在白底容器上看不见）⇒ 选中态退回"每个 tab 自己长蓝底"
       ⇒ 切换时**蓝底瞬间跳**（旧蓝底消失、新蓝底出现）= 乐哥这次报的
       「tab 切换动画还是没有」。
     ▸ 现在：ink 的外观改成**与 `.tab.is-active` 完全一致的品牌渐变胶囊**，
       于是它那套既有的「JS 定位 + CSS transition」重新生效
       ⇒ 选中框从旧位置**滑**到新位置（位移本身说明"我从哪切到哪"）。
     ⚠️ 必须配对改 `.tab.is-active`（背景→transparent、阴影→none，见下方），
        否则"ink + tab 自己"两层蓝底叠加、且其中一层是瞬间跳。
     ⚠️ 降级兜底见 `.tabs:not(:has(> .tabs__ink))` 那条（JS 没跑时选中态照常可见）。 */
  background: var(--gradient-brand);
  box-shadow: var(--shadow-pill-active);
  pointer-events: none;                  /* 纯装饰：不拦截点击，下面照常点得到 tab */
  will-change: transform;                /* 滑动属性：提前升层，减轻滑动时的重绘抖动 */
  /* ⚠️ 曲线：**指示器平移专用** = `--motion-ease-glide`（慢起慢收、中段匀速、无过冲）。
     ⛔ 别用 --motion-ease-in-out：那是"快起 + 落位过冲"的位移曲线，实测 45ms 就走完
        76% 行程 ⇒ 读作"唰一下到位"，与乐哥要的「**丝滑**地滑到下一个」相反。
     ⭐ 2026-10-08 时长 --motion-base(155) → **--motion-tab-slide(250)**：
       155ms 读作"跳一下就走完了"，250ms 才有"滑行"的观感；
       且与"内容面板滑动"（is-panel-enter-*）同源同时长。 */
  transition: transform var(--motion-tab-slide) var(--motion-ease-glide),
              width var(--motion-tab-slide) var(--motion-ease-glide),
              height var(--motion-tab-slide) var(--motion-ease-glide),
              /* ⭐ 2026-09-29：**透明度必须无过冲曲线** —— 原为 `--motion-ease`(过冲 13.6%)，
                 透明度冲到 1 以上会被 clamp、回落时肉眼读作"闪一下"（本项目踩过的同类坑）。
                 位移三条仍用 in-out（滑过去的元素，两端缓才不吃力）。 */
              opacity var(--motion-fast) var(--motion-ease-standard);
}
/* ⭐⭐⭐ 2026-10-08 12:5x **撤掉「全站隐藏 ink」**（乐哥：「tab 选中那个框，能左右丝滑地
   滑到下一个选中框」）
   ──────────────────────────────────────────────────────────────────────────────
   ▸ 2026-09-28 曾把 ink 全站隐藏 —— 当时它还是**白块**，在白底容器上看不见，
     于是选中态退回"每个 tab 自己长蓝底" ⇒ 切换时蓝底**瞬间跳**、没有滑动
     （这就是乐哥 2026-10-08 报的「tab 切换动画还是没有」的根因）。
   ▸ 现在 ink 已改成**品牌渐变胶囊**（见基类那条），必须恢复显示 ——
     它才是"能滑动的那个选中框"。
   ▸ 选中态的**文字颜色**仍由 `.tab.is-active` / `.sf-tab.is-active` 表达
     （ink 是纯背景块、不含文字），所以即使 ink 缺失也"看得出哪个被选中"，
     另见 `.tabs:not(:has(> .tabs__ink))` 的完整兜底（背景/阴影也退回 tab 自己）。 */
/* 未选中项 hover：**只变文字色，不给底色**（2026-10-08 15:3x 乐哥：「二级页面这个白色的
   卡片还没去除干净」）
   ─────────────────────────────────────────────────────────────────────────────────────
   ▸ 真机取证（乐哥截图 2582×1034，沿胶囊中线 y=300 逐点取色）：
       蓝块右侧紧邻约 50 CSS px 是 **(248,250,250)** —— 正是 `rgba(255,255,255,0.55)`
       压在灰托盘(241,243,245)上的结果 ⇒ **那不是"一张卡"，是鼠标悬停块**（他截图时
       鼠标正停在「盈亏」上）。在灰托盘里，这块半透明白读起来就像"第二个选中/一张白卡"。
   ▸ 改法：与**一级页胶囊**（`.pill-filter__item:hover` / `.seg__btn:hover` 一直只变文字色）
     统一 —— 悬停只变文字色，不给底色。可点性由文字色承担（紫/主色字足够）。
   ⛔ 别再加回底色：`.is-inking` 那套"滑动期间把 hover 块设透明"的兜底仍然保留
     （它现在没有底色可挡，但保留它可避免以后有人加回底色时又出"切断蓝块"的老毛病）。 */
.tab:not(.is-active):hover { color: var(--color-accent-text); }
.sf-tab:not(.is-active):hover { color: var(--color-accent-text); }

/* ⭐⭐⭐ 2026-10-08 14:1x **滑动途中不显示 hover 块**（乐哥报的"粘滞感"第一因）
   ──────────────────────────────────────────────────────────────────────────────
   🐞 机理：hover 块叠在 tab 自己身上（`position:relative`，同层后者在上），
      而 ink（选中蓝块）是宿主**首个子元素、绘制在最下** ⇒ 蓝块滑过被悬停的那一项时
      **被那块底切掉一截**，观感就是"蓝块卡在两项之间挪不动"（粘滞/卡住）。
   ✅ 做法：`paintTabInk` 在**过渡分支**给宿主挂 `.is-inking`（约 320ms）
      （首帧定位那一支不挂 —— 否则开机就会闪一下）；
      这一期间把 hover 块设成透明，保证"正在滑"的那 250~300ms 里没有任何东西压住蓝块。
   ⚠️ 三族都要覆盖（`.tabs` / `.sf-tabs__track` / `.table-tabs`）—— ink 的宿主就是这三个。 */
.tabs.is-inking .tab:not(.is-active):hover,
.sf-tabs__track.is-inking .sf-tab:not(.is-active):hover,
.table-tabs.is-inking .pill-filter__item:not(.is-active):hover,
.table-tabs.is-inking .seg__btn:not(.is-active):hover {
  background: transparent;
}

/* ⭐⭐⭐ 2026-10-08 15:2x 曾在这里放「滑动期间把新选中项文字**压回常态色**」（压深）——
   2026-10-08 19:2x **已整条删除**。
   ────────────────────────────────────────────────────────────────────────────────────────
   ▸ 15:2x 加它的原因（仍然成立，只是换了实现）：`.is-active` 的 color 会**立刻**变白，
     而 ink 要 250ms 才滑到 ⇒ 白字压在 `#F1F3F5` 浅灰托盘上 = 「一块白卡一闪而过」。
   ▸ 19:2x 为什么删：压深本身成了新的观感问题 —— 逐帧实测（`diag-tab-text-flash.js`）：
       hover 时文字是**蓝**(0,100,224)，点击后被压成**深灰**(63,71,83)，
       200ms 后再翻白 ⇒「**先闪黑子然后再显示白字**」（乐哥 19:21 原话）。
   ▸ 现在由各 `.is-active` 规则上的
       `transition: color 0s linear var(--motion-tab-slide)`（延迟一个滑动时长 + 瞬时）承担：
       推迟期间文字**保持原色**（不再被压成深灰）⇒ 既没有"白卡一闪"、也没有"闪黑"，
       且翻白时刻仍**精确等于蓝块到位那一帧**。
   ⛔ 别再加回任何"压深/改色"规则：那会让 hover 的蓝字突变成深灰（= 闪黑）。 */

.tab {
  position: relative;
  /* ⭐ 2026-09-28 32 → **34**（乐哥拍板全站 tab 统一）：与一级页胶囊同高。
     一级页 `.table-tabs--in-header .pill-filter__item` 一直是 34 —— 统一时以它为准。 */
  height: 34px;
  padding: 0 16px;                       /* 14 → 16：与一级页胶囊同值（横向留白也统一） */
  background: transparent;
  border: none;
  border-radius: var(--radius-seg);      /* 8 → 6：与一级页胶囊同值 */
  color: var(--color-fg-2);              /* 未选中：正文色（原 --color-muted 偏浅，
                                            在浅灰托盘上对比度更低） */
  font-size: var(--font-size-sm);        /* 16 → 14（令牌 sm） */
  font-weight: var(--font-weight-medium);
  white-space: nowrap;
  cursor: pointer;
  transition: color var(--motion-fast) var(--motion-ease-standard),
    background-color var(--motion-fast) var(--motion-ease-standard);
}

/* ⚠️ 下划线指示条的 `::after` 规则**已整条删除**（不是置空）。
   原因：托盘方案里"选中"由白块 + 轻投影表达，若沿用老的 `::after` 下划线描边，
   选中态会同时出现"白块 + 下划线"两个指示 —— 一个控件两种"选中"表达。
   保留一条永不生效的空规则（如 `content: none`）只会让后来者以为它还在用，故直接删。 */

/* ⚠️ 2026-09-26：hover 底色**排除选中项**（选中项已是当前项，不需要"可点"暗示）。
   ⭐ 2026-09-28：选中态改**渐变蓝胶囊**（全站统一，白底容器上唯一可见的选中表达）。 */
.tab:not(.is-active):hover {
  color: var(--color-accent-text);
  /* 2026-10-08 15:3x 去掉 hover 底色（乐哥：「白色的卡片还没去除干净」）——只变文字色 */
}

.tab.is-active {
  /* ⭐⭐⭐ 2026-10-08 12:5x **背景与阴影让给 `.tabs__ink`**（乐哥：「tab 选中那个框，
     能左右丝滑地滑到下一个选中框」）
     ──────────────────────────────────────────────────────────────────────────
     ▸ 选中框（蓝底 + 浮起阴影）现在由 **ink** 画 —— 它是托盘里唯一的那个块，
       切换时从旧位置**滑**到新位置。
     ▸ 本规则只保留"文字层面"的选中表达：白字 + 字重加粗。
     ⛔ 别把 background / box-shadow 加回来：那会变成两层蓝底
        （一层在滑动、一层是瞬间跳的硬块）。
     ▸ 降级兜底（JS 没跑、没有 ink 时）：见下面 `.tabs:not(:has(> .tabs__ink))` 那条。 */
  background: transparent;
  color: var(--color-accent-on);          /* 白字（蓝底滑过来之后才读得到） */
  /* ⭐⭐⭐ 2026-10-08 19:2x **翻白改成"延迟到蓝块到位 + 瞬时"**（乐哥：「现在切换的时候，
     还是**先闪黑子**然后再显示白字，很奇怪不自然」）
     ──────────────────────────────────────────────────────────────────────────────
     🐞 旧写法（`transition: none` + `.is-inking` 压深）为什么"闪黑"——逐帧实测
        （`scripts/frontend-debug/diag-tab-text-flash.js`，rAF 每帧 + 真实鼠标路径）：
          hover 中：文字 = **蓝** rgb(0,100,224)（hover 反馈色）
          点击后  ：被 `.is-inking` 压成 **深灰** rgb(63,71,83)  ← **"闪黑"就是这一跳**
          到位后  ：瞬时白
        ⇒ 蓝 → 黑 → 白，中间那次"从蓝突变到深灰"就是他说的不自然。
     ✅ 新写法：目标色仍是白，但**延迟一个滑动时长**才生效、且 **duration = 0s**：
        推迟期间文字**保持原色**（hover 过 = 蓝、没 hover = 未选中灰）⇒ 没有"变黑"这一跳；
        到点瞬时翻白，正好是**蓝块到位那一帧**。
     ⛔ 别写回 `transition: none`（颜色立即变白 ⇒ 白字压浅托盘 = "白卡一闪"，15:2x 报过）；
     ⛔ 也别给 color 任何**非 0 的 duration**（15:4x 栽过：delay 180 + 渐变 155 把翻白推到
        ≈500ms，蓝块 250ms 就到了 ⇒"蓝块压着字、字还是深色"）。
     完整机理与三处反面教训见 table.css `.table-tabs .pill-filter__item.is-active` 那条长注释。 */
  transition: color 0s linear var(--motion-tab-slide);
  font-weight: var(--font-weight-semibold);
  box-shadow: none;
  /* ⭐⭐⭐ 2026-10-08 15:4x **删掉 `transition-delay: 180ms`**（原来是"延迟变色"）
     ────────────────────────────────────────────────────────────────────────────────
     乐哥：「切换的时候，**框框到文字的时候文字还是黑色的**，不对，要框进去的时候得是白色的」。
     ▸ 旧机制为什么不够：delay 180ms 是"蓝块快到位时字才亮"的近似做法，但它与
       **滑动期压深**（`.is-inking`，见上一条）叠加后，白色的实际到达时刻 =
       180(delay) + 155(color) ≳ **500ms**，而蓝块 250ms 就到位了
       ⇒ 蓝块压在文字上那 250ms 里，字是**深色**的 —— 正是他看到的那一帧。
     ▸ 现在：**只有一条时序来源** —— 滑动由 ink 的 `transitionend` 结束时摘 `.is-inking`
       （见 app.js），摘掉的同一帧文字就翻白（本条 delay 0 + 下面把 duration 收到 80ms）
       ⇒ "蓝块框到文字 = 字变白"（翻白用基类那条 `color var(--motion-fast)`，不另写时长）。
     ⛔ 别再把它加回来：任何"延迟变色"都会让"框到字"与"字变白"错开。 */
}

/* 达人表单/详情统一：外壳限宽 1120 居中（详情页 .detail-shell 与表单页 .sf-shell 同一宽度） */
.sf-shell {
  background: var(--color-surface);
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-lg);
  box-shadow: none;
  overflow: hidden;
  max-width: var(--form-max-width);
  margin-left: auto;
  margin-right: auto;
}

/* 达人表单 Tab 栏：**灰托盘 + 白块**（2026-09-26，与 `.tabs` 同款 —— 见上文 `.tabs` 的说明）
   ----------------------------------------------------------------------------
   ⚠️ 与 `.tabs` 的关系：**同一套视觉、两份 DOM 结构**。
      `.tabs` 直挂按钮；`.sf-tabs` 多两层（`__inner` 限宽 1120 居中 + `__track` 托盘）。
      托盘画在 `__track` 上，`.sf-tab` 的项样式与 `.tab` 逐项对齐。
   ⚠️ 历史教训（2026-09-22 已栽过）：这两套曾经尺寸不一致（44 vs 40），
      根因就是"同一个东西两份实现、只改了一处"。⇒ 将来改尺寸**两边一起改**
      （`.tab` / `.sf-tab` 的 height / padding / font-size 必须同值）。 */
.sf-tabs {
  display: flex;
  align-items: center;
  /* ⭐ 2026-09-28 三次修订（乐哥：「胶囊和底框左边的间距，我看没统一」）——
     **水平 padding 16/24 → 0**：让框（track）贴内容列左缘，与详情页的 tab 行**同一起点**；
     框内 24px（见 `.sf-tabs__track`）⇒ 胶囊距内容列左 = 25px，与详情页完全一致。
     ⛔ 改前外层留 24px + 框内 6px ⇒ 胶囊落在 49px，比详情页(25) 右 24px —— 这就是"没统一"。
     下 0（间距由内容区 padding-top 唯一承担）。
     ⭐⭐ 2026-09-28 第五次修订：**上内边距 → 上外边距**（padding-top → margin-top）——
     为让"表单页 tab 吸顶"生效，sticky 必须挂在本层（原因见下），
     而**内边距会跟着吸顶一起固定在顶上**（玻璃条会离顶 16px）；
     外边距在 sticky 元素的框外，吸顶时**不占位**，玻璃条正好贴顶。 */
  margin-top: var(--space-4);
  padding: 0;
  border-bottom: 0;                      /* 去基线（同 .tabs：托盘的灰底已表达分组边界） */
  /* ⭐ 2026-09-28 外层白底 → 透明（乐哥：「tab 这个背景没圆角」）——
     改前本层铺一条**通栏直角白条**，把内层圆角胶囊"包"在直角白底里 ⇒ 看起来背景是方的。
     改透明后，视觉载体只剩内层 `.sf-tabs__track`（白底 + 圆角 16 + 1px 边框），
     胶囊圆角直接落在页面灰底上，一眼是"圆角容器"。 */
  background: transparent;
  overflow-x: visible;                   /* 托盘自己 flex-wrap，不需要横向滚动 */
  /* ⭐⭐ 2026-09-28 第五次修订：**表单页 tab 吸顶**（乐哥：「所有页面的 tab …
     然后悬浮，透白，整个逻辑和一级页面的一致」）。
     ⛔⛔ **sticky 必须挂在本层，不能挂 `.sf-tabs__track`**（先挂错了一次，实测抓到）：
        sticky 元素只能在**其父容器的盒子范围内**吸附 ——
        `.sf-tabs__track` 的父级是 `.sf-tabs__inner`，而那个 inner 的高度**恰好等于 track**
        （实测两者都是 60px）⇒ 父容器没有可吸附的余量 ⇒ sticky 形同虚设
        （实测：滚动 600px 后 track 的 top 直接变成 **-407**，跟着页面滚走了）。
        挂到本层后父级是 `.sf-shell`（实测高 1621）⇒ 吸附余量充足。
     ⚠️ 吸顶位置与一级页 / 二级页**同一条公式**，四处必须同值
     （layout.css:1656 / components.css:878 / pages-influencer.css:266 / 本行）。
     ⭐⭐⭐ 2026-10-07「边框锁死 · 框内滚动」：公式改为**只留 `--tabs-bar-height`**，
        `+ 4px` 删掉 —— 吸附参照从视口变成了 `.app-main`（框），
        顶栏下沿 = 框顶 + tabs-bar-height（实测留 4px 会悬空 4px）。 */
  position: sticky;
  top: var(--tabs-bar-height);
  z-index: 60;
  transition: box-shadow var(--motion-base) var(--motion-ease-standard);
}

.sf-tabs__inner {
  display: flex;
  align-items: center;
  width: 100%;
  /* ⭐⭐ 2026-09-28 硬编码 1120 → **跟随令牌 var(--form-max-width)**。
     ───────────────────────────────────────────────────────────────────────────
     🐞 漏项事故（乐哥下午追问 `新建排期 tab 与新增达人不一样` 时才查出来）：
        上一轮把 `--form-max-width` 从 1120 放宽到 **1280**，验证时量的是
        **`.sf-shell`（透明外层容器）**＝1280 就宣布"内容区 1280 生效"——
        但用户**看不见**那个容器：表单页真正的限宽在更内层的 4 处**硬编码 1120**：
          ① `.sf-tabs__inner`（本处，tab 内层）          ✅ 生效中（computed 1280）
          ② `.sf-body__content`（内容列）                ✅ 生效中（computed 1280）
          ③ `.sf-shell > .form-actions .form-actions__inner`（底部按钮）
                ⚠️ **实测为死规则**：被 `#page-root .form-actions > .form-actions__inner`
                   `{ max-width: none }`（ID 选择器，特异性更高）覆盖 ⇒ computed = none。
                   底部按钮是**浮岛**（fit-content + 右对齐到卡片内缘 1696），**本就该贴合内容**、
                   不是"限宽 1280 的容器"。本处保留 `var(...)` 仅作防御（若将来浮岛规则移除，
                   这里仍能限制按钮行），**不要把它当成在生效的限宽**。
          ④ `.sf-shell > .form-actions--total .eb-total__inner`（多明细提单页的合计条）
                ✅ 生效中（computed 1280，实测 440→1720，与内容列同宽）
        实测后果：新增达人内容列仍是 **1120**（改前 1064，实际只 +56px，而非所报的 +216px）。
        ⇒ **这是一次假验证**：验了"外层容器"，没验"用户真正看到的元素"。
     ⚠️ 教训（已记入技能库）：
        ① 验宽度必须量**最内层的可见载体**（首张内容卡 / 输入框 / tab 托盘），不是外层包装容器；
        ② 报告"某规则生效"之前，必须读**该元素的 computed 值** —— 写过的规则可能被更特异的选择器
           覆盖（本处 ③ 就是：三处真限宽、一处死规则，只看源码分不出来）。
     ⚠️ ①②④ 三处**必须同值**（tab 内层 / 内容列 / 合计条）—— 两端要对齐成一条竖线；
        改一处不改其余会立刻左右错位。 ③ 不在其中（见上）。 */
  max-width: var(--form-max-width);
  margin: 0 auto;
  padding: 0;
}

/* 胶囊容器（与 `.tabs` 同款，2026-09-28 起为**白底 + 1px 边框**，令牌见 design-tokens） */
.sf-tabs__track {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  /* ⭐ 2026-09-28 三次修订：垂直 6 → 8、水平 6 → **24**（与 `.tabs` / `.sf-shell .tabs` 统一）
     —— 乐哥：「胶囊和底框左边的间距，我看没统一」（实测本处 7px vs 详情页 25px）。 */
  /* ⭐ 2026-09-28 四修订：垂直 8 → 10（同 .tabs，三处一致）
     ⭐⭐ 2026-09-28 第五次修订（全站 tab 统一）：**10 → 12px** ——
     本层现在是"白卡"（里面还装内层小框 44px），12 + 44 + 12 + 2(边框) = **70px**，
     与一级页 `.page-header--has-tabs`、二级页 `.sf-shell .tabs` **逐像素一致**。 */
  /* ⭐⭐ 2026-09-28 垂直内边距 12 → **9px**（乐哥：「这个 tab…感觉有一点点高了，要不矮个 10%」）——
     容器高 = 9×2 + 内层框44 + 2(边框) = **64**（原 70，矮 8.6%）；
     无内层框的页面（编辑排期）60 → **54**（正好 −10%，命中乐哥给的幅度）。
     ⚠️ 12 → 9 是**全站四处同改**（`.tabs` / `.sf-shell .tabs` / `.sf-tabs__track` /
        `.page-header--has-tabs`）——只改一处会让"tab 栏高度"重新分叉。
     ⚠️ 9px 不是 `--space-*` 档位值（令牌里没有 9）—— 它由"矮 10%"倒推得到，
        不是随手写的；改它之前先看这句注释。
     ⚠️ 胶囊（34）**不动**：乐哥当天上午刚要求过"胶囊太矮了"提到 34（见该处注释），
        这次说的"高"是**容器**厚，不该再把胶囊压回去。 */
  /* ⭐⭐⭐ 2026-10-07 13:40 垂直 14 → **9px**（乐哥：「左右都是贴着边的（没问题），
     但是**高度上下余量太多了**」）—— 四处同改 ⛔勿单改。
     实测容器 84 → 74（省 10px）；9 是 09-28「矮个 10%」的实测值。 */
  /* ⭐⭐⭐ 2026-10-07 14:40 **9px 4px → 10px**（乐哥：「tab 上下和左边的间距也不一致」）
     —— 上下左右统一，理由与 `.page-header--has-tabs` 那处完全相同（三处同源）。
     ⛔ 别再写 9/4 这类"上下一个值、左右另一个值"的组合：它没有推导过程，
        是多轮调整叠出来的，每改一处另外几处就对不上。 */
  padding: 10px;
  /* ⭐⭐⭐ 2026-10-08 底色 **灰 → 白**（乐哥：「新增达人和新增排期，这个 tab 显示不对」）
     ────────────────────────────────────────────────────────────────────────────────
     ▸ 病根：10-08 那轮「托盘统一成 Apple 灰」把本层也刷成了 `--tab-tray-bg`
       —— 但本层是**外层骨架**（通栏 16 圆角 + 10 内距的白卡），灰属于**内层托盘**
       `.tabs__tray`（wrapTabTrays 建的那个 42 高小框）。
       两层同灰 ⇒ 读起来是"一坨 64px 厚的灰块"，内层托盘的边界完全看不出来
       （实测：外层 bg=rgb(241,243,245) 内层 bg=rgb(241,243,245)，与达人详情页
         "外层白 255 + 内层灰 241" 对照即现形）。
     ▸ 同类已于同日修过：`.sf-shell .tabs`（见本文件上方那段，注释里写着
       "⛔ 别再写回 `--tab-tray-bg`，那会让整行变成一条灰带"）——
       **本处是同类漏改**（`.sf-tabs` 与 `.sf-shell .tabs` 是两套 DOM，只改了一套）。
     ▸ 层次目标（全站同一套）：白内容卡 → **白行骨架** → 浅灰托盘 → 蓝胶囊。 */
  background: var(--color-surface);
  /* ⭐ 2026-09-28 补外框（同 `.tabs`：白底容器必须有边框，否则在白卡里隐形）；
     边框色与详情页 Tab 行**同源**（gray-300）—— 乐哥要求"两种 tab 一个样"。 */
  border: 1px solid var(--color-gray-300);
  border-radius: var(--tab-tray-radius);
  flex-wrap: wrap;
  /* ⭐⭐ 2026-09-28 第五次修订：**表单页 tab 吸顶**（乐哥：「所有页面的 tab …
     然后悬浮，透白，整个逻辑和一级页面的一致」）。
     ⛔⛔ sticky 挂在**外层 `.sf-tabs`**（不是本层）—— 原因见 `.sf-tabs` 的注释：
        本层的父级 `.sf-tabs__inner` 高度恰好等于本层 ⇒ **没有可吸附余量**，
        sticky 会形同虚设（实测：滚动 600px 后本层 top 直接 -407，跟着页面滚走）。
     ⚠️ 本层保留定位上下文（`position: relative`）—— 它是 `.tabs__ink`（绝对定位）的参照。 */
  position: relative;
}

/* ⭐⭐⭐ 2026-09-28 第五次修订：表单页 tab「贴顶才变液态玻璃」（乐哥拍板，同二级页）
   —— 判定逻辑见 app.js updateStuck；强度 = L3 档（乐哥选定），令牌见 design-tokens。 */
.sf-tabs.is-stuck .sf-tabs__track {
  background: var(--liquid-bg);
  backdrop-filter: var(--liquid-blur);
  -webkit-backdrop-filter: var(--liquid-blur);
  border-color: var(--liquid-edge);
  box-shadow: var(--liquid-shadow);
}
/* 内层小框一起做玻璃（乐哥：「内层小框要不要一起做玻璃」→ 做）
   ⭐ 2026-10-05：边框色统一为 --color-border（与静态态一致）——
      理由同上一条（`.sf-shell .tabs.is-stuck .tabs__tray`）。三处吸顶托盘**必须同值**。 */
.sf-tabs.is-stuck .tabs__tray {
  background: var(--liquid-tray-bg);
  border-color: var(--color-border);
}

/* 表单页「内容分组 Tab」的项（达人表单 / 签约公司表单用；报销单刻意没有 .sf-tabs）
   ----------------------------------------------------------------------------
   ⚠️ 与 `.tab` **逐项同值**（height / padding / font-size / 圆角 / 三态）——
      见上文 `.tabs` 段的说明。历史教训：2026-09-22 因为"同一个东西两份实现
      只改了一处"，出现「达人详情页 tab 44px、达人编辑页 tab 40px」（乐哥实报
      「达人列表查看，和编辑的整体 ui 有些差异」）。⇒ 改尺寸必须两边一起改。
   ⚠️ `line-height` 不再写死 44px：托盘方案的项高 32px，写死行高会把文字顶偏；
      用 flex 居中（`.sf-tabs__track` 是 align-items:center）。 */
.sf-tab {
  position: relative;
  /* ⭐ 2026-09-28 32 → **34**（全站 tab 统一）：与 `.tab` / 一级页胶囊同高。
     ⚠️ 这两套是"同一个东西的两份实现"，改一处必须同时改另一处 ——
        `tests/tab-size-consistency.test.js` 会当场报红（本次就是它抓出来的）。 */
  height: 34px;
  padding: 0 16px;                     /* 与 .tab 对齐（14 → 16，同一批统一） */
  border: none;
  border-radius: var(--radius-seg);    /* 8 → 6：与 .tab / 一级页胶囊同值 */
  background: transparent;
  color: var(--color-fg-2);
  font-size: var(--font-size-sm);      /* 与 .tab 对齐：16 → 14（令牌 sm） */
  font-weight: var(--font-weight-medium);
  white-space: nowrap;
  cursor: pointer;
  transition: color var(--motion-fast) var(--motion-ease-standard),
    background-color var(--motion-fast) var(--motion-ease-standard);
}

.sf-tab:not(.is-active):hover {
  color: var(--color-accent-text);
  /* 2026-10-08 15:3x 去掉 hover 底色（乐哥：「白色的卡片还没去除干净」）——只变文字色 */
}

.sf-tab.is-active {
  /* ⭐⭐⭐ 2026-10-08 12:5x 与 `.tab.is-active` **完全同源**（背景/阴影交给 ink，
     只留文字层 + 延迟变色）—— 详见 `.tab.is-active` 那条的长注释。
     ⛔ 两者必须一致：motion-system 门禁锁着"同为渐变蓝胶囊 / 同一套选中表达"。 */
  background: transparent;
  color: var(--color-accent-on);
  /* ⭐⭐⭐ 2026-10-08 19:2x **翻白改成"延迟到蓝块到位 + 瞬时"**（乐哥：「现在切换的时候，
     还是**先闪黑子**然后再显示白字，很奇怪不自然」）
     ──────────────────────────────────────────────────────────────────────────────
     🐞 旧写法（`transition: none` + `.is-inking` 压深）为什么"闪黑"——逐帧实测
        （`scripts/frontend-debug/diag-tab-text-flash.js`，rAF 每帧 + 真实鼠标路径）：
          hover 中：文字 = **蓝** rgb(0,100,224)（hover 反馈色）
          点击后  ：被 `.is-inking` 压成 **深灰** rgb(63,71,83)  ← **"闪黑"就是这一跳**
          到位后  ：瞬时白
        ⇒ 蓝 → 黑 → 白，中间那次"从蓝突变到深灰"就是他说的不自然。
     ✅ 新写法：目标色仍是白，但**延迟一个滑动时长**才生效、且 **duration = 0s**：
        推迟期间文字**保持原色**（hover 过 = 蓝、没 hover = 未选中灰）⇒ 没有"变黑"这一跳；
        到点瞬时翻白，正好是**蓝块到位那一帧**。
     ⛔ 别写回 `transition: none`（颜色立即变白 ⇒ 白字压浅托盘 = "白卡一闪"，15:2x 报过）；
     ⛔ 也别给 color 任何**非 0 的 duration**（15:4x 栽过：delay 180 + 渐变 155 把翻白推到
        ≈500ms，蓝块 250ms 就到了 ⇒"蓝块压着字、字还是深色"）。
     完整机理与三处反面教训见 table.css `.table-tabs .pill-filter__item.is-active` 那条长注释。 */
  transition: color 0s linear var(--motion-tab-slide);
  font-weight: var(--font-weight-semibold);
  box-shadow: none;
}

/* ⭐ 2026-10-08 12:5x 降级兜底：**没有 ink 时**（JS 未跑 / 抛错），选中框退回
   "tab 自己长蓝底 + 浮起阴影"，保证任何情况下都看得出哪个被选中。
   ⚠️ 用结构判据 `:has()` 而不是 JS 维护一个 `.has-ink` 类 —— ink 是 JS 注入的，
      `:has()` 天然同步，不存在"两处状态不同步"（本项目踩过多次）。
   ⚠️ 必须写在 `.tab.is-active` / `.sf-tab.is-active` **之后**（同特异性下靠后者覆盖）。 */
.tabs:not(:has(> .tabs__ink)) .tab.is-active {
  background: var(--gradient-brand);
  box-shadow: var(--shadow-pill-active);
}
.sf-tabs__track:not(:has(> .tabs__ink)) .sf-tab.is-active {
  background: var(--gradient-brand);
  box-shadow: var(--shadow-pill-active);
}

/* 内容区 */
.sf-body {
  background: var(--color-surface);
}

/* 内容列统一限宽（跟随 --form-max-width，含左右 24px padding），与 Tab 栏内层、底部操作区对齐
   ⭐ 2026-09-28 硬编码 1120 → var(--form-max-width)：它是「对齐链」四环之一，
      详见 `.sf-tabs__inner` 的漏项说明（改一处必须四处同改）。 */
.sf-body__content {
  padding: var(--space-5) var(--space-6);
  max-width: var(--form-max-width);
  margin: 0 auto;
}

.sf-panel-body {
  width: 100%;
}

/* 达人表单 · 平台相关：一行一个平台 —— 左列「平台名 + 开通开关」，右列配置内容。
   左列平台名按 4 个中文字符预留宽度，长短不齐的平台名也能对齐。 */
.platform-group {
  border: 1.5px solid var(--color-border-soft);
  border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-4);
  margin-bottom: var(--space-2);
}
.platform-group:last-child {
  margin-bottom: 0;
}
/* 第一行：平台名 + 开通开关 + 平台账号ID + 达人类型，**全部同一行**。
   踩坑（2026-09-10 乐哥反馈"UI 布局有问题"）：上一版把「平台名 + 开关」做成 132px 的
   独立左列且 align-items:center + align-self:stretch —— 它会在这块区域的整体高度里
   垂直居中，于是看着跟右边的「平台账号ID」不在同一行。现在改为第一行内联。 */
.platform-group__main {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  min-height: 44px;
}
.platform-group__head {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  align-self: stretch;
  padding-right: var(--space-4);
  border-right: 1px solid var(--color-border-soft);
}
.platform-group__name {
  min-width: 56px;   /* 按 4 个中文字符预留 */
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  color: var(--color-fg);
}
.platform-group__head .switch { margin: 0; flex-shrink: 0; }
/* 未开通：第一行右侧给一行灰提示，避免"右边空着"的错觉 */
.platform-group__off {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  font-size: var(--font-size-xs);
  color: var(--color-fg-3);
}
/* ⭐⭐ 字段：**标签在左、控件在右**（2026-10-09 乐哥第二次定，覆盖当天上午的"全站一律标签在上"）。
   ──────────────────────────────────────────────────────────────────────────
   乐哥原话（附截图）：「平台与账号那个，平台账号id和达人类型的输入框，现在做成了上下，
   然后输入框被挤压了。我觉得**这种场景可以做左右**，就是左边是名字右边输入框，
   输入框可以不用那么宽（就是**根据页面判断**）。」
   ⇒ 这是他当天上午说"全站一律标签在上"之后，亲自划出的**例外场景**：
     **密集多列网格里的短标签字段**（平台账号卡：3 个平台 × 若干字段挤在一张卡里，
     且每个字段的标签只有 4~7 字）—— 这种地方**标签在上会白吃掉一整行高度**，
     字段区反而被压扁；改成"标签在左"后，标签与控件同行、同一列宽能放更多信息。
   ⚠️ 与上午那条**不冲突**，是范围不同（那条管的是弹窗/整页的常规两列表单，
     列宽 380+、标签能完整成行；**这里列宽只有 ~250**，标签在上就纯属浪费）。
   ⛔ 标签**左对齐 + 定宽**（不用 `text-align: right`）—— 全站门禁 `form-unify ⑥`
     禁的是"标签右对齐"，左对齐同样能让控件起点成一条竖线，且不触发门禁。
   ⛔ 控件**限宽**（"输入框可以不用那么宽"）：平台账号ID/机构名这类标识符 40 字符就够，
     撑满列反而拉长视线、也让"3 个平台"看起来各占一行。 */
.pg-field {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
}
.pg-field__label {
  flex: 0 0 auto;
  width: 84px;
  font-size: var(--font-size-sm);
  color: var(--color-fg-2);
  white-space: nowrap;
  /* 行内小标签不参与"文字右内缩 6px"（那是给块级字段标签的，见 design-tokens 的
     `--field-label-indent`）—— 它左侧本来就贴着控件列的起点。 */
  padding-left: 0;
  margin-bottom: 0;
}
.pg-field__ctl { flex: 1 1 auto; min-width: 0; max-width: 260px; }
/* 行内右侧的字段区：固定两列网格 —— 这样第一行「平台账号ID / 达人类型」与下面
   「签约机构」行的「机构名称 / 机构编码」列宽、起点完全一致（乐哥要求"输入框宽度和第一行对齐"）。
   列用 minmax(0, 1fr) 才能在内容过长时正常收缩。 */
.platform-group__fields {
  flex: 1 1 auto;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-3) var(--space-5);
  min-width: 0;
}
/* 区块内第二行起：上缘一条浅线分隔（第一行不带线） */
.platform-group__line {
  border-top: 1px solid var(--color-border-soft);
  margin-top: var(--space-3);
  padding-top: var(--space-3);
}
/* 该行右侧没有内容的行（如「腾讯互选」）：不画右侧竖线，名称 + 开关仍与其他行左对齐 */
.platform-group__head--bare {
  padding-right: 0;
  border-right: none;
}
/* 「签约机构」开关打开后展开的字段区：JS 用这个类做显隐钩子（关闭时置 display:none），
   布局沿用 .platform-group__fields 的两列网格，这里给出"展开"时的默认 display */
.platform-agency-box {
  display: grid;
}

/* 窄屏（手机）：第一行改上下堆叠、平台名与开关占满整行，
   字段区收成一列，否则同一行里塞不下 */
@media (max-width: 768px) {
  .platform-group__main {
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-3);
  }
  .platform-group__head {
    align-self: auto;
    padding-right: 0;
    padding-bottom: var(--space-3);
    border-right: none;
    border-bottom: 1px solid var(--color-border-soft);
  }
  .platform-group__head--bare {
    padding-bottom: 0;
    border-bottom: none;
  }
  .platform-group__fields { grid-template-columns: 1fr; }
  /* 窄屏平台账号区退回单列 ⇒ 标签列可再收一点，给控件让位 */
  .pg-field__label { width: 72px; }
}

/* 二级胶囊筛选已收敛为全局通用组件 .pill-filter（见 components.css），达人表单不再使用 */

/* 底部操作区：外层整宽（分隔线贯穿），内层限宽、按钮右对齐到内容列右边缘 */
.sf-shell > .form-actions {
  display: block;
  padding: 0;
  border-top: 1px solid var(--color-border-soft);
  background: var(--color-surface);
}

.sf-shell > .form-actions .form-actions__inner {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-3);
  width: 100%;
  /* ⭐ 2026-09-28 硬编码 1120 → var(--form-max-width)（「对齐链」第 ③ 环，
     详见 `.sf-tabs__inner`）：否则内容列放宽后按钮组会比内容窄 160px、右缘对不齐。 */
  max-width: var(--form-max-width);
  margin: 0 auto;
  padding: var(--space-4) var(--space-6);
}

@media (max-width: 1024px) {
  .sf-tabs__inner {
    padding: 0 var(--space-4);
  }
  .sf-tab {
    margin-right: var(--space-4);
  }
  .sf-body__content {
    padding: var(--space-4);
  }
  .sf-shell > .form-actions .form-actions__inner {
    padding: var(--space-4);
  }
}

@media (max-width: 1024px) {
  .info-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

/* ⭐ 2026-09-27 乐哥需求：佣金历史弹窗里的**合并时间轴**
   （达人通用佣金 ＋ 排期单独维护合并切分："这个达人的费率随时间怎么变"）。
   排期覆盖行用左侧竖条 + 浅色底强调 —— 它是"例外"，与普通配置段要一眼可分。 */
.comm-timeline {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-md);
  overflow: hidden;
}
.comm-timeline__row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  font-size: var(--font-size-sm);
  border-top: 1px solid var(--color-border-soft);
}
.comm-timeline__row:first-child { border-top: none; }
.comm-timeline__row.is-sched {
  background: var(--color-warning-bg);
  box-shadow: inset 3px 0 0 var(--color-warning);
}
.comm-timeline__when {
  flex: none;
  min-width: 150px;
  color: var(--color-fg-2);
  font-variant-numeric: tabular-nums;
}
.comm-timeline__rate { flex: 1; color: var(--color-fg); font-weight: var(--font-weight-medium); }
.comm-timeline__src { flex: none; color: var(--color-muted); font-size: var(--font-size-xs); }

/* 佣金配置：渠道分组（白底 + 渠道色边框，视频号绿框 / 抖音蓝框）+ 场景开关 */
.comm-channel {
  margin-bottom: var(--space-4);
  padding: var(--space-3);
  border-radius: var(--radius-lg);
  background: var(--color-surface);
  border: 1.5px solid var(--color-success);
}
.comm-channel--dy {
  border-color: var(--color-info);
}
.comm-channel:last-child { margin-bottom: 0; }
.comm-channel-title {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  color: var(--color-fg);
  margin-bottom: var(--space-2);
}
/* ⭐ 2026-10-12：渠道卡标题行最右的**渠道开关**（排期编辑页 · 本场播不播这个渠道）。
   ⚠️ 开着时**不要**给它 margin-left:auto —— 左边的「本场摘要」（.comm-channel-sum）
      也吃 auto，两个 auto 会**平分**剩余空间，摘要就不贴右了。
      只有**关掉的渠道**（只剩标题行、没有摘要）才需要 auto 把开关推到最右。 */
.comm-channel-act { flex: none; }
.comm-channel--off .comm-channel-act { margin-left: auto; }
/* 关掉的渠道：卡片只剩这一行标题（下面的明细已整块隐藏）⇒ 收紧内边距、弱化文字，
   让"本场不播"一眼可辨，也不占太多高度。 */
.comm-channel--off { padding: var(--space-2) var(--space-3); }
.comm-channel--off .comm-channel-title { margin-bottom: 0; color: var(--color-muted); }
/* ⭐ 2026-09-27（乐哥：「有些乱、内容有些多，排期里佣金那个展示方式就非常好」）：
   块头右侧的「当前佣金摘要」—— 学排期详情 .sd-comm-ch__note 的信息架构，
   不展开就能扫到这个渠道当前用的是多少（不必在输入框/历史行之间来回找）。
   ⚠️ 必须显式写 color：.comm-channel-title--dy 会给整个 title 上蓝色（--color-info），
      摘要继承下来会变成"蓝色强调"，抢占渠道名的视觉权重。 */
.comm-channel-sum {
  margin-left: auto;
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-regular);
  color: var(--color-muted);
  font-variant-numeric: tabular-nums;
}
.comm-channel-sum--none { color: var(--color-fg-2); opacity: .65; }
.comm-channel-title::before {
  content: '';
  width: 4px;
  height: 14px;
  border-radius: 2px;
  background: var(--color-success);
  flex-shrink: 0;
}
.comm-channel-title--dy {
  color: var(--color-info);
}
.comm-channel-title--dy::before {
  background: var(--color-info);
}
.comm-channel-warn {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 8px;
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-medium);
  line-height: 1.4;
  color: var(--color-warning);
  background: var(--color-warning-bg);
  border-radius: var(--radius-pill);
  vertical-align: middle;
}
/* ⭐ 2026-10-12（审计修）：达人侧「沿用公司」标记 —— 与上面的"平台未添加"同为渠道级标记，
   但**语义是中性信息**（不是异常）⇒ 用 info 色，不占用警示色。
   用途：该渠道达人没单独配、实际按公司通用佣金算（见 influencer-detail.js channelCard）。 */
/* ⛔ 2026-10-12：`.comm-channel-inherit`（卡头的「沿用公司」胶囊）已删除 ——
   它与同一行里 `noteHtml` 的加粗提醒（「沿用公司通用佣金（在「xxx」页维护）」）重复。
   别再补回来：出处说明由 `noteHtml` 承担（`.co-ch-note__src`）。 */
/* 场景表格「卡头下的说明行」插槽（`CommissionShared.sceneTable({ noteHtml })`）——
   达人侧用它写"实际按公司通用佣金计算：线上 30% · 线下 10%（在 XX 公司页维护）"。
   ⚠️ 默认不传 noteHtml 时**不产生这个节点**（公司侧/排期侧输出逐字不变）。 */
.co-ch-note {
  padding: var(--space-2) var(--space-4) 0;
  font-size: var(--font-size-sm);
  line-height: 1.6;
  color: var(--color-fg-2);
}
/* ⭐ 2026-10-12 乐哥：「文字提醒沿用了 xxx 佣金，**提醒显眼点加粗**」——
   沿用来源是**读数依据**（"这个数从哪来、要改去哪儿改"）⇒ 加粗 + 与卡头「沿用公司」
   胶囊同色的信息蓝。⛔ 别退回灰字：那样用户会把它当装饰、继续以为"表格是空的=没佣金"。 */
.co-ch-note__src { font-weight: 600; color: var(--color-info); }

/* ⭐⭐ 2026-10-12 乐哥：「样式还有文字展示方式，还是和我截图的（达人排期）不一样，包括表头，
   表头和下面内容的排版等」—— 佣金**查看态**改用排期「本场佣金块」的同一套结构语言
   （`.sd-comm-ch` 系列定义在 pages-schedule.css，index.html 全量加载 ⇒ 任何页面可用）。
   这里只补**详情页特有**的两样东西：① 一个渠道卡里要放**多个场景** ⇒ 场景小标题行；
   ② 未单独维护/未配置的小字标记。⛔ 别在此处重写 `.sd-comm-ch*`（那是公共类，改了排期页跟着变）。
   ⭐⭐ 2026-10-12（同日晚）乐哥拿一张手绘表格说「这个展示方式你理解错了，我要的是这样的」，
   我照做改成带格线的 `<table>`，他当即纠正：「**不要做成表格**，我只是展示布局，之前我们 ui 没问题」。
   ⇒ 那张图表达的是**布局**（渠道名在最上面 / 专场照旧「标签在上、值在下」/ 混播·私域**只留名字**），
      卡片这套样式**保持不动**。所以本段的 `.co-cv__*` 只补「场景小标题 + 状态小标签」，
      数值那一段继续复用排期页的 `.sd-comm-ch__grid`（同一份列宽）。
   ⛔ 别再把这段改成表格 / 列线 / 每格边框 —— 已被明确否掉一次。 */
/* 一个场景 = **一行两列**（乐哥 2026-10-13：「专场那个标签文字，和佣金那些放同一行」）：
   第 1 列 = 场景名（+ 状态小标签），第 2 列 = 4 列「标签在上、值在下」。
   ⚠️ 名字列**定宽 118px**，右侧列 `minmax(0,1fr)` ⇒ 所有场景的 4 个数值左边界一致（跨行成列）。
   ⚠️ `align-items: center`：名字与右侧那个「标签行 + 值行」的**两行块垂直居中**对齐
      （= 乐哥手绘图里「专场」跨两行的样子）；⛔ 别改成 baseline/start（名字会贴到最上面一行）。
   ⚠️ 只画了名字、没有数值的行（混播/私域）仍走同一个网格 ⇒ 名字起点与「专场」严格对齐。 */
.co-cv__row {
  display: grid;
  grid-template-columns: 118px minmax(0, 1fr);
  column-gap: var(--space-3);
  align-items: center;
}
.co-cv__row + .co-cv__row {
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  border-top: 1px dashed var(--color-border-soft);
}
/* 名字 + 状态小标签**同一行**（标签跟在名字后面，如「混播 未单独维护，沿用专场佣金」）。
   ⚠️ 必须 `nowrap`：名字列只有 118px，而「未单独维护，沿用专场佣金」比它宽
      ⇒ 不写会在名字列里折成两行（真机实测："未单独维护，沿用专" / "场佣金"）。
      这类行右侧本来就没有数值 ⇒ 文字**横向溢出**到空着的数值区反而是对的，不会打架。 */
.co-cv__sc {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  white-space: nowrap;
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  color: var(--color-fg);
}
.co-cv__tag { font-size: var(--font-size-xs); color: var(--color-muted); }
/* 「单独维护」是**中性标记**（不是警告）：说明这个场景的口径不跟着专场走 —— 用信息蓝 */
.co-cv__tag--own { color: var(--color-info); }
.co-cv__off { font-size: var(--font-size-sm); color: var(--color-muted); }
/* 混播/私域开关关闭时，整行置灰禁用 */
.comm-off {
  opacity: 0.5;
}

/* 佣金配置卡片式：场景块（白底，与渠道淡底区分）+ 附加佣金（摘要常显 + 展开编辑） */
.comm-scene {
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-md);
  padding: var(--space-3);
  margin-bottom: var(--space-2);
  background: var(--color-surface);
}
.comm-scene:last-child { margin-bottom: 0; }
.comm-scene-head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  margin-bottom: var(--space-3);
}
.comm-scene-name { font-weight: var(--font-weight-medium); font-size: var(--font-size-sm); }
.comm-scene-period {
  font-size: var(--font-size-xs);
  color: var(--color-muted);
  margin-left: 6px;
}
/* 当前生效佣金一行蓝色字（时间段 + 佣金内容），放在场次下方 */
/* ⭐ 2026-09-27：改前是蓝字（--color-info）且内容是"生效期 + 完整佣金摘要"——
   摘要与输入框/块头重复。改后**只留生效期**、改灰字：它是辅助信息，不是强调项。
   （强调项 = 输入框的值 + 块头摘要） */
.comm-current {
  margin-top: var(--space-2);
  font-size: var(--font-size-xs);
  line-height: 1.5;
  color: var(--color-muted);
}
/* ⭐ 2026-09-27 **结构改版**（乐哥：「我以为你是有做结构上的调整啊，原来也没有啊」）
   ─────────────────────────────────────────────────────────────────────────
   改前：每个场景 = 一个输入框 + 一个「附加佣金」折叠区 ⇒ 这是**表单结构**，
         一眼看不出"这个场景总共几个点、分别多少"。
   改后：学排期详情「本场佣金」——**指标格**（线上/线下/年框/其他），
         标签在上、数值在下、等分 4 列，**先看后改**；输入区收进「编辑佣金」。
   ⚠️ 与排期页 .sd-comm-ch__grid 保持同一视觉语言（等分 + tabular-nums 对齐），
      但类名独立，避免动到排期详情/排期编辑共用的样式。 */
.comm-rate-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--space-3);
  padding: var(--space-2) 0 var(--space-2);
}
.comm-rate-lbl {
  display: block;
  font-size: var(--font-size-xs);
  color: var(--color-muted);
  margin-bottom: 2px;
}
.comm-rate-val {
  font-size: var(--font-size-lg);
  font-weight: var(--font-weight-semibold);
  color: var(--color-fg);
  font-variant-numeric: tabular-nums;
  line-height: 1.4;
}
.comm-rate-val--off { color: var(--color-muted); font-weight: var(--font-weight-regular); }
/* ⭐ 2026-09-27：未配置时不铺 4 个「—」（一片空白比没数字更难看），给一行灰字 */
.comm-rate-empty {
  padding: var(--space-1) 0 var(--space-2);
  font-size: var(--font-size-xs);
  color: var(--color-muted);
}
.comm-rate-inc {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: var(--font-size-xs);
  color: var(--color-fg-2);
  padding-bottom: var(--space-1);
}
/* 场景头/展开区里的「编辑佣金」按钮靠右（不抢场景名视线） */
.comm-scene-head-act { margin-left: auto; }
.comm-scene-edit-act { display: flex; justify-content: flex-end; margin-top: var(--space-2); }
/* 编辑层：默认收起（.comm-edit-panel 由 JS 控制 display），展开后与展示层用虚线分隔 */
.comm-edit-panel {
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  border-top: 1px dashed var(--color-border-soft);
}
/* 开票与税点：豁免标记（橙色标签） */
.comm-exempt-tag {
  display: inline-block;
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-medium);
  color: var(--color-warning);
  background: var(--color-warning-bg);
  padding: 1px 8px;
  border-radius: var(--radius-pill);
  line-height: 1.4;
}
/* 「开票与税点」卡内的两个板块（① 佣金开票豁免 / ② 佣金税点）：
   板块标题行（名称靠左、新增按钮靠右）+ 说明文字 + 各自的表格
   —— 概念优先：渠道降级成表格里的一列，不再拿渠道当外框（那样会把两件事挤在一起） */
.it-blk-head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}
.it-blk-head__name {
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
  color: var(--color-fg);
}
.it-blk-head .btn {
  margin-left: auto;
}
.it-blk-desc {
  margin-top: var(--space-2);
  font-size: var(--font-size-xs);
  line-height: 1.7;
  color: var(--color-fg-2);
}
.it-blk-desc b {
  font-weight: var(--font-weight-semibold);
  color: var(--color-fg);
}
/* 专场单独维护：一致/不一致状态标签 + 差异数字标红 */
.lock-ok, .lock-diff {
  display: inline-block;
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-medium);
  padding: 1px 8px;
  border-radius: var(--radius-pill);
  line-height: 1.4;
}
.lock-ok { color: var(--color-success); background: var(--color-success-bg); }
.lock-diff { color: var(--color-warning); background: var(--color-warning-bg); }
.lock-diff-num { color: var(--color-danger); font-weight: var(--font-weight-medium); }
.comm-scene-tag {
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-medium);
  color: var(--color-accent-text);
  background: var(--color-primary-100);
  padding: 1px 8px;
  border-radius: var(--radius-pill);
  line-height: 1.4;
}
.comm-follow {
  font-size: var(--font-size-xs);
  color: var(--color-success);
  margin-left: auto;
  background: var(--color-success-bg);
  padding: 2px 10px;
  border-radius: var(--radius-pill);
  line-height: 1.4;
}
.comm-channel--dy .comm-follow {
  color: var(--color-info);
  background: var(--color-info-bg);
}
.comm-scene-fields {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  flex-wrap: wrap;
}
.comm-scene-body {
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  border-top: 1px dashed var(--color-border);
  /* ⚠️ 2026-09-27 修（乐哥截图：「你觉得你改的好看吗？」）：
     原来是 `display:flex; align-items:center` 横排 —— 那是给「输入框 + 附加佣金按钮」两件套用的。
     塞进**指标格**后，格子 / 编辑按钮 / 输入框被挤在同一行（截图里的乱象）。
     ⇒ 改**竖排**：指标格一行、按钮一行、输入区一行，各占各的。 */
  display: block;
}
.comm-field {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
.comm-field-label { font-size: var(--font-size-xs); color: var(--color-fg-2); }
.comm-input-suffix {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
.comm-input-suffix input { width: 80px; }
.comm-unit { font-size: var(--font-size-xs); color: var(--color-fg-2); }
.comm-more-wrap {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
}
/* ⚠️ .comm-extra-summary 仍被排期编辑页（schedule-form.js）使用 —— **不能删**。
   达人页已改为把摘要并进「附加佣金」按钮文案（.comm-more-sum，见下），
   原因是独立胶囊与右侧「跟随专场」胶囊**同款样式**（绿底绿字/蓝底蓝字），
   一个是"已配置的值"、一个是"跟随状态"，视觉上分不清（乐哥说的"乱"之一）。 */
.comm-extra-summary {
  font-size: var(--font-size-xs);
  color: var(--color-success);
  line-height: 1.6;
  background: var(--color-success-bg);
  padding: 2px 10px;
  border-radius: var(--radius-pill);
}
/* ⭐ 2026-09-27 新增：「附加佣金」按钮内的已配置摘要（中性灰，与绿色状态胶囊区分） */
.comm-more-sum {
  margin-left: 6px;
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-regular);
  color: var(--color-muted);
  font-variant-numeric: tabular-nums;
}
.comm-more-ico { display: inline-flex; align-items: center; margin-right: 2px; }
.comm-channel--dy .comm-extra-summary {
  color: var(--color-info);
  background: var(--color-info-bg);
}
.comm-extra {
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  border-top: 1px dashed var(--color-border);
  width: 100%;
}
.comm-extra-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: var(--space-3);
}
.comm-extra-item { display: flex; flex-direction: column; gap: 6px; }
.comm-extra-item > label { font-size: var(--font-size-xs); color: var(--color-fg-2); }
.comm-extra-item .comm-input-suffix { width: 100%; }
.comm-extra-item .comm-input-suffix input { flex: 1; width: auto; }
.comm-extra-hint { margin-top: var(--space-2); }

/* 下游佣金：达人列表（表头按场景分列：专场/混播/私域，场景列内按渠道分行） */
.dc-table {
  table-layout: fixed;
}
.dc-table thead th.dc-th-scene {
  position: relative;
  border-left: 1px solid var(--color-border-soft);
}
.dc-scene-cell {
  padding: var(--space-2) var(--space-3);
  border-left: 1px solid var(--color-border-soft);
}
.dc-cell-line {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 2px 0;
  min-height: 24px;
}
.dc-cell-line--off { opacity: 0.55; }
.dc-cell-text {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  font-size: var(--font-size-sm);
}
/* 达人佣金：线上 / 线下 / 年框 三行分色（仅数字用彩色，标签/日期用默认灰） */
.dc-rates {
  display: flex;
  /* ⚠️ 原来是 `flex-direction: column` —— 同一平台的多个费率项（线上 + 返点 + 年框…）
     会**逐项堆叠**，一个达人配 3 项就把该格撑到 147px（乐哥 2026-09-13 排查「明显高行」）。
     改为横向排列 + 自动换行：列宽放得下就并排（省一半高度），放不下自动折行——
     最坏情况与原来一致，不会更差。 */
  flex-direction: row;
  flex-wrap: wrap;
  gap: 3px 10px;
  min-width: 0;
  flex: 1;
}
.dc-rate-row {
  display: flex;
  align-items: center;
  gap: 5px;
  flex-wrap: nowrap;      /* 一个费率项内部不再折行（外层 .dc-rates 负责换行） */
  font-size: var(--font-size-sm);
  line-height: 1.35;      /* 1.5 → 1.35：密集表格里的多行费率，行距略收更耐看 */
}
/* 分色：线上=主色钴蓝 / 线下=橙 / 年框=绿 / 返点=紫 / 激励=红 */
.dc-rate--online { color: var(--color-accent-text); }
.dc-rate--offline { color: var(--color-warning); }
.dc-rate--yearly { color: var(--color-success); }
.dc-rate--rebate { color: var(--color-chart-7); }
.dc-rate--incentive { color: var(--color-danger); }
.dc-rate-row .dc-rate-label {
  font-size: var(--font-size-xs);
  color: var(--color-fg-2);
  white-space: nowrap;
}
.dc-rate-row .dc-rate-num {
  font-weight: var(--font-weight-semibold);
  color: inherit;
  font-variant-numeric: tabular-nums;
}
.dc-rate-row .dc-rate-period {
  font-size: var(--font-size-xs);
  color: var(--color-fg-2);
  white-space: nowrap;    /* 「2026-09-10 起」不能被拆成两行 */
}
.dc-extra-chips {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  flex-wrap: wrap;
}
.dc-extra-chip {
  font-size: var(--font-size-xs);
  color: var(--color-fg-2);
  background: var(--color-gray-100);
  padding: 2px 8px;
  border-radius: var(--radius-pill);
  white-space: nowrap;
  line-height: 1.4;
}
.dc-chip-tag {
  display: inline-block;
  font-weight: var(--font-weight-medium);
  color: var(--color-success);
  background: var(--color-success-bg);
  padding: 2px 9px;
  border-radius: var(--radius-pill);
  flex-shrink: 0;
  font-size: var(--font-size-xs);
  line-height: 1.5;
  white-space: nowrap;
}
.dc-chip-tag--dy {
  color: var(--color-info);
  background: var(--color-info-bg);
}
.dc-follow {
  font-size: var(--font-size-xs);
  color: var(--color-fg-2);
  background: var(--color-gray-100);
  padding: 2px 10px;
  border-radius: var(--radius-pill);
  white-space: nowrap;
}
/* 上游佣金：卡片网格（货架式），按单品/套餐分两块，佣金率大字突出 */
.up-grid { display: flex; flex-direction: column; gap: var(--space-4); }
.up-section-head {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  color: var(--color-fg);
  margin-bottom: var(--space-2);
}
.up-section-count {
  font-size: var(--font-size-xs);
  color: var(--color-fg-2);
}
.up-cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: var(--space-2);
}
.up-card {
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-lg);
  background: var(--color-surface);
  padding: var(--space-3);
}
.up-card-top {
  display: flex;
  align-items: center;
  gap: 10px;
}
.up-card-img {
  width: 40px;
  height: 40px;
  border-radius: var(--radius-md);
  object-fit: cover;
  flex-shrink: 0;
  background: var(--color-gray-100);
}
.up-card-img--empty {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--color-fg-2);
}
.up-card-name {
  flex: 1;
  min-width: 0;
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.up-card-rate {
  font-size: var(--font-size-lg);
  font-weight: var(--font-weight-semibold);
  color: var(--color-accent-text);
  flex-shrink: 0;
  font-variant-numeric: tabular-nums;
}
.up-card-bottom {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: var(--space-2);
}
.up-card-type {
  font-size: var(--font-size-xs);
  color: var(--color-fg-2);
}
.up-card-actions {
  margin-left: auto;
  display: flex;
  gap: 2px;
}

/* 结算单：达人单元格多渠道标签（视频号/抖音 + 应付小计，点标签切渠道） */
.st-ch-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-top: 4px;
}
.st-ch-tag {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 2px 8px;
  border-radius: var(--radius-pill);
  border: 1px solid transparent;
  background: var(--color-success-bg);
  color: var(--color-success);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-medium);
  cursor: pointer;
  transition: color var(--motion-fast) var(--motion-ease-standard),
    background-color var(--motion-fast) var(--motion-ease-standard),
    border-color var(--motion-fast) var(--motion-ease-standard);
}
.st-ch-tag--dy {
  background: var(--color-info-bg);
  color: var(--color-info);
}
.st-ch-tag:hover {
  border-color: var(--color-border);
}
.st-ch-tag.is-active {
  background: var(--color-primary-50);
  border-color: var(--color-primary-200);
  color: var(--color-accent-text);
  font-weight: var(--font-weight-medium);
}
.st-ch-tag__amt {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum", "lnum";
}

/* 静态渠道标签（单渠道行，乐哥 2026-09-15：渠道从独立列挪到达人名下）
   ⚠️ 与可点标签用同一个 .st-ch-tag 基类（保证 pill 外观/配色一致），
      只覆盖"可交互"的三处视觉暗示 —— 否则单渠道标签看着能点、点了却没反应。
      它**不带 data-ch**，绑定侧也据此跳过（见 influencer-settlement.js 的渠道标签事件）。 */
.st-ch-tag--static {
  cursor: default;
}
.st-ch-tag--static:hover {
  border-color: transparent;   /* 去掉"悬停高亮"这个可点暗示 */
}

/* 达人佣金：历史生效版本（对应佣金下一条条展示） */
.comm-history {
  margin-top: var(--space-2);
}
.comm-history-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--font-size-xs);
}
.comm-history-table td {
  padding: 4px 8px;
  border-bottom: 1px solid var(--color-border-soft);
  vertical-align: top;
  color: var(--color-fg-2);
}
/* 最新 3 段：整行蓝色 */
.comm-history-row--cur td {
  color: var(--color-info);
  font-weight: var(--font-weight-medium);
}
.comm-history-period {
  width: 38%;
  white-space: nowrap;
  color: var(--color-muted);
}
.comm-history-row--cur .comm-history-period {
  color: var(--color-info);
}
.comm-history-toggle {
  margin-top: 6px;
  padding: 0;
  color: var(--color-fg-2);
}
.comm-history-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

/* 特殊结算佣金（达人 × 渠道×场景 × 商品/套餐） */
.spc-section .spc-group { margin-bottom: var(--space-3); }
.spc-group__head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  color: var(--color-fg);
  margin-bottom: var(--space-2);
}
.spc-group__head::before {
  content: '';
  width: 3px;
  height: 14px;
  border-radius: 2px;
  background: var(--color-accent);
}
.spc-table { margin: 0; }
.spc-lock-tag {
  display: inline-flex;
  align-items: center;
  padding: 4px 10px;
  border-radius: var(--radius-sm);
  background: var(--color-surface-2);
  color: var(--color-fg-2);
  font-size: var(--font-size-xs);
}
.spc-toggle { gap: 6px; }

/* 排期佣金覆盖确认：排期编号链接 + 时间 */
.spc-sched-row { display: flex; align-items: center; gap: var(--space-3); }
.spc-sched-link { color: var(--color-info); font-weight: var(--font-weight-medium); text-decoration: none; cursor: pointer; }
.spc-sched-link:hover { text-decoration: underline; }
.spc-sched-time { color: var(--color-fg-2); }

/* 排期佣金覆盖确认：佣金对比（当前 → 覆盖后） */
.spc-sched-card { padding: var(--space-1) 0 var(--space-2); }
.spc-sched-card + .spc-sched-card { border-top: 1px solid var(--color-border-soft); }
.spc-sched-comm { display: flex; gap: var(--space-2); align-items: baseline; color: var(--color-fg-2); margin-top: 2px; }
.spc-sched-comm-label { color: var(--color-muted); font-size: var(--font-size-xs); flex-shrink: 0; }

/* 覆盖确认弹窗：前后佣金差异红色高亮 */
.spc-comm-diff { color: var(--color-danger); font-weight: var(--font-weight-semibold); }

/* 佣金配置板块卡片：板块之间清晰分隔（佣金配置 / 开票与税点 / 特殊佣金） */
.commission-section {
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-lg);
  padding: var(--space-4);
  margin-bottom: var(--space-4);
  background: var(--color-surface);
}
.commission-section:last-child { margin-bottom: 0; }
.commission-section__sub {
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px solid var(--color-border-soft);
}

/* ==========================================================================
   达人经营 · 明细表：数值单元格禁止断行（2026-09-17）
   --------------------------------------------------------------------------
   问题：`-¥15,280.55` 会在负号后断成两行 —— 第一行只有 "-"、第二行是 "¥15,280.55"。
        视觉上像两个内容，且**负号与数字分离有误读成正数的风险**（老板要念数出去）。
   实测（1900px）：成本类目列与「利润（预估）」列都有此现象。
   修法：数值单元格禁止换行 + 同步加宽列（见 influencer-profit.js 的 W_MONEY/W_CAT）。
   ⚠️ 只作用于本页明细表（`#ip-body table`），不动全站 .table ——
      其他表列宽预算不同，全局 nowrap 会让那些表的内容溢出单元格。
   ========================================================================== */
#ip-body table.table tbody td.is-num {
  white-space: nowrap;
}

/* 达人明细表：窄屏**不压缩列宽**，改为整体横向滚动（2026-09-17）
   --------------------------------------------------------------------------
   本表是 13 列的数据密集表，宽度由「金额列要放得下六位数」决定（见 W_MONEY）。
   不加 min-width 时，窄屏会把**唯一的弹性列（达人/明细项）**压到 101px，
   达人名 + 编号 + 场次 + 「其中未满 35 天 GSV ¥169,930.04」折成 7~8 行
   （实测 1600px 屏平均 7.1 行）—— 比横向滚动难读得多。
   1560px = 固定列合计（箭头 32 + 金额 4×118 + 类目 5×110 + 利润率 86 + 状态 96）
            + 达人列最小可读宽 ≈ 260px。
   1920px 屏不受影响（实测表格自然宽 1614px > 1560）。
   ========================================================================== */
#ip-body table.table { min-width: 1560px; }

/* ==========================================================================
   签约公司 · 「添加旗下达人」弹窗的达人选择行（2026-09-22）
   --------------------------------------------------------------------------
   乐哥：「我们之前做的『达人公司』功能…UI 界面也跟我们系统的不一样，整体调整一下」。

   改前：裸的 `<label><input type=checkbox><span>名字</span></label>` 一行 ——
        没有头像、没有行悬停/选中态、复选框极小不好点，明显不是本系统的观感。
   改后：与全站达人选择器（`.fp-*`）同一套语言 ——
        圆形头像 + 名字 + 编号右对齐 + 整行可点 + 选中态高亮。
   ⚠️ 颜色一律走 design-tokens，不写死色值（改主题时这里要跟着变）。
   ⚠️ 本块只作用于 `.co-pick-list`（弹窗内），不动全站 label / input 的任何样式。
   ========================================================================== */
.co-pick-list {
  max-height: 360px;
  overflow: auto;
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-md);
}
.co-pick-item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  cursor: pointer;
  border-bottom: 1px solid var(--color-border-soft);
  transition: background-color var(--motion-fast) var(--motion-ease-standard);
}
.co-pick-item:last-child { border-bottom: 0; }
.co-pick-item:hover { background: var(--color-surface-2); }
/* 选中态：淡主色底 + 左侧主色条，一眼看出勾了哪几行 */
.co-pick-item.is-picked {
  background: var(--color-primary-50);
  box-shadow: inset 3px 0 0 var(--color-primary-500);
}
.co-pick-item__name {
  font-size: var(--font-size-sm);
  color: var(--color-fg);
}

/* ══════════════════════════════════════════════════════════════════════════
   达人详情页 / 编辑页 —— 信息层级（2026-09-27）
   ──────────────────────────────────────────────────────────────────────────
   乐哥：「主次模块之间感觉还是分得不清楚」
   ⭐ 实测诊断（改动前，两页共 6 张卡）——**每一个视觉维度都完全相同**：
        底色 rgb(255,255,255) ｜ 边框 1px rgb(235,236,239) ｜ 圆角 16px
        内边距 20px ｜ 标题 14px/500 ｜ 卡片间距一律 32px
      ⇒ 视觉上是一坨"平级的卡"：读不出哪个重要、哪几张属于一组。
   解法（三层，全部只用**既有令牌**；⛔ 不动骨架、不动 .module-card 基础规格）：
      ① 组标题 —— 模块按"组"组织，标题说明这组是什么
      ② 组内紧 / 组间松 —— 组内 16px、组间 40px（邻近性原则：距离表达从属关系）
      ③ 卡片两档 —— 主卡（白底 + 边框）｜次卡（浅底、无边框、更轻）
   ⛔ 约束：主次**不许**用"彩色描边 / 外发光"表达（全站明令禁止，见 components.css 顶部）；
      只能用「底色深浅 + 边框有无 + 间距」。
   ══════════════════════════════════════════════════════════════════════════ */

/* ── ⓪ 字号层级（2026-09-27 第二批：乐哥「文字标题是不是可以大一点？跟上面的正文重叠了」）
   ─────────────────────────────────────────────────────────────────────────
   实测（改动前）：组标题 14px / 卡片标题 14px / 正文 14px —— **三档完全同号** ⇒ 标题淹没在正文里。
   按设计令牌的既定语义恢复层级（令牌注释里本来就写着"卡片标题 = lg"）：
     · 组标题   --font-size-lg (16px) + semibold(600) + 主色竖条   ← 最重
     · 卡片标题 --font-size-lg (16px) + medium(500) + 标题栏分隔线 ← 中
     · 正文     --font-size-base (14px)                            ← 轻
     · 注释     --font-size-xs (12px)                              ← 最轻
   ⚠️ `.module-card__title` 原为**全站共用**（66 页），2026-09-27 时只能 scoped 到 `.ia-page`；
      2026-09-28 全站统一提档为 lg（components.css 同值）⇒ 本 scoped 规则已成冗余，已移除。 */
/* ── ① 分组容器与组标题 ───────────────────────────────────────────────── */
/* ⭐ 2026-09-28 组卡化（乐哥：「概览里合作与平台、经营数据这两个标题为什么在卡片外面？
   之前说了要放卡片里面」）——
   分组 = **一张白卡**，组标题嵌在卡内当卡头；组内原 module-card 去卡化降为平铺板块。
   与 2026-09-28 看板页拍板的「一个模块一张白卡、标题嵌在卡内」同一条设计语言，
   也与页头包盒（.detail-head / .page-header--has-tabs）同规格：白底 + 1px 边框 + 圆角 16。
   ⚠️ 只作用 .ia-page（达人详情页 + 编辑页，全站仅此两页用 iaGroup）。 */
.ia-group {
  margin-top: var(--space-4);   /* ⭐ 2026-09-28：20 → 16（全站卡片间距收一档，乐哥：「小一点点」） */
  background: var(--color-surface);
  border: 1px solid var(--color-frame-border);
  border-radius: var(--radius-lg);
  /* ⭐⭐ 2026-10-07（乐哥：「首先我说的是**外部的那个要框，要有悬浮效果**，
     就是一个模块一个模块那种」）**补上悬浮阴影**
     ────────────────────────────────────────────────────────────────────────
     ▸ 根因（真机实测）：这一层**只有边框、没有阴影** ⇒ 在近白底盘上"贴"着，
       读起来是"贴在白底上的一块"而不是"浮起来的一张卡"。
     ▸ **原来的去卡化前提已经不成立**：见下方「组内板块去卡化」那条注释——
       2026-09-28 去掉内层 module-card 的卡是为了避免「白卡套白卡」，
       而那时底盘是**灰**的，外层白卡压在灰底上层次清楚。
       ⛔ 但2026-10-06 壳层把底盘改成接近白（#F6F6F6）、内容区本身也是白卡
       ⇒ 白上白 ⇒ 层次全靠边框硬撑 ⇒ 外层不浮、内层那块影反而更突出（层级反了）。
     ▸ 用 `--shadow-card`（全族统一的那个令牌，刻意很淡 .03/.05）
       —— 底盘近白，阴影重了会显脏。
     ⛔ 别只给这一处加自定义阴影：卡片族必须走 `var(--shadow-card)`
       （门禁 card-boundary-line-only ①②③ 锁着）。 */
  box-shadow: var(--shadow-card);
  padding: var(--space-6);
}
.ia-group:first-child { margin-top: 0; }

/* ⚠️ 2026-10-09 表单统一（第一批）：本分区的**外观**（字号/字重/字色/横排/序徽标/说明行）
   已全部并入 overlays.css 的 `.form-sec` 别名组 ⇒ 这里**只留间距**。
   ⛔ 不许再把 font-size / color / width / border-radius 之类加回来（门禁 form-unify ①）。 */
.ia-group__title {
  margin-bottom: var(--space-4);
}
/* 序号徽标（⭐ 2026-09-27 乐哥给 PMS「新建项目」参考图：「整个 UI 的二级页面应该按照
   项目管理系统那个模型去做」。该模型第一眼线索就是这个编号：`① 基础信息` `② 业务与计费`
   —— 给出"一共几部分、现在在哪一部分"的顺序感。原实现是 3px 竖条，只有"这里是一组开头"。）
   ▸ 外观见 overlays.css 的 `.form-sec__no`：18×18/r5/浅蓝底 → **20×20 · r6 · 浅蓝底主色字** */
/* .ia-group__hint：外观见 overlays.css 的 `.form-sec__hint` */

/* ── ② 组内卡片收紧 + 去卡化 + 首卡标题接管（⭐ 2026-09-28 B 方案）────────────
   ⚠️ 必须用后代选择器限定在 .ia-group 内 —— 直接改 `.module-card + .module-card`
      会波及全站 66 个页面。 */
.ia-group .module-card + .module-card { margin-top: var(--space-4); }  /* ⭐ 2026-09-28：同全站口径 20 → 16 */

/* 组内板块去卡化：原 .module-card（白底+边框+padding）在组白卡内降为平铺板块 ——
   否则「白卡套白卡」，与昨晚修掉的卡套卡是同一个毛病。
   ⭐⭐ 2026-10-07（乐哥选 A 档）：**再去掉它的阴影** ——
   ▸ 为什么要去影（这是「莫名多了个模块」的真身）：
     实测（改动前）这一层是「**无边框 + 有阴影**」⇒ 一个带框白卡里又浮着一个
     只有阴影、没有边框的影卡 ⇒ **两套边界语言**（外层用线、内层用影）叠在一起，
     读起来就像"多了一个模块"，也就是乐哥截图里圈的那两处。
   ▸ A 档的层级关系（乐哥要的"一个模块一个模块"）：
     `外层 .ia-group`（有框 **+ 有影**，真的浮起）
       → `内层 .module-card`（**无框无影**，纯平铺，靠标题与间距分组）
       → `内层 .kpi-card`（有框 + 有影，保留 —— 乐哥：「里面这个小框可以要」）
   ⛔ 别给 `.module-card` 单独写阴影值：要么走 `--shadow-card`（**顶层卡片**的待遇），
     要么 `none`（**卡内子块**的待遇）。它是卡内子块（父链上有 .ia-group 卡片）⇒ `none`。
     判据见 DESIGN-SPEC「哪些块浮哪些不浮」。 */
.ia-page .ia-group > .module-card {
  background: transparent;
  border: 0;
  border-radius: 0;
  padding: 0;
  /* ⭐ 卡内子块**不许浮**（否则"框里套框"）；浮起只由外层 .ia-group 承担。 */
  box-shadow: none;
}

/* 首卡标题由组标题接管（乐哥：「b，标题重复了」）——
   组标题与首卡标题近义（「合作与平台」vs「合作信息」、「经营数据」vs「业绩概览」、
   「基础信息」vs「基本信息」…共 10 组全重复），两行并排是噪音。
   ⚠️ 只藏标题**文字**、不藏 head 本身：head 里可能还有控件
      （编辑页「收货地址」的「添加地址」按钮就在 head 里，margin-left:auto 靠右），
      整块藏会把控件一起弄丢。 */
/* ⚠️⚠️ 2026-10-13（乐哥截图：「这个 ui 显示有问题」）：上面这条曾经**只认相邻**形态
   `.ia-group__title + .module-card`。2026-10-12 把「口径生效时间」卡（`.card`）插到了
   分区标题与佣金卡之间 ⇒ 相邻关系被打破 ⇒ 编辑页的「佣金配置（渠道 × 场景）」**显示了两遍**
   （真机实测：y=246 分区标题 + y=473 卡头标题；查看态不渲染生效卡 ⇒ 照旧隐藏，所以只有编辑页犯）。
   ⇒ 把两种形态都收进来（`:is()` 只写一遍，两条规则共用同一个"首卡"前缀）：
        · `.ia-group__title + .module-card`          —— 原形态（其余 9 个分区）
        · `.ia-group__title + .card + .module-card`  —— 中间夹了生效卡（佣金分区①，仅编辑页）
   ⛔ 别再退回单一 `+ .module-card`：只要中间再插一个元素就复发（这次就是这么坏的）。
   ⚠️ 加了新形态后，下面那条"head 无控件则整块收缩"也要同步（同一前缀，改一处要改两处）。 */
.ia-page .ia-group > :is(.ia-group__title + .module-card, .ia-group__title + .card + .module-card) > .module-card__head > .module-card__title {
  display: none;
}
/* head 里除标题外别无内容（无控件）→ 整个 head 收缩，不留空 padding / 分隔线。
   ⚠️ 判据 `:has(> :not(.module-card__title))` = 「head 的直接子级里存在非标题元素」：
      判假 ⇒ 只有标题 ⇒ 藏整个 head；判真 ⇒ 有控件 ⇒ 保留 head（上一行已藏标题文字）。 */
.ia-page .ia-group > :is(.ia-group__title + .module-card, .ia-group__title + .card + .module-card) > .module-card__head:not(:has(> :not(.module-card__title))) {
  display: none;
}
/* ⚠️ 上面两条只够得着"**卡头里的**标题"。佣金分区③「特殊商品/套餐佣金」（`.spc-section`，
   来自共享模块 `special-commissions.js`）的标题**不在卡头里** —— 它在
   `.spc-section > .detail-section__head > .detail-section__title` ⇒ 得单独藏，否则标题显示两遍。
   ⚠️ 同时把那个 head 改成**右对齐**：它原本靠 `space-between` 把「新增特殊商品/套餐佣金」按钮
   顶到右边，标题一藏按钮就会滑到左边（`space-between` 单子元素时贴左）。
   ⛔ 该模块在别处（佣金总览等）也在用 ⇒ 判据**必须限定**在"被编号分区包起来的场合"。 */
.ia-page .ia-group > .ia-group__title + .module-card .spc-section > .detail-section__head > .detail-section__title {
  display: none;
}
.ia-page .ia-group > .ia-group__title + .module-card .spc-section > .detail-section__head {
  justify-content: flex-end;
}

/* ── ③ 次模块卡：降一档（附属信息）───────────────────────────────────────
   用途：本身是"上面那张主卡的细节/附属"的模块 ——
     详情页「平台账号 / 机构」＝「合作信息」的明细；
     编辑页「带货小号」＝「平台相关」的附属。
   用全站既有的"次级底"令牌 --color-surface-2，不新造颜色、不加阴影
   （柔底已有色彩区分度，加阴影反而发闷 —— 见 KPI 柔底卡的做法）。 */
.module-card--sub {
  background: var(--color-surface-2);
  border-color: transparent;
}
/* ⭐ 2026-09-28 组内次卡（浅底分区）：去卡化的 `padding: 0` 与本条同特异性(0,3,0)，
   靠**源码顺序**取胜 —— 本条写在去卡化之后（下文位置）⇒ 内边距生效。
   ⚠️ 若将来把本条上移，次卡会变"浅底块内容贴边"（静默、不报错）。 */
.ia-page .ia-group > .module-card--sub {
  background: var(--color-surface-2);
  border-radius: var(--radius-md);
  padding: var(--space-4) var(--space-5);   /* 16/20：卡内分区比外层卡（24）收一档 */
}
/* 次卡的头必须透明，否则 .module-card__head--plain 的白色底会"贴"在浅底上 */
.module-card--sub > .module-card__head { background: transparent; }
/* ⚠️ 次卡内的表头也要透明 —— 全站表头底是**白色**，比次卡底(#F1F5F9)更亮，
   留着会变成"一张白表头浮在灰卡上"（实测踩到：次卡底 rgb(241,245,249) vs 表头 rgb(255,255,255)）。
   透明后由卡片底色统一承载，整卡是一块柔和的浅底。 */
.module-card--sub .table thead th { background: transparent; }

/* ── 表单行：标签在左、右对齐成一列（⭐ 2026-09-27 照 PMS「新建项目」模型）
   ──────────────────────────────────────────────────────────────────────────
   原实现（标签在上、控件在下）的问题：每个标签各贴各的控件，**不成列** ——
   扫读时标签与正文混在一个层级里（乐哥：「标题跟上面的正文重叠了」）。
   改后：标签固定宽 + 右对齐 + 垂直居中，控件占满剩余宽 ⇒ 标签成一条整齐竖线。
   ⚠️ `.field-label` 是全站公共类，故新规则一律限定在 `.form-row` 内。 */
.form-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-width: 0;
}
.form-row > .field-label {
  flex: none;
  width: 112px;               /* ⭐ 2026-09-27：84 → 112。84px 只能放 6 个汉字，
                                 实测「是否沿用公司口径 / 达人基础信息备注 / 年框合同开始时间」
                                 等 8 字标签需要 112px —— 全被**裁剪**（scrollWidth 112 > 84）。
                                 标签被裁 = 信息丢失，比换行更糟。 */
  margin-bottom: 0;          /* 覆盖 .field-label 的 margin-bottom: 8px（那是"标签在上"用的） */
  text-align: right;
  /* ⭐ 2026-09-29 兜底：放开折行（原 nowrap）。现行最长标签 8 字（112px 恰好放下）不受影响；
     今后出现 9 字以上标签自动折成 2 行右对齐，**绝不裁剪**（裁剪=信息丢失）。
     文案约定不变：选项名尽量 ≤8 字，更长的解释进 field-hint / ⓘ。 */
  line-height: 1.35;
}
.form-row__ctrl { flex: 1; min-width: 0; }
/* ⭐ 2026-09-29 乐哥：「有的输入框上下挨着太紧了，比如排期列表的备注」。
   实测确认：节点 `备注` 这类"网格之下的整行字段"，与上方 `.form-grid` 的最后一行
   **垂直间距 = 0px**（真贴着）。原因是网格行距由 grid 自己给、而它后面的 `.form-row`
   不带任何 margin ⇒ 上下两组之间没有分隔。
   ⇒ 凡「.form-grid 后面紧跟一个 .form-row」的形态（备注/说明类整行字段），统一补间距。
   ⚠️ 只加"紧邻"这一种（用相邻兄弟选择器），不影响网格内部的行距。

   ⭐⭐ 2026-09-29 二次修正（乐哥：「备注和上面其他输入框间距还是不够」）：
   第一版给的 12px（--space-3）**比网格内部行距还小** —— 上一批把 `.form-grid` 的
   gap 从 16 提到 20（呼吸感）时，漏了同步这一条，导致「节点内行距 20px、
   但备注只剩 12px」⇒ 备注比普通行还紧，视觉上像被压住。
   实测复现：网格底 669 → 备注顶 681（净距 12），而网格内部行净距 20。
   ⇒ 改为 24px（--space-6）：比行距大一档，备注读作"独立的一组"而不是"没对齐的第三行"。
   ⚠️ 别再退回 --space-3/--space-4 —— 必须 **≥ 网格 gap**，否则又会出现"比行距还紧"。 */
.form-grid + .form-row { margin-top: var(--space-6); }
/* 多行控件（textarea / 备注）不适合垂直居中，改为顶对齐 */
.form-row:has(> .form-row__ctrl > textarea) { align-items: flex-start; }
.form-row:has(> .form-row__ctrl > textarea) > .field-label { padding-top: 10px; }
/* 窄屏：标签在左会挤压控件宽度 ⇒ 退回"标签在上"（与全站默认一致） */
@media (max-width: 900px) {
  .form-row { display: block; }
  .form-row > .field-label { width: auto; text-align: left; margin-bottom: var(--space-2); }
}

/* ══════════════════════════════════════════════════════════════════════════
   签约公司「查看 + 编辑」两个页面的规范对齐（2026-10-11 乐哥）
   乐哥：「签约公司这个查看还有编辑，里面的**标题字号还有排版**，都按照我们设计规范来，
          **该删掉的注释删掉，该进？的进？，该增加数字的增加数字**，现在都乱七八糟的，没个重点」
   ══════════════════════════════════════════════════════════════════════════ */

/* ── 详情页 · 卡片标题（统一档位）────────────────────────────────────────────
   ⭐ 改前本页**三种字号混用**，这是"乱七八糟"最直接的来源：
        · 概览「合同信息 / 开票信息」= 14/500（各写一段 inline style）
        · 概览「合同条款」           = 13/500（借了 `.ct-detail__section-hd`，那是**抽屉**的档）
        · 佣金配置 / 开票与税点 Tab   = 16/500（`.module-card__title`）
   ⇒ 全部收敛到规范既定层级：**卡片标题 16/500 · 正文 14/400 · 注释 12/400**
      （与 `.module-card__title` 同规格；该注释在 components.css 里就是这么写的）。
   ⛔ 别再给某一张卡单独写 font-size —— 一处漂移就会被看出来。 */
.co-card__t {
  font-size: var(--font-size-lg);
  font-weight: var(--font-weight-medium);
  color: var(--color-fg);
}

/* ── 编辑页 · 「佣金默认值」表格：开关与输入框**同行** ──────────────────────
   ⭐ 乐哥（截图圈着「线下」那格）：「这个开启的框和输入框你完全可以放一行，
      佣金最多 3 个数字，你不用担心宽度不够」。
   ▸ 改前：开关一行 + 输入框一行（`width:100%`）⇒ 3 列 × 3 行每格都撑两行高，表格虚高近一倍。
   ▸ 76px = 0~100 的佣金率（含 1 位小数，如 12.5）足够放；
     `flex:none` 防被表格列宽压扁。 */
/* ── 场景列（`CommissionShared.sceneTable` 的「场景」这一格）────────────────────
   ⭐⭐ 2026-10-12 补。改前这**三个类一个样式都没有** ⇒ 行开关与场景名按 inline 基线排，
      开关下沉 3px、名字被推挤（乐哥截图框着开关：「位置确实要调整下」）。
   ⛔ 不要再删掉这里的 flex：删了就回到"基线对齐 + 名字左边界参差"。 */
.co-sc-name { display: flex; align-items: center; gap: 6px; min-height: 34px; }
.co-sc-name .sc-label { font-weight: var(--font-weight-medium); font-size: var(--font-size-sm); flex: none; }
/* 「必填」：与达人侧 `.comm-scene-tag` 同一观感（同令牌、同圆角），只是换了个前缀 */
.co-sc-name .co-sc-tag {
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-medium);
  color: var(--color-accent-text);
  background: var(--color-primary-100);
  padding: 1px 8px;
  border-radius: var(--radius-pill);
  line-height: 1.4;
  flex: none;
}
/* 行开关靠右：与"左标签 · 右开关"的全站惯例一致，且开关自成一列好扫读 */
.co-sc-name .co-sc-sw { margin-left: auto; flex: none; }
/* 容器已是 flex+gap ⇒ 去掉 `.co-follow` 自带的 6px（否则间距变 12px） */
.co-sc-name .co-follow { margin-left: 0; }
/* ⛔ 2026-10-12：`.co-sc-remark`（场景表格下方的「返点备注」一行）已删除 ——
   乐哥要求"返点备注和返点放同一行"，排期侧改成返点单元格里的第二个输入框
   （见 `schedule-form.js` 的返点格）。别再把这个类加回来当独立一行用。 */
.co-rate { display: flex; align-items: center; gap: var(--space-2); }
/* ⭐ 2026-10-12 乐哥：「佣金查看的时候…直接显示值，不要显示输入框」——
   查看态的一个格子：**只有一个值**（可能再跟一句返点备注），没有开关/输入框。
   ⚠️ 字号/字重与输入框里的数字**刻意不同**（输入框 13px 常规、这里 14px + 600）：
      查看态要"一眼读到数"，也不该看起来像"能点的框"。 */
.co-rate--view { gap: var(--space-2); }
.co-rate__val { font-size: var(--font-size-base); font-weight: var(--font-weight-medium); font-variant-numeric: tabular-nums; }
.co-rate__val--off { color: var(--color-muted); font-weight: var(--font-weight-regular); }
/* 返点备注（查看态可见）：说明文字，比数值小一号、颜色弱一档 */
.co-rate__remark-txt { font-size: var(--font-size-xs); color: var(--color-muted); }
.co-rate__sw {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  flex: none;
  font-size: var(--font-size-sm);
  color: var(--color-fg-2);
  cursor: pointer;
  white-space: nowrap;
}
.co-rate__in { width: 76px; flex: none; }
/* ⭐ 2026-10-12 返点备注输入框（三处共用，`commission-shared.gridCell` 渲染）——
   与排期侧 `.cr-remark` 同宽 140px：它只是"这一行的返点是什么"的一小段文字。 */
.co-rate__remark { width: 140px; flex: none; }

/* ── 详情页 · 「跟随专场」提示（2026-10-11 乐哥）──────────────────────────────
   乐哥：「之前我们的规则不是**佣金如果只维护了专场，都跟随专场**吗，那应该在界面上有体现，
         但是当前没有，要完善」。
   ⚠️ 这是把**已有规则显性化**（取值侧 `configAtWithFallback` 一直是"混播/私域没配 → 回退专场"），
      不是新增规则。样式对齐达人侧 `.comm-follow`（同样是 pill），
      但公司侧这个格子不是 flex 容器 ⇒ 不用 `margin-left:auto`，改成贴在场景名后面。 */
.co-follow {
  display: inline-block;
  margin-left: 6px;
  font-size: var(--font-size-xs);
  color: var(--color-success);
  background: var(--color-success-bg);
  padding: 1px 8px;
  border-radius: var(--radius-pill);
  vertical-align: 1px;
  cursor: help;
}

/* ── 详情页 · 激励阶梯描述（「设阶梯」按钮下方一行小字：满1万返5% · 满2万返8%）
   同达人侧 `.c-incentive-desc` 的语义，只是那边写在折叠区里、这边直接跟在按钮同格。 */
.co-tier-desc { margin-top: 4px; }
.co-tier-btn { flex: none; }
/* ⚠️ 已删 `.co-rate__in--wide`（原为"激励那格填阶梯文本"留的 130px 宽输入框）——
   2026-10-11 起激励改成「设阶梯」按钮，没有长文本输入框了。
   留着是死规则，且会让人以为"还有一处宽输入框"（本项目对死规则一律删掉，别攒着）。 */

/* ── 详情页 · 概览的 KPI 行 ──────────────────────────────────────────────────
   ⭐ 乐哥：「**该增加数字的增加数字**…现在都乱七八糟的，没个重点」——
      本页改前 **0 个 KPI**（而达人详情页「经营数据」早就是 `.kpi-row` + `.kpi-card` 了）
      ⇒ 页面从头到尾都是 12px 小灰字 + 表格，读者抓不到"这家公司规模多大、什么口径"。
   ⚠️ 只用全站既有的 `.kpi-row` / `.kpi-card`，**不新造**；字号/内距都由公共类给。 */
.co-kpi { margin-bottom: var(--space-4); }
.co-kpi .kpi-card__value { white-space: nowrap; }

/* ══════════════════════════════════════════════════════════════════════════
   公司侧佣金卡的**渠道色**（2026-10-11 乐哥）
   乐哥：「公司的佣金，也和达人一样，分抖音和视频号，**颜色语义也要一致**，
         包括里面编辑佣金，查看佣金二级页面也一样」
   ────────────────────────────────────────────────────────────────────────
   🔑 语义与令牌**逐项对齐达人侧**（本文件 1344 行起的 `.comm-channel` 系列）：
        · 视频号(wechat_shop) = `--color-success`（绿）
        · 抖音(douyin)       = `--color-info`（钴蓝）
      落点也一一对应：**卡片边框 + 标题前的 4px 竖条 + 标题文字色**（+ 卡内「跟随专场」pill）。
   ⛔ 别在这里写死色值、也别另造一套渠道色 —— 渠道色的唯一来源就是这两个令牌。
      （`Dict.platformColor / platformBg` 是**平台标签**色板，用途不同，别混用。）
   ⚠️ 公司侧用的是 `module-card`（全站通用卡），达人侧是 `.comm-channel`（佣金专用卡），
      结构不同 ⇒ 这里用**修饰类**而不是去套达人侧的类；但色值语义必须一模一样。
   ⚠️ 视频号卡**不给标题上色**（保持深墨）—— 与达人侧一致：那边也只有 `--dy` 会给标题变蓝，
      故意如此：绿卡靠边框+竖条识别，标题再上绿会与"当前生效"等绿章抢语义。 */
.co-ch { border-width: 1.5px; }
.co-ch--wx { border-color: var(--color-success); }
.co-ch--dy { border-color: var(--color-info); }
.co-ch__t { display: flex; align-items: center; gap: 8px; }
.co-ch__t::before {
  content: '';
  width: 4px;
  height: 14px;
  border-radius: 2px;
  background: var(--color-success);
  flex-shrink: 0;
}
.co-ch--dy .co-ch__t { color: var(--color-info); }
.co-ch--dy .co-ch__t::before { background: var(--color-info); }
/* 卡内的「跟随专场」pill 也跟随渠道色（对齐达人侧 `.comm-channel--dy .comm-follow`） */
.co-ch--dy .co-follow { color: var(--color-info); background: var(--color-info-bg); }

/* ⭐⭐ 2026-10-12 乐哥：「公司佣金维护，也需要和达人排期那个一样，每个渠道设置个开关」
   ──────────────────────────────────────────────────────────────────────────
   · `.co-ch__sw` —— 渠道开关放卡头**最右**（`margin-left:auto` 把标题/摘要往左推）。
     与排期侧 `.sf-plat-sw`（卡头右侧开关）同一手感：**关掉的渠道仍画卡头 + 开关**
     （否则关了再也点不开），只隐藏明细。
   · `.co-ch--off` —— 关闭态：卡头**去渠道色**（竖条与标题走 muted），
     表达"这个渠道现在不维护"，避免用户一眼看过去仍以为它在生效。 */
.co-ch__sw { margin-left: auto; }
.co-ch--off { border-color: var(--color-frame-border) !important; }
.co-ch--off > .module-card__head .co-ch__t { color: var(--color-muted); }
.co-ch--off > .module-card__head .co-ch__t::before { background: var(--color-muted); }
.co-ch-note--off { color: var(--color-muted); }

/* ══════════════════════════════════════════════════════════════════════════
   公司侧「全渠道激励」独立块（2026-10-11 乐哥：「应该在平台佣金里面独立出来」）
   放在佣金配置区里，与「渠道 × 场景」两张表**并列**（不再占表格的一列）。
   ⚠️ 金额一律由服务端算好（`GET /influencer-companies/:id/incentive`），
      前端只画 —— 别在这里做任何计算。
   ══════════════════════════════════════════════════════════════════════════ */
.co-inc-stats {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-3);
  margin-top: var(--space-2);
}
.co-inc-stat {
  background: var(--color-surface-2);
  border-radius: var(--radius-md);
  padding: var(--space-2) var(--space-3);
}
.co-inc-stat .l { font-size: var(--font-size-xs); color: var(--color-muted); }
.co-inc-stat .v { font-size: var(--font-size-lg); font-weight: 500; margin-top: 2px; }
/* 「命中档位」命中时用成功色（与「当前生效」等绿章同一语义） */
.co-inc-hit { color: var(--color-success); }
.co-inc-desc { font-weight: 500; }
/* 「已停用」pill：与「跟随专场」同形状、走中性色（停用 ≠ 绿色那种"生效"语义） */
.co-follow--off { color: var(--color-muted); background: var(--color-surface-2); }

/* ⭐⭐ 2026-10-13 「佣金与开票 · 沿用公司」那一格 = 开关 + 右侧「公司当前佣金」按钮。
   （乐哥：「佣金与发票那个间距不对，然后佣金这些展示方式很奇怪，要不放在沿用公司那一行右边，
     有个按钮一点就弹一个窗口，表格方式展示」⇒ 蓝色提示条整块删掉，改按钮 + 弹窗表格。）

   ⚠️⚠️ **高度必须写在开关自己身上**（`.co-inherit-cell .switch`），⛔ 绝不能写在外层容器上：
      外层写 `min-height: 44px` 时，22px 的开关会被"居中在 44px 的空框里"，
      于是**标签到开关的视觉间距凭空多出 (44−22)/2 = 11px**，而左边"标签→下拉框"只有 8px
      —— 这正是乐哥说的"间距不对"。写在开关上 ⇒ 开关顶边距标签与左侧下拉一致，
      同时轨道仍在 44px 的高度内垂直居中、与左侧下拉齐平。 */
.co-inherit-cell {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}
.co-inherit-cell .switch { min-height: var(--control-height); }
/* 没选公司时开关不可用：公共 `.switch` 的 disabled 规则只把**轨道**调半透明，
   标签文字仍是正常色 ⇒ 看着像"能点却点不动"。这里把标签也弱化。
   ⚠️ 只在本格覆盖，**不动**公共 `.switch`（那会连带全站其他禁用开关）。 */
.co-inherit-cell .switch input:disabled ~ .switch__label { opacity: 0.5; }

/* ── 弹窗：公司当前佣金（表格）─────────────────────────────────────────────
   读数在弹窗里、填表在页面上，两边不重复说同一件事。
   表格用全站同一套类（`table-wrap` + `table table--zebra`）。 */
.co-comm__msg {
  padding: var(--space-6) 0;
  text-align: center;
  color: var(--color-muted);
}
.co-comm__note {
  margin-top: var(--space-3);
  font-size: var(--font-size-xs);
  color: var(--color-muted);
}



/* ==== /css/pages-schedule.css ==== */
/* ==========================================================================
   达播工作台 — pages-schedule.css
   排期日历 / 排期列表 / 抽屉详情
   ========================================================================== */

/* ============================ 排期日历 ============================ */

.calendar-toolbar {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
  margin-bottom: var(--space-4);
}

.calendar-filters {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
  margin-bottom: var(--space-3);
}

.calendar-filters > * {
  flex: 0 0 auto;
}

.calendar-month {
  font-size: var(--font-size-lg);
  font-weight: var(--font-weight-semibold);
  min-width: 140px;
  text-align: center;
  font-feature-settings: "tnum", "lnum";
  font-variant-numeric: tabular-nums;
}

.legend {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  flex-wrap: wrap;
  /* ⭐ 2026-10-11：日历行里 `.legend` **靠右**（`margin-left: auto`）。
     以前靠右的是自造的 `.view-switch`（已删），图例占着它的位置看起来是居中的；
     view-switch 搬进页头后，这一行只剩「翻月按钮 + 年月 + 图例」⇒ 图例顶到最右，
     与上方页头的主操作右缘对齐，整块读起来才是"一行左、一行右"。 */
  margin-left: auto;
}

.legend-item {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--font-size-xs);
  color: var(--color-fg-2);
  cursor: pointer;
  user-select: none;
}

.legend-item__dot {
  width: 10px;
  height: 10px;
  border-radius: var(--radius-sm);
}

.legend-item.is-off {
  opacity: 0.4;
}

/* ⭐⭐ 2026-09-26 晚 · 日历格子「白色模块化」（乐哥：「日期格子能不能做成那种白色模块的样子，
   又或者不需要线条，反正就是很简约、很直观的那种」）
   ──────────────────────────────────────────────────────────────────────────
   ▸ 改前：容器白底 + 1px 网格线（`border-right/bottom`）+ 格子透明
     ⇒ 读起来像"表格/文档"，线条是主视觉，格子本身没有存在感。
   ▸ 改后：**容器当画布（浅灰、无边框）+ 格子当白色圆角卡 + 灰缝** ——
     线条全部撤掉，靠"白卡浮在浅灰画布上"分隔。这是主流日历（飞书/Notion/Linear）的做法。
   ▸ ⭐ 关键实现选择：**灰缝用 `box-shadow` 描边画，不用 `gap`** ——
     实测两者都成立，但 `gap` 有代价（吃掉布局宽度）：
       · 真 gap 6px：格子宽 217.9 → **211**（-7px）、文本区 141 → **138**、
         截断率 **39% → 44%**（把上一轮刚优化的宽度又吃回去）
       · 阴影描边：格子宽 **217.3**（-0.6px，仅圆角/阴影的抗锯齿误差）、
         文本区 **140**、截断率 **39%** —— **与改前完全一致，零代价**
     ⇒ 盒子尺寸不变，只是"看起来有缝"。缝宽 = 阴影扩展半径（桌面 3px / 手机 2px）。
   ⚠️ 为什么要 `.calendar-cell:nth-child(7n) { border-right: none }` 之外还清 border：
     容器无边框后，格子原有的网格线必须**全清**，只留 `box-shadow` 一层视觉。
   ⚠️ `overflow: hidden` 仍在 —— 事件条超出格子的部分要被裁掉（否则会盖住白卡圆角）。
   ⚠️ 选中框（`::after` 内缩 3px 圆角 10px）**与新圆角 12px 同族**，两版视觉不打架；
      且选中框画在卡**内侧**，与外侧的灰缝 ring 不冲突（实测五态共存的截图见汇报）。 */
.calendar {
  background: var(--color-surface-2); /* 画布：浅灰（原白底）*/
  border: none;                        /* 原 1px 外框撤掉 —— 线条语言整体退场 */
  border-radius: var(--radius-lg);
  overflow: hidden;
}

.calendar__weekdays {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  background: transparent;      /* 原 --table-header-bg（画布已上色，表头不需要单独底色）*/
  border-bottom: none;          /* 原 1px 分隔线 */
}

.calendar__weekday {
  padding: var(--space-2);
  text-align: center;
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-medium);
  color: var(--table-header-fg);
}

.calendar__grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  /* 只加内边距（让首行/首列的白卡不贴容器边），**不加 gap** ——
     灰缝由格子自己的 `border` 画，见下。
     6px 的由来：格子 border 3px，相邻两格各出一半 ⇒ 格间缝 = 3+3 = 6px；
     容器边要与之**等宽**，故 padding = 3px（容器边 3+3 = 6px，与格间一致）。 */
  padding: 3px;
}

.calendar-cell {
  /* 桌面格子：182px = 原 140px × 1.3（乐哥 2026-09-24「格子高度再增加 30%」）。
     ⚠️ 改这个值必须同步 schedule-calendar.js 的 maxEvents —— 条数上限与格子高度是绑死的，
        只改高度不改条数 ⇒ 格子里空出一大块（4 条只占 111px / 182px）。
        182px 容量核算（padding 5×2 + border 3×2 + 日期 24 + 容器 margin-top 4 = 44）：可放 6 条（6×20+5×1=125 ≤ 138）。 */
  min-height: 182px;
  min-width: 0; /* 让 grid-template-columns: repeat(7, 1fr) 真正按 1/7 均分，不被 cell 内长文字 min-content 撑爆 */
  /* ⭐ 白卡本体 */
  background: var(--color-surface);
  border: 3px solid var(--color-surface-2);
  /* ⭐⭐ 灰缝必须画在**格子自己的盒内**（border），不能再用 `box-shadow` 向外画环
     ──────────────────────────────────────────────────────────────────────────
     ⛔ 被否掉的做法：`box-shadow: 0 0 0 3px <画布色>`（2026-09-26 晚第一版）。
        乐哥实测报障：「每一日那个框框 **4 个角的圆角不一致，尤其是右下角**」。
        根因（绘制顺序，不是我以为的"圆角写错了"）：
          外扩的阴影环画在格子盒**之外**，而相邻格子是**互相紧贴**的；
          CSS 按 DOM 顺序绘制 ⇒ 后面的格子盖住前面格子的环。
          · 该格的「上、左」两侧：环盖在**前一个格子**上 ⇒ **可见** ✓
          · 该格的「下、右」两侧：环被**后一个格子**盖住 ⇒ **不可见** ✗
          于是下/右两侧的缝改由**邻居**的环提供 —— 而邻居的环是**直边**，
          会把这格自己的圆角**横切掉** ⇒ 下方两角看着是"方"的。
        实测四角灰度占比（30×30 特写 @4x）：**上面两角 23.2% / 下面两角 16.3%**
        —— 不对称是客观的，不是错觉。
     ✅ 改为 `border`：边框属于格子自己的盒内（`box-sizing: border-box`），
        四边四角的缝全部由**自己**画 ⇒ 跟着自己的 `border-radius` 走 ⇒ 四角天然一致。
     ⚠️ 代价必须抵消：border 会占内容宽度 ⇒ **padding 8 → 5**（8 = 5 + 3）。
        实测文本区宽度**保持不变**（否则会把"事件条宽度压缩"那轮的成果吃回去）。 */
  border-radius: 12px;
  padding: 5px;
  position: relative;
  overflow: hidden;
}

/* 上月格子：透明 —— 让"不属于本月"一眼可辨（比原来"填浅灰底"更干净，
   因为画布本身就是浅灰，填灰底会和画布糊在一起）。
   ⚠️ 边框也要转透明（不能用 `border: none`）—— 否则它仍是 6px 宽的实心灰框，
      会在画布上留出一圈"看不见的格子"，与"无卡"的意图矛盾。 */
.calendar-cell.is-outside {
  background: transparent;
  border-color: transparent;
}
.calendar-cell.is-outside .calendar-cell__day {
  color: var(--color-muted);
}

.calendar-cell.is-today .calendar-cell__day {
  background: var(--color-accent);
  color: var(--color-accent-on);
  border: none;
}

.calendar-cell__day {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  padding-top: 1px; /* 补偿中文字面偏上，日期视觉居中 */
  border-radius: var(--radius-pill);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  color: var(--color-fg-2);
  font-feature-settings: "tnum", "lnum";
  font-variant-numeric: tabular-nums;
}

.calendar-cell__events {
  display: flex;
  flex-direction: column;
  gap: 1px;
  margin-top: var(--space-1);
  overflow: hidden;
}

/* ⭐⭐ 2026-09-26 傍晚 · 事件条宽度压缩（乐哥：「这个日历视图你看一下有没有可以优化的」）
   ──────────────────────────────────────────────────────────────────────────
   ▸ 实测病根：**标题普遍被截断**。1920 视口下 59 条里 51 条带省略号（86.4%），
     1440 视口下 100%。原因不是文字太长，而是**宽度预算被固定支出吃掉**：
       事件条 200.9px = 色条 3 + 间隙 12(4×3) + 时间 31 + 内边距 8 + 锁图标 12 + 文本 135
     而「达人简称：事项-地点」这个标题格式的需求宽度分布是（实测 59 条）：
       p25/p50 = 144px、p75 = 148px、max = 188px
     ⇒ 文本区给到 **144px 就能覆盖中位数**，现状只给 135px，差 9px。
   ▸ 本版 4 处「零取舍」微调（每处 1px 级，不丢任何信息）合计腾出 7px：
       ① gap 4→3px          省 3px（3 个间隙）
       ② padding 4→3px      省 2px
       ③ 锁图标 12→10px     省 2px
       ④ 色条 3→2px         省 1px
       （时间字重 medium→regular 不省宽度，只是让数字少一点"抢眼"，
         好把注意力让给标题 —— 时间是辅助信息，标题才是内容。）
   ▸ 实测收益（1920）：截断率 **86.4% → 30.5%**（51 条 → 18 条），文本区 135 → 141px。
     1440 视口下无解（格子仅 149px、文本区上限 ~78px）—— 那是结构性取舍，
     需要动"显示时间 or 换行 or 缩标题格式"，另议（见汇报）。
   ⚠️ 改这些值必须同步复核：① tooltip（title）必须仍在（截断的兜底）；
     ② 移动端（≤768px）有自己的 `.cal-event` 覆盖块（源码在本段之后 ⇒ 仍生效）。 */
.cal-event {
  display: flex;
  align-items: center;
  gap: 3px;
  height: 20px;
  padding: 0 3px;
  border-radius: var(--radius-sm);
  font-size: var(--font-size-xs); /* 默认缩小 DPI：色条更紧凑，一格能显示 5 个事项 */
  line-height: 1;
  cursor: pointer;
  overflow: hidden;
  white-space: nowrap;
}

.cal-event__bar {
  width: 2px;
  height: 100%;
  border-radius: var(--radius-sm);
  flex-shrink: 0;
}

.cal-event__time {
  /* 字重降一档（medium→regular）：tabular 数字等宽，宽度不变，只是笔画轻一点 ——
     让"时间"退到背景，把视觉重量让给右边的标题。 */
  font-weight: var(--font-weight-regular);
  color: var(--color-fg-2);
  flex-shrink: 0;
}

.cal-event__text {
  overflow: hidden;
  text-overflow: ellipsis;
}

.cal-event:hover {
  filter: brightness(0.96);
}

/* 已结束的排期（日历）：色条淡化，悬停轻微恢复 */
.cal-event.is-past {
  opacity: 0.5;
}
.cal-event.is-past:hover {
  opacity: 0.85;
}

.cal-more {
  font-size: var(--font-size-xs);
  color: var(--color-muted);
  padding: 0 var(--space-1);
}

/* ---- 月视图底部：本月待定排期（大概排期达人） ---- */
.cal-approx {
  margin-top: var(--space-4);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-md);
  background: var(--color-surface);
}
.cal-approx__title {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
  color: var(--color-fg-2);
  margin-bottom: var(--space-2);
}
.cal-approx__list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}
.cal-approx-item {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1) var(--space-3);
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-pill);
  background: var(--color-surface-2);
  cursor: pointer;
  transition: border-color var(--motion-fast) var(--motion-ease-standard);
}
.cal-approx-item:hover { border-color: var(--color-accent-text); }
.cal-approx-item__dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--color-muted);
  flex-shrink: 0;
}
.cal-approx-item__name {
  font-size: var(--font-size-sm);
  color: var(--color-fg);
  font-weight: var(--font-weight-medium);
}
.cal-approx-item__type {
  font-size: var(--font-size-xs);
}
.cal-approx-item__bd {
  font-size: var(--font-size-xs);
  color: var(--color-muted);
}

/* ---- 抽屉详情 ---- */
.drawer-detail {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.detail-section__title {
  font-size: var(--font-size-lg);   /* ⭐ 2026-09-28：14(sm) → 16(lg) —— 与 pages-influencer 同标准，
                                       板块标题不再与正文(14)同号 */
  font-weight: var(--font-weight-semibold);
  color: var(--color-fg);
  margin-bottom: var(--space-3);
}

/* 详情抽屉大模块之间用虚线分隔（非标题下虚线） */
.detail-block + .detail-block {
  border-top: 1px dashed var(--color-border-soft);
  padding-top: var(--space-3);
}

.detail-section__title--with-action {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

/* 事项标题（在事项色块内，用事项颜色高亮，略大更显眼） */
.detail-item-title {
  font-size: 16px;
  font-weight: var(--font-weight-semibold);
}

.detail-item-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--space-2);
}

/* 时间轴事项标签（按事项类型标准色着色） */
.timeline__lbl {
  font-weight: var(--font-weight-semibold);
  font-size: var(--font-size-sm);
}

.timeline__item.is-current .timeline__lbl {
  font-weight: var(--font-weight-semibold);
}

/* 行内编辑卡片 */
.ie-box {
  border: 1px solid var(--color-border-soft);
  border-left: 3px solid var(--color-accent);
  border-radius: var(--radius-lg);
  padding: var(--space-3) var(--space-4);
  background: var(--color-surface-2);
  margin-bottom: var(--space-4);
}

.ie-actions {
  display: flex;
  gap: var(--space-2);
  margin-top: var(--space-3);
  justify-content: flex-end;
}

/* 详细信息突出展示卡片（整个排期所有事项的完整信息） */
.item-detail {
  border: 1.5px solid var(--color-accent);
  border-radius: var(--radius-lg);
  padding: var(--space-3) var(--space-4);
  background: var(--color-surface-2);
}

.detail-item-block {
  padding: var(--space-2) 0;
}
.detail-item-block + .detail-item-block {
  border-top: 1px dashed var(--color-border-soft);
  margin-top: var(--space-2);
  padding-top: var(--space-3);
}
.detail-item-block__head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
}
.detail-item-block__dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex-shrink: 0;
}
.detail-item-block__label {
  font-weight: var(--font-weight-semibold);
  font-size: var(--font-size-sm);
}
.detail-item-block__fields {
  padding-left: var(--space-4);
}

.kv-row {
  display: flex;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-2) 0;
  font-size: var(--font-size-sm);
}

.kv-row__k {
  color: var(--color-muted);
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  gap: 5px;
}

.kv-row__icon {
  display: inline-flex;
  align-items: center;
  color: var(--color-muted);
  flex-shrink: 0;
}
.kv-row__icon svg {
  display: block;
}

.kv-row__v {
  color: var(--color-fg);
  text-align: right;
  word-break: break-all;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  flex-wrap: wrap;
  gap: 4px;
}

/* ============================ 排期列表 / 表单 ============================ */

.schedule-conflict {
  margin-top: var(--space-3);
}

/* ============================ 排期表单：卡片化布局 ============================ */

/* ⭐⭐ 2026-09-28 顶栏「达人信息」改**一行横排 + 去框**（乐哥：
   「上面那个达人信息，要不直接一行展示，不要外面的框框了，多余」）。
   ▸ 改前：外层 `.card`（白底/边框/24px 内边距）+ 内层 `.detail-hero`
     （**又一层**白底 + 边框 + 圆角 16）⇒ 双层白框、整块 116px 高，
     其中纯"框 + 内边距"的占位就有 50px。
   ▸ 改后：本类**不带任何盒模型**（无背景/边框/内边距），只做一行横排，
     直接落在页面灰底上。

   ⭐⭐ 2026-09-28 晚·**加回白卡**（乐哥：「选择达人后，就是 tab 栏上面那个达人名
      看下咋处理，也给加背景还是啥」）—— 拍板**方案 A 白卡**。
   ─────────────────────────────────────────────────────────────────────────
   ⚠️ 为什么又要框（与上面"去框"不矛盾）：当天上午去的是**双层套娃**（白卡里再套白框，
      纯占位 50px）；现在加的是**单层**，且与下方内容卡**同款**（白底 + 1px
      `--color-frame-border` + 圆角 16），内边距用 14/22 而非 24 ——
      目的是让「头像左缘」与「tab 托盘 / 内容卡内文字」的缩进落在同一条竖线上。
   ⚠️ 选了达人之后这一行才真正有信息（头像 + 名字 + 商务 + 平台/类型标签）。
      裸放时它和下方 tab 之间没有分界，读起来像散文字 ⇒ 有了白卡才"是一块信息"。
   ⚠️ 宽度仍 `max-width: var(--form-max-width)` + 居中 —— 与 shell / 内容列**同宽同左缘**，
      否则白卡会比下面的卡宽出一截（实测对齐基准：1920 视口下左缘 440）。 */
.sf-hero-line {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
  max-width: var(--form-max-width);
  margin: 0 auto var(--space-4);
  /* 白卡（方案 A）—— 与 `.card` 同款：白底 + 1px 浅边框 + 圆角 16 */
  background: var(--color-surface);
  border: 1px solid var(--color-frame-border);
  border-radius: var(--radius-lg);
  padding: 14px var(--space-5);
}
/* ⚠️ `.detail-name`（20px 半粗）与 64px 头像在一行里居中：用 `align-items: center`
    而不是 `baseline` —— 头像没有文字基线，baseline 对齐会让名字沉到头像下缘。
   ⚠️ `#sf-hero-meta` **原本是 `flex-direction: column`**（每个平台一行）——
      那是"看着占三行"的另一半原因；这里显式改横排 + 允许换行（平台多时不至于撑破）。
   ⚠️ 内部的 `.cell-person__tags` 自带 `margin-top: 3px`（它在表格单元格里的对齐留白），
      横排时会让标签比名字低 3px ⇒ 在 `align-items: center` 下视觉偏移，这里归零。 */
.sf-hero-line #sf-hero-meta {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
}
.sf-hero-line #sf-hero-meta .cell-person__tags { margin-top: 0; }

.sf-card {
  max-width: var(--form-max-width);
  margin-left: auto;
  margin-right: auto;
  margin-bottom: var(--space-5);
}
.sf-card .card__body {
  max-width: none;
}

/* 分产品 GSV 动态行（业绩表单） */
.pf-prod-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
}
.pf-prod-row .icon-btn {
  color: var(--color-danger);
}

/* sticky 底部操作栏（全宽背景，内部内容限宽右对齐） */
.sf-actions {
  position: sticky;
  bottom: 0;
  z-index: 10;
  background: var(--color-bg);
  border-top: 1px solid var(--color-border-soft);
}
.sf-actions__inner {
  max-width: var(--form-max-width);
  margin: 0 auto;
  display: flex;
  justify-content: flex-end;
  gap: var(--space-3);
  padding: var(--space-4) 0;
}

/* 非 sticky 的底部操作区（普通 form-actions）：与 sf-actions 内容区对齐 */
.page-header ~ .form-actions {
  max-width: var(--form-max-width);
  margin-left: auto;
  margin-right: auto;
  display: flex;
  justify-content: flex-end;
  gap: var(--space-3);
}

/* ============================ 排期表单：左侧时间轴 ============================ */

/* 「分场次维护」——单个直播场次里的「本场提成点位」（助播 / 商务助理）。
   与上方的人员字段用一条浅分隔线断开，表明这是"本场独立的一组参数"、不与人员混在一起。 */
.node-rate-block {
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  border-top: 1px solid var(--color-border-soft);
}
.node-rate-block__title {
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-medium);
  color: var(--color-fg-2);
}

/* 基本信息卡里「各场点位」的只读汇总（第 1 场 0.2% / 第 2 场 0.1%）。
   高度对齐同排的输入框（40px），避免与左侧商务输入框错位。 */
.sf-rate-sum {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  min-height: 40px;
}
.sf-rate-sum__item {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 4px var(--space-3);
  background: var(--color-surface-2);
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-full);
  font-size: var(--font-size-xs);
  line-height: 1.6;
}
.sf-rate-sum__k { color: var(--color-muted); }
.sf-rate-sum__v {
  color: var(--color-fg);
  font-weight: var(--font-weight-medium);
  font-variant-numeric: tabular-nums;
}
/* 该场留空 = 回退默认档：灰字提示，别让它看起来像一个已配好的值 */
.sf-rate-sum__v.is-empty {
  color: var(--color-muted);
  font-weight: var(--font-weight-regular);
}
.sf-rate-sum--empty {
  display: flex;
  align-items: center;
  height: 40px;
  color: var(--color-muted);
  font-size: var(--font-size-base);
}

/* 「各场提成点位」表格（2026-09-15 从「行程排期」每场搬到「基本信息 → 提成点位」卡，乐哥拍板方案 A）。
   形态：**场次 × 角色** 两列输入框。
   为什么是表格而不是一行行堆叠：表格天然把「哪个值属于哪一场」对齐 ——
   这正是当初把它放在场次里的理由；现在带上「第 N 场 + 时间」，在基本信息里同样不会配错场次。 */
.rate-sched { width: 100%; border-collapse: collapse; }
.rate-sched th {
  text-align: left;
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-medium);
  color: var(--color-muted);
  padding: 0 var(--space-3) var(--space-2) 0;
  border-bottom: 1px solid var(--color-border);
}
.rate-sched td {
  padding: var(--space-2) var(--space-3) var(--space-2) 0;
  border-bottom: 1px solid var(--color-border-soft);
  vertical-align: middle;
}
.rate-sched tr:last-child td { border-bottom: 0; }
.rate-sched th:last-child, .rate-sched td:last-child { padding-right: 0; }
/* 输入框不给满宽：跟项目其它表单一致（过宽的 number 输入框会显得像"要填长内容"） */
.rate-sched input.input { width: 100%; max-width: 160px; }
.rate-sched__k { font-size: var(--font-size-sm); color: var(--color-fg); white-space: nowrap; }
.rate-sched__d { font-size: var(--font-size-xs); color: var(--color-muted); margin-left: var(--space-2); }

/* 「已按人配」标记（2026-09-15）：本场配了逐人点位时，整场值对本场**不生效**（后端 P1 优先于 P2）。
   为什么必须标：不标的话用户在本场填了值却算不出预期金额，会以为系统算错。
   ⚠️ 2026-09-15 晚改为**纯提示**（原来是 button、点了滚到下方「按人配点位」区块）：
      那块 UI 已按乐哥要求下线，跳转目标不存在 → 去掉 cursor:pointer 与 hover，
      否则会呈现"看着能点、点了没反应"的假可点。 */
.rate-sched__tag {
  margin-left: var(--space-2);
  padding: 1px var(--space-2);
  border: 1px solid var(--color-warning-border);
  border-radius: var(--radius-md);
  background: var(--color-warning-bg);
  color: var(--color-warning-text);
  font-size: var(--font-size-xs);
  line-height: 1.6;
  white-space: nowrap;
}

/* 被逐人点位覆盖的场次：整行略作弱化，提示"这里的整场值不是最终生效值" */
.rate-sched tr.is-by-person td input.rsc-input {
  border-style: dashed;
  color: var(--color-muted);
}

/* ⚠️ 已废弃（2026-09-15 晚）：`.member-rate-session*` 与 `rate-session-flash` 原是
   「按人配点位（助播 / 商务助理）」按场次分组的壳 + 从「已按人配」跳过来时的高亮动画。
   该区块已按乐哥要求下线（「功能多余，不要」），故整段删除。
   商务侧的逐人点位是**平铺列表**（`.member-rate-list`，见 pages.css），不用分组壳。 */

/* 「默认点位档」在**混合排期**（部分场有商务助理、部分没有）退化成两档并排：
   生产实测只有 1 条这种排期（#57 农科之声），所以样式从简 —— 两个数字 + 各自适用条件。 */
.sf-readonly--two {
  gap: var(--space-3);
  justify-content: flex-start;
}
.sf-readonly--two > span {
  display: inline-flex;
  align-items: baseline;
  gap: 4px;
}
.sf-readonly--two em {
  font-style: normal;
  font-size: var(--font-size-xs);
  color: var(--color-meta);
}
.sf-readonly__sep {
  width: 1px;
  height: 14px;
  background: var(--color-border);
}

/* ⚠️ 已废弃（2026-09-15）：`.node-rate-block` 原是「行程排期 → 每个直播场次」里的
   「本场提成点位」区块，该 UI 已搬到基本信息卡（见上面的 `.rate-sched`）。
   样式**保留**仅因 `.render-check/POINTS-LAYOUT-方案对比.html` 那版方案原型还在引用；
   日后清理原型文件时可连同这段一起删。 */

/* 「大概排期」与下方时间轴的**上下呼吸**（2026-09-15 乐哥：「上下间距大点」）。
   该块已改用 `.module-card--bare`（去卡片化 + 一条分隔线收尾）——分隔线两侧要**等宽留白**，
   读起来才是"一组结束 / 下一组开始"，而不是"线贴在某一侧"。
   ⚠️ 两侧留白来自**两个不同的属性**，必须成对调整：
       上方 = bare 块自己的 `padding-bottom`（components.css 给的是 --space-6 = 24px）
       下方 = 时间轴自己的 `margin-top`（#sf-timeline **不是** .module-card，
              拿不到 `.module-card + .module-card` 那 24px）
      只改一个 → 线两侧一边 24 一边 32，肉眼看着"线偏了一边"。
   ⚠️ 分隔线上下**必须等宽**（实测第一版 25 / 32 → 线明显"偏下"，像贴在时间轴那侧）：
      两侧都统一到 --space-8（32px），上面的用 padding-bottom 补、下面的用 margin-top 补。
   ❌ 踩过的坑（2026-09-15 当日更正）：本块与 tab 栏之间的间距**原本不需要补** ——
      `#sf-tab-body` 已经给了 20px，而普通 tab 的首个卡片靠 `-16px` 负 margin 抵消卡片内边距后，
      标题行的边界正好也落在 20px 上。我先前误判成"靠卡片 16px 内边距撑着"，
      又给它加了 `margin-top: --space-2`(8px) → 区块起点变成 28px，比"基本信息"tab 低 8px，
      切 tab 时标题会上下跳。**正确做法是让标题行自己补上内边距**（见 components.css 的
      `.module-card--bare > .module-card__head { padding: var(--space-3) 0 0 }`），
      这里保持 margin 为 0，两个 tab 的区块起点与标题文字顶才会同时对齐。 */
.sf-tab-panel[data-panel="detail"] > #sf-approx-card {
  padding-bottom: var(--space-8); /* 分隔线**上方**留白（与下方 32px 对齐） */
}
.sf-tab-panel[data-panel="detail"] > #sf-timeline {
  margin-top: var(--space-8); /* 分隔线**下方**留白 */
}

.tl {
  display: flex;
  flex-direction: column;
  position: relative;
  /* ⚠️ 竖线的两个变量由 JS（`adjustLine`）按真实 DOM 覆盖；这里只是**默认值**
     （无 JS / 首帧渲染前的兜底布局：起点 = 第一个圆点中心 = margin-top 20 + 圆点半径 6）。
     用"局部定义默认值 + 引用不带兜底"的写法，而不是 `var(--x, 默认)`——
     后者是项目明令禁止的写法（令牌拼错会被静默掩盖，见 ui-token-standards 门禁）。 */
  --tl-line-top: 26px;
  --tl-line-h: 0;
}

/* 连续时间轴线：从第一个 dot 中心连到最后一个 dot 中心。
   ⚠️ top / height 两个值都由 JS（`adjustLine`）从真实 DOM 算出并 setProperty 覆盖；
      默认值定义在上方 `.tl` 规则里（不用 `var(--x, 默认)` 那种被禁的写法）。
      ⛔ 别再在这里写死像素起点：圆点与卡片标题对齐后（2026-09-29）它的 margin-top 会变，
         写死就会出现"竖线悬空往上伸"或"末端多伸一截"。
   ⚠️ left 9px（宽 2 ⇒ 中心 10）= 圆点中心，严格同轴（原 10px 会让中心偏右 1px）。 */
.tl::before {
  content: '';
  position: absolute;
  left: 9px;
  top: var(--tl-line-top);
  height: var(--tl-line-h);
  width: 2px;
  background: var(--color-border);
  pointer-events: none;
}

.tl-item {
  display: flex;
  gap: var(--space-3);
}

.tl-item__rail {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 20px;
  flex-shrink: 0;
}

.tl-item__dot {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--color-accent);
  /* ⭐ 2026-09-29：5 → 20px（--space-5）。原 5px 是**无浅底卡时代**的值
     （那时标题就贴在卡片顶，5px 恰好让圆点与标题居中）。加上卡片 `padding-top: 16px` 后
     标题整体下移，圆点没跟着走 ⇒ 实测**圆点中心比标题中心高 15.5px**
     （乐哥：「这个标题跟左边的坐标没有对齐」）。
     ⚠️ 20 这个数**不是随便取的**：= 卡片 padding-top(16) + 圆点与标题行半高的差(4)。
        若改卡片 `padding-top` 或标题字号，这里要同步重算（改完必须真机量 delta 应 ≈ 0）。 */
  margin-top: var(--space-5);
  flex-shrink: 0;
  z-index: 1;
  position: relative;
  box-shadow: 0 0 0 3px color-mix(in srgb, currentColor 18%, transparent);
}

.tl-item__content {
  flex: 1;
  min-width: 0;
  margin-bottom: var(--space-4);
}

/* 节点之间的分隔线 + 居中「新增其他事项」按钮 */
.tl-add-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  /* ⚠️ 左缩进 = rail 20 + gap。gap 于 2026-09-29 由 12 收窄到 6（见 #sf-timeline 的 gap 覆盖），
     故此处同步 32 → 26px，保持"与卡片左缘对齐"。实际生效的是下方那条 #sf-timeline 覆盖。 */
  margin: calc(-1 * var(--space-1)) 0 var(--space-3) 26px;
}
.tl-add-row__line {
  flex: 1;
  border-top: 1px dashed var(--color-border);
}
.tl-add-row__btn {
  flex-shrink: 0;
}

.tl-item__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-2);
}

.tl-item__title {
  font-weight: var(--font-weight-semibold);
  font-size: var(--font-size-base);
  color: var(--color-fg);
}

.tl-item__title .is-sub {
  color: var(--color-muted);
  font-weight: var(--font-weight-regular);
  font-size: var(--font-size-sm);
}

.tl-item__time {
  color: var(--color-muted);
  font-weight: var(--font-weight-regular);
  font-size: var(--font-size-sm);
  margin-left: var(--space-2);
  font-feature-settings: "tnum", "lnum";
  font-variant-numeric: tabular-nums;
}

/* 额外支持 / 住宿信息：开关子项（开关独占一行 + 间距 + 文字加粗） */
.sf-switch {
  display: flex;
  width: fit-content;
  margin: var(--space-3) 0 var(--space-2);
}
.sf-switch .switch__label {
  font-weight: var(--font-weight-semibold);
}
/* 大概排期开关：文字浅色不加粗（注释性质，区别于加粗的功能开关） */
.sf-switch--muted .switch__label {
  font-weight: var(--font-weight-regular);
  color: var(--color-muted);
}
/* ⭐⭐ 2026-10-09 表单统一（第二批）：这一块原来是「**标签在左、控件在右同行**」
   （⭐ 2026-09-29 乐哥：「最新的不是都在同一行吗」）—— 标签定宽 80px + 右对齐。
   ⇒ 乐哥 2026-10-09 改为**全站一律「标签在上」**，所以这几条覆盖**整块删掉**：
     `.sf-field-col` 的**默认**形态本来就是「标签在上」（见下方
     `.sf-field-col { flex-direction: column }` + `.sf-field-col .sf-label { display:block; … }`），
     删掉这层 `.sf-card-row` 专属覆盖 ⇒ 本卡自动回到与全站一致。
   ⛔ 别再把 `flex-direction: row` / `width: 80px` / `text-align: right` 加回来
      （门禁 tests/form-unify.test.js 拦"标签右对齐"这一类）。 */
.sf-card-row {
  display: flex;
  gap: var(--space-5);
  margin-bottom: var(--space-4);
  align-items: flex-start;
}
.sf-card-row:last-child { margin-bottom: 0; }
.sf-field-col {
  display: flex;
  flex-direction: column;
  min-width: 0;
  flex: 1;
}
.sf-field-col .sf-label {
  display: block;
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-medium);
  color: var(--color-fg-2);
  /* 与全站字段标签一致：文字右内缩 6px（令牌见 design-tokens.css） */
  padding-left: var(--field-label-indent);
  margin-bottom: var(--space-2);
  line-height: 1.4;
}
.sf-field-col .sf-label .req { color: var(--color-danger); margin-left: 2px; }
/* 输入框宽度撑满列；高度/字号已由全局 .input/.select/.textarea 统一（40px / 14px），此处不再硬编码 */
.sf-field-col .input,
.sf-field-col .select {
  width: 100%;
}
.sf-field-col textarea {
  width: 100%;
}
/* fp 自定义（达人选择/所属商务）：统一框高 40px + 字号随全局 */
.sf-field-col .fp__box {
  min-height: 40px;
  font-size: var(--font-size-base);
}
.sf-field-col .fp__value { font-size: var(--font-size-base); }
.sf-field-col .fp__search { font-size: var(--font-size-base); }
/* 只读 div（达人简称）：统一高 40px + 字号随全局 */
.sf-field-col .sf-readonly {
  display: flex;
  align-items: center;
  height: 40px;
  padding: 0 12px;
  font-size: var(--font-size-base);
  border: 1px solid var(--input-border);
  border-radius: var(--radius-md);
  background: var(--input-disabled-bg);
  color: var(--color-fg-2);
}
.sf-field-col.sf-grow-1 { flex: 1; }
.sf-field-col.sf-grow-15 { flex: 1.5; }
.sf-field-col.sf-w-live { flex: 0 0 200px; }  /* 2026-09-29：170→200（标签在左占 80+12） */
/* 品牌（2026-10-13 多品牌 P0）：与「直播类型」同宽 —— 它俩与预估GMV 同处第二行，
   宽度不一致会让这一行的三个控件看着不是一个规格。⛔ 不要改成 flex:1：
   预估GMV 是 300px 定宽，品牌撑开会把它挤走、第三列掉行。 */
.sf-field-col.sf-w-brand { flex: 0 0 200px; }
.sf-field-col.sf-w-gmv  { flex: 0 0 300px; }  /* 2026-09-29：220→300（同上） */
/* 直播平台多选 + 佣金配置按平台分套 */
.sf-platform-checks {
  display: flex;
  gap: var(--space-4);
  align-items: center;
  min-height: 40px;
}
.sf-check {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--font-size-base);
  color: var(--color-fg);
  cursor: pointer;
}
.sf-check input[type="checkbox"] {
  width: 16px;
  height: 16px;
  accent-color: var(--color-accent);
  cursor: pointer;
}
.pta-checks {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-3);
  padding-top: 2px;
}
/* 排期表单各面板：内容间距统一由 `.module-card + .module-card` 保证（见 components.css 模块卡标准），
   这里不再需要容器 gap，也不再需要佣金/提成的中间包裹层 */
.sf-commission-label {
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-medium);
  color: var(--color-fg-2);
  margin-bottom: var(--space-2);
}
/* 模块卡的容器与标题栏已抽为**全站通用组件**，定义在 components.css：
   .module-card / .module-card__head / .module-card__head--plain / .module-card__title
   规则：标题栏有状态底色就不加线，没底色才用线补分界；状态色只进标题栏，不外溢到卡片边界。
   本页只保留排期表单特有的部分（状态开关、支持行、面板间距）。 */
/* 状态开关（仅佣金配置 / 提成点位的「单独维护」用）：开启 = 橙，呼应开启态的浅黄标题栏。
   只覆盖组件变量，不去重写 .switch 的选择器（那样会靠加载顺序生效、易静默失效）。 */
.sf-switch--state {
  --switch-active-bg: var(--color-warning);
}
/* ⭐ 2026-10-12 乐哥：「视频号、抖音渠道选择，能不能开关放在佣金框的标题行」
   —— 渠道开关从卡内单独一行搬进卡头。
   ⚠️⚠️ 卡头里的 `.switch` 默认有 `margin-left:auto`（`components.css` 的
      `.module-card__head .switch`，为的是让单个开关靠右）——**容器内的必须清零**，
      否则两个渠道开关各自被 auto 撑到两端，中间空出一大块（看着像坏了）。
      让渠道开关**紧跟标题**（它是主设置），"单独维护"仍靠右（模式开关）。
   ⚠️ 别给容器加 `margin-left:auto`：那会把渠道开关也推到最右、与"单独维护"挤在一起。 */
.sf-plat-switches {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}
.sf-plat-switches .switch { margin-left: 0; }
/* 返点格里的备注输入框（乐哥：「返点备注和返点放同一行，因为就几个字」）——
   它跟着返点同行，固定窄宽（`.co-rate` 是 flex，不设宽会被拉伸、把返点列撑得很怪）。
   ⚠️ 备注**不受返点开关影响**（它是文字说明，不参与算钱）——关掉返点也照常可填。 */
#sf-commission-rates .cr-remark { width: 140px; flex: none; }
/* ⛔ 2026-10-12：「本场佣金」卡头的那句状态文字（`.sf-comm-state`）已删除 ——
   它与每张渠道卡标题行里的来源句重复（乐哥：「这一句多余了，因为下面已经有了」）。
   别再补回来：状态已由**整卡描边（is-on）**+ **渠道卡内的来源句**表达。 */
/* 状态提示：无底色，直接用状态色文字（橙=单独维护 / 绿=跟随达人） */
/* ⭐⭐ 2026-10-12 乐哥：「这块要分开，首先豁免那个事开票的，和佣金没关系」
   —— 「开票豁免」从佣金卡移出，**独立成一张「开票」卡**（紧跟佣金卡之后）。
   卡内就两行：① 一行开关（开关 + 文字 + ⓘ）② 一句后果说明。
   ⚠️ 说明走 `.cell-sub` 同款节奏（12px / muted），与佣金卡里的状态条同一层级；
   ⛔ 别在这里再包一层 .card（会套成双层白底）。 */
.sf-inv-row {
  display: flex;
  align-items: center;
  gap: var(--space-1);
}
.sf-inv-note {
  margin-top: var(--space-2);
  font-size: var(--font-size-xs);
  line-height: 1.6;
  color: var(--color-muted);
}
/* ⭐ 2026-10-12：「（沿用公司通用佣金）」= 读数依据 ⇒ 加粗 + 信息蓝（与卡头「沿用公司」同色系） */
.sf-comm-ref__src { font-weight: 600; color: var(--color-info); }
.sf-comm-mode-tip {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--font-size-xs);
  line-height: 1.6;
  margin-bottom: var(--space-3);
}
.sf-comm-mode-tip svg {
  flex-shrink: 0;
}
.sf-comm-mode-tip--follow { color: var(--color-success-text); }
.sf-comm-mode-tip--lock { color: var(--color-warning-text); }
/* ⭐ 2026-10-12：「当前生效」参照行（只在**单独维护**态出现）——
   告诉用户"本场要覆盖掉的那个口径是多少、来自达人还是公司"。
   ⚠️ 走中性色（不是状态色）：它是**参照值**，不是状态本身（状态由上面那条 tip 表达）。
   ⚠️ 与 tip 同一行距节奏，别再撑出第三个层级。 */
.sf-comm-ref {
  font-size: var(--font-size-xs);
  line-height: 1.6;
  color: var(--color-muted);
  margin: calc(var(--space-2) * -1) 0 var(--space-3) 0;
}
/* ⭐⭐ 2026-09-27：排期详情「本场佣金」按渠道分块 —— 用渠道色边框把每套佣金框住。
   乐哥：「用（之前沟通的）渠道颜色的框把这个模块框住，这样更直观」。
   视觉语言与达人页「佣金配置」的渠道分组（.comm-channel，白底 + 1.5px 渠道色边 +
   标题色竖条）**完全一致**，本类是详情页的只读版本。
   ⚠️ 渠道色规则也与达人页一致：视频号绿（默认）/ 抖音蓝 / 其它中性灰。
   ⚠️ 块内 4 个指标用 grid 等分（标签在上、数值在下）——替代原五列表格，
      消除"平台列被长注释撑宽、佣金列被挤窄、表头与数字错位"的问题。 */
.sd-comm-ch {
  border: 1.5px solid var(--color-success);
  border-radius: var(--radius-lg);
  background: var(--color-surface);
  padding: var(--space-3) var(--space-4);
}
.sd-comm-ch + .sd-comm-ch { margin-top: var(--space-3); }
.sd-comm-ch--dy { border-color: var(--color-info); }
.sd-comm-ch--plain { border-color: var(--color-border-soft); }
/* ⭐ 2026-10-12：卡头最右的**渠道开关**插槽（只排期编辑页传 headRight；其它调用点没有这个元素）。
   靠 margin-left:auto 贴右 —— head 是 flex+wrap，不加会被排在 note 后面。 */
.sd-comm-ch__act { margin-left: auto; flex: none; }
/* 关掉的渠道（排期编辑页）：只剩一行卡头（渠道名 + 开关），下面的明细已隐藏 ⇒ 收紧 + 弱化 */
.sd-comm-ch--off { padding: var(--space-2) var(--space-3); }
.sd-comm-ch--off .sd-comm-ch__name { color: var(--color-muted); }
.sd-comm-ch--off .sd-comm-ch__note { color: var(--color-muted); }
.sd-comm-ch__head {
  display: flex; align-items: center; flex-wrap: wrap;
  gap: var(--space-1) var(--space-3);
  margin-bottom: var(--space-2);
}
.sd-comm-ch__name {
  display: inline-flex; align-items: center; gap: 8px;
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  color: var(--color-fg);
}
.sd-comm-ch__name::before {
  content: '';
  width: 4px; height: 14px; border-radius: 2px;
  background: var(--color-success);
  flex: none;
}
.sd-comm-ch--dy .sd-comm-ch__name::before { background: var(--color-info); }
.sd-comm-ch--plain .sd-comm-ch__name::before { background: var(--color-muted); }
.sd-comm-ch__note { font-size: var(--font-size-xs); color: var(--color-muted); }
/* ⭐ 2026-10-12 乐哥：「文字提醒沿用了 xxx 佣金，**提醒显眼点加粗**」——
   来源那句回答"这个数从哪来"（读数依据）⇒ 加粗 + **渠道色**（抖音卡蓝、视频号卡绿，
   与卡边框同色，一眼能把它和这张卡对上）；`--warn` 只在"达人+公司都没配"时用告警色。
   附注（取数区间）保持灰字小一号 —— 主次分明，不让两句一样重。 */
.sd-comm-ch__src { font-weight: 600; font-size: var(--font-size-sm); color: var(--color-success); }
.sd-comm-ch--dy .sd-comm-ch__src { color: var(--color-info); }
.sd-comm-ch--plain .sd-comm-ch__src { color: var(--color-fg-2); }
.sd-comm-ch__src--warn { color: var(--color-danger) !important; }
.sd-comm-ch__basis { font-size: var(--font-size-xs); color: var(--color-muted); }
.sd-comm-ch__grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--space-3);
}
.sd-comm-ch__lbl {
  display: block;
  font-size: var(--font-size-xs);
  color: var(--color-muted);
  margin-bottom: 2px;
}
/* ⛔ 2026-10-13 删掉 `.sd-comm-ch__grid--inline`（2026-10-12 我自作主张加的）——
   乐哥：「上线，要」（= 排期这块退回两行），并在此前说过「不要做成表格，我只是展示布局，
   之前我们 ui 没问题」。排期块现在与达人/公司详情页同构：标签在上、值在下。
   ⛔ 判据：`tests/comm-view-vs-edit.test.js` ⑪ 反向锁「不许再出现 `--inline`」。 */
.sd-comm-ch__val {
  font-size: var(--font-size-lg);
  font-weight: var(--font-weight-semibold);
  color: var(--color-fg);
  /* 等宽数字：多平台/多指标之间数字按同一位宽对齐 */
  font-variant-numeric: tabular-nums;
  line-height: 1.4;
}
.sd-comm-ch__val--off { color: var(--color-muted); font-weight: var(--font-weight-regular); }
/* ⭐ 2026-09-27：跨段排期的**分段展示**（订单按下单日期取率 ⇒ 一个排期可能跨多个佣金段）。
   每段一行：左侧日期范围（8/01~8/05），右侧沿用同一套 4 指标 grid。 */
.sd-comm-segs {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.sd-comm-seg {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-2) 0;
  border-top: 1px dashed var(--color-border-soft);
}
.sd-comm-seg:first-child { border-top: none; padding-top: 0; }
.sd-comm-seg:last-child { padding-bottom: 0; }
.sd-comm-seg__when {
  flex: none;
  min-width: 86px;
  padding-top: 2px;
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-medium);
  color: var(--color-muted);
  font-variant-numeric: tabular-nums;
}
.sd-comm-seg .sd-comm-ch__grid { flex: 1; }
/* 「变更记录」入口：靠右（与卡片头其它元素拉开），文案型按钮不抢视线 */
.sf-comm-hist-btn {
  margin-left: auto;
  flex: none;
}
/* 「佣金配置」卡内的「本场直播平台」行：它是佣金套的渠道来源（勾哪个平台就出哪套佣金），
   与佣金同处一块，用一条浅线把它和下面的佣金设置分开。 */
.sf-comm-plat {
  padding-bottom: var(--space-4);
  border-bottom: 1px solid var(--color-border-soft);
}
.sf-comm-plat:last-child {
  padding-bottom: 0;
  border-bottom: none;
}
.sf-commission-block {
  padding: var(--space-4);
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-md);
  margin-bottom: var(--space-4);
  background: var(--color-surface);
}
.sf-commission-block:last-child { margin-bottom: 0; }
.sf-commission-title {
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  color: var(--color-fg);
  margin-bottom: var(--space-3);
}
/* 达人选择器：右侧加下拉箭头指示（与 select/输入框箭头一致） */
.sf-inf-box {
  position: relative;
  padding-right: 30px;
}
.sf-inf-box::after {
  content: '';
  position: absolute;
  right: 10px;
  top: 50%;
  transform: translateY(-50%);
  width: 12px;
  height: 12px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12' fill='none'%3E%3Cpath d='M3 4.5L6 7.5L9 4.5' stroke='%238595A4' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: center;
  background-size: 12px 12px;
  pointer-events: none;
}
/* 只读展示（达人简称等）：与输入框等高、垂直居中 */
.sf-readonly {
  display: flex;
  align-items: center;
  height: 40px;
  padding: 0 var(--space-3);
  color: var(--color-fg-2);
  font-size: var(--font-size-base);
  border: 1px solid var(--input-border);
  border-radius: var(--radius-md);
  background: var(--input-disabled-bg);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* 移动端：字段换行成单列 */
@media (max-width: 768px) {
  .sf-card-row { flex-wrap: wrap; }
  .sf-card-row .sf-field-col { flex: 1 1 100% !important; }
}

/* 电脑端默认：手机版图例/订阅隐藏（仅手机端显示） */
.calendar-legend-mobile,
.calendar-subscribe-mobile {
  display: none;
}

/* 移动端：日历月/周视图压缩，日期格子拉高以显示至少 5 个事项、色条更小（缩小 DPI） */
@media (max-width: 768px) {
  .calendar-cell {
    /* 135px = 原 104px × 1.3，与桌面同一比例（乐哥 2026-09-24）。同步 maxEvents 见页面脚本。 */
    min-height: 135px;
    padding: 3px;
    overflow: hidden;
    /* ⭐ 白色模块化在手机端要**单独调小**（2026-09-26）：
       手机格子只有 ~40px 宽，桌面的 12px 圆角 + 3px 灰缝占比过大 ⇒
       格子看着像"细长药丸"，灰缝也吃掉太多可视宽度。
       实测 6px 圆角 + 2px 灰缝最平衡（4px 太方、8px 开始像药丸）。
       ⚠️ 本块源码位置在基础规则**之后** ⇒ 同特异性下后写生效，能正确覆盖。
       ⚠️ 灰缝已改为格子自己的 `border`（见基础规则那段的长注释）：
          border 占内容宽 ⇒ padding 3 → 1（3 = 1 + 2），文本区宽度保持不变。 */
    border: 2px solid var(--color-surface-2);
    border-radius: var(--radius-seg);
    padding: 1px;
  }
  /* 上月格子在手机端保持"无卡无缝"（与桌面一致） */
  .calendar-cell.is-outside {
    border-color: transparent;
  }
  .calendar__grid {
    padding: 2px; /* 手机端外圈留白同步收窄 */
  }
  .calendar-cell__day {
    width: 18px;
    height: 18px;
    font-size: var(--font-size-xs);
  }
  .calendar-cell__holiday,
  .calendar-cell__workday {
    font-size: var(--font-size-xs);
    line-height: 13px;
  }
  .cal-event {
    height: 14px;
    padding: 0 3px;
    gap: 2px;
    font-size: var(--font-size-xs);
    border-radius: 3px;
  }
  .cal-event__bar {
    width: 2px;
    height: 9px;
  }
  /* 手机端也显示「还有 N 项」折叠（超过 3 个时） */
  .cal-more {
    display: block;
    font-size: var(--font-size-xs);
  }
  /* 周视图：横向滚动，表头与格子同步（每天固定宽） */
  .calendar--week {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }
  .calendar--week .calendar__weekdays,
  .calendar--week .calendar__grid {
    min-width: 620px;
  }
  .calendar__grid--week .calendar-cell {
    min-height: 160px;
  }
  /* 工具栏/筛选栏在手机上重排 */
  .calendar-toolbar .calendar-month {
    min-width: 0;
    flex: 1 1 auto;
  }
  /* 电脑端工具栏图例隐藏，手机版图例显示在月视图下面 */
  .calendar-toolbar .legend {
    display: none;
  }
  .calendar-legend-mobile {
    display: flex;
    margin-top: var(--space-3);
  }
  /* 电脑端 header 订阅按钮隐藏，手机版订阅显示在最下面 */
  .page-actions #cal-subscribe {
    display: none;
  }
  .calendar-subscribe-mobile {
    display: block;
    margin-top: var(--space-4);
    text-align: center;
  }
  /* 筛选栏：达人选择器全宽一行 */
  .calendar-filters > * {
    flex: 1 1 auto;
    max-width: 100%;
  }
  #cal-filter-inf {
    flex: 1 1 100% !important;
  }
  /* 场地 + 直播类型：一行两个，各占一半。
     ⚠️ 2026-10-11：场地下拉不再包在 `.input--search` 里（页头整编时改成裸 `.select`）——
     窄屏这条**必须把 `#cal-filter-loc` 一起列进来**，否则它没有 50% 上限，
     两个下拉 + 开关会把整行撑宽（实测：容器 587 > 视口 390、达人选择器被挤到 x=-98）。 */
  .calendar-filters .input--search,
  .calendar-filters #cal-filter-loc,
  .calendar-filters #cal-filter-live-type {
    flex: 1 1 calc(50% - 6px) !important;
    max-width: calc(50% - 6px);
  }
  .calendar-filters .select {
    width: 100% !important;
  }
  .calendar-filters .input--search .select {
    width: 100% !important;
  }
  /* 窄屏：筛选整行都留在**内容区**（页头只留标题 + TAB + 主操作）——
     吸顶卡是 sticky 的，手机上塞不下会变 4 行、永久占屏（与 app.js 的 768 判据同源）。 */
  #cal-only-mine-switch {
    flex: 1 1 auto;
    justify-content: flex-end;
  }
}

/* 基本信息板块内：大概排期开关上方分割线 */
.support-body {
  margin-top: var(--space-2);
  margin-bottom: calc(var(--space-3) * 2.25);
}
/* 模块卡内的支持内容：底部留白交给卡片内边距，不再叠加 */
.module-card .support-body {
  margin-bottom: 0;
}
/* 额外支持：卡内多行（行 = 左侧名称 + 右侧开关；开关打开时该行下方展开内容） */
.sf-support-item {
  border-bottom: 1px solid var(--color-border-soft);
}
.sf-support-item:last-child {
  border-bottom: none;
}
.sf-support-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-3) 0;
}
.sf-support-row .sf-switch {
  margin: 0;
}
.sf-support-row__name {
  font-size: var(--font-size-sm);
  color: var(--color-fg);
}
.sf-support-body {
  padding-bottom: var(--space-3);
}
.sf-support-hint {
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-medium);
  color: var(--color-fg-2);
  margin-bottom: var(--space-2);
}

.tl-item__actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.tl-add {
  margin-top: var(--space-2);
}

/* 节点内字段卡片（去掉外框，改浅底） */
.tl .node-card {
  border: none;
  padding: 0;
  margin-top: 0;
  background: transparent;
}

/* ⭐⭐⭐ 2026-09-29 事项浅底块（乐哥：「每个事项和每个事项的间隔，总感觉不是很清晰」→ 点选方案B）
   ─────────────────────────────────────────────────────────────────────────────
   实测根因是**间距倒挂**：事项**内**（字段行距 20、字段→备注 24）比事项**间**
   （备注→分隔线 12 + 分隔线 34 + →下一标题 12）还松 ⇒ 眼睛读不出"这里是两个事项的交界"。
   又因为 `.tl .node-card` 是透明底、整段没有任何边界，只能靠留白区分 —— 留白没拉开就必然糊成一片。

   ⇒ 给每个事项的 `__content` 加**浅底块**（复用全站「次卡」语言：`--color-surface-2` 无边框，
     同 `.module-card--sub`），事项边界一眼可辨：
        · 事项内 36px（padding 16 + 行距）
        · 事项间 102px（卡间距 20 + 分隔行 34 + …）
     实测：卡片 `rgb(241,245,249)`，卡间距 70~102px，边界清晰。

   ⚠️ 三个配套，漏一个就出新毛病：
     ① **同步取消卡内 380px 限宽**（`.form-grid .input` 那条）—— 卡片内容宽 998，
        两列格子各 489，控件限 380 会让**右列字段右缘（1569）比备注右缘（1602）短 33px**
        ⇒ 卡内控件右边缘参差（实测）。卡内撑满格子后三处右缘齐平。
     ② **标签列仍 64px**（见下方规则），卡内左端也对齐。
     ③ `__head` 的下边距 8 → 12：卡内标题与字段行要拉开一点，否则贴着。
   ⚠️ 只限 `#sf-timeline`（住宿 `#sf-acc-list` 是另一种结构、且乐哥本次只提时间轴）。 */
#sf-timeline .tl-item__content {
  background: var(--color-surface-2);
  border-radius: var(--radius-lg);
  padding: var(--space-4) var(--space-5);
  margin-bottom: var(--space-5);
}
#sf-timeline .tl-item__head { margin-bottom: var(--space-3); }
/* 卡内控件撑满格子（覆盖全站 380 限宽）—— 否则右列字段比备注短一截、边缘参差 */
#sf-timeline .form-grid .input,
#sf-timeline .form-grid .select,
#sf-timeline .form-grid .dtp,
#sf-timeline .form-grid .fp__box { max-width: none; }

/* ⚠️ 分隔行（虚线 + 「新增其他事项」按钮）：卡片已有浅底做边界，这里只做"两条卡之间的分隔"，
   上下各留 16px，并把左缩进对齐到卡片左缘（26 = rail 20 + gap 6，gap 见下方收紧规则）。
   ⛔ 别再写负 margin（原 `-4px 0 12px`）—— 负值会让按钮**探进卡片底边**，
      加了浅底后这个重叠会直接显形（圆角处露出按钮的方角）。 */
#sf-timeline .tl-add-row { margin: 0 0 var(--space-4) 26px; }

/* ⭐⭐⭐ 2026-09-29（乐哥点选方案C）：圆点与事项「对齐 + 贴近 + 连线」
   ─────────────────────────────────────────────────────────────────────────────
   乐哥原话：「这个标题跟左边的坐标没有对齐，也没关联」。

   **根因（上一步加浅底卡时引入）**：见 `.tl-item__dot` 的 margin-top 注释 ——
   圆点比标题高 15.5px；且圆点离卡片 16px，读起来像个孤立的装饰点。

   **三条一起做（少一条就不成立）**：
     ① **垂直对齐** → 圆点 margin-top 5 → 20px（改在基础规则里，见上）。
     ② **水平贴近** → `.tl-item` 的 gap 12 → 6px：圆点到卡片由 16px 缩到 10px。
        ⚠️ gap 一改，分隔行的左缩进也要跟着从 32 改成 26（已同步）。
     ③ **一条短线连到卡片** → 圆点 `::after`，宽 10px（**恰好**是圆点右缘到卡片左缘的距离，
        让它"接上"而不是"伸进去"），颜色取 `currentColor`（= 该事项的节点色，dot 上已内联设了
        `color`）、透明度 .45（是"指向"不是"强调"，别抢标题的注意力）。
   ⚠️ 只限 `#sf-timeline`（住宿 `#sf-acc-list` 是 `.acc-item` 另一种结构，且它自己的圆点定位不同）。 */
#sf-timeline .tl-item { gap: 6px; }
#sf-timeline .tl-item__dot::after {
  content: '';
  position: absolute;
  left: 100%;            /* 从圆点右缘起画（圆点已 position: relative） */
  top: 50%;
  margin-top: -1px;      /* 2px 高 ⇒ 上移 1px 才是垂直居中 */
  width: 10px;           /* = 圆点右缘 → 卡片左缘的距离（rail 20 − dot 12)/2 + gap 6 + dot 12 = 10 */
  height: 2px;
  border-radius: 1px;
  background: currentColor;
  opacity: .45;
  pointer-events: none;  /* 别让它挡住圆点/卡片的点击 */
}

/* ⭐ 2026-10-09 表单统一（第二批）：本段原来是为「标签在左」做的**节点内收窄**
   （「每个环节要填的内容向右移动了很奇怪」⇒ 标签列从 112px 收到 64px）。
   现在字段一律「标签在上」⇒ 上面那条「标签列 64px」**不再需要**（标签不吃横向空间）。
   ⚠️ 下面那条**必须保留**：`.input` 默认**没有 width**，去掉 `.form-row__ctrl` 包装壳后
      不写这条就跑不满（备注等不在 `.form-grid` 里的输入框靠它撑满）。
   ⛔ 选择器要挂在**新结构**上（`.field-label + .input`），别再写回 `.form-row__ctrl`。
   ⚠️ 只限 #sf-timeline / #sf-acc-list（住宿），不影响其他 tab。 */
#sf-timeline .field-label + .input,
#sf-acc-list .field-label + .input { width: 100%; }

/* ============================ 视图切换 / 节假日 / 周视图 / 日程视图 ============================ */

/* ⛔ 2026-10-11 删除自造的 `.view-switch`（月/周/日程）——
   乐哥：「日历视图，按照设计规范整理下，**tab栏悬浮**」⇒ 它已换成全站标准形态
   `pill-filter table-tabs table-tabs--in-header` + `pill-filter__item`，并住进
   `.page-header--has-tabs`（滚动吸顶 + 液态玻璃），由 app.js / layout.css 那套统一接管。
   删它的理由（不止"不统一"）：它是 `.seg` 的**自造副本**（28px 按钮、自己一套托盘），
   既不吸顶、又不在页头，与发票 / 提成总览 / 达人列表那批页面的 tab 是两个长相。
   ⛔ 别再加回来 —— 要视图切换就用页头那族类名。 */

/* ============================ 页头筛选区（吸顶栏里那一行） ============================ */
/* ⭐⭐ 2026-10-11：`搜达人 / 全部场地 / 全部直播类型 / 只看与我相关` 与一级 TAB **同一行**，
   装在 `.page-header--has-tabs` 里（达播台基线形态，见 DESIGN-SPEC §13.7）。
   ⚠️⚠️ **高度必须显式提到 42**（`--control-height-row`）：页头里 tab 托盘 / 主操作按钮
      由 layout.css 提到 42，而本容器是**页头的孙子**、不在
      `.page-header--filter-bar > …` 那几条直接子级规则的作用域内
      ⇒ 不写这条，下拉会留在 44（`.select` 标准档）⇒ 同一行里 44 / 42 混排。
   ⚠️ `.fp__box`（达人选择器）同理 —— 它是第 5 类基础控件，`.select` 的规则管不到它。 */
.cal-head-filters {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-width: 0;
}
.cal-head-filters #cal-filter-inf { width: 190px; flex: 0 0 auto; }
.cal-head-filters #cal-filter-loc { width: 160px; flex: 0 0 auto; }
.cal-head-filters #cal-filter-live-type { width: 140px; flex: 0 0 auto; }
.cal-head-filters .select,
.cal-head-filters .fp__box {
  height: var(--control-height-row);
  min-height: var(--control-height-row);
}
/* 开关（筛选区末尾）：不参与压缩 */
.cal-head-filters #cal-only-mine-switch { flex: 0 0 auto; }

/* 日历自身的导航行（上月/今天/下月/全屏）：与页头筛选行**同档 42** ——
   两行在同一屏里紧挨着，差 8px 一眼能看出来（页头 42 / 这里原来是 34）。 */
.calendar-toolbar > .btn {
  height: var(--control-height-row);
  min-height: var(--control-height-row);
}

/* 节假日 / 周末 / 调休标注 */
.calendar-cell__day { position: relative; }
.calendar-cell.is-weekend:not(.is-today) .calendar-cell__day { color: var(--color-muted); }
.calendar-cell.is-holiday:not(.is-today) .calendar-cell__day { color: var(--color-muted); font-weight: var(--font-weight-semibold); }
/* 节假日/调休标注放格子右上角（.calendar-cell 已 position:relative） */
.calendar-cell__holiday {
  position: absolute;
  top: 4px;
  right: 4px;
  padding: 0 3px;
  border-radius: var(--radius-sm);
  background: var(--color-danger-bg);
  color: var(--color-danger);
  font-size: var(--font-size-xs);
  line-height: 15px;
  pointer-events: none;
}
.calendar-cell__workday {
  position: absolute;
  top: 4px;
  right: 4px;
  padding: 0 3px;
  border-radius: var(--radius-sm);
  background: var(--color-surface-2);
  color: var(--color-muted);
  font-size: var(--font-size-xs);
  line-height: 15px;
  pointer-events: none;
}

/* 周视图：格子更高，显示全部事项 */
/* ⚠️ 遗留选择器：周视图已改 agenda 卡片渲染，JS 里已无 .calendar__grid--week / .calendar--week（含 936 行移动端那条）。
   数值按 2026-09-24 同一比例 ×1.3 更新，仅为将来恢复网格时保持一致；当前不生效。 */
.calendar__grid--week .calendar-cell {
  min-height: 416px; /* 原 320px × 1.3 */
}

/* 日程视图 */
.agenda { display: flex; flex-direction: column; gap: var(--space-3); }
.agenda-day {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  overflow: hidden;
}
.agenda-day__head {
  padding: var(--space-3) var(--space-4);
  background: var(--table-header-bg);
  border-bottom: 1px solid var(--color-border-soft);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
  color: var(--color-fg);
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
.agenda-day__items { padding: var(--space-2); display: flex; flex-direction: column; gap: 2px; }
.agenda-day__items .cal-event { cursor: pointer; }

/* ---- 点「某一天」抽屉：当日全部事项列表 ---- */
.calendar-cell[data-date] { cursor: pointer; }

/* ⭐ 2026-09-26 乐哥：「我截图这个选中日期的，选中样式能不能做成框？」
   ──────────────────────────────────────────────────────────────────────────
   ▸ 实测取证（真机逐状态读 computedStyle）：乐哥截图里 9/23 的淡蓝底是 **hover**
     （`rgb(229,238,254)` = `--table-row-hover`）；而 `.calendar-cell` **根本没有"选中"状态**
     —— 点开那天的抽屉后格子毫无标记（背景回透明），"我在看哪天"全靠记忆。
   ⇒ 两件事一起做：
     ① hover 从"整块填充"改成**框**（乐哥要的形态，不再糊住格子里的日程条）；
     ② 补上原本缺失的**选中态**（抽屉正开着的那天），也用框、颜色更实（accent）。
   ⚠️ 两种框都用 `inset` 阴影画、**不动 border**：格子的 border-right/bottom 是 7 列网格线，
      改它会串动整张网格。inset 画在 border 内侧 ⇒ 网格线仍在框外，不遮不叠。
   ⚠️ hover 框用 38% accent（"能点"的提示），选中框用 100% accent（"在看这天"）——
      两级强度不混淆；且与"今天"（日期数字蓝底白字）互不干扰，两者可同时出现。

   ⭐⭐ 2026-09-26 傍晚 **第二版**（乐哥看真机后的反馈）：
      「这个日历视图你看一下有没有可以优化的……还有一个当前那个选中某一天的
        那个框啊，看着不好看，不直观」
      ▸ 第一版是 `inset 0 0 0 2px`（直角、紧贴格沿）—— 问题在于它和格子的网格线**同轴**，
        读起来像"格子被描了一圈边"，框与格子是同一个矩形，没有"浮起来"的层次；
        而且**直角**与全站语言（卡片 16 / 容器 20 / 胶囊 9999）都不搭。
      ▸ 第二版三处改动：
        ① 框改用 **`::after` 伪元素内缩 3px + 圆角 10px** —— 框与网格线脱开，
           读起来是"这一天被圈出来了"而不是"格子描边"；圆角呼应全站语言。
        ② 选中时**日期数字跟着变蓝加粗** —— 焦点落到"哪一天"本身（行业做法：
           飞书/Google 日历选中日都是强化日期数字），一格 149px 里日期是最稳定的锚点。
        ③ 几何**常驻**（transparent 边框占位），只换 `border-color` ⇒ 有过渡动画、
           且不同状态间零重排（与 `.day-item.is-selected` 那条"别动 border-width"同一教训）。
      ⛔ **为什么不直接给 `.calendar-cell` 加 `border-radius`**：它的 `border-right/bottom`
         就是 7 列网格线本身，加圆角会让网格线在四角弯折断裂（格子之间出现豁口）。
         所以圆角只能画在**不参与布局的伪元素**上。
      ⛔ **为什么不用 `inset` box-shadow 做圆角框**：内阴影的圆角半径由宿主元素的
         `border-radius` 决定 —— 要圆角还是得改宿主圆角，绕回上一条的死结。
      ⚠️ 2026-09-26 晚（灰缝改 `border` 之后）：格子的**定位基准变成了 padding box**
         （绝对定位元素的包含块 = 最近定位祖先的 padding box，而 border 现在有 3px）。
         为保持"框在白色卡面内缩 3px"的观感，`inset` 从 3px 改为 0
         —— 0（相对 padding box）+ 3px（border）= 仍是白卡内缩 3px，视觉不变。 */
.calendar-cell[data-date]::after {
  content: '';
  position: absolute;
  inset: 0; /* 相对 padding box；+ 外层 3px border = 白卡内缩 3px（见上方说明） */
  border: 2px solid transparent; /* 常驻占位：只换颜色 ⇒ 可过渡、零重排 */
  border-radius: var(--radius-md);
  pointer-events: none; /* 伪元素不能吃掉格子的点击（否则点边框处打不开抽屉） */
  /* ⭐ 2026-10-08 浮起影的**常驻透明占位**（与 border 同一手法）：box-shadow 无法从
     `none` 过渡到具体阴影（不可插值）⇒ 不占位的话选中时阴影是"啪"地出现。 */
  box-shadow: 0 2px 6px rgba(0, 100, 224, 0);
  transition: border-color var(--motion-fast) var(--motion-ease-standard),
              box-shadow var(--motion-fast) var(--motion-ease-standard);
}
.calendar-cell[data-date]:hover::after {
  border-color: color-mix(in srgb, var(--color-accent-text) 38%, transparent);
}

.calendar-cell[data-date].is-selected {
  background: color-mix(in srgb, var(--color-accent-text) 5%, transparent);
}
.calendar-cell[data-date].is-selected::after {
  border-color: var(--color-accent-text);
  /* ⭐ 2026-10-08 补「凸起」：选中框浮起来（与事项卡片 / 全站胶囊同一条浮起影）。 */
  box-shadow: var(--shadow-pill-active);
}
/* 选中时日期数字也变蓝加粗 —— 焦点落到"哪一天"上。
   ⚠️ 必须 `:not(.is-today)`：今天已是「蓝底 + 白字」，若再被本规则改成蓝字
      会变成**蓝底蓝字**（对比度 1.0，直接看不见）。今天本身已足够醒目，无需再强调。 */
.calendar-cell[data-date].is-selected:not(.is-today) .calendar-cell__day {
  color: var(--color-accent-text);
  font-weight: var(--font-weight-semibold);
}
/* 选中 + hover：保持选中框的强度（否则鼠标一进来框就"变淡"，像掉了选中） */
.calendar-cell[data-date].is-selected:hover::after {
  border-color: var(--color-accent-text);
}

/* ⚠️⚠️ 手机端的框要单独调小，而且**必须写在这里**（基础规则之后）——
   同特异性下源码顺序在后的胜出，媒体查询不改变特异性 ⇒ 写进上面的媒体查询块会被反向盖掉（静默失效）。
   手机格子只有 ~40px 宽（135px 高），内缩 + 10px 圆角在小格子上占比过大、会显得"框糊住格子"。 */
@media (max-width: 768px) {
  .calendar-cell[data-date]::after {
    inset: 0; /* 相对 padding box；手机端 border 2px ⇒ 视觉上白卡内缩 2px */
    border-width: 1.5px;
    border-radius: var(--radius-seg);
  }
}

.day-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.day-item {
  display: flex;
  align-items: stretch;
  gap: var(--space-3);
  padding: var(--space-3);
  /* ⭐ 2026-09-26 **回滚**：卡片配色退回改动前的「淡彩底 + 中性细边」。
     ──────────────────────────────────────────────────────────────────────────
     ▸ 当天先试过一版 A 方案（白底 + 事项类型色描边、选中框跟随类型色），
       乐哥看了真机效果后要求「这个事项，还是换回之前的样式」⇒ 原样退回本版。
     ▸ 颜色信号由 JS 内联的 `*-soft` 淡彩底承担（见 dayItemCard），边框保持中性 ——
       一屏多张卡时整体更安静；而右侧「事项详情」块一直是
       `border-color: 类型色 + 淡彩底`（见 itemDetailHtml 的内联样式，本次未动）。
     ⚠️ 注意：JS 内联的 `*-soft` 会**覆盖**本类的 `background`（内联优先级更高）——
        所以底色的真正来源在 JS；本行的值只在"没下发内联"时兜底。
     ⚠️ 选中态的可辨性靠**边框 + inset 描边**（见 .day-item.is-selected），不靠底色。 */
  background: var(--color-surface-2);
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: border-color var(--motion-fast) var(--motion-ease-standard);
}

.day-item:hover {
  border-color: var(--color-accent-text);
}

/* 已结束的排期（日历）：事项卡片淡化，悬停恢复 */
.day-item.is-past {
  opacity: 0.55;
}
.day-item.is-past:hover {
  opacity: 1;
}

/* ══════════════════════════════════════════════════════════════════════════
   当日排期抽屉的「多级展开」两栏（乐哥 2026-09-26 需求①）
   ──────────────────────────────────────────────────────────────────────────
   一级：点日期 → 抽屉里是这一天的全部事项（列表铺满）。
   二级：点某一项 → 列表**左移收窄**、右侧展开该事项详情；可以连着点下一项快速切换。
   ⛔ 为什么不是"再叠一个详情抽屉"：两个 560px 抽屉**完全重合**（实测 x 都是 865），
      列表被整个盖住 —— 既看不到这一天还有哪些事项，也没法快速切换（这是原实现的症结）。
   ⚠️ 两栏各自滚动：分栏态的 `.drawer__body` 要改 `overflow:hidden`（见下方），
      否则外层滚动条与两栏自己的滚动条会打架（滚一下两栏一起动）。
   ⚠️ 用 `backwards` 而不是 `both`：容器上的动画一旦 `both` 就会永久保留 transform
      ⇒ 给容器留下层叠上下文，里面的下拉/浮层被外层元素盖住（2026-09-26 全站事故，
      见 tests/motion-system.test.js ⑪）。
   ══════════════════════════════════════════════════════════════════════════ */
.day-split {
  display: flex;
  align-items: stretch;
  height: 100%;
}
.day-split__list {
  flex: 1 1 auto;
  min-width: 0;                 /* flex 子项默认 min-width:auto ⇒ 内容宽会顶破容器 */
  overflow-y: auto;
  padding: var(--space-5);
}
.day-split__detail { display: none; }
.day-split.is-open .day-split__list { flex: 0 0 384px; }
/* ⭐ 右侧详情块的进场（2026-09-26 晚 · 乐哥报障「打开日程看详细事项的时候没动画，直接傻快出来」）
   ──────────────────────────────────────────────────────────────────────────
   实测（真机读计算样式）：改前是 `animation: fade-in var(--motion-fast)` ——
     · 纯**透明度**变化、**零位移** ⇒ 在"面板出现"这种大块内容上几乎察觉不到（就是"傻快"）；
     · `--motion-fast`（110ms）是给 hover/选中的档位，用在**面板**上太短。
   ✅ 改为 `split-detail-in`：从右滑入 + 淡入（当日参数：16px / --motion-base 155ms，
      2026-10-08 再调为**整幅 100% / --motion-slow 300ms** —— 见下方规则处的说明）。
   ⚠️ `backwards` 不是 `both`：容器动画一旦 `both` 会**永久保留 transform** ⇒
      给容器留下层叠上下文，里面的下拉/浮层被外层元素盖住（2026-09-26 全站事故，
      见 tests/motion-system.test.js ⑪）。`backwards` 只在动画开始前应用首帧，播完即释放。
   ⚠️ 不用改 JS：`renderDayDrawerInto` 里是 `d.body.innerHTML = ...` **整体重建**，
      所以**每次切换事项 / 打开详情**都是新元素 ⇒ 动画自然重播（这正是想要的）。
   ⭐ 2026-10-08 两处调参（乐哥：「选中某一个事项**划入动画很快**」+「切换这一天的某个
      事项的时候，应该是**淡入淡出，不是滑入**」）：
       · 时长 `--motion-base`(155) → **`--motion-slow`(300)**：右侧详情与外层抽屉同属
         "大浮层展开"档。155ms 只有抽屉的一半，读作"唰一下就没了"（乐哥说的"很快"）。
       · 位移 24px → **100%（整幅从右边缘滑入）**：24px 配 300ms 几乎看不出"划入"。
         实测过的**反例**（别再走这条路）：让左侧列表"左移收窄"跟着一起动
         （flex-basis 100%→384px 的 @keyframes，确实能插值）—— 但右栏宽度会**从 0 长出来**，
         里面的表单内容在动画途中被挤成竖条（"6677舞蹈夫妇"折成 3 行、编辑按钮溢出），
         比原来的"瞬间到位"还难看。⇒ 结论：**宽度必须一步到位**（内容零重排），
         要动就动 transform。
   ⚠️ 整幅位移靠父级 `overflow: hidden` 裁掉（分栏态 `.drawer--split .drawer__body` 有）
      ⇒ 不会溢出抽屉、也不会出横向滚动条。 */
@keyframes split-detail-in {
  from { opacity: 0; transform: translateX(100%); }
  to   { opacity: 1; transform: translateX(0); }
}
@keyframes split-detail-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}
.day-split.is-open .day-split__detail {
  display: block;
  flex: 1 1 auto;
  min-width: 0;
  overflow-y: auto;
  padding: var(--space-5);
  border-left: 1px solid var(--color-border-soft);
  /* ⭐ 2026-09-29：曲线 --motion-ease(过冲) → --motion-ease-sheet（无过冲）——
     它是"右侧详情面板展开"，属面板类；过冲会让面板边缘"撞"一下左栏分隔线。 */
  animation: split-detail-in var(--motion-slow) var(--motion-ease-sheet) backwards;
}
/* 分栏态下**换一项**（不是首次打开）：只淡入，不位移。
   ⚠️ 旧内容的"淡出"由 JS 先加 `.is-fading-out` 播（110ms）再重建 —— CSS 管不了
      被 innerHTML 直接换掉的旧节点，只能由 JS 把重建**延后**到淡出播完。 */
.day-split.is-open .day-split__detail.is-detail-switch {
  animation: split-detail-fade var(--motion-base) var(--motion-ease-standard) backwards;
}
.day-split.is-open .day-split__detail.is-fading-out {
  opacity: 0;
  transition: opacity var(--motion-fast) var(--motion-ease-standard);
}

/* 分栏态的抽屉加宽：560 放不下两栏（右侧详情只剩 176px，字段会挤成竖条） */
.drawer--split { width: 1000px; }
.drawer--split .drawer__body { padding: 0; overflow: hidden; }

/* 当前选中的那一项（列表里对应的卡片）—— 与右侧详情呼应，避免"右栏是谁"要靠猜。
   ⭐ 2026-09-26 回滚：选中框回到**统一的 accent 描边**（A 方案那版曾改成"跟随卡片类型色"，
      乐哥看真机后要求换回之前的样式 ⇒ 一并退回）。
   ⚠️ 加粗靠 `inset` 阴影、不靠 border-width —— 改 border-width 会改变内容盒尺寸，
      连续点不同事项时卡内文字会左右挪 1px（肉眼可见的抖动）。
   ⚠️ 本条的 `background` 实际**不生效**：JS 内联的 `*-soft` 淡彩底优先级更高。
      选中观感 = 淡彩底 + accent 实描边。这行是历史遗留（改动它会牵连别的调用方），保留。 */
.day-item.is-selected {
  border-color: var(--color-accent-text);
  background: var(--color-surface);
  /* ⭐⭐ 2026-10-08 补「浮起」（乐哥：「之前说的那个选中凸起的，也一起做了吧」）——
     inset 描边负责"选中"，品牌蓝柔影负责"凸起"，与全站 tab 胶囊 / 视图切换同源。
     ⚠️⚠️ 两者必须写在**同一条 box-shadow** 里：box-shadow 是"整条覆盖"而不是叠加，
        拆成两条规则 ⇒ 后者胜 ⇒ 描边与浮起必定丢一个（静默失效，肉眼才发现）。 */
  box-shadow: inset 0 0 0 1px var(--color-accent-text), var(--shadow-pill-active);
}

/* 窄屏（手机/平板）：两栏改**上下** —— 并排时每栏只剩 ~180px，谁都读不了。
   列表在上（限高、可滚）+ 详情在下。 */
@media (max-width: 900px) {
  .drawer--split { width: 100%; max-width: 100vw; }
  .day-split.is-open { flex-direction: column; }
  .day-split.is-open .day-split__list {
    flex: 0 0 auto;
    max-height: 38%;
    border-bottom: 1px solid var(--color-border-soft);
  }
  .day-split.is-open .day-split__detail { border-left: 0; }
}

.day-item__bar {
  width: 3px;
  border-radius: var(--radius-pill);
  flex-shrink: 0;
}

.day-item__body {
  min-width: 0;
  flex: 1;
}

.day-item__head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.day-item__type {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-medium);
  flex-shrink: 0;
}
.day-item__type svg {
  flex-shrink: 0;
}

/* 达人名称行：名称（显眼）+ 达人类型/平台标签同行 */
.day-item__person {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  margin-top: var(--space-2);
  min-width: 0;
}

.day-item__name {
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-semibold);
  color: var(--color-fg);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.day-item__tags {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  flex-shrink: 0;
}

.day-item__meta {
  font-size: var(--font-size-xs);
  color: var(--color-muted);
  margin-top: var(--space-2);
  line-height: 1.7;
  word-break: break-word;
}

.day-item__meta span + .day-item__sep,
.day-item__meta .day-item__sep + span {
  margin: 0 var(--space-1);
  color: var(--color-meta);
}

.day-item__sep {
  color: var(--color-meta);
}

/* 时间（放事项后面）：muted 小字，紧跟事项名 */
.day-item__time {
  font-size: var(--font-size-xs);
  color: var(--color-muted);
  flex-shrink: 0;
  white-space: nowrap;
}

/* 协助人（角色标签在外 + 胶囊头像+人名在内，如「商务助理 [头像]叶平彬」） */
.day-item__staffs {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px 12px;
  margin-top: var(--space-2);
}
.day-item__staff {
  display: inline-flex;
  align-items: center;
  gap: 5px;
}
.day-item__role {
  font-size: var(--font-size-xs);
  color: var(--color-muted);
  flex-shrink: 0;
}
.day-item__chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  height: 24px;
  box-sizing: border-box;
  padding: 0 9px 0 2px;
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-pill);
  background: var(--color-surface-2);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-medium);
  color: var(--color-fg);
  line-height: 1;
}
.day-item__chip-name {
  max-width: 160px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  line-height: 1;
  padding-top: 1px; /* 补偿中文字面偏上，与同行头像视觉居中 */
}
.day-item__avatar {
  padding-top: 1px; /* 补偿中文字面偏上 */
  width: 20px;
  height: 20px;
  border-radius: var(--radius-pill);
  background: var(--color-primary-100);
  color: var(--color-accent-text);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-medium);
  flex: none;
  overflow: hidden;
  box-sizing: border-box;
}
.day-item__avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.day-item__remark {
  font-size: var(--font-size-xs);
  color: var(--color-fg-2);
  background: var(--color-surface-2);
  border-radius: var(--radius-sm);
  padding: 4px 8px;
  margin-top: var(--space-2);
  line-height: 1.5;
}

/* ══════════════════════════════════════════════════════════════════════════
   待决事项标记（乐哥 2026-09-26 需求①）
   --------------------------------------------------------------------------
   三处形态：卡片尾部旗标按钮（可点）/ 卡片内说明行 / 日历格子角标。
   色走 warning 橙 —— 与"提醒类"语义一致（本项目规矩：提醒文字用醒目色，
   但**状态标识**保持原色，故锁定锁图标仍是黄、不改成橙）。

   ⚠️ 按钮**常显**（不写 hover 才出现）：日历页在手机/平板上也用，没有 hover；
      用 `:hover` 显示会让触屏用户完全发现不了这个功能。
   ⚠️ 「未标记」态要**轻**（灰描边、无底色）—— 一屏十几张卡片，若每张都挂个橙按钮
      会喧宾夺主；「已标记」才实心橙，一眼能扫出哪些要跟进。
   ══════════════════════════════════════════════════════════════════════════ */
.day-item__actions {
  /* 尾部动作组（锁图标 + 旗标）：自己顶到最右 —— 两个子元素**不能各自**写
     margin-left:auto（flex 会平分空白、把锁图标甩到中间）。 */
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
}
.day-item__lock {
  display: inline-flex;
  align-items: center;
  color: var(--color-warning);   /* 状态标识：锁定中=黄，保持原色 */
}
.day-item__pending-btn {
  width: 26px;
  height: 26px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--color-meta);
  cursor: pointer;
  transition: background-color var(--motion-fast) var(--motion-ease-standard),
    border-color var(--motion-fast) var(--motion-ease-standard),
    color var(--motion-fast) var(--motion-ease-standard);
}
.day-item__pending-btn:hover {
  border-color: var(--color-warning);
  color: var(--color-warning);
}
/* 已标记：实心橙（一眼可扫） */
.day-item__pending-btn.is-on {
  background: var(--color-warning);
  border-color: var(--color-warning);
  color: #fff;
}
.day-item__pending-btn.is-on:hover {
  background: var(--color-warning-accent);
  border-color: var(--color-warning-accent);
}
.day-item__pending-note {
  display: flex;
  align-items: flex-start;
  gap: 6px;
  margin-top: var(--space-2);
  padding: 5px 8px;
  border-radius: var(--radius-sm);
  /* 走既有警示令牌（--notice-warn-* 一族的底/字/图标色同源）——
     ⚠️ 不要自己写裸橙色：本项目的浅底+深字是配套设计的（对比度已验），
        单独取一个浅橙配深字很容易把对比度改坏。 */
  background: var(--color-warning-bg);
  color: var(--color-warning-text);
  font-size: var(--font-size-xs);
  line-height: 1.5;
}
.day-item__pending-note svg {
  flex-shrink: 0;
  margin-top: 2px;
}
/* 已标记的卡片：左边色条之外再加一道橙色描边，扫列表时更跳 */
.day-item.has-pending {
  border-color: var(--color-warning);
}

/* 日历格子里的待决角标（chip 右侧小橙旗） */
.cal-event__pending {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
  color: var(--color-warning);
  margin-left: 2px;
}

/* 当日排期抽屉：标题栏的「上一天 / 下一天」（乐哥 2026-09-26 需求②） */
.day-nav__btn {
  flex-shrink: 0;
}

/* ---- 手机端全屏查看（真全屏 + 横屏 + DPI 密度缩放 + 悬浮退出） ---- */
.cal-fs-toggle { display: none; }
@media (max-width: 768px) {
  .cal-fs-toggle { display: inline-flex; }
}
.cal-fullscreen {
  position: fixed;
  inset: 0;
  z-index: 1150; /* 高于侧边栏/顶栏(1100)，低于抽屉(1200)，点日期抽屉能叠加其上 */
  background: var(--color-bg);
  overflow: hidden;
}
.cal-fs__stage {
  position: absolute;
  inset: 0;
  overflow: auto;
  -webkit-overflow-scrolling: touch;
  background: var(--color-bg);
}
/* 月视图内容（zoom 密度缩放由 JS 控制，缩放会真正重排布局） */
.cal-fs__zoombox .calendar {
  margin: 0 auto;
}
/* 悬浮退出按钮：右上角圆形 X，始终浮在日历上方 */
.cal-fs__exit {
  position: fixed;
  top: 12px;
  right: 12px;
  z-index: 1250;
  width: 42px;
  height: 42px;
  border-radius: 50%;
  border: none;
  background: rgba(0, 0, 0, 0.55);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.35);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
}
.cal-fs__exit:active { background: rgba(0, 0, 0, 0.75); }
/* 悬浮缩放控制：右下角胶囊（- / 百分比 / + / 复位） */
.cal-fs__zoomctrl {
  position: fixed;
  right: 12px;
  bottom: 16px;
  z-index: 1250;
  display: flex;
  align-items: center;
  gap: 6px;
  background: rgba(0, 0, 0, 0.55);
  border-radius: 24px;
  padding: 6px 10px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.35);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
}
.cal-fs__zbtn {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  border: none;
  background: rgba(255, 255, 255, 0.18);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
.cal-fs__zbtn:active { background: rgba(255, 255, 255, 0.35); }
.cal-fs__level {
  min-width: 42px;
  text-align: center;
  font-size: var(--font-size-xs);
  color: #fff;
  font-variant-numeric: tabular-nums;
}
/* 全屏时抽屉变窄（点某天的排期详情别太大太突兀） */
.cal-fs-active .drawer {
  width: 380px;
  max-width: 70vw;
}

/* 排期表单：跟随模式下的只读佣金展示（开关关闭时显示「本场实际使用」的这一套）。
   ⭐⭐ 2026-10-08 乐哥：「这个展示方式和其他地方都不一样，要统一」
   ─────────────────────────────────────────────────────────────────────────
   改造前这里是**自己一套**：`.sf-comm-follow-line` 一行蓝字「视频号 · 专场　线上 0%」，
   与排期详情/列表/达人页的「佣金块」（`.sd-comm-ch`，渠道色边框 + 四指标）不同视觉，
   且 0% 与"未配置"分不清。
   ⇒ 现在内容直接由 `commission-block.js` 出 `.sd-comm-ch` 块（与排期详情**同一份 HTML**），
      本容器只负责"竖排堆叠"；原先那三个 `--line/--plat/--val` 类已无引用，一并删除。
   ⚠️ 不用 flex+gap：`.sd-comm-ch + .sd-comm-ch` 自带 margin-top，再叠 gap 会双倍间距
      （多平台排期实测会多出 8px）。 */
.sf-comm-follow-summary {
  display: block;
}
.sf-comm-follow-empty {
  font-size: var(--font-size-sm);
  color: var(--color-muted);
}

/* ==========================================================================
   「提成点位」tab（2026-09-16 乐哥需求 · 拍板方案 A）
   · .pt-ov —— 顶部「本排期点位总览」：所有人的点位一眼看全（乐哥明确要的）
   · .pt-off —— 矩阵里「该场某人不在场」的占位（灰、不可填，避免误填出无效应配）
   · 表格复用既有 .rate-sched（表头/单元格/输入框宽度已有规范，不另造一套）
   ========================================================================== */
.pt-ov {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-4);
  margin-bottom: var(--space-4);
  padding: var(--space-3);
  border: 1px solid var(--color-primary-200);
  border-left-width: 3px;
  border-radius: var(--radius-md);
  background: var(--color-accent-bg);
}
.pt-ov__grp { font-size: var(--font-size-sm); color: var(--color-accent-text); }
.pt-ov__grp b { font-weight: var(--font-weight-semibold); margin-right: var(--space-1); }
.pt-ov__grp em { font-style: normal; font-variant-numeric: tabular-nums; }
/* ⭐ 2026-10-13：「未配 → 按 0% 计」的人 —— 灰字，与"真配了值的人"区分开。
   ⚠️ 不能只写一个 0 了事：0 可能是"配了 0%"，也可能是"没配所以按 0 算"，
      两者含义不同（前者是显式决定，后者可能是漏配）。灰字 + 「（未配）」后缀说清是后者。 */
.pt-ov__grp em.pt-ov__zero { color: var(--color-muted); }
.pt-off { font-size: var(--font-size-sm); color: var(--color-meta); }
.pt-name { font-size: var(--font-size-sm); color: var(--color-fg); }
.rate-sched td .pt-input { max-width: 130px; }

/* ==========================================================================
   已锁定排期的「只读态」（2026-09-23 乐哥需求）
   --------------------------------------------------------------------------
   乐哥：「锁定排期，我觉得应该在进去后，给个位置，比如点击修改啥的，
          点了后有提示修改后需要等审核再生效」。
   ⇒ 进去**默认只读**（表单主体套 `inert` 属性 + 本类），点横幅上的「修改」并确认后解除。

   ⚠️ 为什么用 `pointer-events: none` 而不是逐个 `disabled`：
      本表单里有多个**自定义组件**（日期选择 / 飞书选人 / 月份选择 / 可清空下拉），
      它们由内层 input + 自定义弹层的触发按钮组成 —— 逐个 disabled 一定会漏，
      而"漏"的表现正是「看着灰、其实能点」（本项目在佣金套上踩过：5 个输入框 0 个禁用）。
      ⇒ `inert`（JS 设置，管交互 + 聚焦）配本类（管鼠标 + 视觉），双保险。
   ⚠️ 只作用于 `#sf-tab-body`（表单主体）：tabs 与「取消」不受影响 ——
      只读态下仍要能切 tab 查看、能返回。
   ========================================================================== */
.is-lock-readonly {
  pointer-events: none;
  opacity: .65;
  user-select: none;
}

/* ==========================================================================
   ⭐⭐ 2026-10-05 排期列表「关键列不折断」（乐哥：「排期时间短应该是开始一行、
   结束一行，现在成了 4 行，然后状态列也是，所属商务列也是」）
   --------------------------------------------------------------------------
   ▸ **根因是"差 0.2 像素"**（真机实测，不是估计）：
       时间列 td 122px − 左右 padding 24 = 可用 **98.2px**
       而「开始 2026-10-30」不折行需要 **98px** ⇒ 差 0.2px
       ⇒ 浏览器在日期的 `-` 处折断 ⇒ 每个 div 折 2 行 ⇒ **两个 div 共 4 行**。
   ▸ `white-space: nowrap` 是**必须的那一半**：
       它把该列的 **min-content 从"最窄可断点"抬到"完整日期宽"**，
       `table-layout:auto` 就必须给该列留够空间 —— 不再出现"差一点点就断"的临界折行。
       （只把页面放宽到 1800 不够：可用 98.2 → 110.5，而需要 110 ⇒ 只剩 0.5px 余量，
        换机器 / 换字体缩放立刻复发。宽度声明 + nowrap 一起才稳。）
   ⚠️ 用 `nowrap` 而非 `overflow:hidden`：后者会把日期**裁掉半个字符**（信息丢失）；
      nowrap 在极窄屏下只是让表格出现横向滚动条 —— 排期列表本来就允许横滚（`.table-wrap`）。
   ⚠️ 只对**这两类列**用 nowrap：它们的内容是**定长短串**（日期 / 徽章），
      不存在"太长需要折行"的场景。达人名、备注等**不要**加（那些该折行）。
   ========================================================================== */
.sch-time,
.sch-time div { white-space: nowrap; }

/* 状态列：两个徽章（状态 + 锁定态）必须**并排一行** ——
   内容 108px + 6px 间距 = 114px，而列宽 150 − padding 24 = 126 ⇒ 余 12px。
   ⚠️ 不加 `flex-wrap`：徽章就两个，永远不该换行；真放不下时宁可横滚也别折。
   ⚠️⚠️ 用**专属类名** `.sch-status`，不用 `td:nth-child(12)` ——
      后者按序号选列：① 多选模式首列会插入 checkbox ⇒ 序号整体右移一位，选错列；
      ② 全站别的表格第 12 列会被无辜牵连。**按序号选列是本项目反复踩的坑。** */
.sch-status { white-space: nowrap; }

/* ⭐⭐ 所属商务列：多个商务**上下各占一行**（2026-10-13 乐哥：「排期列表所属商务 2 个人的
   时候，和达人列表一样，上下排列吧」）
   ──────────────────────────────────────────────────────────────────────────────
   ▸ 改前为什么是"横排"：公共件 `.bd-cell-list` 是 `display:flex; flex-wrap:wrap`
     —— **放不下才换行**。本列 `width:176px` 时两个 chip（各 67px + gap 4 = 138）
     塞得下 ⇒ 横着一行；只有在窄屏、列被压到 91px 时才"偶然"折成两行。
     生产真机实测（同一条排期 PQ000090，商务 = 郑小铄 + 吴书琳）：
       视口 2400 → td 176px → chip top [326,326] ⇒ **1 行**（乐哥看到的样子）
       视口 1680 → td  91px → chip top [317,345] ⇒ 2 行
     ⇒ 「横排还是竖排」**取决于窗口宽度**，本身就不稳定；竖排只是"没塞下"的副产品。
     对照：达人列表那一列**没写死宽度**、被自适应压到 128px ⇒ 一直是 2 行 ——
     这正是乐哥说"和达人列表一样"的由来（见 `.inf-bd-cell`，两处行为现已一致）。
   ▸ 改后：本列**强制竖排**（`flex-direction: column`），不再依赖"放不下"。
     ⚠️ `align-items: flex-start` 不能省 —— flex 默认 `stretch` 会把 chip 拉满整列宽。
   ▸ 列宽同步 176 → 104：176 当年是**为横排留的**（两个 chip 需 160px，见
     `pages/schedule-list.js` 表头处 10-05 的大段说明）；竖排后只需放下**最宽的那一个**
     chip —— 实测 3 字名 chip 67px + td padding 24 = 91px，再按 4 字名（≈80px）预留 ⇒ 104px。
   ⚠️ `nowrap` 保留，它管的是 **chip 内部**（头像 + 姓名）不被折断；
      外层"每 chip 一行"由上面的 `flex-direction` 决定，两者不冲突。
   ⚠️ 只作用于**排期列表**（`.sch-bd` 限定），**不动**公共件 `.bd-cell-list` ——
      详情页 / 货品明细 / 业绩佣金页 / 排期日历的商务仍是横排（那些位置宽度充足、
      横排更省行高，乐哥没提，不擅自扩大改动范围）。 */
.sch-bd { white-space: nowrap; }
.sch-bd .bd-cell-list { flex-direction: column; align-items: flex-start; }

/* ⭐⭐ 2026-10-13 乐哥：「排期列表这个佣金，应该上下居中显示」
   ──────────────────────────────────────────────────────────────────────────
   ▸ 改前：本类是 `vertical-align: top`（2026-10-06 为修「标签与数值错开」而设）。
     生产真机实测：多平台行佣金列的内容**上留白 12.5px / 下留白 33.5px** ⇒ 明显贴顶
     （旁边的达人格子有 3 行，把行撑高，而佣金只有 2 行）。

   ▸ 改后：本类回到 `vertical-align: middle`。**光改这一行会退回 10-06 的 bug**
     （四列内容高度不同 ⇒ 各自居中 ⇒ 标签行与数值行错开），所以必须配上
     JS 侧那一步：**四列用同一个内容盒 `min-height`**（= (平台行数 + 是否有尾行) × 1.5em），
     矮的列被撑到与最高列等高 ⇒ 四列盒子起点一致 ⇒ 逐行仍严格对齐。
     两步是一套，改一个不改另一个就会错位 —— 见 `pages/schedule-list.js` 的 commCell。

   ⚠️ 2026-10-13 起作用于**全部四个佣金单元格**（此前只挂在多平台那一支）——
      乐哥要求「佣金这行前缀显示渠道名」后，单/多平台走**同一条渲染分支**（不再分叉），
      单平台也带标签与统一内容盒 ⇒ 本类必须一起挂上，否则单平台行会贴顶、与其他行不齐。
      见 `pages/schedule-list.js` 的 commCell。
   ⚠️ 不用 `td:nth-child()` 选列 —— 多选模式首列插 checkbox 会错位（本项目反复踩）。 */
/* ⛔⛔ **必须写成 `.table td.sch-comm`（0,2,1）**，不能只写 `.sch-comm`（0,1,0）——
   实测第一版就栽在这：`table.css` 的 `.table td { vertical-align: middle }` 是 (0,1,1)，
   **特异性高于** `.sch-comm` ⇒ 规则写了但不生效（量出来仍是 -12px 错位）。
   诊断法：读 computed —— 明明写了 top 却算出 middle 就是被盖了。
   （与本项目 2026-10-06 那次 `gap` 被 `table.css` 覆盖是同一类坑。）
   ⚠️ 本类的值与 `table.css` 的 `.table td` 相同是**刻意的**（显式写出而不是靠继承），
      因为一旦有人把全表改成 top/baseline，这四列必须仍是 middle。 */
.table td.sch-comm {
  vertical-align: middle;
}


/* ==== /css/pages-workbench.css ==== */
/* ==========================================================================
   公司门户（`/hub`）样式 —— 全部类带 `ptl-` 前缀（portal）

   ⚠️ 与其它页面最大的不同：**它是独立外壳**（app.js 给它加 `is-standalone`，
      隐藏侧栏与页面标签栏），所以它铺满整屏、并**自带头部**（品牌 + 用户 + 退出）。
      这不是"样式偏好"，而是结构必然：侧栏被隐藏后，
      用户在门户上既没有位置感、也没有退出登录的入口。

   ⚠️ 为什么单开一个文件、不用 pages.css：
      pages.css 已 267KB / 全站共用，往里加东西的回归面太大。
      本页是全站唯一的"门户"，类名不会与任何页面冲突。
   ⚠️ 颜色一律取设计令牌（--color-*），不写死 hex —— 本项目禁止硬编码颜色。
   ========================================================================== */

.ptl {
  min-height: 100%;
  display: flex;
  flex-direction: column;
  /* ⭐⭐⭐ 2026-10-09（乐哥：「底色我们不是白色吗，现在怎么是灰色」）
     ────────────────────────────────────────────────────────────────────────────
     ⛔ 原来是 `background: var(--color-bg)` = **#F6F6F6 灰**，原注释理由是
        「用页面底而非白：白色留给卡片，层次才出得来」——
        那条理由属于**旧壳层**（内容是整屏、没有白屏这一层）。
        壳层 2026-10-07 定稿后，内容区是 `.app-main` 的一张**白色**圆角显示屏
        ⇒ `.ptl` 再铺灰 = 白屏上又刷一块灰板 ⇒ 用户看到的"页面底色"变成灰色。
     ▸ 实测（`elementFromPoint` 视觉取色，不是量 `.app-main`）：
        达播台留白 = rgb(255,255,255) 白 / 本板块 = rgb(246,246,246) 灰。
     ⚠️ 白屏上的层次由卡片阴影（`--shadow-card`）承担，不靠底色差。
     ✅ 改透明，与达播台 `.page` 一致。
     ⚠️ 与 `pages-finance.css` 的 `.fin` 是**同一个病**（门户首页实测用的就是 `.fin`），
        两处要么一起改、要么都不改 —— 只改一处会出现"门户首页白、门户子页灰"。 */
  background: transparent;
}

/* ⚠️ 门户要**铺满整屏**，所以必须清掉 `.page` 自带的内边距
   （layout.css：`padding: var(--space-6) var(--page-padding) var(--page-padding)`，
    独立外壳下 top 被 pages.css 改成 var(--space-4)）。
   不清的话，自带头部条四周会露出一圈页面底色，看着像"嵌在框里"而不是通栏。
   ⚠️ 用 `:has()` **精确限定到"装着门户的那个 .page"** ——
      `.page` 是全站几十个页面共用的容器，直接改它会让全站错位。 */
.app-shell.is-standalone .page:has(> .ptl) {
  padding: 0;
}

/* ---- 门户头部（自建，替代被隐藏的侧栏品牌区与底部「我的」）---- */
.ptl-top {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  height: var(--tabs-bar-height);
  padding: 0 24px;
  background: var(--color-surface);
  border-bottom: 1px solid var(--color-border);
  flex-shrink: 0;
}
.ptl-brand {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
.ptl-brand__logo {
  display: inline-flex;
  width: 30px;
  height: 30px;
  flex-shrink: 0;
}
.ptl-brand__en {
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-medium);
  letter-spacing: 0.03em;
  color: var(--color-meta);
}
.ptl-brand__zh {
  font-size: var(--font-size-lg);
  font-weight: var(--font-weight-medium);
  color: var(--color-fg);
}
.ptl-user {
  margin-left: auto;              /* 推到最右 */
  display: flex;
  align-items: center;
  gap: var(--space-3);
}
.ptl-user__n {
  font-size: var(--font-size-sm);
  color: var(--color-fg-2);
}
.ptl-out {
  height: 30px;
  padding: 0 12px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  color: var(--color-fg-2);
  font-family: inherit;
  font-size: var(--font-size-xs);
  cursor: pointer;
  transition: background-color var(--motion-fast) var(--motion-ease-standard),
              color var(--motion-fast) var(--motion-ease-standard),
              transform var(--motion-base) var(--motion-ease-sheet);
}
.ptl-out:hover {
  background: var(--color-surface-2);
  color: var(--color-fg);
}
/* 按压反馈：与全站 `.btn:active` 同一条（见 .ptl-enter 处的说明） */
.ptl-out:active { transform: scale(var(--motion-press)); transition-duration: var(--motion-fast); }
.ptl-out:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: -1px;
}

/* ---- 主体 ---- */
.ptl-main {
  flex: 1;
  width: 100%;
  max-width: 1180px;              /* 门户内容居中，超宽屏不散开 */
  margin: 0 auto;
  /* ⚠️ 与 `.page` / `.fin-main` 同一套令牌（2026-09-29 拉平）：16 / 40 / 40。
     改前是 `--space-8`(32) / 24px / `--space-10`(40) —— 顶部多 16px、左右窄 16px。
     ⚠️ 左右从 24 → 40 会让卡片区窄 32px；实测在 1440 视口下网格仍是 3 列
        （`minmax(330px, 1fr)` 的余量足够）⇒ 不影响排版，只把"贴边"松开。 */
  /* ⭐⭐ 2026-10-08 21:3x 右侧改用 --page-padding-right（名义留白 − 滚动条保留槽）：
       本容器也在 `.app-main`（滚动容器 + `scrollbar-gutter: stable`）内，
       不减的话卡片右缘会比左缘多压 15px（乐哥：「右边还是不等宽」）。 */
  padding: var(--space-4) var(--page-padding-right) var(--page-padding) var(--page-padding);
}
/* ⚠️ 大标题的**样式不在这里** —— 它用的是全站统一的 `.page-title`（layout.css）。
   本类只负责外边距，别把字号/字重再写一遍（那会和全站规范漂移）。 */
.ptl-h1 {
  margin: 0 0 var(--space-5);
}

.ptl-grid {
  display: grid;
  /* ⚠️ auto-fill + minmax：窄屏自动落成一列，不横滚、不裁切 */
  grid-template-columns: repeat(auto-fill, minmax(330px, 1fr));
  gap: var(--space-4);
  align-items: start;
}

.ptl-loading {
  grid-column: 1 / -1;
  padding: var(--space-8) 0;
  text-align: center;
  color: var(--color-meta);
  font-size: var(--font-size-sm);
}

/* ---- 板块卡片 ---- */
.ptl-card {
  display: flex;
  flex-direction: column;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--space-5);
  transition: border-color var(--motion-fast) var(--motion-ease-standard),
              transform var(--motion-fast) var(--motion-ease-standard);
}
/* ⭐⭐ 2026-09-29 悬停反馈对齐全站卡片范式（乐哥：「hub 首页、财务中心、hr 的 ui 动效统一一下」）。
   ⛔ 改前是本项目**已经废弃**的写法，两处都错：
      ① `box-shadow: 0 2px 8px rgba(28,30,33,.06)` —— 既**硬编码了色值**（本项目禁裸写颜色），
         又是"白底上浮一档阴影"这种**看不见的空转**反馈（pages.css 的 `.kpi-card` 注释里
         明确记着这条已被淘汰：白底上阴影差异肉眼读不出来）。
      ② 边框只变到 `--color-primary-200`（浅主色）—— 比全站的 `--color-accent` 弱一档，
         与达播台卡片并排看就是"两种轻重的反馈"。
   ✅ 现在与 `.kpi-card:hover`（全站范式）逐条同源：
      `border-color: var(--color-accent)` + `transform: translateY(-1px)`（**只抬 1px**，
      不是"跳起来"）。曲线 `--motion-fast` + `--motion-ease-standard`（颜色类严禁过冲）。 */
.ptl-card:hover {
  border-color: var(--color-accent);
  transform: translateY(-1px);
}

.ptl-card__t {
  margin: 0 0 var(--space-1);
  font-size: var(--font-size-lg);
  font-weight: var(--font-weight-semibold);
  color: var(--color-fg);
}
.ptl-card__d {
  margin: 0 0 var(--space-4);
  font-size: var(--font-size-xs);
  color: var(--color-muted);
  line-height: var(--font-line-height-body);
}

/* ---- 关键数字 ---- */
.ptl-kpis {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-bottom: var(--space-4);
}
.ptl-kpi {
  flex: 1 1 0;
  min-width: 108px;
  padding: var(--space-2) var(--space-3);
  background: var(--color-surface-2);
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-md);
}
.ptl-kpi__l {
  font-size: 11px;
  color: var(--color-meta);
  margin-bottom: 2px;
  white-space: nowrap;
  display: flex;
  align-items: center;
  gap: 4px;
  overflow: hidden;
}
/* 「预估」标记：未满 T+35 的月份是预估口径，必须标出来 ——
   不标的话老板会把预估数当结算数拿去对账（乐哥 2026-09-17 明确要求）。 */
.ptl-kpi__tag {
  font-size: 10px;
  line-height: 1.4;
  padding: 0 4px;
  border-radius: var(--radius-sm);
  background: var(--color-warning-bg);
  color: var(--color-warning-text);
  flex-shrink: 0;
}
.ptl-kpi__v {
  font-size: var(--font-size-xl);
  font-weight: var(--font-weight-semibold);
  color: var(--color-fg);
  line-height: 1.15;
  /* ⚠️ 金额（如 ¥304.26万）与计数共用这一格，不能让长金额把卡片撑破 */
  overflow: hidden;
  text-overflow: ellipsis;
}
.ptl-kpi__v i {
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-regular);
  font-style: normal;
  color: var(--color-muted);
  margin-left: 2px;
}
.ptl-kpi__s {
  font-size: 11px;
  color: var(--color-meta);
  margin-top: 2px;
}
/* 需要处理的数字（待付款）用危险色 —— 与全站"待办/异常"同一套语言 */
.ptl-kpi--warn .ptl-kpi__v { color: var(--color-danger); }

/* ---- 快捷入口 ---- */
.ptl-links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-bottom: var(--space-4);
}
.ptl-link {
  font-size: var(--font-size-xs);
  color: var(--color-fg-2);
  background: var(--color-gray-50);
  border-radius: var(--radius-sm);
  padding: 4px 10px;
  text-decoration: none;
  transition: background-color var(--motion-fast) var(--motion-ease-standard),
              color var(--motion-fast) var(--motion-ease-standard);
}
.ptl-link:hover {
  background: var(--color-primary-50);
  color: var(--color-accent-text);
}

/* ---- 主按钮 ----
   ⭐⭐ 2026-09-29 补齐「按压反馈」（乐哥：「hub 首页、财务中心、hr 的 ui 动效统一一下」）。
   🐞 改前：这是个 `<a>`，只有 hover 换底色 —— **按下去没有任何反馈**；
      而全站 `.btn:active`（components.css）是 `transform: scale(var(--motion-press))`，
      按下缩 4%、松手平顺收回。同一个"确认"动作，达播台有回弹、门户主按钮是死的。
   ⇒ 按全站同一条实现补齐（含"按下快、松手慢"那半截）：
      基础声明里 transform 走 `--motion-base` + `--motion-ease-sheet`（**松开**的平顺收回），
      `:active` 里用 `transition-duration: var(--motion-fast)` 把**按下**压到 110ms。
   ⚠️ `--motion-press` 是 0.96；0.95 是"廉价感"的线（design-tokens 有注明），别试探。
   ⚠️ 用 `:active` 而非 JS —— 键盘/触摸下的按压语义由浏览器判定，不会漏。 */
.ptl-enter {
  margin-top: auto;                 /* 卡片等高时按钮贴底，一排卡片按钮对齐 */
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  height: 36px;
  border-radius: var(--radius-md);
  background: var(--color-accent);
  color: var(--color-accent-on);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  text-decoration: none;
  transition: background-color var(--motion-fast) var(--motion-ease-standard),
              transform var(--motion-base) var(--motion-ease-sheet);
}
.ptl-enter:hover { background: var(--color-primary-600); }
.ptl-enter:active { transform: scale(var(--motion-press)); transition-duration: var(--motion-fast); }
.ptl-enter__a { font-size: var(--font-size-xs); }

/* ---- 未来板块的占位卡 ---- */
.ptl-card--ghost {
  border-style: dashed;
  border-color: var(--color-gray-300);
  background: transparent;
  justify-content: center;
}
.ptl-card--ghost:hover { border-color: var(--color-gray-400); box-shadow: none; transform: none; }
/* ⚠️ `transform: none` 是**必须**的：上面 `.ptl-card:hover` 会给所有卡片加"上移 1px"，
   而占位卡（未来板块）**不可点、没有去处** —— 会浮起来的死卡比没有反馈更误导
   （用户会以为点了能进）。这里显式撤掉，别按"继承不到"的直觉省这一条。 */
.ptl-ghost__t {
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  color: var(--color-muted);
  margin-bottom: var(--space-3);
}
.ptl-ghost__l { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-bottom: var(--space-3); }
.ptl-ghost__p {
  font-size: var(--font-size-xs);
  color: var(--color-meta);
  border: 1px dashed var(--color-gray-300);
  border-radius: var(--radius-sm);
  padding: 4px 10px;
}
.ptl-ghost__n { font-size: 11px; color: var(--color-meta); }

/* ---- 键盘可达：焦点环（全站只有 button/[role=button]/[tabindex] 有全局规则，a 不在内） ---- */
.ptl-enter:focus-visible,
.ptl-link:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

/* ---- 手机：一列、边距收紧 ---- */
@media (max-width: 768px) {
  .ptl-top { padding: 0 16px; }
  .ptl-brand__en { display: none; }       /* 手机上优先保住中文名与退出按钮 */
  .ptl-main { padding: var(--space-5) 16px var(--space-8); }
  .ptl-grid { grid-template-columns: 1fr; gap: var(--space-3); }
  .ptl-card { padding: var(--space-4); }
  .ptl-kpi { min-width: 0; }
}


/* ==== /css/pages-finance.css ==== */
/* ==========================================================================
   财务模块（`/finance`）样式 —— 全部类带 `fin-` 前缀

   ⚠️ 与门户（`ptl-`）同属"独立外壳"家族：满屏、自带头部、无达播侧栏。
      区别是财务模块**有自己的左侧导航**（门户没有内部页面，只是往别处跳）。
   ⚠️ 颜色一律取设计令牌；不写死 hex。
   ⚠️ 单开一个文件（不塞进 267KB 的 pages.css）：回归面小、类名不会冲突。
   ========================================================================== */

.fin {
  min-height: 100%;
  display: flex;
  flex-direction: column;
  /* ⭐⭐⭐ 2026-10-09（乐哥：「底色我们不是白色吗，现在怎么是灰色」）
     ────────────────────────────────────────────────────────────────────────────
     ⛔ 原来这里是 `background: var(--color-bg)` = **#F6F6F6 灰**。
     ▸ 为什么现在不对了：这句话是**旧壳层**留下的（那时财务是"无侧栏、内容满屏"的
        独立一层，需要自己铺页面底）。
        而壳层 2026-10-07 定稿后，内容区是 **`.app-main` 的一张白色圆角显示屏**
        （`background: var(--color-surface)` #FFF）。`.fin` 再铺一层灰
        ⇒ **等于在白屏上又刷了一块灰板**，用户看到的"页面底色"就成了灰色。
     ▸ 实测铁证（1920×900，`elementFromPoint` 视觉取色 —— 不是量 `.app-main`）：
        · 达播台：「表格卡下方留白」= **rgb(255,255,255) 白**（`.page` 透明，露出白屏）
        · 财务 ：「表格卡下方留白」= **rgb(246,246,246) 灰**（`div.fin` 提供）
        色链：`fin-main(透明) > fin-body(透明) > **fin=rgb(246,246,246)** > page(透明) > app-main=白`
     ⚠️⚠️ 这一条上一轮**没查出来**，因为当时量的是 `.app-main` 的 computed 背景 = `rgb(255,255,255)`
        —— **量错层级**：数字对了，但 `.fin` 盖在上面，用户看到的不是那个值。
        ⇒ 教训：判"用户看到的颜色"**必须用 `elementFromPoint` 从视觉取色**，
          不能拿某个外层容器的 computed 值当结论（技能里"量错层级"那一类假验证）。
     ✅ 改为透明：与达播台 `.page`（`rgba(0,0,0,0)`）**完全一致** ⇒ 露出白屏。
     ⚠️ 白屏上的层次由 `.card` / `.fin-chip` 的 `--shadow-card` 承担（10-06 定稿的做法），
        不靠底色差 —— 别为了"看出层次"把底色再压回去。
     ⚠️ 门户首页也复用本类（实测 `/hub` 的容器就是 `div.fin`）⇒ 一处改动同时修好两个板块。 */
  background: transparent;
}

/* ⚠️ 清掉 `.page` 自带内边距（独立外壳下 top 被 pages.css 改成 --space-4），
   否则头部条四周会露出一圈底色、看着像"嵌在框里"而不是通栏。
   ⚠️ 用 `:has()` 精确限定到"装着财务模块的那个 .page"—— `.page` 是全站共用容器。 */
.app-shell.is-standalone .page:has(> .fin) {
  padding: 0;
}

/* ---- 布局：内容区 ----
   ⭐ 2026-09-21：导航已搬到**应用侧栏**（`#sidebar-nav`），内容区只剩 `main`。
   ⭐⭐ 2026-09-22 深夜：财务侧栏的 **DOM 结构与样式已统一到达播/门户/系统配置同一套**
      （`.nav-group` / `.nav-group-label` / `.nav-subitems` / `.nav-item` / `.nav-icon` / `.nav-text`）。
      原来财务自造的那套 `fin-nav__*` 类**已全部删除**（连同这里约 100 行规则）。
      ⛔ 别把它加回来：加了不会有任何元素匹配（`finance.js` 的 navItemsHtml 已改用标准类），
         只会让后来者误以为还有第二套侧栏样式 —— 那正是"看着不像同一个系统"的来源。
      「导航去哪了」见 `pages/finance.js` 的 renderSidebarNav() 与 app.js 的
      renderFinanceSidebar()（一个管中部导航、一个管品牌区与底部）。
   ⚠️ 侧栏里那条竖分隔线由**侧栏自己**的 `border-right` 承担（base.css 的 `.sidebar`），
      这里**不要**再画一条 —— 否则会出现两条紧挨着的竖线（实测过）。
   ⚠️ `.fin-body` 保留：它仍是 `.fin` 的 flex 子项、承担 `flex:1` 的撑高。 */
.fin-body {
  flex: 1;
  display: flex;
  min-height: 0;
  width: 100%;
}

/* ---- 模块导航项（渲染在**应用侧栏内**，2026-09-21 起）----
   ⚠️ 这些类名保留 `fin-` 前缀（虽然现在挂在侧栏里）：它们**只属于财务模块**，
      与达播侧栏的 `.nav-item` 是两套东西（那边单行、这边两行带描述）。
      改成 `.nav-item` 会连带吃到达播的菜单样式与高亮逻辑（`[data-nav]` 那套绑定），
      反而更容易出错 —— 见 app.js 里关于"品牌区不许用 data-nav"的同类说明。
   ⚠️ 与达播菜单项的体量对齐：高度不写死（两行内容自然撑开），
      但左右内边距与圆角沿用同一套令牌（`--space-*` / `--radius-md`）。 */
/* 选中态复用全站那一套（浅蓝胶囊 + 主色字），不新造样式 */

/* ---- 侧栏里的**分组标签**（2026-09-22 新增，配合"两组菜单"改造）------------
   财务中心侧栏现在分两组：「报销管理」/「我的」。
   ⚠️ 为什么不用达播的 `.nav-group-label`（那个类已存在）：它是**可折叠按钮**
      （40px 高、base 字号、带 hover 与图标），而这里只有两组、每组项数少
      （管理组最多 6 项、我的组 1 项），多数一个折叠动作反而添乱。
      这里用"小灰字"做**纯标签**，只负责"组名在上、项在下"的视觉区隔。
   ⚠️ 整组没权限时**连这个标签一起不渲染**（见 finance.js 的 navItemsHtml）——
      留一个空组名比不显示更糟（像菜单加载失败）。 */
/* 第一组不需要上边距（否则品牌区下面会多一段空白） */

.fin-main {
  flex: 1;
  min-width: 0;
  /* ⚠️ 上下左右一律用与 `.page` **同一套令牌**（2026-09-29 拉平）：
       上 16(`--space-4`) / 左右 40(`--page-padding`) / 下 40(`--page-padding`)
     改前是 `--space-6`(24) / 32 / `--space-8`(32) —— 三处都与达播台不一致
     （达播台 `.page` 是 16/40/40）。根因是 2026-09-25「呼吸感」只改了 `.page`，
     财务用自己的 `.fin-main` 承担留白，**没跟上**。
     ⚠️ 别再写 `--space-6`/`--space-8` 这类"看起来差不多"的值 —— 差 8px 就是
        "同一屏幕切过去内容会撑宽、左对齐线对不上"（本项目反复踩过）。 */
  /* ⭐⭐ 2026-10-08 21:3x 右侧改用 --page-padding-right（名义留白 − 滚动条保留槽）：
       本容器也在 `.app-main`（滚动容器 + `scrollbar-gutter: stable`）内，
       不减的话卡片右缘会比左缘多压 15px（乐哥：「右边还是不等宽」）。 */
  padding: var(--space-4) var(--page-padding-right) var(--page-padding) var(--page-padding);
}
/* ⚠️ 标题用全站统一的 `.page-title`（通用巡检 `probe-all-pages.js` 查的就是它），
   本类只补外边距 —— 别自造语义类，否则该页在通用门禁上永久假红。 */

.fin-loading {
  padding: var(--space-8) 0;
  text-align: center;
  color: var(--color-meta);
  font-size: var(--font-size-sm);
}

.fin-count {
  font-size: var(--font-size-xs);
  color: var(--color-muted);
  margin-bottom: var(--space-2);
}.fin-count b { color: var(--color-fg); font-weight: var(--font-weight-semibold); }

/* ---- 标题旁的「?」使用说明（2026-09-21 乐哥）----
   ⚠️ 三个必须写对的点（每处都对应一个会**静默失效**的坑）：
     ① `.fin-help { margin-right: auto }` —— `.page-header` 是 `flex; space-between`，
        不加的话问号会被推到**页面最右边**（看着像"跑到右上角了"，而不是"标题旁边"）。
     ② `[hidden] { display: none }` 必须**显式写** —— 本元素设了 `display: inline-flex`，
        那会**盖掉** `hidden` 属性自带的 `display:none`
        ⇒ 没有说明的页面也会显示一个空问号。`.fin-help[hidden]`（0,2,0）>
        `.fin-help`（0,1,0），所以不需要 `!important`。
     ③ 面板走**绝对定位** —— 展开/收起绝不能把下面的 KPI 小片与表格整块推下去
        （那会让页面跳一下）。
   ⚠️ 浮层视觉用**浅色卡片档**（白底 + 浅描边 + 12px 圆角 + 浮起阴影），
      **不新造颜色**：与下方表格卡同一族，一眼就是"系统里的说明卡"。
      ⛔ 曾试过复用金额 tooltip 的**深色**浮层（`.af-money__tip` 那套深底反白）——
         截图实测：深色块**视觉重量压过了第一个 KPI 小片**（"替票张数"被盖住时
         看着像页面缺了一块），而且在一屏浅色的页面里显得像"另一个系统的组件"。
         ⇒ 教训：**深色浮层只适合 1~2 行的短提示**（金额 tooltip 就是），
            多行说明要用浅色卡片。
   ⚠️ 面板**盖住第一个 KPI 小片就让它盖** —— 说明是**用户主动点开**的临时层，
      用户正看着它读，此时"不遮挡下方内容"不是需求；
      而"展开时不把下方内容推下去"（不走文档流）才是必须的。
   ⚠️⚠️ 本注释**只能有一个结束标记**：我插第二批说明时多写了一个注释结束符，
      注释**提前闭合** ⇒ 后面三行变成野文本 ⇒ 紧跟着的 `.fin-help { position: relative }`
      **整条规则被吞掉**。后果是"面板定位到页面左下角、问号被 space-between 推到最右边"，
      而 CSS 不报错、门禁也不红（这正是"注释写坏结构"这类坑最难查的地方：
      **改注释也是改代码** —— 本轮 20 分钟就花在这上面）。
      ⚠️ 以及在注释里**提到那个结束符本身**时，必须拆开写（拆成星号 + 斜杠两半），
         否则你会在"解释这个 bug"的同一句话里再制造一次同样的 bug（本轮连踩两次）。 */
.fin-help {
  position: relative;
  display: inline-flex;
  align-items: center;
  align-self: center;
  /* ⭐⭐⭐ 2026-10-09（乐哥：「tab栏的悬浮不对」）：
     `margin-right: auto` → **`margin-left: auto`**（问号从"紧贴标题"改为"靠页头右端"）
     ────────────────────────────────────────────────────────────────────────────
     ▸ 原写法（`margin-right: auto`）是给「**标题可见**」的页头用的 —— 它把剩余空间
        全吃掉，让问号**紧贴标题右缘**（见上方①的说明）。
     ▸ 但财务的 `.page-title` 按 10-08 决策是 `display: none`（页面名已在顶栏面包屑里）
        ⇒ 页头只剩 `[h1 隐藏][fin-help][tab]` 两个可见元素
        ⇒ 配 `space-between` 就是"问号最左、**tab 最右**"，而 `margin-right: auto`
          在这时**一点作用都没有**（已经没有"标题"可贴）。
     ▸ 全站口径（10-08 乐哥拍板 S2）：**「?」落在页头右端**、与页面级控件同区。
        ⇒ 财务也归这一族：`margin-left: auto` 把它顶到最右。
     ⚠️ 原 `margin-left: calc(-1 * var(--space-2))`（负值抵消 gap、让问号贴标题）
        靠右后**必须删掉** —— 负的左边距会让它越出右边界 8px。 */
  margin-left: auto;
  margin-right: 0;
}
.fin-help[hidden] { display: none; }
.fin-help__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--color-meta);
  cursor: pointer;
  /* ⭐ 2026-09-29：补过渡。改前是"颜色瞬间跳变"，而全站所有可点控件（含侧栏菜单项、
     按钮）都是 110ms 平滑过渡 —— 同一个「?」在达播台上是滑进去的、在这里是闪一下。
     ⚠️ 颜色类必须用 `--motion-ease-standard`（无过冲），**不得**用 `--motion-ease`。 */
  transition: color var(--motion-fast) var(--motion-ease-standard),
              background-color var(--motion-fast) var(--motion-ease-standard);
}
/* 悬停 / 展开时用全站"选中态"语言（浅蓝底 + 主色字）——别新造颜色 */
.fin-help__btn:hover,
.fin-help__btn[aria-expanded="true"] {
  color: var(--color-accent-text);
  background: var(--color-accent-bg);
}
.fin-help__btn:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}
.fin-help__panel {
  position: absolute;
  top: calc(100% + var(--space-2));
  left: 0;
  z-index: 30;
  width: 400px;
  max-width: min(400px, 82vw);
  padding: var(--space-4);
  background: var(--color-surface);
  color: var(--color-fg-2);
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  font-size: var(--font-size-xs);
  line-height: 1.75;
  text-align: left;
  white-space: normal;      /* 标题是 nowrap 语境，这里必须**显式**放开换行 */
  /* ⭐⭐ 2026-09-29 补进场动画（面板族统一）。
     🐞 改前：这个说明面板是「瞬间出现 / 瞬间消失」—— 而同一个系统里
        选人框下拉（`.fp__dropdown`）与日期面板（`.dtp-panel`）**都已经**是
        `panel-in` / `dtp-fade-in`（--motion-base + --motion-ease-sheet）。
        并排放在同一个页头下，一个滑出来一个蹦出来，就会被读成"两个系统"。
     ⇒ 复用 overlays.css 的 `panel-in`（同为 -4px + 淡入，与那两个面板观感一致），
        不新造 keyframes；曲线用 sheet（无过冲）—— 面板是"展开"不是"弹"，
        且它紧贴「?」按钮，带过冲会与按钮撞一下。
     ⚠️ 靠 `[hidden]` 的 display 切换重播动画（display:none → block 会重新起动画），
        所以不需要 JS 插手；但**不许**把 `[hidden]` 改成 opacity/visibility 那种
        "隐藏但仍在布局里"的写法 —— 那会同时改变点击热区与 z-index 行为。 */
  animation: panel-in var(--motion-base) var(--motion-ease-sheet) backwards;
}
.fin-help__panel[hidden] { display: none; }
/* 窄屏：400px 定宽会超出屏幕（`max-width:82vw` 已兜底，这里再把左边缘对齐容器） */
@media (max-width: 768px) {
  .fin-help__panel { left: calc(-1 * var(--space-4)); }
}

.fin-h3 {
  margin: var(--space-5) 0 var(--space-3);
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-semibold);
  color: var(--color-fg);
}

/* ---- 说明条 ---- */
.fin-note {
  padding: var(--space-3) var(--space-4);
  margin-bottom: var(--space-4);
  background: var(--color-primary-50);
  border-radius: var(--radius-md);
  font-size: var(--font-size-xs);
  line-height: 1.65;
  color: var(--color-fg-2);
}
.fin-note b { color: var(--color-accent-text); font-weight: var(--font-weight-semibold); }
.fin-note__sub { display: block; color: var(--color-muted); margin-top: 2px; }

/* ---- 单据号 ---- */
.fin-code {
  font-family: var(--font-family-mono);
  font-size: var(--font-size-xs);
  color: var(--color-fg);
}

/* ---- 状态标签 ---- */
.fin-tag {
  display: inline-block;
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-medium);
  padding: 1px 7px;
  border-radius: var(--radius-sm);
  white-space: nowrap;
}
.fin-tag--success { background: var(--color-success-bg); color: var(--color-success-text); }
.fin-tag--danger  { background: var(--color-danger-bg);  color: var(--color-danger); }
.fin-tag--warning { background: var(--color-warning-bg); color: var(--color-warning-text); }
.fin-tag--muted   { background: var(--color-gray-50);    color: var(--color-muted); }
/* ⭐ 「待付款」= **待办**，不是异常（2026-09-21 改）。
   ⚠️ 原来它用 `--danger` 红色 —— 而红色在这套色板里表示"出问题了"（已驳回、缺票号）。
      「待付款」恰恰是**一切正常、就等财务动手**的状态，也是财务每天最该去处理的一批。
      用红色会让人以为这些单有毛病、反而不敢动；也把"真正的异常（已驳回）"稀释掉了。
   ⚠️ 用主色蓝：全站"待办/需操作"的语言就是蓝（按钮、链接、选中态）。
      于是三种色各司其职 —— 蓝=**该你动** / 橙=**别人在办** / 红=**出问题了**。 */
.fin-tag--todo    { background: var(--color-primary-100); color: var(--color-primary-700); }

/* 「缺票号」提醒 —— 这是发票核验页最该被看见的东西 */
.fin-miss {
  display: inline-block;
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-medium);
  padding: 1px 7px;
  border-radius: var(--radius-sm);
  background: var(--color-warning-bg);
  color: var(--color-warning-text);
}

/* ⚠️ 这里原有 `.fin-groups`（"待付款分组 / 按人凑批次"的早期布局）——
   实测零引用（`grep -rn "fin-groups" public/js/ tests/`（含 index.html） 无结果），
   与上面那段一起删除。 */
/* ⚠️⚠️ 2026-09-21 **删掉一段死 CSS，并因此消掉一个类名撞车**：
   这里原来有一组 `.fin-group` / `.fin-group__h/n/c/a/b` + **另一个 `.fin-chip`**
   （灰底、2px 7px 内边距、4px 圆角），是早期"详情页分组标签"的样式。
   实测 `grep -rn "fin-group" public/js/ tests/`（含 index.html） **零引用** ⇒ 纯死代码。
   而那个裸 `.fin-chip` 与 **KPI 小片用的 `.fin-chip` 同名**：
     · 两处特异性都是 (0,1,0)，靠**源码先后**决定胜负（KPI 规则在后面 ⇒ 目前是它赢）；
     · 一旦有人去改那段"死"代码，会**静默影响 KPI 小片**（反之亦然）——
       这正是本项目反复强调的"同一个类名两种用途 ⇒ 必然出事"。
   ⇒ 一并删除。删除前后实测 KPI 小片的 w/h/bg/radius/padding/color **逐项一致**
     （见交付说明里的 before/after 对照），**零行为变化**。 */

/* ---- 键盘可达 ---- */

/* ---- 手机：导航在**侧栏抽屉**里，只做"单行化"适配 ----
   ⚠️⚠️ 2026-09-21 重要修正：原来这一块是「把导航从左侧竖列改成内容区**顶部横排**」
      （`flex-direction: row` + `overflow-x: auto`），因为那时导航是内容区里的独立列、
      窄屏占宽度太狠。**现在导航搬进了应用侧栏**（`#sidebar-nav`）⇒ 那套横排逻辑不成立：
        · 侧栏在手机上本来就是**抽屉**（宽 240、滑入滑出）⇒ 不存在"占太多宽度"的问题
        · 若还保留横排，4 个导航项会在 240px 抽屉里挤成一横排、还要横向滚动（很难用）
      ⇒ 那些"横排容器 / 横排时压缩每项 / 手机上只留标题 / 图标改 static"的规则**已全部删掉**。
   ⭐⭐ 2026-09-22 深夜：财务侧栏改用**达播同一套标准类**之后，这块手机适配**不再需要**——
      达播侧栏本来就有完整的窄屏规则（抽屉 + 单行菜单项），财务直接用同一套即可。
      ⛔ 不要再为财务单写手机侧栏规则：那会又分叉出第二套观感。
   ⚠️ 教训（值得记住）：**把元素从一个容器搬到另一个容器时，要连带检查它所有的
      `@media` 规则** —— 桌面规则好发现，写在文件下半部分媒体查询里的**最容易漏**。
      本轮就是靠部署脚本的静态自检把它抓出来的（判据是"行首出现该类的规则"）。
   📌 写这段注释时的二次教训：说明里**不要原样写出那对"类名 + 花括号"的字符**，
      否则静态自检（`grep`）会把这句说明当成"规则还在"而**误拦部署**。
      这里只用中文描述"横排容器那条规则"，不贴原文。 */
@media (max-width: 768px) {
  .fin-body { flex-direction: column; }
  .fin-main { padding: var(--space-4) var(--space-4) var(--space-8); }
  /* ⚠️ 原来这里有一条 `.fin-inp { width: 100% }`（窄屏搜索框占满）——
     `.fin-inp` 已随"改用标准控件"下线，而标准 `.input--search` 的窄屏规则
     components.css 里**已经有了**（`flex:1 1 auto` + `.input { width:auto !important }`）
     ⇒ 这里不再补规则。**别在本文件里写 .input--search**：pages-finance.css 是**全站引入**的
     （index.html 无条件下载），写在这儿会波及所有模块的搜索框。 */
  /* 窄屏：工具栏右侧分组改成占满一行、去掉左推（标准 .toolbar__right 是 margin-left:auto）。
     ⚠️ 类名跟着 JS 一起从 .fin-bar__right 改成了 .toolbar__right —— 只改 JS 不改 CSS
        会留下"死规则"（不报错、但窄屏布局悄悄失效）。 */
  .toolbar__right { width: 100%; margin-left: 0; }
}

/* ==========================================================================
   2026-09-20 第二轮新增：部门 tab · 审批流程条 · 报销情况旗标 · 详情页 · 发票图
   ⚠️ 颜色一律取设计令牌；不写死 hex（本项目禁硬编码颜色）
   ========================================================================== */

/* ══ 页面结构：一级 tab / 工具栏 / 二级 tab / KPI 小片（2026-09-21 定稿）═══════
   顺序：一级 tab（状态，下划线）→ 工具栏 → 二级 tab（部门，胶囊）+ KPI → 共 N 单 → 表格
   外层：整块包进 `.card`（白卡片）—— 达播台列表页的标准外壳，同时解决"tab 与工具栏重叠"。

   ⚠️⚠️ **达播台的 tab 分两级，而且是由 `app.js` 自动定形的 —— 别自己写样式对抗它**
   （2026-09-21 读代码 + 实测确认）：
      · **一级 tab** = 页面级"视图/状态大类"切换 → **下划线式**（`.table-tabs`）
      · **二级 tab** = 在当前视图内再筛（部门/岗位这类）→ **胶囊**（`.pill-filter`）
   施加者是 `app.js` 三个函数：`fixPageTabs`（页面级工具栏里的 tab）/
   `fixTableTabs`（卡内表格工具栏里的）/ `fixBareSeg`（裸在外面的）。
   它们统一把 tab 变成下划线式、并提到筛选**上面**；**但有一个明确例外**：
      `skipSeg = 页面已有 page-tabs && 卡内无 table-tabs`
      ⇒ 此时卡内的 `.pill-filter`/`.seg` **不被转换、保持胶囊**
        （原话：「页面已有 `.toolbar--has-page-tabs` ⇒ 卡内出现的 seg 只能是筛选条件」）。

   ⇒ 本页顺着框架来：
      · 状态 tab 用 `.seg`、放进页面级 `.toolbar` ⇒ 框架给下划线形态 + 独占第一行；
      · 部门筛选用 `.pill-filter`、放在卡内 `.table-toolbar` ⇒ 框架判为"筛选条件"、保持胶囊。
   ⚠️ 我第一版把状态 tab 写成 `.tabs` 且独立在卡片内 ⇒ 框架认不出它是页面级 tab
      ⇒ 部门胶囊被 `fixTableTabs` 搬出工具栏、转成下划线（类名变 `pill-filter table-tabs`）
      ⇒ 两级长得一模一样，与要求正好相反。
   ⚠️ 还有一个连带坑：KPI 的容器**不能**用 `.table-toolbar__right` ——
      框架会把这个类搬进"表格 tab 行"（实测 KPI 跑到状态 tab 那行去了）；用中性的 `.fin-chips` 就不会被搬。

   ---- 卡片外壳 ---- */
/* `.card > .tabs` / `.card > .toolbar`（左右留白 + 下边框）在 components.css 里已有，不重复定义。
   这里只补"卡片内的二级 tab 行"与"表格底部圆角"。 */
.fin-ovcard { padding: 0; overflow: hidden; }   /* overflow:hidden 让表格贴底时被卡片圆角裁齐 */
.fin-ovcard > .table-toolbar { padding: var(--space-3) var(--space-5); }
/* ⚠️ 同一页有多张表格卡时（付款管理 2 张）必须留间距 —— 实测间距是 **0px**
   （`.card + .card` 没有通用规则），两张白卡直接贴死、中间只隔着各自的 1px 描边，
   看起来像"一张卡被切开"。L4 规范：`.card` 之间 16px。 */
.fin-ovcard + .fin-ovcard { margin-top: var(--space-4); }
/* 卡片内的「共 N 单」提示行：左右留白与其它行对齐，上下收一点（它是辅助信息，不该占太多） */
.fin-ovcard > .fin-count { padding: var(--space-2) var(--space-5); margin-bottom: 0; }
/* 卡片顶部的说明条（2026-09-21：三页的说明条由"卡外灰底"移进卡内）——
   ⚠️ 为什么必须进卡内：说明条夹在工具栏与卡片之间会把两者的间距撑到 > 24px，
      框架 `markToolbarJoin` 就**不合并**了 ⇒ 这一页会长成"灰底工具栏 + 白卡表格"，
      与总览页的"工具栏并进白卡"是两种形态。放卡内则两边都合并，全模块一个样。
   ⚠️ `.fin-ovcard` 是 `padding:0`，所以这里用 margin 给左右留白（与 `.fin-count` 的 20px 对齐）。 */
.fin-ovcard > .fin-note { margin: var(--space-4) var(--space-5) 0; }
.fin-ovcard > .fin-note + .fin-count { padding-top: var(--space-3); }
/* 卡片内的"小节标题"（付款管理两张表各一个）——
   放在 `.table-toolbar` 这个标准槽位里（表格卡"表头上一行"），标题与它管的表格同卡。 */
.fin-cardttl { font-size: var(--font-size-sm); font-weight: var(--font-weight-semibold); color: var(--color-fg); }
.fin-ttl__right { margin-left: auto; display: inline-flex; align-items: center; gap: var(--space-3); }

/* ---- KPI 小片（**全模块唯一的 KPI 形态**）----
   ⚠️ 2026-09-21 三次改（乐哥：「财务中心顶部的那4个卡片，其实可以大一点，整体你看怎么弄协调些」）：
     上一版是"30px 高、数字 14px、4 个挤在左边（只占 612/1241 = 49% 宽）"，
     实测两个可量化的问题：
       ① **太小** —— 小片比表格的**表头（44px）和行（46px）都矮**，
          而它承载的是"全模块的总数"、视觉权重却最低；数字 14px 对比页面标题 24px 也压不住。
       ② **不齐** —— 右半边整块空白，与下方满宽的工具栏 / 表格左右都不对齐。
     ⇒ 改法（同时解决两条）：
       · 高 30 → **44px**（对齐全站 44px 档：表头 / 表单控件同高）
       · 数字 14 → **20px**（`--font-size-xl`，与"模块标题"同级）
       · 4 个 **等宽铺满**（grid 4×1fr）⇒ 与下方元素左右边缘对齐
       · 标签靠左、数字+说明靠右（`margin-left:auto`）⇒ 满宽但不留死白
       · 圆角 8 → **12px**（`--radius-lg`）：44px 高、满宽等分的形态已经**是卡片**，
         与下方表格卡（同为 12px）成同一族；L4 的圆角阶梯"卡片=12 / 控件=8"，
         形态变了档位跟着变 —— 这是**按规范升级**，不是放水。
         ⚠️ 配套：`verify-finance-ui-l4.js` 的 C4/C5 判据已同步改为 12px。 */
/* ⭐⭐⭐ 2026-10-09 15:5x **KPI 小片 → 全站标准卡片**（乐哥：「方案a没问题，小卡片一起换了吧」）
   ──────────────────────────────────────────────────────────────────────────────
   这里原来是财务**自造**的 `.fin-chipbar / .fin-chips / .fin-chip`（约 150 行 + 一个 768 断点）。
   它是全站**唯一**一处"自造 KPI 小片"—— 规范（板块骨架）要求 KPI 用
   **`.kpi-row` + `.kpi-card`**（标签在上、大数字在下），
   经营总览 / 售后三页 / 达人经营 / 报销付款单 / 付款台账… 共 14 个页面都在用那一套。
   ⇒ 渲染改由 `public/js/expense-chips.js` 直接输出标准件（**调用方参数一个字都不用改**），
     本段随之收敛成**只有一条**财务专属的排版补充。
   ⚠️ 唯一的板块差异：图标块与标签**同排**。看板那套把这条挂在 `#kpi-row` 作用域下
      （`#kpi-row .kpi-card__top { justify-content:flex-start; gap:12px }`），
      财务用 `.kpi-row--fin`（板块前缀）承接同一条 ——
      ⛔ 不许写进 pages.css 的公共规则，那会把别处 14 个页面的 KPI 一起改掉。
   ⛔ 别把 `.fin-chip*` 加回来：那是"同一个东西两份实现"（本项目最忌），
      而且它停在 2026-09-26 那一刀 —— 连阴影都与 `.kpi-card` 不一致过（10-09 上午才补上）。
   门禁：`tests/finance-ui-compliance.test.js` ④ 已改为「KPI 必须用 `.kpi-row`/`.kpi-card`」。 */
.kpi-row--fin .kpi-card__top {
  justify-content: flex-start;      /* 图标 + 标签**靠左成组**（`.kpi-card__top` 基类是 space-between） */
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}
/* 财务的 KPI 行与下方工具栏的间距：与改前 `.fin-chipbar` 的下边距同值（16px），
   保证"换组件"这一步**不动页面竖向节奏**。 */
.kpi-row--fin { margin-bottom: var(--space-4); }

/* ══════════════════════════════════════════════════════════════════════════
   ⭐⭐⭐ 2026-10-09「工具栏整合」新增的两个件（乐哥：「这个工具栏还得整合」）
   ──────────────────────────────────────────────────────────────────────────
   都**只用在财务** ⇒ 一律用板块前缀 `fin-` 并定义在本文件
   （规范：确需自造只允许板块前缀、且定义在该板块 CSS 里）。

   ① `.fin-ms` 部门**下拉多选**（替代原来那排"通栏胶囊"）
      · 触发器**借用 `.select` 的外观**（同高 34 / 同圆角 / 同边框 / 同字号），
        只是把"文本 + 箭头"排成两端对齐 ⇒ 与旁边的月份 / 报销人 / 审核节点**逐项同族**
        （规范「控件五类必须一起改」的同族要求：一排筛选控件要像一家人）。
      · 面板 `z-index` 取 `--z-dropdown`(1000)：要盖住表格与吸顶 tab 行（20），
        但**必须低于**弹层（`--z-modal` 1200），否则弹窗会被它压住（本项目踩过层级）。
      · 面板宽 210 起、高 280 封顶并滚动：部门可能很多，不能把页面撑长。
   ② `.fin-acts__pick`「已选 N 单」读数（现已住在页头 `.page-actions`、与主操作同排）
      · 与卡内那句 `.cell-sub` 同字号同色 —— 它们本来就是一个东西，只是换了住处。

   ⚠️⚠️ 两个件都用 `hidden` 属性切显隐 ⇒ **必须各自自带 `[hidden]` 规则**。
      本项目的 `display:` 作者样式盖掉 UA 的 `[hidden]` 已发生 6 次（`.u-selbar` 那次最典型：
      声明了 hidden 却一直显示成一条空条子）⇒ 门禁 `tests/hidden-attr-guard.test.js` 锁着。
   ══════════════════════════════════════════════════════════════════════════ */
.fin-ms { position: relative; display: inline-flex; }
.fin-ms[hidden] { display: none; }
.fin-ms__btn {
  display: inline-flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  min-width: 150px;
  cursor: pointer;
  text-align: left;
}
.fin-ms__txt { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fin-ms__btn > svg { flex: none; color: var(--color-muted); }
.fin-ms__panel {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  z-index: var(--z-dropdown);
  display: block;
  min-width: 210px;
  max-height: 280px;
  overflow: auto;
  padding: var(--space-1);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-card);
}
.fin-ms__panel[hidden] { display: none; }
.fin-ms__head {
  display: flex;
  gap: var(--space-4);
  padding: var(--space-1) var(--space-2) var(--space-2);
  margin-bottom: var(--space-1);
  border-bottom: 1px solid var(--color-border-soft);
}
.fin-ms__act {
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  color: var(--color-accent);
  font-size: var(--font-size-sm);
}
.fin-ms__item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: 7px var(--space-2);
  border-radius: var(--radius-seg);
  font-size: var(--font-size-sm);
  cursor: pointer;
}
.fin-ms__item:hover { background: var(--color-surface-2); }
.fin-ms__item input { flex: none; }
.fin-ms__empty { padding: var(--space-2); font-size: var(--font-size-sm); color: var(--color-muted); }
.fin-acts__pick {
  font-size: var(--font-size-sm);
  color: var(--color-meta);
  white-space: nowrap;
}
/* ⚠️ `.page-actions` 在 layout.css 里设了 `display: flex` ⇒ 用 `hidden` 切显隐
   **必须自带这一条兜底**（否则"隐藏"静默失效、页头上永远留着那对按钮；
   门禁 `tests/hidden-attr-guard.test.js` 锁着这类静默失效）。
   ⛔ 不要改成给 `.btn` 加 `[hidden]`：那会波及全站 14+ 个页面的按钮显隐。 */
.fin-main .page-actions[hidden] { display: none; }

/* ⭐⭐ 2026-10-10 详情/表单屏里，页头那两个"从总览搬上来"的筛选控件要收起来
   ──────────────────────────────────────────────────────────────────────────────
   乐哥：「最上面的搜索还有月份干嘛的？这是看某一条明细，要这些很多余」
   ▸ 它们是 `app.js` 的 `applyFilterBarLayout` 从 `#fin-body` 搬进页头的
     （打了 `data-fbar-moved` 标记），而本模块切屏只重画 `#fin-body`
     ⇒ 搬走的那两个节点不会被销毁，留在页头上（详见 `finance.js` 的 `paint()`）。
   ⚠️⚠️ **必须有这条兜底**：`.input--search` 自己设了 `display: inline-flex`
     ⇒ 只给它 `hidden` 属性是**静默失效**的（作者样式压过 UA 的 `[hidden]`）。
     这就是门禁 `tests/hidden-attr-guard.test.js` 立起来的那个坑
     （`.u-selbar` 声明了 hidden 却常驻成一条空条子）。
   ⚠️ 用**框架自己的标记属性** `data-fbar-moved` 做选择器，不按类名逐个列 ——
     将来框架多搬一类控件（比如日期区间），这条自动跟上，不用改两处。 */
.fin-main .page-header > [data-fbar-moved][hidden] { display: none; }

/* ⭐ 2026-10-10 单据详情：驳回原因（红色底 + 红色字，DESIGN-SPEC §2.4.1）
   「被否 = 危险」——⛔ 不用告警黄（黄是"提醒/注意"，而这单已经断了要重来）。
   改前这一屏**完全没有这一条**：状态标签写着「已驳回」，却看不到为什么被驳。 */
.fin-doc__reject {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  background: var(--color-danger-bg);
  color: var(--color-danger);
  font-size: var(--font-size-sm);
}
.fin-doc__reject > svg { flex: none; }
.fin-doc__reject-k { font-weight: var(--font-weight-medium); }
.fin-doc__reject-v { font-weight: var(--font-weight-semibold); word-break: break-word; }

/* ⭐ 2026-10-10 「明细」区块头：标题左、动作（复制票号）右。
   ⚠️ 用 `.fin-sechead` 而不是往 `.fin-h3` 上加 flex —— 详情页还有别的 `.fin-h3`
     （全流程）不该跟着变。 */
.fin-sechead {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-2);
}
.fin-sechead .fin-h3 { margin-bottom: 0; }

/* ⭐ 2026-10-10 发票号码列：一格里可能有好几个号（一笔多张票，实测一单 11 个），
   **一行一个**、等宽字体（财务要逐字符对着税局平台核）。
   ⚠️ 等宽走 `--font-family-mono`（与全站 `.mono` 同一个令牌），⛔ 不裸写字体名。
   ⚠️ 与「我的报销」的 `.me-d-code` **有意不同**：那边一笔最多两三个号、
     用"浅底小胶囊"好看；这里一笔最多 8 个 —— 8 个胶囊叠一列会变成一片斑马纹，
     反而看不清号码本身。⛔ 别为了"两边长一样"把胶囊加回来。 */
.fin-invcell { max-width: 220px; }
.fin-invno {
  display: block;
  font-family: var(--font-family-mono);
  font-size: var(--font-size-xs);
  color: var(--color-fg-2);
  line-height: 1.7;
  letter-spacing: 0.2px;
  word-break: break-all;
}

/* ---- 报销单号列靠左（2026-09-21 乐哥：「报销单号靠左，金额靠右，其他居中」）----
   ⚠️ 只作用于**本表**（`.fin-ovtbl`）：全站表格规范（乐哥 2026-09-14 定稿）是
      "短文本/编号列一律居中"，这张表是**特例**；不写类名会把全站 16 张表一起改掉。
   ⚠️ 选择器必须写到 `tbody tr td` 层级：全站"全列居中"那条是
      `.table:not(:where(td .table)) > tbody > tr > td`（特异性 0,1,3），
      只写 `.fin-ovtbl td`（0,1,1）**压不过它** ⇒ 会静默不生效（看起来"改了没反应"）。
   ⚠️ 表头**保持居中** —— 与金额列同一处理（表头居中、内容按列性质对齐），
      这是项目既有约定（乐哥 2026-09-15：「其他所有的位置都不变」）。 */
.fin-ovtbl tbody tr td:first-child { text-align: left; }
/* ⚠️「N 笔」小标**不许换行**（2026-09-21）——
   原来它跟在 13 位单号后面，列一窄就被挤到第二行，把行高从 72 抬到更高、且看着像"两个单号"。
   锁 nowrap 后：单号列的最小宽度 = 单号 + 小标，布局算得准，也不会两行。 */
.fin-ovtbl .fin-badge { white-space: nowrap; }
.fin-ovtbl .fin-code { white-space: nowrap; }

/* ⭐ 列宽（2026-09-21 乐哥：「报销单号列窄一点，分给其他列」）——
   宽度由 JS 的 `<colgroup>` 给（按各列内容实测需要分配），这里只保证**固定布局**
   与"单号列不再抢空间"。用 `table-layout: fixed` 让 colgroup 真正说了算
   （auto 布局下浏览器会按内容比例重新分配，colgroup 只是"建议"）。 */
.fin-ovtbl { table-layout: fixed; }
/* ⚠️⚠️ 替票表也必须**固定布局**（2026-09-21 实测）：`table-layout:auto` 下
   「说明」列那串很长的底稿文案（「查验接口返回 HTTP 401（APIG.0303 …）」）
   会把 746px 全抢走，把「开票日期」挤到 94px ⇒ `2026-07-30` **折成两行**
   （同一张表本地 1 行、测试环境 2 行 —— 差别就是说明文案的长短）。
   ⇒ 只有"给了 colgroup 宽度"的表才加 `fin-fixtbl`；没给宽度的表保持 auto
      （否则 fixed + 无宽度会让所有列等宽，反而更差）。 */
.fin-fixtbl { table-layout: fixed; }
/* ⭐ 2026-10-08：替票管理的「操作」列（通过 / 不通过）——
   两个按钮必须**同排**：折行会让这一行的行高翻倍，而固定布局下
   折行与否取决于列宽，看起来就会"有的行高有的行低"。
   ⚠️ 配套的红/绿字色在 `table.css`（`.table td .btn--ghost[data-tk-ok|no]`）——
      那是"表格内按钮"的全站语言所在，不在这里另写一份。 */
.fin-fixtbl .fin-tk-op { white-space: nowrap; }
.fin-fixtbl .fin-tk-op .btn + .btn { margin-left: var(--space-2); }
/* 固定布局下内容超宽会被裁 —— 给单号列与报销人列一个"至少要放得下"的兜底：
   允许它们换行而不是溢出（财务最怕看到被切掉的单号）。 */
.fin-ovtbl tbody tr td { overflow-wrap: anywhere; }
/* 首列仍是靠左（与上面那条同一意图，固定布局后需要重新声明一次以免被覆盖） */
.fin-ovtbl thead th:first-child { text-align: left; }

/* ⭐⭐ 两个状态列的「内容容器给固定宽度」——这是"开头固定位置、对齐"的实现方式
   ──────────────────────────────────────────────────────────────────────────
   乐哥原话：「报销情况，审核状态列的开头要不要固定个位置。对齐」
   实测现象：进度条左缘有 **3 种取值**（973 / 959 / 953）—— 因为整组「条 + 文字」
   被单元格**居中**，而各行文字长短不同 ⇒ 整组宽度不同 ⇒ 居中后左缘就被推来推去。

   ⚠️ 解法有两个方向，这里选后者：
     ① 把单元格改成靠左 —— 但乐哥之前定过「其他列居中」（报销单号靠左、金额靠右），
        为一个对齐去破坏既有对齐规则不划算；
     ② **给内容容器一个固定宽度**（`width`），里面再左对齐 ——
        这样"整组宽度"恒定，**居中之后左缘自然完全一致**。
   两个列都要（审核状态 / 报销情况）—— 两列结构**完全同构**
   （2026-09-21 乐哥定稿：「报销情况也按照审核状态那样子做」）。
   ⚠️ 摆放方式后来改过（乐哥当天下午又调了一次）：**标签移到最前（左）**、
      进度文字移到进度条**上面** ⇒ 对齐不再靠"条区定宽"、而是靠"标签列定宽"，
      见下面 `.pg` 的说明。 */
/* ⭐⭐ 状态块布局（两列共用）—— 乐哥 2026-09-21 下午定稿：
     原话：「把 1/4·审批完成 这个，放在那 4 个进度条上面，
            那个状态标签比如待付款那个放在所有进度的前面，
            然后高度和进度条还有进度条上面的文字整体差不大一样」

     ┌─────────────────────────────────────────┐
     │ [待付款]   4/4 · 审批完成                │  ← 上排：标签（最前/左）+ 进度文字
     │            ━━ ━━ ━━ ━━                  │  ← 下排：进度条
     └─────────────────────────────────────────┘

     实现：grid 2×2，靠**子元素顺序**摆位（标签 / 文字 / 条，见 JS 的 pgBlock）：
       [0] 标签    → 第 1 列、跨两行（垂直居中）
       [1] 进度文字 → 第 2 列第 1 行
       [2] 进度条  → 第 2 列第 2 行
     ⚠️ 第 1 列用 `auto`（按最长标签定宽）—— 现在两列的标签都是 3 个字（待付款/审批中/未开始…），
        宽度自然一致 ⇒ 第 2 列起点一致 ⇒ **文字与进度条左缘都对得齐**。
        （以前靠"条区定宽 100px"对齐，现在靠"标签列定宽"对齐，原因是条换了位置。）
     ⚠️ 行间距 3px 而不是 --space-*：这两行属于**同一个信息组**（一个状态），
        拉开会显得散、且整格变高；3px 既分得开又保持紧凑。 */
/* ⭐⭐ 2026-09-23：`fin-cell-prog` 是**并列的第二个作用域**（选择器列表，不是第二条规则）——
   见下方 `.fin-prog.fin-cell-prog` 的注释：让"没有 `.fin-ovtbl` 的表格"（审批台/我的报销）
   也能拿到同一套两行布局。声明**只有一份**，改这里两边同时生效。 */
.fin-ovtbl .pg,
.fin-cell-prog .pg {
  display: grid;
  grid-template-columns: auto 1fr;
  grid-template-rows: auto auto;
  align-items: center;
  column-gap: var(--space-2);
  row-gap: 3px;
  margin-top: 0;
}
.fin-ovtbl .pg > .fin-tag,
.fin-cell-prog .pg > .fin-tag { grid-column: 1; grid-row: 1 / span 2; }
.fin-ovtbl .pg > .pg__txt,
.fin-cell-prog .pg > .pg__txt { grid-column: 2; grid-row: 1; white-space: nowrap; }
.fin-ovtbl .pg > .pg__bars,
.fin-cell-prog .pg > .pg__bars { grid-column: 2; grid-row: 2; justify-self: start; }
/* ⚠️ 审批流程未同步时的兜底：那一格只有「标签 + 文字」、没有进度条
   ⇒ 文字落回第 2 列第 1 行，形态与正常的行一致（标签仍在最前）。 */
/* ⭐⭐ 状态块：**定宽 + 居中 + 内部左对齐**（乐哥 2026-09-21 15:45）
     原话：「现在这样可以，不过居中显示吧，然后居中后，开始的位置靠左对齐」

     ⚠️⚠️ 关键坑（我第一版就是错的）：**不能再写 `width:100%`**。
        写 100% 时它会撑满单元格 ⇒ 看起来是"左对齐"、根本看不出居中
        （实测：单元格内容宽 292，容器也 292，左右各只剩 padding 的 12px）。
     ✅ 正确做法：给定宽（`160px`）⇒ 单元格是 `text-align:center`（全站规范）⇒
        容器整体**居中**；容器内部 `text-align:left` ⇒ 各行内容**起点一致**。
     为什么是 160px：实测两列内容的自然宽度是 **155px**（审核）/ **154px**（付款）——
        标签 50 + 列间距 8 + max(进度文字 98, 进度条 97)。取 160 留 5px 余量。
        两列用**同一个宽度**，视觉上更整齐。
     ⚠️ `max-width:100%` 是**窄屏降级**：窗口变窄时列宽会小于 160，
        此时退回撑满（放弃居中，但不让内容溢出/被压）。
     为什么定宽是"各行左缘一致"的前提：各行内容长短不同（`0/3 · 待统一提报` vs
        `3/3 · 付款完成`），若是自适应宽度，居中后左缘会被推来推去；
        定宽之后每行的容器左缘相同 ⇒ 内容起点自然对齐。 */
/* ⚠️ 第二条选择器是 `.fin-prog.fin-cell-prog`（**元素自身**同时带两个类），
   不是后代选择器 —— 因为 `fin-cell-prog` 就加在那个 `.fin-prog` 元素上。
   两个选择器特异性都是 (0,2,0)，声明共用同一处。 */
.fin-ovtbl .fin-prog,
.fin-prog.fin-cell-prog { display: inline-block; width: 160px; max-width: 100%; text-align: left; }

/* ⚠️ 原来这里有一套 `.fin-steps` / `.fin-step`（报销情况的"三个文字小标签"），
   2026-09-21 已随结构改造**删除** —— 报销情况改成与审核状态同构（标签 + 进度条）后，
   这两个类在 JS / CSS / 探针里都已无引用，留着只会误导后来者以为还有第二套形态。
   查证：`grep -rn "fin-step" public/ .render-check/ tests/` 无命中。 */

/* ---- 审批流程条（列表里的一行小字）---- */
/* ⭐⭐ 审批进度条（2026-09-21 重做，乐哥：「列表页的审批状态看着不直观」）
   ──────────────────────────────────────────────────────────────────────────
   形态 = 「B 的长条」+「A 的完成/当前/之后区分」+「进度数字」（乐哥拍板的组合）。
   换掉的旧实现是 4 个带底色的文字标签 + `›` 连接符，三个毛病：
     ① "正在等审"与"还没轮到"同色（都橙底）⇒ 看不出进度，只能读旁边的小字
     ② 4 个色块 + 主标签 + 小字 = 占 3 行高，整列全是色块、扫读要一个个读
     ③ 驳回后那些**永远不会执行**的节点仍是橙底"待审批" ⇒ 假信息（像流程还能走）
   ⚠️ 关键：**"还没轮到"用灰色**（`--color-gray-200`），不是 warning 橙 ——
      橙色只留给"当前正在办的那一道"，这样一眼就能看出停在哪。
   ⚠️ 段与段之间留 3px 缝：有缝才看得出"这是第几段"，无缝就成了一条进度条、数不清段数。
   ⚠️ 每段 22px 宽 × 6px 高：够看清颜色，又不至于把整列撑宽（原来 4 个节点名占满了整列）。 */
.pg { display: inline-flex; align-items: center; gap: var(--space-3); margin-top: var(--space-2); }
.pg__bars { display: inline-flex; gap: 3px; flex-shrink: 0; }
.pg__bar {
  width: 22px;
  height: 6px;
  border-radius: 3px;
  background: var(--color-gray-200);   /* 还没轮到 */
}
.pg__bar.is-done { background: var(--color-success); }
.pg__bar.is-cur  { background: var(--color-warning); }
.pg__bar.is-rej  { background: var(--color-danger); }

.pg__txt {
  font-size: var(--font-size-xs);
  color: var(--color-fg-2);
  white-space: nowrap;
}
/* 当前/驳回用更重的字色 + 加粗：让"卡在哪"成为这一格里最先被看到的信息 */
.pg__txt.is-cur  { color: var(--color-warning-text); font-weight: var(--font-weight-medium); }
.pg__txt.is-rej  { color: var(--color-danger); font-weight: var(--font-weight-medium); }
.pg__txt.is-done { color: var(--color-success-text); }

/* 窄屏：进度条小一档 + 文字不换行（手机上「报销单号」列已窄，再换行就串行了） */
@media (max-width: 768px) {
  .pg { gap: var(--space-2); }
  .pg__bar { width: 14px; }
}

/* ---- 报销情况旗标（三小格）---- */
/* ⭐ 「报销情况」三格（2026-09-20 改）
   ────────────────────────────────────────────────────────────────────────
   ⚠️⚠️ 改的原因（可计算，不是"我觉得"）：原来"已勾选"只靠**背景色**区分
      （`--color-success-bg` 绿底 vs `--color-gray-50` 灰底），而这两个底色的
      **对比度只有 1.00** —— 等于在白色卡片上放两块颜色几乎一样的浅色块，
      财务扫一眼根本分不出哪些单已经打款（而"哪些还没打款"正是他每天要看的）。
      实测：文字本身的对比度是达标的（4.57 / 4.86），所以问题不在"看不清字"，
      而在**"状态"没有足够的视觉重量**。
   ⇒ 加强三处（都不依赖配色深浅）：
      ① 已勾选加**实色描边 + 白底的字色加深**（边框是最可靠的"选中"信号）
      ② 图标本身已区分（勾 vs 时钟），再加**文字加粗**强化
      ③ 未勾选**去掉底色**只留浅描边 —— 让"没做的事"退到背景里，
         视觉重点自然落到"已完成"上（而不是两团灰绿并排） */
.fin-flags { display: inline-flex; flex-wrap: wrap; gap: 6px; }
.fin-flag {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  font-size: var(--font-size-xs);
  padding: 1px 7px;
  border-radius: var(--radius-sm);
  background: transparent;
  border: 1px solid var(--color-border);
  color: var(--color-muted);
  white-space: nowrap;
}
.fin-flag.is-on {
  background: var(--color-success-bg);
  border-color: var(--color-success);
  color: var(--color-success-text);
  font-weight: var(--font-weight-medium);
}

/* ---- 可点的行 ---- */
.fin-row { cursor: pointer; }
.fin-row:hover > td { background: var(--color-primary-50); }
/* ⭐ 「刚生成的提报单」整行高亮（2026-09-23 乐哥：「生成之后，生成的单在哪呢？我看不到啊」
   ⇒ 生成成功后原地切到「提报单」档，并把这一张标出来）。
   ⚠️ 照抄本项目**既有先例**（`pages.css` 的 `.table tbody tr.cm-pend-row > td`）：
      用 `--color-warning-bg-soft`（设计令牌里就是给"警示整行底"的，比 `--color-warning-bg` 更浅）
      + 一个更深的 hover 版。别自己调颜色 —— 那是"同一件事两个色值"的老问题。
   ⚠️ 背景必须写在 `> td` 上，不能写在 `<tr>` 上：单元格自带底色，tr 的底色会被盖住
      （看着像"改了没效果"）。
   ⚠️ 选择器要压过上面的 `.fin-row:hover > td`（本行同时带 `.table`/`tbody`/`tr`，
      特异性更高）；否则 hover 一下高亮就没了、移开又回来，像闪烁。 */
.table tbody tr.fin-row--new > td { background: var(--color-warning-bg-soft); }
.table tbody tr.fin-row--new:hover > td { background: var(--color-warning-bg-hover); }
/* 操作列（2026-09-21 乐哥：最后一列应该是操作）——
   ⚠️ 原来的实现是**箭头 `›`**（宽 28px）。换成文字「查看」后：
      ① 宽度必须放宽到能容下两个字，否则「查看」会被挤成两行（28px 装不下）；
      ② 颜色不再用那个"淡灰→hover 变蓝"的箭头配色，改由内层 `.fin-link` 自带
         （主色蓝 + hover 下划线），与发票核验页的「跳到所属报销单」写法一致。 */
.fin-row__go { width: 56px; white-space: nowrap; }
/* ⭐ 2026-10-13：报销付款单列表的操作列要同时放**下载按钮 + 查看**⇒ 56px 装不下，
   这里放宽。⚠️ 写成**双类**覆盖（`.fin-row__go.eb-act`，特异性更高），
   ⛔ 不去改上面那条公共规则 —— 付款管理页的操作列也在用它（那边只有一个按钮的宽度需求）。 */
.fin-row__go.eb-act { width: auto; }
.fin-row__go.eb-act .eb-dl { margin-right: 8px; vertical-align: middle; }
.fin-row__go .fin-link { font-size: var(--font-size-xs); }
.fin-row:hover .fin-row__go .fin-link { text-decoration: underline; }
.fin-link { color: var(--color-accent-text); cursor: pointer; }
.fin-link:hover { text-decoration: underline; }
/* ⚠️⚠️ `.fin-link` 用在 `<button>` 上时必须 reset（2026-09-23）——
   实测付款管理页的提报单号是 `<button class="fin-link">`，而 `.fin-link` 只写了
   `color` / `cursor` ⇒ 浏览器默认样式没被清掉：
     · `background: buttonface`（实测 `rgb(239,239,239)` **灰底**）
     · 外加默认 `border` 与 `padding`
   ⇒ 单号显示成一个"灰色小方块"，与总览页的纯文本单号（`<span class="fin-code">`）不一致。
   ⚠️ 之前只在这一个页面出现，是因为别处都写成 `<span class="fin-link">`；
      修在 CSS 里而不是改调用方 —— 这样**谁以后再写 `<button class="fin-link">` 都不会再踩**。
   ⚠️ `font: inherit` 也要（button 默认用系统 UI 字体，会与表格里其它文字不同）。 */
button.fin-link {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: var(--color-accent-text);
}

.fin-badge {
  display: inline-block;
  margin-left: 6px;
  font-size: var(--font-size-xs);
  padding: 0 5px;
  border-radius: var(--radius-sm);
  background: var(--color-accent-bg);
  color: var(--color-accent-text);
}
/* 「刚生成」小标 —— 必须**放在 `.fin-badge` 之后**（同特异度，靠源码顺序覆盖）。
   ⚠️ 不能写成行内 style：那会让"这个徽标长什么样"散到 JS 里，
      下次改配色要全仓搜。 */
.fin-badge--new { background: var(--color-warning-bg); color: var(--color-warning-text); }
.fin-miss {
  display: inline-block;
  font-size: var(--font-size-xs);
  padding: 1px 7px;
  border-radius: var(--radius-sm);
  background: var(--color-warning-bg);
  color: var(--color-warning-text);
}
.fin-count__hint { color: var(--color-meta); }

/* ---- 说明条：危险版（重复票号）---- */
.fin-note--danger { background: var(--color-danger-bg); color: var(--color-fg); }
.fin-note--danger b { color: var(--color-danger); }
.fin-dup { margin-top: 4px; font-size: var(--font-size-xs); }

/* ---- 详情页 ---- */
.fin-back {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  margin-bottom: var(--space-3);
  padding: 4px 10px 4px 6px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  color: var(--color-fg-2);
  font-family: inherit;
  font-size: var(--font-size-xs);
  cursor: pointer;
}
.fin-back:hover { background: var(--color-surface-2); color: var(--color-fg); }

/* ⭐⭐ 2026-09-23 下钻态（提报单里看它的某张报销单）顶部的**层级条** ——
   把"返回"按钮和一句说明放在同一行，回答"我还在提报单里吗"。
   ⚠️ 为什么需要：这一屏的页头标题是「报销单详情」（内容名实相符）、
      而侧栏高亮是「提报单」—— 两句各自都对，缺一句把它们连起来，
      用户就会以为"我又被扔到报销单那个模块了"（上一轮乐哥报的正是这个）。
   ⚠️ `.fin-back` 自带 `margin-bottom`，在层级条里要去掉，否则整条会偏高。 */
.fin-drill {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-3);
}
.fin-drill .fin-back { margin-bottom: 0; }
.fin-drill__hint { font-size: var(--font-size-xs); color: var(--color-muted); }

/* ══ 单据详情单头（2026-09-24 重整为三层：单号+状态 / 用途 / 元信息）══════════
   ⚠️ 圆角用 `--radius-lg`(12px) 与**相邻的「全流程」卡一致** ——
      原来这里是 `--radius-md`(8px)，两张卡上下紧挨着、圆角不同看着像两套组件
      （本项目在 `.af-step-card` 上定过"卡片档 = 12px"的纪律）。 */
.fin-doc__hd {
  padding: var(--space-4);
  margin-bottom: var(--space-4);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
}
/* 第一行：单号（左） + 状态标签（右）。
   ⚠️ 状态**单独占右端**：它是整单结论，原来排在元信息行末尾、
      与"部门/时间"同级 ⇒ 扫视时最先被漏掉。 */
.fin-doc__top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
}
.fin-doc__state { flex-shrink: 0; }
/* ⭐ 2026-10-10 单号与状态**成组靠左**（不再 `space-between` 拉到两端）——
   改前它们隔了整整一屏，"这一张单 + 它现在什么状态"读起来像两件不相干的事。 */
.fin-doc__id {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-width: 0;
}
/* 元信息行里字段之间的分隔点（4 个字段挨着排、靠间距读不出"哪段是哪段"） */
.fin-doc__dot { color: var(--color-border); }
.fin-doc__no {
  font-size: var(--font-size-lg);
  font-weight: var(--font-weight-semibold);
  color: var(--color-fg);
  min-width: 0;                     /* 长号（30 位实例码）时不撑破右端的状态标签 */
  word-break: break-all;
}
/* 用途说明独立一行、字号比元信息大一档 —— 它是"这单干什么用的"，
   与单号是两种信息（原来挤在单号后面只差 2px，看着像同一句话折行）。 */
.fin-doc__reason {
  font-size: var(--font-size-sm);
  color: var(--color-fg-2);
  margin-top: var(--space-2);
  line-height: 1.5;
}
.fin-doc__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-4);
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  /* 与用途/单号之间加一条极浅的分隔线 —— 三层结构靠它"分组"，
     比单纯加大间距更明确（间距在小字号下不够醒目）。 */
  border-top: 1px solid var(--color-border-soft);
  font-size: var(--font-size-xs);
  color: var(--color-muted);
}
.fin-doc__meta b { color: var(--color-fg); font-weight: var(--font-weight-medium); }
/* 金额：元信息行里**唯一**加重的项（财务扫这一屏第一个要找的数）。
   ⭐ 2026-10-10 乐哥：「金额什么的我记得之前说过，**红色加粗**显示」
   ⇒ 走 `--color-price`（全站"结论性金额"那一档红，与 `--color-danger` 同源，
      见 design-tokens 的说明）；不动字号档位，只改颜色（原来已经是 lg + semibold）。 */
.fin-doc__amt {
  font-size: var(--font-size-sm);
  color: var(--color-fg-2);
}
.fin-doc__amt b {
  color: var(--color-price);
  font-weight: var(--font-weight-semibold);
  font-size: var(--font-size-lg);
}

/* 审批流程（4 道节点，横向排） */

.fin-paybar { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; }

/* 发票图（乐哥要的"明细下面还有对应的发票图"） */
.fin-pics {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: var(--space-3);
}
.fin-picbox {
  padding: var(--space-3);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
}
.fin-picbox__h { font-size: var(--font-size-xs); color: var(--color-fg-2); margin-bottom: 3px; }
.fin-picbox__no { font-size: var(--font-size-xs); color: var(--color-muted); font-family: var(--font-family-mono); margin-bottom: var(--space-2); }
.fin-picbox__b { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.fin-pic {
  display: block;
  width: 132px;
  text-decoration: none;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: var(--color-surface-2);
}
.fin-pic img { display: block; width: 100%; height: 96px; object-fit: cover; }
/* ⭐ 2026-09-23：发票缩略图从 `<a target="_blank">` 改成 `<button>`（点击**就地弹层看大图**，
   不再跳到浏览器新标签 —— 那是"下钻到最底层却被弹出系统"）。
   ⚠️ button 自带 padding / 字体 / 居中，**必须 reset** 回原来 `<a>` 的外观，
      否则缩略图会"胖一圈、字体变样"（这类静默外观漂移最容易漏）。
   ⚠️ 只 reset `button.fin-pic`，别碰 `.fin-pic--none`（它是 `<span>`，样式已单独覆盖）。 */
button.fin-pic {
  padding: 0;
  font: inherit;
  text-align: left;
  color: inherit;
  cursor: pointer;
}
button.fin-pic:hover { border-color: var(--color-accent); }
button.fin-pic:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 1px; }
.fin-pic__n {
  display: block;
  font-size: var(--font-size-xs);
  color: var(--color-muted);
  padding: 2px 4px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* 只有飞书文件码时不能渲染成图，给一个明确的说明块（避免显示破图） */
.fin-pic--none {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  width: auto;
  padding: 6px 10px;
  font-size: var(--font-size-xs);
  color: var(--color-muted);
  border-style: dashed;
}

/* ---- 付款：勾选栏 ---- */
.fin-ckcol { width: 40px; }

@media (max-width: 768px) {
  .fin-pics { grid-template-columns: 1fr; }
  .fin-doc__meta { gap: var(--space-3); }
}

/* ══════════════════════════════════════════════════════════════════════════
   标签栏里的「我的」（2026-09-20 乐哥「上面的标签该保留的也保留」）
   ──────────────────────────────────────────────────────────────────────────
   财务模块不渲染侧栏 ⇒ 渲染在**侧栏底部**的那个「我的」菜单没有落脚点，
   于是把它放进标签栏右侧工具组（见 app.js 的 profileHtml / bindProfileIn）。
   ⚠️ 复用 layout.css 的 .user-chip / .profile-menu **原样样式**，这里只加
      "在标签栏里" 需要的三点适配：容器定高、与左侧工具组的分隔线、间距。
      ⚠️ 不要在这里重写 .user-chip 的内部样式 —— 那会让它和达播侧栏那处
         长得不一样，而它们是**同一个组件**。
   ══════════════════════════════════════════════════════════════════════════ */
#finance-profile {
  display: flex;
  align-items: center;
  align-self: center;
  height: 40px;
  /* 与左边的搜索/通知之间划一条竖线：沿用板块切换器那条 `.gs-entry-wrap` 的
     "分组分隔"语言（左边是标签操作、右边是全站能力、最右是「我」）。 */
  margin-left: var(--space-1);
  padding-left: var(--space-3);
  border-left: 1px solid var(--color-border);
}
/* 标签栏只有 52px 高，把 chip 的上下内边距收一点，避免贴边 */
#finance-profile .user-chip {
  padding-top: 2px;
  padding-bottom: 2px;
}

/* ══════════════════════════════════════════════════════════════════════════
   审批流程 · 横向步骤条（2026-09-20 乐哥：「当前那个审批流程，太丑了」）
   ──────────────────────────────────────────────────────────────────────────
   换掉的旧实现（.fin-flow）：4 张等价卡片 + 小号 `›` 连接符，问题是没有进度感、
   连不成线、撑不满留宽、状态只靠 3px 左边框表达太隐晦。
   新实现：圆形节点 + 连通线。已通过=实心绿勾、驳回=红叉、进行中=橙圈+光晕、未开始=灰圈。

   ⚠️ 全部走设计令牌（禁裸 hex，仅 #fff/#000 例外）；字号 ≥12px（规范下限）。
   ⚠️ 连接线用 `flex:1` 自适应，**节点数是变长的**（飞书侧可改流程）
      ⇒ 不能用固定间距，否则 2 道时会留大片空白、6 道时会挤爆。
   ⚠️ 圆点 28px，连接线靠 `margin-top:13px` 与圆点垂直居中（不是 align-items:center，
      因为节点是"圆点 + 多行文字"的纵向结构，居中对齐会让线跑到文字中间）。
   ══════════════════════════════════════════════════════════════════════════ */

/* 步骤条放进标准卡片里（与页面其它区块同语言：白底 + 1px 浅描边 + 12px 圆角）。
   ⚠️ 12px 圆角 = 规范里 `.card` / `.radius-lg` 的值；旧代码里的 .fin-chain__n 用的是
      `--radius-md`(8px)，那是"按钮/输入框"档，卡片档应是 12px。
   ⚠️ 卡的内距同步收一档（24/20 → 12/20 · 2026-10-10）：全流程收成一行摘要后，
      摘要行本身只有 28px 高，留着 24px 内距会让"收起来"只收了个寂寞
      （173 → 90，而不是 173 → 52）。12px 是本项目"卡片档"的既定值。 */
.af-step-card {
  background: var(--color-surface);
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-lg);
  padding: var(--space-3) var(--space-5);
}

/* ══ 详情页「全流程」= 审批 + 付款 **并排在同一条节点行、分两段**（2026-09-24 乐哥）══
   ──────────────────────────────────────────────────────────────────────────
   乐哥原话：「红框审批流程，我觉得是不是可以放在同一行，但是两个阶段，
             分别是提报销和报销付款，两个阶段可以不同颜色区分，
             这样的话下面明细就有更多地方展示内容了」

   ⛔ 不是"合成一条 7 步的流程"（2026-09-21 已论证：审批有否决权、付款没有，
      混成一条会让财务以为付款也要等人批）⇒ 仍是**两段**，只是横向并排。
   「两段」靠三处区分，**缺一处就会看成一条 7 步流程**：
     ① 卡头两个段标签（配色不同）  ② 节点形状（圆点 vs 方点）
     ③ 节点配色（审批绿系 vs 付款主色蓝系）

   ⚠️ 删掉了原来的 `.fin-chain__sep`（居中那条"审批通过 · 已入账"分隔行）：
      它是"上下两行"的产物，并成一行后再插一条横线会把这一块撑回两段式的高度（白改）。
      衔接关系改由 ④⑤ 之间的**桥接线** `.af-step__bar--bridge` 表达（见下）。 */
.fin-chain { display: block; }

/* ══ 全流程「一行摘要」（2026-10-10 乐哥：「报销流程不是重点，不要占那么大位置」）══
   实测（生产 · BX20260917122）：节点卡 **173px** ≈ 明细表 183px（只有 2 行）
   ⇒ 配角与主角等重，这就是"重点没突出"。
   ⇒ 默认只显示这一行；完整节点卡进 `.fin-flow__full`（默认 hidden，点「展开流程」出现）。
   ⚠️ 卡的内距已同步收一档（见 `.af-step-card` 那条规则的说明）——
      否则"收起来"只收了个寂寞。 */

.fin-flow__sum {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-3);
  min-height: 28px;
}
.fin-flow__k {
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-semibold);
  color: var(--color-muted);
}
/* 审批那一段直接由 `EP.stepsBar` 渲染（`.pg`）——
   这里只清掉它在列表里的 `margin-top`（摘要行自己是 flex、不需要它）。 */
.fin-flow__sum .pg { margin-top: 0; }
.fin-flow__vsep {
  width: 1px;
  height: 14px;
  flex: 0 0 auto;
  background: var(--color-border-soft);
}
.fin-flow__pay { font-size: var(--font-size-xs); color: var(--color-fg-2); }
.fin-flow__pay b { color: var(--color-fg); font-weight: var(--font-weight-semibold); }
.fin-flow__note { font-size: var(--font-size-xs); color: var(--color-muted); }
/* ⭐ 2026-10-10 摘要行上的**时间**（当前/终态那一道的 `acted_at`）。
   ⚠️ 用比正文更浅的 `--color-muted` 且小一档：它是"补充读数"，
     抢了「0/4 · 主管审批」那句的视线就背离了主次。
   ⚠️ `.fin-flow__grp` 把"进度条 + 第几道/环节名 + 时间"裹成**一个 flex 子项**：
     不裹的话 `.fin-flow__sum` 的 `gap: 12px` 会在时间前面拉开一道口子，
     读起来像"另一个读数"（详见 `finance.js` 的 `flowSummaryHtml`）。 */
.fin-flow__grp {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
}
.fin-flow__when {
  font-size: var(--font-size-xs);
  color: var(--color-muted);
}
/* 「展开流程」把两个按钮一起推到右端（`margin-left:auto` 只加在**第一个**上，
   否则两个各自 auto、中间会裂开一大片 —— 本项目在动作组上踩过）。 */
.fin-flow__tog { margin-left: auto; }
.fin-flow__full {
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px solid var(--color-border-soft);
}

/* ══ 金额：红色加粗（2026-10-10 乐哥：「金额什么的我记得之前说过，红色加粗显示」）══
   ⚠️ 颜色+字重的规则**不在本文件**，在 `table.css` 的 `.num-price`（与 `.num-pos` 并列）——
      那是"表格里数字语义色"的唯一归口，且已经处理好特异性（本页写单类 `.num-price` 也会
      会被 `.table tbody td { color: var(--color-fg) }` 压过 ⇒ **死规则**，第一版就这么错的）。
   ↓ 这里只留"合计行"的底色（它是本页表格的专属形态）。 */
.fin-sum td {
  background: var(--color-surface-2);
  border-top: 1px solid var(--color-border);
  font-weight: var(--font-weight-medium);
}
.fin-sum td:first-child { color: var(--color-fg-2); font-size: var(--font-size-sm); }
.fin-sum b { color: var(--color-fg); font-weight: var(--font-weight-semibold); }

.fin-chain__head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-4);
  flex-wrap: wrap;      /* 窄屏时两个段标签可换行，不挤坏右侧按钮 */
}
.fin-chain__lab {
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
  color: var(--color-fg);
  padding: 2px 10px;
  border-radius: var(--radius-sm);
  background: var(--color-success-bg);      /* 审批 = 绿系（与它的"已完成"同色语言） */
  color: var(--color-success-text);
}
.fin-chain__lab--pay { background: var(--color-accent-bg); color: var(--color-accent-text); }
.fin-chain__hint { font-size: var(--font-size-xs); color: var(--color-meta); }
/* ⛔ `.fin-chain__act` 已删（2026-10-10）——「刷新审批进度」挪到外层摘要行
   （`.fin-flow__tog` 承担靠右），留在这里会成为没人用的死规则（`css-dead-rules` 门禁会抓）。 */
/* 两个段标签之间的竖分隔 —— 让"这是两段"在卡头就成立（光靠间距容易被读成"审批 4 道 · 付款 3 步"一句） */
.fin-chain__vsep {
  width: 1px;
  height: 14px;
  background: var(--color-border);
  margin: 0 var(--space-2);
  flex-shrink: 0;
}

/* 一条节点行装 7 个节点（审批 4 + 付款 3）。
   ⛔⛔ 节点宽度**不能写死** —— 乐哥 2026-09-24：「后面付款提报流程要好几个哦，
      你别画少了」。付款段以后要加环节（老板批 / 财务转账 / 财务转给主管 /
      主管转给部门的人），加完就是 10~12 个节点；宽度写死的话加到第 9 个就会挤爆。
   ✅ 改成**弹性**：`flex: 1 1 84px` ⇒ 7 个节点时每个 118~124px（铺满、宽松），
      12 个节点时自动收到 84px（仍放得下 5 字的节点名）。
      实测边界：12 节点（84px 基准 + 11 条 16px 连接线 = 1184px）≤ 卡内 1206px ✅；
      再多则由下面 `.fin-chain__row` 的横向滚动兜底，**不会挤成看不清**。
   ⚠️ 审批节点原来 104px / 付款 138px：不收窄的话 4×104 + 3×138 = 830，
      连接线只剩不到 40px ⇒ "走到哪一步"的进度感会消失（连接线是进度感的主要载体）。 */
.af-step__n {
  flex: 1 1 84px;
  min-width: 72px;          /* 再压就放不下 5 字的节点名（"打款给主管"= 60px） */
  max-width: 124px;         /* 再多节点也别把它拉成大字块（视觉上会像"标题"） */
  width: auto;              /* ⛔ 不再写死宽度 */
}
/* ⚠️ 付款节点**不单独设宽度**：原来给它写 124px 是"为 3 步定制的"，
   加环节后那个数就不成立了。审批与付款的区分靠**形状（圆点/方点）+ 配色（绿/蓝）**，
   不靠宽度 —— 宽度只由"总共有几个节点"决定（弹性）。 */

/* 桥接线：审批段与付款段之间那一条。
   ⚠️ 语义 ≠ 普通连接线 —— 普通线表示"同一段内的先后"，它表示"**过了这一关才进下一段**"。
      所以：未过 = 虚线（还没轮到），已过 = 绿色实线（`.is-done`）。
   ⚠️ 用虚线的另一层作用：并成一行后，它是唯一能"分段"的视觉断点（形状上区分两段）。 */
.af-step__bar--bridge {
  background: none;
  border-top: 2px dashed var(--color-border);
  height: 0;
  margin-top: 13px;         /* 与圆点垂直居中（与普通连接线同高） */
  min-width: 28px;
  flex: 1 1 28px;
}
.af-step__bar--bridge.is-done {
  border-top-style: solid;
  border-top-color: var(--color-success);
}

/* ⚠️ 极端兜底：节点数超出卡宽时**横向滚动**（而不是挤成一团/撑破卡片）。
   · `padding: 4px 0` 是为了不被裁掉 `is-current` 那圈 4px 光晕（overflow 会裁 box-shadow）。
   · 只在真的放不下时才出现滚动条，7~12 节点都不会触发。 */
.fin-chain__row { overflow-x: auto; padding: 4px 0; }

/* 付款段 = 方点（与审批的圆点区分）。其余完全复用 `.af-step` 的结构与三态配色。
   ⛔⛔ 付款的**配色规则不在这里** —— 见文件下方「付款段配色」那一块。
      原因（本轮实测踩到）：`.af-step__n--pay.is-done .af-step__dot` 与
      `.af-step__n.is-done .af-step__dot` **特异性完全相同**（都是 3 个类），
      此时**后写的生效**。写在这一段（在审批三态规则之前）会被审批的绿**静默覆盖** ——
      现象是"付款已完成的节点还是绿的"，不报错、也不容易看出来。 */
.af-step__n--pay .af-step__dot { border-radius: var(--radius-sm); }

/* 审批段"无流程"占位节点（导入的历史数据没有审批快照时，占住审批段那一格）。
   ⚠️ 不写死宽度：它也是一个 flex 项，跟着弹性规则走（写死会在这个"只有一格"的
      情况下把它拉得特别宽，与旁边的付款节点不成比例）。 */
.af-step__n--empty { flex: 1 1 140px; max-width: 200px; }
.af-step__dot--empty {
  background: var(--color-surface-2);
  border-style: dashed;
  color: var(--color-muted);
}

.af-step { display: flex; align-items: flex-start; }

/* ⚠️ 节点宽度/伸缩只在**上面那条** `.af-step__n` 规则里定义（弹性，可容纳更多节点）。
   这里只放"内部排版"三项 —— 原来这里也写了 `width: 104px; flex-shrink: 0;`，
   两条规则同名会**后覆盖前**（CSS 后来居上），弹性的那条直接失效且不报错
   （表现是"改了 CSS 但布局没变"）。要改宽度请改上面那条。 */
.af-step__n {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
}

.af-step__dot {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-semibold);
  background: var(--color-surface);
  color: var(--color-muted);
  border: 2px solid var(--color-border);
  box-sizing: border-box;
}

.af-step__n.is-done .af-step__dot {
  background: var(--color-success);
  border-color: var(--color-success);
  color: #fff;
}
.af-step__n.is-rejected .af-step__dot {
  background: var(--color-danger);
  border-color: var(--color-danger);
  color: #fff;
}
/* 进行中：橙色圈 + 一圈光晕。
   ⚠️ 光晕是"当前节点"最直观的表达 —— 只靠颜色深浅（橙 vs 灰）区分度不够，
      加一圈同色淡晕后视觉重量明显不同，扫一眼就知道停在哪。 */
.af-step__n.is-current .af-step__dot {
  background: var(--color-warning-bg);
  border-color: var(--color-warning);
  color: var(--color-warning-text);
  box-shadow: 0 0 0 4px var(--color-warning-bg);
}

.af-step__t {
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  color: var(--color-fg);
  margin-top: var(--space-2);
}
.af-step__n.is-current .af-step__t { color: var(--color-warning-text); }
.af-step__m {
  font-size: var(--font-size-xs);
  color: var(--color-fg-2);
  margin-top: 2px;
  /* 审批人可能很长（"张三、李四、王五"）⇒ 允许折行但不撑破节点宽度 */
  word-break: break-all;
}
.af-step__d {
  font-size: var(--font-size-xs);
  color: var(--color-muted);
  margin-top: 1px;
}
.af-step__n.is-rejected .af-step__d { color: var(--color-danger); }

/* 连接线：与圆点垂直居中（圆点 28px ⇒ 上边距 13px） */
.af-step__bar {
  flex: 1;
  height: 2px;
  min-width: 16px;
  background: var(--color-border);
  margin-top: 13px;
}
.af-step__bar.is-done { background: var(--color-success); }
/* ⚠️ 付款段的连接线（含桥接线）**不用绿** —— 桥接线的实线色在上面单独给了绿
   （它表达的是"审批这一关过了"，属于**审批的语言**，用绿是对的）。
   而付款**段内**的连接线表示"这一步办完了"，用主色蓝，与它的节点同色。 */
.af-step__bar--pay.is-done { background: var(--color-accent); }

/* ══════════════════════════════════════════════════════════════════════════
   付款段配色 = **主色蓝系**（与审批的绿系区分开）—— 乐哥要的"两个阶段不同颜色"
   ──────────────────────────────────────────────────────────────────────────
   ⚠️ 为什么付款不用绿：绿在全站语义是"已完成/通过"，而付款的"完成"与审批的"通过"
      是两件事 —— 同色会让人把两段读成一段的延续（那正是"并成一行"最需要避免的误读）。

   ⛔⛔ **这一块必须放在文件靠下的位置**（在审批三态配色之后）——
      付款的选择器 `.af-step__n--pay.is-*` 与审批的 `.af-step__n.is-*`
      **特异性完全相同**（3 个类），此时**后写的生效**。
      本轮实测踩到：先写付款蓝、后写审批绿 ⇒ 付款已完成的节点**仍然显示绿色**
      （不报错，且只有"造一张付款已完成的单"才看得出来 ——
       探针第一次跑就红了这条，靠的就是"单独跑一趟已入账单"）。
      ⇒ 若日后调整本文件的顺序，**不要把这一块移到审批配色之前**；
        真需要移，就把选择器改成 `.af-step .af-step__n--pay.is-done .af-step__dot`
        这类更高特异性的写法（但那样更脆，不推荐）。
   ========================================================================== */
.af-step__n--pay.is-done .af-step__dot {
  background: var(--color-accent);
  border-color: var(--color-accent);
  color: var(--color-accent-on);
}
.af-step__n--pay.is-current .af-step__dot {
  background: var(--color-accent-bg);
  border-color: var(--color-accent);
  color: var(--color-accent-text);
  box-shadow: 0 0 0 4px var(--color-accent-bg);
}
.af-step__n--pay.is-current .af-step__t { color: var(--color-accent-text); }
/* 付款"待办"节点的说明文字也用主色（与橙色的审批待办区分开） */
.af-step__n--pay.is-current .af-step__d { color: var(--color-accent-text); }

@media (max-width: 768px) {
  /* 窄屏：横向 4 道在手机上放不下，硬挤会把节点名压断 ⇒ 转纵向。
     纵向时隐藏"审批人/时间"（信息密度让位给可读性），状态仍靠圆点区分。 */
  .af-step-card { padding: var(--space-4) var(--space-4); }
  .af-step { flex-direction: column; }
  .af-step__n {
    flex-direction: row;
    align-items: center;
    gap: var(--space-2);
    width: 100%;
    text-align: left;
  }
  .af-step__t { margin-top: 0; }
  .af-step__m, .af-step__d { margin-top: 0; margin-left: auto; }
  .af-step__bar {
    width: 2px;
    height: 18px;
    flex: none;
    min-width: 0;
    margin: 2px 0 2px 13px;
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   报销人（头像 + 姓名）—— 乐哥 2026-09-21：「报销人得和达播台一样，有飞书头像（没有的用首字）」
   ──────────────────────────────────────────────────────────────────────────
   列表里用 `.cell-person`（**达播台的标准件**，table.css 里定义，含 28px 圆形头像、
   无图时姓名字首兜底、名字与徽章同一行的排布）—— 本文件不重写它，只做两处"在财务表格里"的适配。
   ══════════════════════════════════════════════════════════════════════════ */

/* ① 列表行更紧凑一点：财务表一屏行数多，28px 头像会把行高明显顶起来。
      收 4px 后与原来无头像时的行高接近，同时保留头像的可辨识度。 */
.fin td .cell-person__avatar {
  width: 24px;
  height: 24px;
  font-size: var(--font-size-xs);
}

/* ② 详情页头部那处「报销人」是**行内**展示（`<b>` 里），不是表格单元格 ——
      `.cell-person` 是 flex 块，直接塞进 <b> 会把基线与后续文字错开。
      这里改成 inline-flex + 与文字垂直居中对齐。 */
.fin-person {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-weight: var(--font-weight-medium);
}
.fin-person .cell-person__avatar {
  width: 24px;
  height: 24px;
  font-size: var(--font-size-xs);
  vertical-align: middle;
}

/* ══════════════════════════════════════════════════════════════════════════
   统一提报 / 付款台账（2026-09-22《方案-报销流程重构》第 5~7 步）
   ──────────────────────────────────────────────────────────────────────────
   ⚠️ 全部带 `eb-` / `ebd-` 前缀，**不复用** `.fin-*` —— 那一套是列表/详情的通用件，
      本组是"勾选汇总"这类新形态（选中汇总条、生成弹窗、详情操作条），
      蹭用公共类会在别的页面上产生意外样式（本项目在 cost-chart 上定过同一条纪律）。
   ⚠️ 颜色一律走 design-tokens 变量（`--color-*` / `--space-*`），不写死色值 ——
      写死会在暗色主题下变成看不清的浅底白字。
   ══════════════════════════════════════════════════════════════════════════ */

/* ── 选中汇总条（待提报页顶部，跟着勾选实时变）──
   ⚠️ `position: sticky` 而不是 fixed：它只在**自己那一屏**可见时粘住，
      滚到列表底部时不遮挡内容（本项目在提单页的"费用汇总"上定过同一条）。 */
.eb-summary {
  position: sticky;
  top: 0;
  z-index: 2;
  margin-bottom: var(--space-3);
}
.eb-bar {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
}
.eb-bar__k { font-size: var(--font-size-sm); color: var(--color-fg-2); }
.eb-bar__u { font-size: var(--font-size-sm); color: var(--color-fg-2); margin-right: var(--space-3); }
.eb-bar__n { font-size: var(--font-size-lg); font-weight: var(--font-weight-semibold); color: var(--color-fg); }
/* ⚠️ 金额用等宽数字：一列金额上下对齐才不会跳动（本项目表格 `.is-num` 同款理由） */
.eb-bar__v {
  font-size: var(--font-size-lg);
  font-weight: var(--font-weight-semibold);
  color: var(--color-fg);
  font-variant-numeric: tabular-nums;
}
.eb-bar__btns { margin-left: auto; }

/* ── 生成弹窗 / 打款弹窗的表单块 ── */
.eb-dlg__sum {
  padding: var(--space-3);
  margin-bottom: var(--space-4);
  background: var(--color-gray-50);
  border-radius: var(--radius-sm);
  font-size: var(--font-size-sm);
  color: var(--color-fg-2);
}
.eb-dlg__sum b { color: var(--color-fg); font-variant-numeric: tabular-nums; }
/* ⚠️ 警告条用 warning 色族的**浅底**，不是纯红 —— 它是"你先看看"，不是"出错了"。
   红色留给真正的失败（本项目在状态色档上定过：danger 只给"出问题了"）。 */
/* ⛔ 2026-09-27 修：这两条原来写成 `var(--color-warning-bg));`（**多一个右括号**）⇒
   浏览器把它们当语法错误**整条丢弃** ⇒ 结果是这条「审批/风险」提示条**没有底色、没有左侧色条**，
   看起来和普通正文一样（而它是用来提醒"审批中、不能打款"的，没样式就容易被忽略）。
   ⚠️ 判据：`calc(var(--a) + var(--b))` 结尾的 `))` 是**正常**的（一个闭 var、一个闭 calc）；
      只有「**单个 var() 后面还多一个 `)`**」才是笔误 —— 别把 calc 那批一起"修"了。
   ⚠️ 同类笔误全站还有约 15 处（`pricing-compare.css` / `layout.css` / `components.css`），
      属于**别的模块**，本次**不动**（修好它们 = 那些样式会突然生效、视觉会变，得单独评估）。 */
.eb-warn {
  padding: var(--space-3);
  margin-bottom: var(--space-3);
  background: var(--color-warning-bg);
  border-left: 3px solid var(--color-warning);
  border-radius: var(--radius-sm);
  font-size: var(--font-size-sm);
  color: var(--color-fg);
}
/* 必填星号 —— 与全站其它表单一致 */
.eb-dlg .req { color: var(--color-danger); margin-left: 2px; }

/* ── 推送付款审批：**收款账户核对块**（2026-10-15 加）────────────────────────────
   乐哥：「填了的让确认账号，确认后才能推送」——钱打错最难追回，推之前让本人过一眼。
   ⚠️ 全部走令牌（全站两条硬门禁：禁裸 hex、禁写死 999px）。 */
.eb-bank-ck {
  border: 1px solid var(--color-border-soft);
  /* 左侧色条与 `.eb-warn` 同款形态，但用 **accent**（这是"请核对"，不是"出错了"） */
  border-left: 3px solid var(--color-accent);
  background: var(--color-gray-50);
  border-radius: var(--radius-sm);
  padding: var(--space-3) var(--space-4);
}
.eb-bank-ck__hd {
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
  color: var(--color-fg);
  margin-bottom: var(--space-2);
}
.eb-bank-ck__row {
  display: flex;
  gap: var(--space-3);
  font-size: var(--font-size-sm);
  line-height: 1.9;
  color: var(--color-fg-3);
}
.eb-bank-ck__row > span { flex: none; width: 4em; }
.eb-bank-ck__row > b { color: var(--color-fg); font-weight: var(--font-weight-medium); }
/* 卡号用等宽数字，四位一组才看得出哪一组不对 */
.eb-bank-ck__row > b.is-acct { font-variant-numeric: tabular-nums; letter-spacing: .02em; }
/* 「我已核对」勾选行（复用全站 `input[type=checkbox]` 样式，这里只管排版） */
.eb-ck-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: var(--space-3);
  font-size: var(--font-size-sm);
  color: var(--color-fg);
  cursor: pointer;
}
/* 「以后在哪改」的指路行 —— 乐哥：「也告诉他后面在哪更改」 */
.eb-where {
  margin-top: var(--space-3);
  font-size: var(--font-size-xs);
  line-height: 1.9;
  color: var(--color-fg-3);
}
.eb-where b { color: var(--color-fg-2); font-weight: var(--font-weight-medium); }

/* ── 提报单详情：头部右侧的状态标签 + 操作条 ── */
.ebd-head__r { display: flex; align-items: center; gap: var(--space-3); }
.ebd-actions {
  display: flex;
  flex-wrap: wrap;
  /* ⭐ 2026-10-10 晚 乐哥：「（发起飞书报销申请）这个按钮位置是不是放最右边比较好」——
     ① 整条动作条**贴右**：与全站口径一致（`.form-actions`、审批台的 `.ap-actions` 都是
        `justify-content: flex-end`），**只有这里漏了** ⇒ 按钮一直挤在左边、右边空一大片。
     ② 主动作（`btn--primary`）在最右（顺序见页面模块里那段注释）。 */
  justify-content: flex-end;
  gap: var(--space-2);
  margin-bottom: var(--space-4);
}

/* ── 提报单详情：费用类型构成卡（2026-09-23）─────────────────────────────
   乐哥：「费用类型，看能不搞个图表展示出来，这样老板看更直观」。
   ⚠️ 卡头**不给 `.table-toolbar`**：那个类的样式是 `justify-content: space-between`
      ⇒ 两个子元素会被推到行的两端（"标题左、说明飞最右"）。
      财务总览页的部门 tab 就因为这个"跑到标题右边"被乐哥框过（见 finance.js 的注释）。
      这里要的是"标题 + 紧跟一句说明"，所以用自己的一行 flex、`gap` 控制间距。
   ⚠️ 卡头下方的图表区留 `padding`（`CostChart` 自己不带外距）——
      不留的话环会贴着卡边。 */
.ebd-chart { padding: var(--space-4); margin-bottom: var(--space-4); }
.ebd-chart__hd {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  margin-bottom: var(--space-3);
}
.ebd-chart__t { font-size: var(--font-size-base); font-weight: var(--font-weight-medium); color: var(--color-fg); }
.ebd-chart__h { font-size: var(--font-size-xs); color: var(--color-muted); }

/* ══════════════════════════════════════════════════════════════════════════
   费用构成卡：**并排两栏**（左=按费用类型，右=按报销人） 2026-09-23 深夜
   ──────────────────────────────────────────────────────────────────────────
   乐哥：「图表右边不是还有一片空的吗，可以做柱状图，展示每个提报人，
          报销得费用金额（里面不同费用类型用不同费用颜色占据）」
   （原来只有一张环形图，实测 1060px 宽的卡里右侧空着约 400px。）

   ⚠️⚠️ 为什么必须 `flex-wrap: wrap`：
      两栏的最小可用宽度加起来约 920px（环 180 + 列表 ~320 + 柱图 ~380 + 间距）。
      不给 wrap 时窄屏会把**环形图挤扁**（`.cc-list { min-width: 280px }` 会顶住不让，
      结果挤压环本身）—— 本项目在"并排布局点一下会多出一块"上踩过同类坑
      （见 cost-chart.css 里 `.cb-flex` 那段的说明）。
   ⚠️ 两栏的 `flex-basis` 是**实测出来的**：卡内宽约 1020px 时
      左 540 + 间距 24 + 右 440 = 1004 ⇒ 恰好并排且两边都不挤。
      改这两个数之前先量一下环右侧列表那三列（名称/金额/占比）会不会被压变形。
   ⚠️ 右栏（柱图）**不给 `max-width`**：它就是来吃"那片空"的，
      限宽等于又把空白还回去。 */
.ebd-charts { display: flex; gap: var(--space-6); align-items: flex-start; flex-wrap: wrap; }
.ebd-charts__c { min-width: 0; }
.ebd-charts__c--type { flex: 1 1 540px; }
.ebd-charts__c--who { flex: 1 1 440px; }
/* 右栏小标题（左栏的小标题由 CostChart 自己的分组标题给，两边视觉一致） */
.ebd-subhd { display: flex; align-items: baseline; gap: var(--space-3); margin-bottom: var(--space-2); }
.ebd-subhd__t { font-size: var(--font-size-sm); font-weight: var(--font-weight-medium); color: var(--color-fg); }
.ebd-subhd__h { font-size: var(--font-size-xs); color: var(--color-muted); }

/* 柱图的画布容器。
   ⚠️⚠️ **必须给固定高度**：Chart.js 用 `maintainAspectRatio: false` 时会去量父容器的高度，
      父容器 height:auto ⇒ 画布塌成 0 高 ⇒ 图看不见，而且**不报任何错**。
   ⚠️ 高度按"给柱顶合计标注 + X 轴人名 + 底部图例"留够：实测 220px 里
      绘图区约 150px，4 个人 3 个类型时柱高差异仍看得清。 */
.ebd-canvas { position: relative; height: 220px; }
/* 空态（组件缺失 / 字段不存在 / 确实为空）在容器里**居中**——
   顶在左上角会像是"图渲染失败后残留的一行字"。 */
.ebd-canvas .cc-empty { display: flex; align-items: center; justify-content: center; height: 100%; }
/* ⚠️⚠️ 「作废」用 ghost + danger 文字（不是实心红按钮）：它是破坏性但低频的动作，
   实心红会让它比「登记打款」还抢眼，反而诱导误点。
   ⛔⛔ 但**只改 `color` 不改 `background` 是个真事故**（2026-09-22 实测，截图才看出来）：
      `.btn--danger` 本身是**实心红底 + 白字**（`--button-danger-bg` = `--color-danger` = #E41E3F、
      `--button-danger-fg` = #FFFFFF）。我只覆盖了 `color` ⇒ 字也变 #E41E3F
      ⇒ **红底红字、对比度 1:1、按钮上的字完全看不见**（页面上出现一个莫名的红色方块）。
      ⚠️ 这个 bug 的隐蔽之处：文字的 `innerText` **是有的**（"作废"），
        任何"按钮里有文字吗"的断言都会通过 —— 只有**算文字色与背景色的对比度**才抓得到。
      ⇒ 三条都要写全：背景（常态）、背景（hover）、以及 `filter: none`
        （不写的话 `.btn--danger:hover` 的 `brightness(0.9)` 会把底色压暗，
          在浅红底上不易察觉但会让 hover 与常态的差别变糊）。 */
.ebd-actions .btn--danger {
  background: var(--button-ghost-bg);          /* transparent ⇒ 落在页面白底上 */
  border-color: var(--color-border);
  color: var(--color-danger);
}
/* ⚠️ 用 `:not(:disabled)` 与 components.css 里的 `.btn--danger:hover:not(:disabled)` 对齐特异性
   —— 平手时靠加载顺序（本文件在后）取胜，写成不带 `:not()` 的话会被那条反超。 */
.ebd-actions .btn--danger:hover:not(:disabled) {
  background: var(--color-danger-bg);
  border-color: var(--color-danger);
  color: var(--color-danger);
  filter: none;
}

/* ── 详情的关键信息区（键值对网格）──
   ⚠️ `auto-fill + minmax` 让窄屏自动折行，不用写媒体查询。 */
.fin-kv {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: var(--space-3) var(--space-4);
  padding: var(--space-4);
  margin-bottom: var(--space-4);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
}
.fin-kv__i { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.fin-kv__k { font-size: var(--font-size-xs); color: var(--color-fg-2); }
.fin-kv__v {
  font-size: var(--font-size-sm);
  color: var(--color-fg);
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}

/* ══════════════════════════════════════════════════════════════════════════
   报销附件（发票 / 支付凭证）缩略图 —— `.ea-*`（2026-09-23）
   渲染实现见 `public/js/expense-attach.js`（**全站唯一实现**）
   ──────────────────────────────────────────────────────────────────────────
   乐哥：「提的报销单里的附件，就是发票还有付款凭证的，应该在对应的那一行
   报销明细上看吧？这样费用才能一一对应」

   ⚠️ 为什么要"分组标签 + 缩略图"两层，而不是只有一排图：
      发票和付款凭证是**两类不同的东西**（票证明花了什么钱、凭证证明这笔钱付了），
      混在一排图里财务分不清哪张是哪类 ⇒ 每组前面标一个「发票 2」「凭证 1」。
   ⚠️ 尺寸**只在这里定**（JS 只负责给语义，不给尺寸）：
      同一套缩略图要出现在两种容器里 —— 表格里（列窄）和详情页大卡里（宽），
      尺寸写进 JS 就会与别处各写一套；写在这里、用容器选择器分流。
   ⚠️ `.ea-cell` 是表格里那一格的类：给它在窄列里一个**下限宽度**，
      否则缩略图会被压成一条线（表格自动布局会优先挤"没有最小宽度"的列）。 */
.ea-wrap { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.ea-set { display: inline-flex; align-items: center; gap: 4px; }
.ea-set__lab {
  font-size: var(--font-size-xs);
  color: var(--color-muted);
  white-space: nowrap;
}
.ea-thumb {
  display: block;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface-2);
  overflow: hidden;
  cursor: pointer;
  /* ⚠️ `<button>` 自带 padding / 字体 / 居中 ⇒ 必须 reset 回"一张图"的样子，
     否则缩略图会胖一圈、边框与图片之间出现缝（这类**静默外观漂移**最难发现）。 */
  font: inherit;
  line-height: 0;
}
.ea-thumb img { display: block; width: 100%; height: 100%; object-fit: cover; }
.ea-thumb:hover { border-color: var(--color-accent); }
.ea-thumb:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 1px; }
/* ⭐ 2026-10-09 非图片附件（PDF / 只有飞书文件码）的占位态 —— 画一个文件图标。
   ⚠️ 必须显式居中：`.ea-thumb` 是 `display:block` + `line-height:0`
     （为让 `<img>` 严丝合缝填满），内联 SVG 会**贴左上角**，
     看着像"图裂了一个角"（而不是"这是一份 PDF"）。
   ⚠️ 底色用 `--color-surface-2`（比内容底浅一档），与真图区分：
     同色会让占位块看起来就是"一张空白图片"。 */
.ea-thumb--file {
  display: flex;
  align-items: center;
  justify-content: center;
  line-height: normal;
  color: var(--color-muted);
}
/* ⭐⭐ 2026-10-10 **PDF 缩略图**（乐哥：「pdf 格式的能不能显示缩略图案」）
   ──────────────────────────────────────────────────────────────────────────
   ▸ 结构：`<canvas data-pdf-src>`（真图，由 `expense-attach.renderPdfThumbs()` 懒渲染）
           + `<span class="ea-thumb__ph">`（图标 + PDF 角标，**压在下面当底**）。
   ▸ 尺寸与普通缩略图**完全一致**（它就是"图还没渲染出来"，不是"另一种小图标"）。
   ▸ 渲染成功后把占位层藏掉 —— 否则文件图标会叠在真图上面。 */
.ea-thumb--pdf { position: relative; }
.ea-thumb__cv { display: block; width: 100%; height: 100%; }
.ea-thumb__ph {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  line-height: normal;
  color: var(--color-muted);
}
.ea-thumb__pdf {
  position: absolute;
  right: 1px;
  bottom: 1px;
  font-size: 8px;
  font-style: normal;
  line-height: 1.2;
  padding: 0 2px;
  border-radius: 2px;
  background: var(--color-danger);
  color: #fff;
}
/* ⚠️ 相邻兄弟选择器：canvas 后面紧跟的占位层 —— 渲染成功（`data-pdf-ok`）就藏掉 */
.ea-thumb__cv[data-pdf-ok] + .ea-thumb__ph { display: none; }
.ea-more {
  font-size: var(--font-size-xs);
  color: var(--color-muted);
  white-space: nowrap;
}
.ea-empty, .ea-none { color: var(--color-muted); }

/* 表格里的附件格：缩略图压小一号（表格行高有限，44px 会把行撑高、整表节奏变乱） */
.table .ea-cell { min-width: 96px; }
/* ⭐⭐ 2026-10-10 乐哥：「不要隐藏，展示不下就**另起一行**」
   ──────────────────────────────────────────────────────────────────────────
   ▸ 明细行的附件不再截断（`max: Infinity`，见 `finance.js` 的 `attachCell`）⇒
     一格可能要装 8~11 张缩略图。96px 的下限会让它们**一列排到底**、
     行高被拉到几百像素。
   ⇒ 报销单详情的明细表把附件列放宽（只加宽这一张表：其它表沿用 96px），
     让缩略图一行能放几张、超出的再换行 —— 这才是他说的"另起一行"。
   ⭐⭐ 2026-10-10 再改：附件**拆成两列**（发票 / 付款凭证）后每列收到 **150px**
     （原来是单列 200px ⇒ 现在两列合计 300px，比原来只宽 100px）。
     150px 一行能放 4 张 32px 缩略图 + 组内 6px 间距，装不下仍换行。
   ⚠️ 只加 `min-width`，不写 `width` —— 表格是自动布局，写死宽度会与
     其它列抢空间（实测列宽是按内容摊的，硬指定会让"说明"列被压成两行）。 */
.table .ea-thumb { width: 32px; height: 32px; }
.table .ea-set__lab { font-size: 11px; }
/* ⭐⭐ 2026-10-10 乐哥：「**一行 5 个**这样展示」
   ──────────────────────────────────────────────────────────────────────────
   ▸ 明细表里"发票 / 付款凭证"各占一列（见上面 `--inv` / `--proof` 的说明），
     这两列里的缩略图**固定排 5 个一行**、第 6 个自动换行。
   ▸ 用 `grid` 而不是 `flex-wrap`：`grid-template-columns: repeat(5, 32px)`
     是**硬性 5 列**，缩略图个数变化时列位置不会跳（flex-wrap 会按内容宽度
     凑出"有时 4 个有时 5 个"的参差）。
   ▸ 宽度算账：5 × 32 + 4 × 6(gap) = **184px** ⇒ 列下限给 200px（含 padding）。
     ⚠️ 只加宽这两列，⛔ 不动 `.table .ea-cell` 的 96px（其它表沿用）。
   ⚠️ 只作用于 `.ea-cell--inv / --proof`（**单列模式**）——
     审批台凭证弹窗是"一格里两段"的多列模式，那里带组标签 `.ea-set__lab`，
     进 grid 会占掉一格 ⇒ 不能一起改。 */
.table .ea-cell--inv .ea-set,
.table .ea-cell--proof .ea-set {
  display: grid;
  grid-template-columns: repeat(5, 32px);
  gap: 6px;
  align-items: center;
}
/* 列下限 **150 → 200px**：5 × 32 + 4 × 6(gap) = 184 ⇒ 200 刚好放得下 5 个。
   ⚠️ `.me-detail`（我的报销详情 · 乐哥那张截图就是它）原来吃的是通用 96px，
      必须一起给 —— 否则那边还是排不出 5 个。 */
.fin-doc .table .ea-cell { min-width: 200px; }
/* ⚠️⚠️ 「我的报销详情」那两列的类名**不是** `.ea-cell`，是 `me-itbl__files` ——
   只写 `.ea-cell` 会**漏掉它**（而乐哥那张截图正是这个页面）。
   （财务·报销单详情 / 提报单清单用的是 `ea-cell ea-cell--inv`，两类都有。） */
.me-detail .table .me-itbl__files { min-width: 200px; }
/* ⭐ 2026-09-24：表格这一格里**两类附件挨得太挤** —— 组内（图↔图）与组间
   （发票组↔凭证组）都还是详情页那套 4px/6px，而这里是**行内小图**、
   尺寸只有详情页的 0.7 倍 ⇒ 同样的间距在小尺寸下看着"糊成一坨"（实测截图），
   财务扫一行时要花时间分辨"哪几张是票、哪几张是证"。
   ⚠️ 只加大 `.ea-cell` 里的间距，**不动全局 `.ea-*`** —— 详情页那些大卡里的
      44px 图用 6px 间距是合适的，全局改会把那边也改挤。
   ⚠️ 组间(12px)必须明显大于组内(6px)：2 倍差距才看得出"这是两组"。
      ⭐ 2026-10-10 起两类**各占一列**（单列模式下 `.ea-wrap` 只有一个子元素），
      这条 12px 在这三张表里**已不起作用** —— 留着是为了"两类并排"的其它容器
      （审批台的凭证弹窗仍是一格里两段），别顺手删。
   ⚠️ `.ea-cell--inv` / `.ea-cell--proof` 是这两列各自的挂点（现在只用于
      提高可读性/将来单列调宽），⛔ 别拿它们去写第二条 `min-width` ——
      与上面那条**同特异性**，靠源顺序决胜，改起来会出现"改了没生效"。 */
.table .ea-cell .ea-wrap { gap: 12px; }
.table .ea-cell .ea-set { gap: 6px; }

/* ── 旧的单图样式（`.fin-pic*`）已由上面 `.ea-*` 取代 ──
   `.fin-pics` / `.fin-picbox*` 仍是**容器**（在 `finance.js` 的「发票 / 凭证」区用），
   但单个缩略图 `.fin-pic` 已无消费方（改为 `ExpenseAttach.thumbs()` 产出 `.ea-thumb`）。
   ⚠️ 保留 `.fin-pic--none`：它是"只有飞书文件码、页面渲不了图"那一种的**说明块**，
      在 `.ea-*` 里改成了"不渲染 + 点开时提示"，但本类可能被别处引用，故留着不删。 */

/* 「只有飞书文件码」时的文件占位按钮（页面渲不了图，但必须可点、有说明）
   —— 与图片缩略图同尺寸，视觉上不会让该组的排列错位。 */
.ea-thumb--file {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--color-muted);
  background: var(--color-surface-2);
  border-style: dashed;
}
.ea-thumb--file:hover { color: var(--color-accent); border-color: var(--color-accent); }

/* ⛔⛔ `.ea-view*` 整段已删（2026-10-10）—— 那是 `expense-attach.js` 里**自造画廊**的样式。
   ──────────────────────────────────────────────────────────────────────────
   删的原因不是"不好看"，而是它造成了两个真 bug（乐哥这一批报的）：
     ① 「**这个 pdf 的不能左右键切换快速查看**」—— 画廊的 ←→ 监听要求"自己是最上层弹层"，
        而 PDF 阅读器压在上面 ⇒ 它永远收不到键，阅读器当时又没有方向键处理；
     ② 「**关闭的时候，还会闪一下点击预览的那个窗口**」—— 关阅读器时先播它的退场动画
        （`_exitAnimate` 动画结束才回调 `onClose`），动画走完才关画廊 ⇒ 中间那 200ms
        画廊被露出来闪了一下。
   ⇒ 根治办法是**别叠两层**：左右切换做进 `AttachmentViewer`（它成了附件查看的唯一窗口），
     缩略图点击只开那一个。样式随之搬到 `pages.css` 的 `.av-nav` / `.av-none` /
     `.av-note` / `.av-bar__name` / `.av-bar__scope`（公共件归公共样式表）。
   ⛔ 别因为"以前有这段"就把自造画廊加回来 —— 那等于把上面两个 bug 一起加回来。
   ══════════════════════════════════════════════════════════════════════════ */

/* ══════════════════════════════════════════════════════════════════════════
   一级状态 tab 行**吸顶**
   ──────────────────────────────────────────────────────────────────────────
   ⚠️⚠️⚠️ 2026-10-09 **本段已作废，规则保留但不再命中**（乐哥：「财务中心系统ui，
      要按照设计规范做，当前的这个不对，包括tab，还有页面底色等」）
      见下方「为什么作废」。⛔ 别删 —— 里面的实测记录是本轮判断的依据。

   ▸ **为什么作废**（真机 1920×950 实测，四条连锁）：
     ① 本规则把**整个工具栏**变吸顶液态玻璃，而它当时**里面住着一级 tab**
        —— 形态与达播台基线不符（达播的 tab 在**页头行** `.page-header--has-tabs`，
        吸顶由 `layout.css` 那条 `position: sticky; top: var(--tabs-bar-height)` 承担）。
     ② tab 在工具栏里 ⇒ `components.css` 的
        `.seg.table-tabs:not(.table-tabs--in-header) { flex: 0 0 auto }`（0,3,0）
        **压过** `table.css` 的 `.toolbar--has-page-tabs > .table-tabs
        { flex-basis: 100% }`（0,2,0）⇒ **tab 不独占行，与筛选控件挤在同一行**
        （实测 tab rect.y=233、搜索框 rect.y=242）。
     ③ 同一族组件两套类名，基类 10-08 已统一托盘内距到 **3px**，
        而这条覆盖规则写死 **4px** ⇒ 全站最后一个 4px 漏网（托盘厚 38 vs 基线 36）。
     ④ 页头因此**塌成空行**：财务「报销单总览」`.page-header` 高 **0px**、
        「替票管理」24px（只剩一个「?」）；达播台是 **64px**。

     ✅ **修法（不是改 CSS）**：`app.js` 新增 `liftFinanceTabsToHeader()`
        在 `arrangeTabs` 末步把财务的一级 tab 从工具栏**搬进页头行**，
        复用基线既有的 `.page-header--has-tabs` + `.table-tabs--in-header`。
     ⇒ 搬进页头后：① tab 的吸顶由 `layout.css` 承担（同达播台）；
                  ② 它不再在工具栏内 ⇒ `.seg` 那条覆盖规则**不再命中** ⇒ ② ③ 同时消失；
                  ③ 页头有了内容 ⇒ ④ 的空行消失，高度回到 64px。
     ⚠️ 所以下面这条规则现在**没有元素命中**（财务的 tab 已不在工具栏内）；
        留着是因为它是"为什么不能回退到工具栏形态"的那份证据。

   ▸ 以下为 2026-10-08 的原始判断（当时的实测依据，仍然有效，只是落点变了）：

   ▸ **为什么挂 sticky 就能吸住**（实测依据，不是照抄）：
     财务中心的滚动容器是**达播壳层的 `.app-main`**（`overflow-y: auto`；
     实测滚动时**只有它** `scrollTop > 0`）。而 `.fin` / `.fin-body` / `.fin-main`
     全是 `overflow: visible` ⇒ 都**不是**滚动容器
     ⇒ sticky 会穿过它们、相对 `.app-main` 吸附 —— 正是我们要的。
   ⚠️ 令牌用 `--liquid-tray-bg` + `--glass-blur`（与达播 tab 吸顶态**同一套**）——
      不新造色值：玻璃全站只有一套。
   ⚠️ 选择器带 `.fin-main` 前缀 + `> ` 限定直接子级：只作用于**财务内容区的一级 tab**，
      不碰卡片内的二级胶囊（`.pill-filter` 是另一层，不该吸顶）。 */
/* ⭐⭐ 2026-10-12：淡蓝玻璃背景已由全站 `.toolbar--join-card` 兜底（乐哥拍板全站标准），
   本条**只保留 sticky 吸顶定位**——财务的筛选工具栏吸顶（其余板块吸顶的是页头 tab 栏）。
   ⛔ 别把 background/backdrop-filter 加回来：同一件事两处实现必然漂移。 */
.fin-main .toolbar--has-page-tabs:not(.toolbar--tabs-up) {
  position: sticky;
  top: 0;
  z-index: 20;
}
/* ⭐⭐⭐ 2026-10-09 第二轮：搬走 tab 之后"别再吸顶"改用**专用标记**，而不是摘类
   ────────────────────────────────────────────────────────────────────────────────
   ▸ `app.js` 的 `liftFinanceTabsToHeader` 把一级 tab 搬进页头后，会给工具栏加
     `toolbar--tabs-up` ⇒ 上面那条不命中 ⇒ 筛选行不再整条吸顶。
   ⛔⛔ **不要改回"摘掉 `toolbar--has-page-tabs`"**（第一版就是这么写的，实测出错）：
      那个类还担着布局职责 —— `table.css` 的
        `.toolbar--has-page-tabs > .table-tabs { flex: 0 0 100% }`
      负责让**部门筛选行独占一整行**（乐哥截图里那条通栏浅灰托盘）。
      摘了它以后，同一页两次进入会得到**两种宽度**（首次 1558 通栏 / 回来 158 收缩），
      因为"摘不摘"取决于那一刻工具栏里还有没有 `.table-tabs` —— 时序相关、结果不稳定。
   ⚠️ 保留这个类**不会**带来别的副作用吗？会有一个：`fixPageTabs` 的 `skipSeg` 判据
      把"本页已有页面级 tab"当成"卡内出现的 seg 只能是筛选条件" —— 那正是财务要的语义
      （且 tab 确实还在本屏，只是住到了页头）。所以留着是对的。
   ⚠️ 万一将来 tab 真被搬回工具栏形态（去掉 `toolbar--tabs-up`），上面那条会重新生效
      —— 两处是一对，别单删。 */

/* ══════════════════════════════════════════════════════════════════════════
   ⭐⭐⭐ 2026-10-09 财务页头的**一级 tab 必须靠左**（乐哥：「tab栏的悬浮不对」）
   ──────────────────────────────────────────────────────────────────────────
   🐞 症状（真机 1920×620 实测）：财务的一级 tab 跑到**页面最右边** ——
      `tab 位置 x=1481`（页头右缘 ≈1690），左边空一大片，只剩一个「?」孤零零在最左。
      达播台同位置是 **x=283（紧贴左缘）**。

   ▸ 根因：`.page-header` 是 `justify-content: space-between`
      （那是为「**只有两个元素**：标题左 + 按钮右」设计的）。
      达播台的页头靠 `.page-header--filter-bar`（`justify-content: flex-start` + `column-gap: 24`）
      把元素**靠左成组**，那个类由 `app.js` 的 `applyFilterBarLayout()` 打上 ——
      **而它对财务早退**：该函数第一句就是
        `var acts = ph.querySelector('.page-actions'); if (!acts) return;`
      财务页头**没有 `.page-actions`**（它的页面级控件只有 tab + 自造的 `.fin-help`）
      ⇒ 拿不到 `--filter-bar` ⇒ 退回 `space-between` ⇒ tab 被推到最右。

   ▸ 为什么**不**去给财务补 `.page-actions`（那能让 `applyFilterBarLayout` 不再早退）：
      那个函数接下来会把 `.input--search` 与 `.mp-host`/`.dtp-trigger` **搬进页头**
      （把筛选控件上浮到吸顶卡，达播台就这么做的）。
      财务「报销单总览」有搜索框 ⇒ 会被一并搬上来、页头形态大变 ——
      **那超出乐哥本次的诉求**（他只说 tab 悬浮不对）。
      ⇒ 本轮只补**布局**（让元素靠左成组），不触发那套搬运。

   ▸ 值必须与 `.page-header--filter-bar` **同值**（`flex-start` / `column-gap: 24px`），
      否则两套形态又分叉。
   ⚠️ 用 `column-gap` 而非 shorthand `gap`：`gap: var(...)` 在 CSSOM 里会被短路成
      空的 `column-gap`，而 `table.css` 有一条 `.page-header--has-tabs { gap: 16px }`
      （**table.css 在 layout.css 之后加载**，同特异性后者胜）⇒ 写 `gap` 会看不出问题、
      调试时也读不到值（layout.css 里那条 `--filter-bar` 的注释记过这个坑）。
   ⚠️ 特异性 (0,3,0) —— 高于 `.page-header`(0,1,0) 与 `--filter-bar`(0,2,0)，
      且与后者**同值** ⇒ 将来若财务真的拿到了 `--filter-bar`，本条不产生冲突。
   ⚠️ 只在 ≥769px 生效：与 `--filter-bar` 的媒体查询**同域**（手机端页头空间紧张，
      保持原有布局，避免换行把吸顶卡撑厚 —— 那条的注释记过：390 视口下
      页头卡会从 124px 涨到 220px，而吸顶卡是常驻的、等于永久少一屏）。 */
@media (min-width: 769px) {
  .app-shell.is-finance .page-header--has-tabs {
    justify-content: flex-start;
    column-gap: var(--space-6);
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   ⭐⭐ 2026-10-21 · 财务四页展示统一（乐哥：「我其实需要的是 UI，还有展示上的统一」）
   规范正文见 docs/财务四页展示规范-20261021.md，这里只落样式。

   背景（四页实测的 8 处不一致，见 out/财务四页展示统一-对比.html）：
     标签色板 4 种写法（#FEF3E2 / #FEF5E7 / #E4F4F7 / #EAF6EF 各自硬编码）、
     KPI 有的页有有的没有、表格密度不一、口径条四页四样。
   ⇒ 本节把「标签语义色」收敛成 5 个类，全部走 design-tokens，**页面里不再写 hex**。
   ⚠️ 特异性必须是「.tag.tag--x」（0,2,0）：压过 `.tag` 基类（0,1,0）。
      写裸 `.tag--x` 与 `.tag` 同特异性，靠源顺序决胜，换文件顺序就翻车
      （components.css 里 `.tag.comm-src--*` 的注释记过同一条）。
   ⚠️ 只覆盖**配色**，形状全部交给 `.tag` / `.tag--sm` —— 以后标签改形状这里自动跟随。
   ══════════════════════════════════════════════════════════════════════════ */
.tag.tag--caliber { color: var(--color-tone-orange-fg); background: var(--color-tone-orange-bg); }
.tag.tag--state   { color: var(--color-tone-teal-fg);   background: var(--color-tone-teal-bg); }
.tag.tag--channel { color: var(--color-success);        background: var(--color-success-bg); }
.tag.tag--risk    { color: var(--color-danger);         background: var(--color-danger-bg); }
.tag.tag--kind    { color: var(--color-tone-purple-fg); background: var(--color-tone-purple-bg); }


/* ==== /css/pages-kq.css ==== */
/* ==========================================================================
   人事行政管理板块（/hr）—— pages-kq.css
   2026-09-28 建立 · 2026-09-29 按 DESIGN-SPEC 整改 · 2026-09-29 晚 **内容页全面重做**
   --------------------------------------------------------------------------
   与财务中心同形态：独立外壳 + 自带侧栏 + 显示标签栏。

   ⭐⭐ 2026-09-29 晚重做（乐哥：「hr 系统，内容页 ui 完全不对，需要按照设计规范完全重做」）
   ─────────────────────────────────────────────────────────────────────────────
   上一轮（同日上午）改对了三件事：表格换 `.table`、8 个未定义令牌映射到真实值、
   控件统一到两档制。但乐哥看完**仍然说不对** —— 因为那一轮修的是"零件"，
   而 **页面骨架**整体不一样：hr 少了一整层「卡片」。
   本轮 = 把 5 个内容页的骨架按全站列表页**逐层重写**（结构见 `kq.js` 顶部对照表）。

   ⭐ 本文件随之**大幅瘦身**：凡公共件已提供的规格一律删除，只留三类 ——
     ① **板块壳**（侧栏/标签栏/内容让位三件套）；② **本板块独有的东西**（趋势柱状条、
        照片预览、手写弹窗、弹窗内紧凑表单）；③ **页面竖向节奏**（KPI 行 → 卡片 → 工具栏）。

   ── 本轮删掉的自造样式（都有公共件，留着就是"第二处实现"）────────────────────
     `.kq-chips` / `.kq-chip*`      → 全站 `.kpi-row` + `.kpi-card`（14 个页面在用）
     `.kq-toolbar`                 → 全站 `.toolbar`（含控件紧凑档兜底）
     `.kq-table-wrap`              → 全站 `.table-wrap`
     `.kq-table-meta`（"共 N 条"） → `.table-toolbar__right` + `.table-toolbar__total`
     `.kq-in-sm` / `.kq-in-md`     → 宽度内联（与付款台账/达人管理同写法）
     `.kq-desc`（正文里那段说明）   → 页头「?」面板（`.fin-help`，财务中心同款）
     `.kq-dry`                     → `.badge--warning`
     `.kq-page .table ...:first-child` 那两条 20px 归零 → **删**（见下方块尾说明）
     `.kq-table-meta` 之外，原先"表格首末列不需要公共 20px 留白"的那组规则也删了 ——
       那条的前提是"表格**不在卡片里**"。现在表格装进 `.card`，首末列那 20px 正是
       用来与卡片内工具栏文字对齐的（table.css 里的原始用途），归零反而是错的。

   ⚠️ 保留的三个**板块既定取舍**（改之前先读，别当成"不合规"又改一遍）：
     ① 弹窗**保留手写**（`.kq-mask` / `.kq-modal`，460px 小表单）—— 不用 `UI.openModal`
        是因为那套默认 880px + `min-height: 420px`，会把这个小表单撑成一大块空板；
        但**规格与动效全部对齐公共弹窗**（`--radius-xl` / `--space-4 var(--space-6)` /
        `--shadow-raised` / `modal-in` + `--motion-slow` + sheet 曲线）。
     ② 弹窗内表单标签宽 **62px**（不用整页表单的 112px）：弹窗总宽只有 460px，
        112px 会吃掉四分之一 —— 属「弹窗内紧凑表单」量级。
     ③ 趋势柱状条**保留自造**（`.kq-trend__bar`）：它不是 Chart.js 图表（只有几根柱、
        没有轴与图例），套 `.chart-card`（`min-height: 240px`）会占掉半屏。
        但已按规范装进 `.card` + `.card__header` 图头 + 基线。
   ========================================================================== */

/* ══ ① 板块壳 ══════════════════════════════════════════════════════════════
   三条都不能少（2026-09-28 真机踩到，侧栏整块不显示）：
     ① **必须带 `!important`** —— 项目那条隐藏规则是
        `.app-shell.is-standalone .sidebar { display: none !important; }`，
        `!important` 的优先级**高于普通声明**，即便我们选择器特异性更高也照样输
        （特许性只在"都没有 !important / 都有 !important"时才比）。
        finance / admin / portal 三个板块都写了 `!important`，hr 一开始漏了 ⇒ 侧栏 display:none。
     ② **选择器要带 `.app-shell` 前缀** —— 与那三个板块保持同一份写法，
        避免将来有人调隐藏规则时两边不同步。
     ③ `.page-tabs-bar` 同理（财务**保留**标签栏是乐哥明确要求的，门户才不显示；hr 与财务同档）。 */
.app-shell.is-standalone.is-hr .sidebar { display: flex !important; }
.app-shell.is-standalone.is-hr .page-tabs-bar { display: flex !important; }

/* ⚠️⚠️ `.app-main` 让位（2026-09-29 补，与 finance/admin/portal **逐条同构**）
   ──────────────────────────────────────────────────────────────────────────
   🐞 真机实测（1920 视口）：只显示侧栏、不还内容左边距 ⇒ 内容左缘 ≈200px，
      被侧栏（右缘 254px）**压住约 54px** —— KPI 第一片、筛选行首、表格「姓名」列全部被裁。
   根因：`.app-shell.is-standalone .app-main { margin-left: 0; width: 100% }`
      的前提是"独立外壳没有侧栏"；上面强制显示了侧栏，这两条就必须**还回去**。
   ⚠️ 手机端必须显式收回（@media 不改变特异性 ⇒ 桌面 240px 会带进手机，
      390 视口只剩 150px 宽 —— 财务板块 2026-09-21 真踩过）。
   ⚠️⚠️ 2026-10-09：`margin-left` **补 `--shell-inset`**（本轮第 5 处同族修复）
      —— 壳层定稿（2026-10-07「显示屏浮在底色上」）要求中缝 = `--shell-inset`(8)，
      只写 `var(--sidebar-width)` ⇒ **缝 = 0**，侧栏读成"内容左边另一块板"而不是底色。
      实测（1920×950 真机）：人事行政中缝 0，达播台 8。 */
.app-shell.is-standalone.is-hr .app-main {
  margin-left: calc(var(--sidebar-width) + var(--shell-inset));
  width: auto;
}
@media (max-width: 768px) {
  .app-shell.is-standalone.is-hr .app-main {
    margin-left: 0;
    width: auto;
  }
}
/* ⚠️⚠️ 2026-09-29 **删除**了一条错误的覆盖（乐哥：「全部一起拉平」时查出）
   原写法：`.app-shell.is-standalone.is-hr .page { padding-top: var(--page-padding); }`
   注释理由是「有标签栏 ⇒ 与财务同档」，但**两个前提都不成立**：
     · 财务的 `.page` 内边距被 `:has(> .fin) { padding: 0 }` 清零了，它内容区的留白
       在 `.fin-main` 上 —— 所以"与财务同档"这个参照对象本身不存在；
     · 独立外壳的通用默认（`pages.css` 的 `.app-shell.is-standalone .page`）
       就是 `--space-4`(16px)，**与达播台一致**，并非"无标签栏专用"。
   实测后果：hr 顶部留白 32px，而达播台 16px ⇒ 同一屏切过去内容整体下移 16px。
   ⇒ 删掉本覆盖，让 hr 走通用默认 16px（与达播台 / 财务 / 门户一致）。 */

/* ══ ② 页面竖向节奏 ════════════════════════════════════════════════════════
   骨架：`.page-header` → KPI 行 →（趋势卡）→ 筛选栏 + 表格卡。
   按全站"卡片间距 16px"的口径排（components.css：内 24 / 间 16）。

   ⚠️ 为什么在 CSS 里写、不用内联 `style="margin-bottom:…"`：
      components.css 顶部已明确「若以后新增卡片，直接在 CSS 里排版式（别再用内联
      margin-bottom）」—— 内联那 27 处是历史债，新的不该再加。 */
.kq-page .kpi-row { margin-bottom: var(--space-4); }
/* ⚠️ `.kpi-row` 的默认下边距是 `--space-6`(24)；本板块收到 16 与卡片口径一致。
   ⚠️ 必须显式写这一条：新页面把 KPI 放进 `.kq-page` 就自动对，不依赖谁记得加类。 */
.kq-page .card + .toolbar { margin-top: var(--space-4); }

/* ══ ③ 列表内的小字（公共件没有的小东西）═══════════════════════════════════ */
.kq-muted { font-size: var(--font-size-xs); color: var(--color-muted); }
.kq-hidden-file { display: none; }

/* 分页容器（`UI.pagination` 往这里画）。
   ⚠️ `:empty` 兜底：空数据时 `listCardHtml` 根本不会渲染它；但万一将来有人在
      "有数据但没挂分页"的分支里留下空容器，一个空的 div 会在卡片底部多出 0 高度占位。 */
.kq-pager:empty { display: none; }

/* ══ ④ 每日打卡量（趋势柱状条）═════════════════════════════════════════════
   ⚠️ 改前：一根 36px 高的**实心蓝带**裸放在页面底上（柱宽 208px、只有顶部圆角、
      没有底座、没有卡片）—— 看着像"一条蓝色横条"，不像图表。
   现在：装进 `.card` + `.card__header` 图头 + `.card__body`，柱下方加一条**基线**，
      柱才有"站在轴上"的读法；柱高由数据算（%），hover 提亮。
   ⚠️ 柱高用百分比：`align-items: flex-end` + 容器有确定高度 ⇒ `height:%` 才生效
      （写 `px` 需要 JS 回去测容器高，等于把布局知识搬进 JS）。
   ⚠️ 只说"图形高度"（`--space-12` = 48px）：这不是间距，但走令牌才能跟着全站一起调。 */
.kq-trend > .card__body { padding-top: 0; }
.kq-trend__meta { font-size: var(--font-size-xs); color: var(--color-muted); }
.kq-trend__bars {
  display: flex;
  align-items: flex-end;
  gap: var(--space-1);
  height: var(--space-12);
  /* 基线：柱"站在"这条线上（没有它，柱像是浮在卡片里） */
  border-bottom: 1px solid var(--color-border-soft);
}
.kq-trend__bar {
  flex: 1 1 0;
  min-width: 1px;
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  background: var(--color-accent);
  opacity: 0.72;
  /* 颜色/透明度类 ⇒ 必须用 `--motion-ease-standard`（无过冲），别用 `--motion-ease` */
  transition: opacity var(--motion-fast) var(--motion-ease-standard);
}
.kq-trend__bar:hover { opacity: 1; }

/* ══ ⑤ 弹窗（员工档案 新增/编辑）═══════════════════════════════════════════
   ⚠️ 本弹窗是**手写的**（见文件顶部的既定取舍①），不会自动继承公共弹窗的动效，
      必须显式对齐：
        遮罩  fade-in  / fade-out，`--motion-slow` + `--motion-ease-sheet`
        弹窗  modal-in / modal-out，同上；退场 ×0.62（与全站同一条比例）
   ⚠️ 退场必须由 JS 打 `data-closing` 触发（`kq.js` 走 `UI.exitAnimate` 统一执行器）——
      直接 `innerHTML = ''` 会把刚加上的退场动画一起抹掉（等于没加）。
   ⚠️ 圆角/内边距/阴影一律取规范弹窗同一套令牌：`--radius-xl` / `--space-4 var(--space-6)` /
      `--shadow-raised`。 */
.kq-mask {
  position: fixed; inset: 0; z-index: var(--z-modal);
  background: color-mix(in srgb, var(--color-gray-900) 45%, transparent);
  display: flex; align-items: center; justify-content: center;
  animation: fade-in var(--motion-slow) var(--motion-ease-sheet) both;
}
/* 退场：遮罩与弹窗**同时**退、用同一条时长（错开会出现"内容没了、黑底还在"的瞬间） */
.kq-mask[data-closing] {
  animation: fade-out calc(var(--motion-slow) * 0.62) var(--motion-ease-exit) both;
}
.kq-modal {
  width: 460px; max-width: calc(100vw - var(--space-8));
  background: var(--color-surface);
  border-radius: var(--radius-xl);
  overflow: hidden;
  box-shadow: var(--shadow-raised);
  animation: modal-in var(--motion-slow) var(--motion-ease-sheet) both;
}
.kq-modal[data-closing] {
  animation: modal-out calc(var(--motion-slow) * 0.62) var(--motion-ease-exit) both;
  pointer-events: none;   /* 正在消失时不再接受点击，避免"点了两次" */
}
.kq-modal__head {
  padding: var(--space-4) var(--space-6);
  font-size: var(--font-size-lg);
  font-weight: var(--font-weight-medium);
  color: var(--color-fg);
  border-bottom: 1px solid var(--color-border-soft);
}
.kq-modal__body { padding: var(--space-5) var(--space-6); }
.kq-modal__foot {
  padding: var(--space-4) var(--space-6);
  display: flex; justify-content: flex-end; gap: var(--space-3);
  border-top: 1px solid var(--color-border-soft);
}

/* ── 表单字段（弹窗内）──
   ⚠️ 这里**不自造控件尺寸**：输入框 / 下拉已有 `.input` / `.select` 公共类
      ⇒ 自动是标准档 44px + 规范圆角 + 聚焦光晕。
      本类只负责**布局**（标签与控件同行、控件撑满剩余宽度）。 */
.kq-field {
  display: flex; align-items: center; gap: var(--space-3);
  margin-bottom: var(--space-3);
}
/* ⚠️ 标签宽 62px 是刻意的（见文件顶部取舍②）：整页表单是 112px，但那是整页的量级；
   这里弹窗总宽只有 460px，112px 会吃掉四分之一。 */
.kq-field > span { flex: 0 0 62px; font-size: var(--font-size-sm); color: var(--color-muted); }
.kq-field .input,
.kq-field .select { flex: 1 1 auto; }
.kq-field > span i { color: var(--color-danger); font-style: normal; }
.kq-field--col { display: block; }
.kq-field--col > span { display: block; margin-bottom: var(--space-2); }

/* ── 照片上传区（2026-10-10 二改：乐哥「上传照片，能不能不要放侧面，然后那么小一点，重心偏左」）──
   改前：左边一个 88px 虚线方块、右边「正脸清晰照 + 两行说明」 ⇒
         ① **太小**（88px，手机上一屏里就一小块）；
         ② **重心偏左**（左有块右只有字，视觉重心整个歪在左边）。
   ✅ 现在：**整行一块**（宽 100%，比例 `5 / 2`），图标 + 「拍照 / 选图」**居中**；说明文字移到**下方居中**。
        重心回到中间、可点面积也大了好几倍（这是手机上最该好点的东西）。
   ⚠️ 高度用 `aspect-ratio` 而不是写死 px：**随宽度自适应**（手机 309→124、桌面 510→204），
      既不用进裸 px 白名单，也不会在两种宽度下一高一矮。
   ⚠️ 它是 `<button>` ⇒ 必须把浏览器默认样式压平（padding / font / background），
      否则会带出按钮的灰底与内边距、和虚线框打架。 */
.kq-photo-row { display: block; }   /* ⛔ 不再左右分栏（那正是"重心偏左"的来源） */
.kq-photo-preview {
  width: 100%;
  aspect-ratio: 5 / 2;
  border-radius: var(--radius-md);
  border: 1px dashed var(--color-border);
  display: flex; align-items: center; justify-content: center;
  font-size: var(--font-size-xs); color: var(--color-muted);
  overflow: hidden;
  /* ↓ 压平 button 默认样式（见上） */
  padding: 0;
  font-family: inherit;
  text-align: center;
  background: var(--color-surface);
  cursor: pointer;
  transition: border-color var(--motion-fast) var(--motion-ease-standard),
              color var(--motion-fast) var(--motion-ease-standard);
}
/* ⚠️ 令牌名要对：本项目主色是 `--color-primary-400~600`（**没有** `--color-primary` 这个裸名，
   门禁 ⑥ 会抓"引用了全站未定义的令牌"）。描边用 400、文字用 `--color-accent-text`。 */
.kq-photo-preview:hover { border-color: var(--color-primary-400); color: var(--color-accent-text); }
.kq-photo-preview--img { border-style: solid; }
/* 空态：相机图标 + 「拍照 / 选图」竖排（整块居中） */
.kq-photo-empty { display: flex; flex-direction: column; align-items: center; gap: var(--space-2); }
/* ⚠️ 字号显式给 `--font-size-sm`：它继承的是父级的 `--font-size-xs`，
   手机上被小屏令牌压到 **11px**（实测），比旁边所有字都小、糊成一团。 */
.kq-photo-empty em { font-style: normal; font-size: var(--font-size-sm); }
/* 相机图标用主色：整块是**可点的**，图标先给出"这里能操作"的信号（文字仍留灰，不抢）。 */
.kq-photo-empty svg { color: var(--color-primary-500); }
/* 选好照片后：**正方形预览居中**（不铺满整块 —— 整块是 5:2，铺满会把脸裁掉）。 */
.kq-photo-empty--filled { display: flex; width: 100%; height: 100%; align-items: center; justify-content: center; }
.kq-photo-empty--filled img { height: 100%; width: auto; aspect-ratio: 1 / 1; object-fit: cover; }
.kq-photo-preview--img .kq-photo-empty--filled img { border-radius: var(--radius-sm); }
/* 说明文字：**整行块的下方、居中**（跟着图标走，回到中轴线上）
   ⚠️ 只留一层说明 —— label 已经是「照片（门禁刷脸用）」，再写标题就是同一句话出现三次
      （label / 块内「拍照 · 选图」/ 块下），2026-10-10 二改时把 `.kq-photo-title` 那层去掉了。 */
.kq-photo-side { margin-top: var(--space-3); text-align: center; }

/* 手机号「已自动带入」的说明（只在**建档**且他有号时出现）。
   ⚠️ 必须是**独立一整行**（在 `.kq-field` 外面）—— `.kq-field` 是 flex 行布局，
      放进去会被挤成窄条在输入框右侧折行。 */
.kq-phone-note { margin: 0 0 var(--space-3); font-size: var(--font-size-xs); }

/* 「上次同步到门禁」的说明（编辑弹窗顶部，2026-10-10）。
   ⚠️ 位置就在这里、**不放姓名列**：乐哥说「没必要一直放在名字旁边吧」——
      它是一个"档案状态"，属于打开编辑才关心的事（而且已知有一类是改不了的平台限制）。
   ⚠️ 同样是独立整行（`.kq-field` 之外）。用最浅的警示底，是"说明"不是"报错"。 */
.kq-sync-note {
  margin-bottom: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-warning-border);
  border-radius: var(--radius-md);
  background: var(--color-warning-bg-soft);
  color: var(--color-warning-text);
  font-size: var(--font-size-xs);
  line-height: 1.6;
}

/* ── 「点整行看某人门禁明细」（2026-10-10 乐哥：「点击某一天某个人…」「点击某一个人…」）──
   ⚠️ 项目里没有"可点行"的公共件（达播台那两处是内联 `style="cursor:pointer"`）。
      本板块不写内联 style —— 那会绕过 `tests/hr-ui-compliance` 的统一检查，
      所以在 kq 命名空间下建这一个类（与 .kq-muted 等同族）。 */
.kq-row-click { cursor: pointer; }
.kq-row-click:hover > td { background: var(--color-surface-2); }

/* 地点列里的「+手机」小标：这天的地点取自**门禁**，但同时还存在手机打卡。 */
.kq-loc-tag { margin-left: var(--space-1); font-size: var(--font-size-xs); color: var(--color-muted); }

/* ── HR 列表里的姓名 chip：比达播台那套**大一号**（2026-10-10 乐哥）──
   乐哥原话：「名字和头像那个可以整体大一点，文字和其他的文字保持一样大」。

   ⚠️ 达播台那套（`pages-influencer.css` 的 `.bd-cell-*`）是**长列表用的紧凑规格**：
      头像 20px、字号 `xs`(12px) —— 比表格正文（`sm` 14px）小一号。
      HR 这边一行信息少、可读性优先 ⇒ 在**本板块内**覆盖两处：
        ① 字号提到 `--font-size-sm`（= 表格正文，与其他列一样大）；
        ② 头像用 **em** 放大到 1.8em（≈25px）——
           用 em 而不是写死 px，字号将来再调时头像自动跟着变，
           也无需进 `hr-ui-compliance` 的裸 px 白名单。
   ⛔ 只覆盖，**绝不改 `pages-influencer.css`** —— 那会把达播台的达人列表一起改掉。
   ⚠️ 选择器必须带 `.kq-page` 前缀（提高特异性 + 限定范围），否则全局生效。 */
.kq-page .bd-cell-chip {
  font-size: var(--font-size-sm);
  gap: var(--space-2);
}
.kq-page .bd-cell-avatar {
  /* ⚠️⚠️ `em` 是相对**本元素自身**的字号算的，而达播台给头像单独设了 `font-size: xs`(12px)
     ⇒ 不先把字号提上来，`1.8em` 只会算出 21.6px（改了跟没改一样，首版就栽在这儿）。
     所以必须**先设字号**，再让宽高按 em 走。顺便首字也跟着变大，和名字同号、更协调。 */
  font-size: var(--font-size-sm);
  width: 1.8em;
  height: 1.8em;
}

/* ── 人员页「状态筛选」胶囊 tab（2026-10-11 乐哥：「这个 tab 做成状态筛选」）────────────
   ⭐ 只写**容器**这三条：胶囊本体复用公共件 `.pill-filter__item`（高度 / 内边距 / 选中品牌渐变
      全部来自 components.css）⇒ 将来公共件调色，这里自动跟随，不必改两处。
   ⛔⛔ **容器类名刻意用 `.kq-pp-tabs`（板块前缀），不借 `.pill-filter`** ——
      框架的 `fixPageTabs` / `fixTableTabs` / `fixBareSeg` **按类名搬 DOM / 加类**：
      真机实测（借 `.pill-filter` 那版）它被加上 `table-tabs` + `flex: 0 0 100%`
      ⇒ **占满整行**、把「新增人员」按钮挤到第二行（量到 `pfFlex: "0 0 100%"`）。
      同款先例：`finance.js` 的部门多选刻意用中性前缀 `.fin-ms`，注释写明"不复用 pill-filter/seg"。
   ⚠️ 托盘内距用 `--space-1`(4) 而非 `.pill-filter` 的 3px：差 1px 肉眼无差，
      但写 3px 会撞 `hr-ui-compliance ⑤`（裸 px 白名单里没有这一条，也不值得为 1px 去扩）。 */
.kq-pp-tabs {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-1);
  background: var(--tab-tray-bg);
  border-radius: var(--radius-md);
}

/* ── HR 侧栏（2026-10-10 建组 / 2026-10-11 两次对齐规范）────────────────────────
   ⭐ DOM 与样式**全部复用主站**（`.nav-group[data-domain]` / `.nav-group-label` /
   `.nav-subitems` / `.nav-item--sub` / `.nav-group-icon`，全在 base.css）
   ⇒ 本板块**已经没有侧栏专属覆盖了**，这一块只留说明。
   ⚠️ 这里曾有一条 `.sidebar .kq-nav-group .nav-item--sub .nav-icon { display:inline-flex }`
      （2026-10-11 上午：放行二级项图标，因为乐哥 10-10 说过「该做的图标也坐上去」）。
      当天下午乐哥改口「**二级我们不需要图标的**」⇒ 二级项已不带 `.nav-icon`，
      **那条覆盖随之删除**（留着就是死规则，还会误导下一个人以为二级该有图标）。
   ⚠️ 「不做折叠」那两条（`cursor:default` + hover 不变色）也已在同日**上移到 base.css**
      的 `.nav-group-label--static` —— admin / workbench / HR 三处共用一处定义。 */


/* ── 快捷建档页（手机端为主，飞书快捷入口直达）2026-10-10 乐哥 ────────────────
   直达网址 `https://hr.gz-starmedia.com/hr#/quick-add`（配到飞书工作台快捷入口）。
   场景：招人 / 入职时当场在手机上录入 —— 拍张正脸照 + 填姓名手机号，保存即同步门禁。

   ⭐⭐ 2026-10-10 重做（乐哥：「这个 ui 太丑了，手机端怎么做的这个 b 样」）。
   真机实测（375×812）把"丑"拆成三条可量的事实，逐条修：
     ① **输入框只有 172px 宽**（卡片 343px）—— 只占一半，右边一大块空，placeholder
        被拦腰切断（"11 位（与飞书一致，考…"）。
        根因：`.input` 全站**不设宽度**，靠父级 `flex: 1` 撑满（见 `.kq-field .input`）；
              本页用的是 `display: block` 的竖排字段（手机上更好填），
              input 于是退回**浏览器默认宽**（≈172px）。⇒ 必须自己写 `width: 100%`。
     ② **卡片内边距 0** —— 内容贴着卡片边框，右边空一大块。
        `.card` 本身不带内边距（全站由 `.card__body` / 内联提供）⇒ 自建卡片要自己给。
     ③ **顶部一条空白页头** —— 本页走统一 `shell()`，而 `.page-title` / `.fin-help`
        被全站 `display: none` ⇒ 页头高度 0、只剩 24px 外边距 = 一条白带。
        ⇒ 这一页的页头**直接隐藏**（它本来就没有可见内容）。

   ⚠️ `max-width: 560px` 是"表单可读宽度"（桌面上别铺满整屏；手机上自然是 100%）——
      已登记进 `hr-ui-compliance` 的裸 px 白名单并写了理由（同 `.kq-photo-preview` 的 5/2 比例）。 */
/* ③ 本页页头是空壳（标题与「?」都被全站隐藏）⇒ 藏着，别留一条白带 */
.kq-page:has(.kq-quick) .page-header { display: none; }

.kq-quick { max-width: 560px; margin: 0 auto; }
.kq-quick__head { margin-bottom: var(--space-5); }
.kq-quick__title { font-size: var(--font-size-xl); margin-bottom: var(--space-2); }
.kq-quick__desc { color: var(--color-muted); font-size: var(--font-size-sm); }
/* ② 卡片内边距（`.card` 自身不带 —— 见上方 ②） */
.kq-quick__card { padding: var(--space-6); }
/* 竖排字段（标签在上）—— 手机上是最好填的形态。
   ⛔ **不复用 `.kq-field`**：那套是"标签在左 62px"的紧凑布局（给弹窗省宽用的），
      手机上会把输入框挤得只剩一小条。 */
.kq-qfield { display: block; margin-bottom: var(--space-5); }
.kq-qfield > span { display: block; margin-bottom: var(--space-2); font-size: var(--font-size-sm); color: var(--color-fg-2); }
.kq-qfield > span i { color: var(--color-danger); font-style: normal; }
/* ⭐ ① 根因在此：竖排字段里的 input 必须自己撑满（`.kq-field` 那边是靠 flex 撑的） */
.kq-qfield .input { width: 100%; }
.kq-quick__save { width: 100%; margin-top: var(--space-2); }

@media (max-width: 768px) {
  /* ⭐ 手机宽度下**把侧栏收掉** —— 否则 375px 的屏被 224px 侧栏吃掉一大半，表单只剩 150px。
     ⚠️ 用 `:has()` **自限定在这一页**（先例：`layout.css` 的 `:has(.page-help-btn)`）——
        这样**完全不用改 `app.js`**（那边有并行会话在动，能不动就不动）。 */
  .app-shell.is-hr:has(.kq-quick) .sidebar { display: none !important; }
  .app-shell.is-hr:has(.kq-quick) .app-main { margin-left: 0; }
  .app-shell.is-hr:has(.kq-quick) .kq-quick { max-width: 100%; }

  /* 手机上收一档内边距：375px 屏减去两侧 16 + 卡片 24×2 只剩 295px 可填，太挤。 */
  .kq-quick__head { margin-bottom: var(--space-4); }
  .kq-quick__card { padding: var(--space-4); }
  .kq-qfield { margin-bottom: var(--space-4); }
  /* ⭐ 输入框与保存按钮的字号提到 16px：**iOS 上 <16px 的输入框一聚焦就把整页放大**
     （用户还得手动缩回去），这是移动端的硬约束，不是为了"好看"。
     桌面端保持全站 14px 不动 —— 所以只在手机断点里写。 */
  .kq-quick .input,
  .kq-quick__save { font-size: 16px; }
}

/* ── 门禁监控页（2026-10-11 乐哥：「门禁不正常就推消息…识别依据我维护」）──────────
   ⚠️ `.card` 自身不带内边距（全站由 `.card__body`/内联提供）⇒ 自建卡片必须自己给 padding。
   ⚠️ 尺寸一律用 `em`（跟随字号）—— 免得为几个宽度去扩 `hr-ui-compliance` 的裸 px 白名单；
      分隔线用 `border-top: 1px`（那条已在白名单里），**不用 `height: 1px`**（会被抓）。 */
.kq-door__card { padding: var(--space-5); }
.kq-door > div + div { margin-top: var(--space-4); }
.kq-door__hd {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--space-3); margin-bottom: var(--space-3); flex-wrap: wrap;
}
.kq-door__title { font-size: var(--font-size-lg); }
.kq-door__switch {
  display: inline-flex; align-items: center; gap: var(--space-2);
  font-size: var(--font-size-sm); margin-bottom: var(--space-4);
}
.kq-door__row { display: flex; flex-wrap: wrap; gap: var(--space-3) var(--space-6); align-items: center; }
.kq-door__field {
  display: inline-flex; align-items: center; gap: var(--space-2);
  font-size: var(--font-size-sm); color: var(--color-fg-2);
}
.kq-door__field .input { width: 5em; }
.kq-door__field .select { width: 8em; }
.kq-door__field--wide { flex: 1 1 100%; }
.kq-door__field--wide .input { width: 100%; }
.kq-door__note {
  margin-top: var(--space-3); font-size: var(--font-size-xs);
  color: var(--color-muted); line-height: 1.9;
}
/* ⏳ 还做不到的两条：用警示色标出来，别让人以为"没生效=坏了" */
.kq-door__todo { color: var(--color-warning-text); }
.kq-door__sep { border-top: 1px solid var(--color-border-soft); margin: var(--space-5) 0; }
.kq-door__actions {
  display: flex; align-items: center; gap: var(--space-3);
  margin-top: var(--space-4); flex-wrap: wrap;
}

/* 门禁明细抽屉顶部的摘要行 */
.kq-ev-sum { margin-bottom: var(--space-3); font-size: var(--font-size-sm); color: var(--color-fg); }

/* ── 人员排除页：弹窗里的"排除对象"与"后果提示" ──────────────────────────
   ⚠️ 两个都必须**留在弹窗里**（不是收进「?」）：
      · 排除对象 = 操作对象，点错人就是删错人的考勤（必须眼看确认）
      · 后果说明 = 这一步**会删数据**，动手前必须看到"会删卡、门禁不受影响"
      按小字注释分级（技能 dabotai-note-tip-collapse）属"① 必要信息 → 留在页面上"那一档。 */
.kq-ex-target {
  margin-bottom: var(--space-3);
  font-size: var(--font-size-sm);
  color: var(--color-fg);
}
.kq-ex-tip {
  margin-top: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  background: var(--notice-bg);
  color: var(--notice-fg);
  font-size: var(--font-size-xs);
  line-height: 1.7;
}
.kq-ex-tip b { font-weight: var(--font-weight-medium); }
.kq-photo-tag { font-size: var(--font-size-xs); color: var(--color-accent-text); }

/* 「门禁已有」—— 这是个**正常**状态（人脸在门禁里，只是本系统没有图），
   所以不跟"未上传"一样用弱化的灰，改用主文字色：三级递进一眼可辨
   （门禁已有=实、已上传=主色、未上传=灰）。见 kq.js#photoCell 的实测说明。 */
.kq-photo-tag--linked { color: var(--color-fg); }

/* 档案 → 门禁「同步失败 / 部分失败」的标记（2026-10-10 乐哥报「改名没生效」——
   其实任务执行了、是优倍快拒了（绑了 UI 云账号的用户姓名改不了），
   但**页面上没地方显示原因**，只能来问。⇒ 必须显眼地标出来 + 悬停给原因。
   ⚠️ 2026-10-10 晚改用**公共徽标** `.badge.badge--danger`（原来这里有个自造的 `.kq-sync-warn`
      裸红字）—— 理由是它跟着 chip 后面像个尾巴、位置不稳；换成徽标后与「考勤状态」列同款，
      既不抢眼又不会漏看。类已删，别再引用。 */

/* 姓名里的头像/名字 chip —— 复用达播台的 `.bd-cell-chip / .bd-cell-avatar / .bd-cell-name`
   （定义在 pages-influencer.css；本项目所有板块共用同一个 index.html ⇒ 这边也能用）。
   ⛔ 不在这里自造一套 kq 头像：门禁「不许自造公共件已有的东西」会拦，且两边样式会漂移。

   ⚠️ 门禁（优倍快）里的名字**不再行内显示**（原来有个 `.kq-name-sub` 小字跟在 chip 外面，
      乐哥 2026-10-10：「显示位置很奇怪，要不隐藏掉」）⇒ 改成 chip 的 `title`，悬停可见。
      信息没丢（改名过渡期还能查），列里只剩一个干净的名字。 */

/* ⚠️ 这里原本有个自造的 `.kq-hint`（塞在筛选栏里的一行小字）—— 2026-09-29 已删，原因两件：
     ① **它把筛选栏挤折行**：那一行总宽 ≈1330px > 容器 1090px（日期区间 346 + 本句 332
        + 搜索 220 + 三个下拉 292 + 共N条 100 + 间距）⇒ 折成两行、第二行只剩两个下拉。
        ⚠️⚠️ 本地空库时下拉选项少、勉强不折 ⇒ **本地绿、生产红**（真机才暴露）。
     ② 这类"先说清口径"的条全站已有公共件 `.st-rule`（华大对账在用）⇒ 不该自造。
     现在口径条走 `.st-rule`，位置在**筛选栏之外**（见 kq.js 的 stRule()）。 */

/* ═══════════ 部门多选（复用财务的 `.fin-ms`）· 去重箭头 ═══════════
   🐞 2026-10-14 乐哥：「选择部门那个下拉箭头得改下，现在多了个」
   根因：`.fin-ms__btn` 同时挂着 `class="select"`，而 `.select` 自带一个**背景 svg 箭头**
        （`components.css` 的 `background-image: url("data:image/svg+xml,…chevron…")` +
         `padding-right: 32px` 就是给它留的位），.fin-ms 里**又**插了一个 SVG 图标箭头
        ⇒ 显示成两个箭头。
   ✅ 去掉背景那个：图标那个才是 `.fin-ms` 原设计要的（`.fin-ms__btn > svg` 有独立配色，
      悬停能跟随主题）。右侧那 32px 留白也一起收回。
   ⚠️ 用 **id 限定**（不是 `.kq-page` 前缀）—— 考勤记录页的根不是 `.kq-page`，
      而 id 更精确、且**不会碰财务的 `.fin-ms`**（财务那两处是同样的双箭头，但不在本次范围，
      要改得单独跟乐哥确认，别顺手动别的板块）。 */
#kq-att-dept > .fin-ms__btn,
#kq-pp-dept > .fin-ms__btn {
  background-image: none;
  padding-right: var(--space-3);
}

/* ═══════════ 页面内的「悬浮 TAB 行」（2026-10-14）═══════════
   乐哥：「红框的内容，应该按照设计标准，放在最上面的悬浮 tab 栏里面」+「考勤记录也是」+「刷脸记录也是」
   ⇒ 三页各有一条：**左 = 主筛选 tab，右 = 页面级操作按钮**，整行 sticky 吸顶。
   📐 同站同构先例 = 项目管理页的 `.pms-pj__tabbar`（乐哥 2026-10-11：「新增项目这个按钮，
      应该放在悬浮 tab 里」）—— 本次是同一句话换了个页面。

   ⛔ **外观一处都不在这里写**：背景 / 边框 / 圆角 / 阴影 / padding / margin 全部由全站标准的
      `.page-header--has-tabs` 提供（元素上直接挂那个类名）—— 自己抄一遍值就会造出
      "同一种东西两套数值"，本项目对此零容忍（项目页那条注释也是这么立的规矩）。
   ✅ 这里只补两样它在"页头"时由 `.page-header` 提供、搬进页面后缺掉的东西：**布局** 与 **吸顶**。
   ✅ 吸顶的**玻璃态**不用自己写滚动监听：`app.js` 会扫 `.page-header--has-tabs` 并切 `is-stuck`
      （见该处 `bars` 那行的选择器表）。 */
.kq-tabbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
  /* ⭐ 吸顶位 = 顶栏高（与项目页 `.pms-pj__tabbar` 同值）
     ⛔ 别加 `--frame-gap`、也别写 `+4`：会让吸顶行与顶栏之间多出一条缝。 */
  position: sticky;
  top: var(--tabs-bar-height);
  z-index: 20;
}
.kq-tabbar__acts {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}
/* ⭐⭐⭐ 2026-10-17 全站 UI 统一：**页头主操作按钮 = 42，与左侧 tab 托盘同高**。
   ──────────────────────────────────────────────────────────────────────────────
   ▸ 实测病根（1680 / dpr1）：`.kq-pp-tabs` 托盘 **42** ｜ `.kq-tabbar__acts .btn` **34**
     ⇒ 「导出 CSV」「立即同步」比左边那排胶囊托盘矮 8px，同一行不齐。
   ▸ 42 不是新数值：达播 / 财务在"页头带 tab"时本来就是 42（`--control-height-row`，
     `layout.css` 2026-10-07 乐哥拍板「控件升到 42 与托盘同高」），人事板块漏了 ⇒ 补齐。
   ▸ ⛔ 不改 `.btn--sm` 本身（34 是全站工具栏/表格行内的主流，400+ 处引用）。
   ⚠️ 只限 `.kq-tabbar__acts`（页头这一行）—— 卡内工具栏的按钮仍走 34。
   ⚠️ 排除「?」圆钮（`.fin-help__btn`，24px）：它不是按钮族，拉高会变成一个大圆球。 */
.kq-tabbar__acts .btn:not(.fin-help__btn) {
  height: var(--control-height-row);
  min-height: var(--control-height-row);
  /* 与达播 / 财务 / 项目的页头主操作**逐值同源**（含内边距） */
  padding-left: var(--space-4);
  padding-right: var(--space-4);
}

/* ── 门禁刷脸时间 vs 飞书考勤时间「不一致」标记（2026-10-21）──────────────
   背景（乐哥报障：员工说刷脸了、飞书时间不对）：同一天员工既刷了门禁、又在飞书 App
   上打了卡，飞书会取 App 那条（带照片定位），于是页面上"我们的刷脸时间"和"飞书的时间"
   差几十秒到几小时。以前只在悬停里能看见，列表里看不出来。
   ⇒ 差异 ≥5 分钟时在时间后面挂一个黄色小签，写明差多少分钟、飞书取的是几点、来源是什么。
   颜色与全站警示色同族（暖黄底 + 深黄字），不用红色——这不是错误，是"需要人看一眼"。 */
.kq-gate-diff {
  display: inline-block;
  margin-left: var(--space-1);
  padding: 0 var(--space-1);
  font-size: var(--font-size-xs);
  border-radius: var(--radius-sm);
  background: var(--color-warning-bg);
  color: var(--color-warning-text);
  border: 1px solid var(--color-warning-border);
  vertical-align: middle;
  cursor: help;
  white-space: nowrap;
}

/* ── 人员总表：选中列里"不可选"的占位（2026-10-22 多选）──────────────────────
   「已建档但还没刷过脸」的人**没有门禁身份**（无 actor_id）⇒ 后端排除接口认不了他，
   所以这一格不给框、给一个灰色破折号，比一个点不动的禁用框更清楚。
   只调颜色与对齐，不设固定宽度 —— 宽度由 `.table td.col-pick` 的内边距决定，
   与旁边 `.row-pick`（26px）天然对齐，零新增硬编码尺寸。 */
.kq-pick-none {
  color: var(--color-fg-3);
  text-align: center;
}


/* ==== /css/pages-project.css ==== */
/* ==========================================================================
   项目管理板块（/project）—— pages-project.css
   2026-09-30 建立（批次 1：M0 基础配置）
   --------------------------------------------------------------------------
   与财务中心 / 人事行政同形态：独立外壳 + 自带侧栏 + 显示标签栏
   （`is-standalone + is-project`，让位三件套见 pages.css 的同款段落）。

   ⭐⭐ 本文件**刻意很薄** —— 按 DESIGN-SPEC 的纪律，能用公共件的一律不重写：
     页面骨架   `.page` / `.page-header` / `.page-title` / `.page-actions`   ← pages.css
     说明面板   `.pms-help-tab`（弹窗内的页签行）+ 全站的 `.help-section` 族 见下条
     KPI        `.kpi-row` / `.kpi-card`                                     ← pages.css
     工具栏     `.toolbar`（含控件紧凑档兜底）                                ← components.css
     卡片       `.card` / `.card__header` / `.card__title`                    ← components.css
     表格       `.table`                                                     ← table.css
     分页/空态  `UI.pagination` / `UI.emptyState`                            ← components.css
     控件       `.input` / `.select` / `.textarea`（两档制靠全局兜底）         ← components.css
     表单行     `.form-row` / `.field-label` / `.form-row__ctrl` / `.field-hint` ← pages-influencer.css
     弹窗       `UI.openModal`（.modal 族，进场/退场动效现成）                 ← overlays.css
     徽章       `UI.badge`                                                   ← components.css

   ⚠️⚠️ 开头的「让位三件套」不是可选项 —— 见下方那段（漏了侧栏整块不显示）。

   ⇒ 本文件只写**四类本板块独有的东西**：
     ① 页面竖向节奏（说明条 / KPI / 工具栏 / 卡片之间的间距）
     ② 表格里的「主文本 + 次文本」双行单元格（`.pms-cell-main` / `.pms-cell-sub`）
     ③ 品牌 × 渠道 勾选矩阵（`.pms-links*`）——原型里的核心交互，公共件没有
     ④ 表单弹窗内的两列网格（`.pms-grid` / `.pms-span2`）——PMS 模型要求的 2 列

   ⚠️ 三条硬约束（违反会被门禁拦，也都是本项目踩过的坑）：
     · **禁裸 hex**，颜色一律走令牌（`--color-*`）；
     · **禁 `var(--令牌, 兜底值)`** 写法（兜底会掩盖令牌名拼错 ⇒ 静默失效）；
     · 过渡/动画时长一律引 `--motion-*` 令牌。
   ========================================================================== */


/* ══════════════════════════════════════════════════════════════════════════
   ⚠️⚠️ 板块壳：让位三件套（**漏一条就出可见故障**，与 finance / hr 逐条同款）
   ──────────────────────────────────────────────────────────────────────────
   ① `.sidebar { display: flex !important }` —— 必须带 `!important`：
      `pages.css` 里有 `.app-shell.is-standalone .sidebar { display: none !important }`，
      **`!important` 的优先级高于普通声明**，我方特异性更高也照样输。
      漏了的现象极具迷惑性：**DOM 里侧栏内容全对，屏幕上什么都没有**。
   ② `.page-tabs-bar { display: flex !important }` —— 同理（独立外壳默认藏标签栏）。
   ③ `.app-main { margin-left: var(--sidebar-width); width: auto }` —— **把让位还回去**：
      is-standalone 那条把它设成 `margin-left: 0; width: 100%`（前提是"没有侧栏"）。
      不还回去 ⇒ 内容被压在侧栏底下 240px。
      ⚠️ 还必须 `width: 100%` → `auto`，否则"240px 外边距 + 100% 宽"会横向撑出滚动条。
   ⚠️⚠️ 并在 **768 断点收回** `margin-left`：`@media` **不改变选择器特异性**，
      桌面这条（3 个类的选择器）在手机上照样生效 ⇒ 内容只剩 ~150px 宽
      （本项目在财务板块上真踩过，且**不报错、也不出横向滚动条**，
      只能靠窄视口量宽度才发现）。
   ══════════════════════════════════════════════════════════════════════════ */
/* ⚠️⚠️ 2026-10-09：`margin-left` **补 `--shell-inset`**（本轮第 4 处同族修复）
      —— 壳层定稿（2026-10-07「显示屏浮在底色上」）要求中缝 = `--shell-inset`(8)，
      内容屏左缘应是 `sidebar-width + inset`，而这里只写 `sidebar-width` ⇒ **缝 = 0**。
      与上面 ③ 的理由（"把让位还回去"）是同一件事的两半：**让位量要含那条缝**。
      实测（1920×950 真机）：项目管理中缝 0，达播台 8。
      ⚠️⚠️ 2026-10-09 补注释头：这段落（2026-10-09 加）原先**漏了注释的起始符号**（斜杠+星号），
      浏览器把它当选择器解析失败 ⇒ 连带把**紧随其后的第一条规则**
      （`.sidebar{display:flex}`）整条吞掉 ⇒ **项目管理侧栏整块不显示**
      （DOM 里 23 个菜单项都在，屏幕上什么都没有，不报错）。
      与 2026-09-29「CSS 包分隔符吞每个文件首条规则」是**同一个坑的另一个入口**：
      那次是拼接分隔符，这次是注释没闭合头。详见 docs/ 项目板块侧栏消失-诊断-20260909.md。 */
.app-shell.is-standalone.is-project .sidebar { display: flex !important; }
.app-shell.is-standalone.is-project .page-tabs-bar { display: flex !important; }
.app-shell.is-standalone.is-project .app-main {
  margin-left: calc(var(--sidebar-width) + var(--shell-inset));
  width: auto;
}
@media (max-width: 768px) {
  .app-shell.is-standalone.is-project .app-main {
    margin-left: 0;
    width: auto;
  }
}

/* ⭐⭐ 2026-10-08：**内层 `.page` 不再重复给留白**（修"项目板块那圈比别处宽一倍"）
   ──────────────────────────────────────────────────────────────────────────────
   🐞 实测（生产真机 `deploy/verify-page-padding-prod.js`）：
       项目板块的卡片距窗口左边缘 **32px**，而达播台 / 门户 / 财务 / 人事都是 **16px**。
     结构原因：外层已有一个 `.page`（板块外壳），而 `pages/project.js:179` 返回的容器
       **又套了一层** `<div class="page pms-page">` ⇒ `.page` 的左右留白给了**两次**。
     改前是 40 + 40 = **80px**（同样翻倍）—— 不是本次令牌改动引入的，是这轮才量出来。
   ▸ 修法：内层不给留白（`padding: 0`），留白由**最外层** `.page` 一份承担，
     与其余四个板块对齐；内层的其他样式（flex 等）不受影响。
   ⚠️ `>` 限定直接子级 + `.pms-page` 自限定 ⇒ 只命中这一处嵌套，
      任何"正常单层 `.page`"的页面完全不受影响。
   ⛔ 别删 `pms-page` 这个类 —— `.pms-page .pms-body` 等排版规则挂在它上面。 */
.page > .page.pms-page {
  padding: 0;
}

/* ══════════════════════════════════════════════════════════════════════════
   ⭐⭐ 第 46 批（乐哥：「每个板块一个模块，一个语义颜色」）—— **环节层级色**
   ──────────────────────────────────────────────────────────────────────────
   用途：①「新建项目」表单的五个环节板块（标题与左侧色条）；
        ②「项目列表」的表头（品牌 / 服务商端口 / 投放平台 / 投放链路 / 产品）。
   ⚠️ 与 `#pms-bl-body` 里那三个（脑图节点的类型色 `--pms-node-*`）是**同一档色**
      （都取设计令牌层的 `--color-tone-*-fg`），只是那一组定义在业务线视图的容器上，
      表单弹窗拿不到 ⇒ 这里给一份本页别名。
      ⛔ 别在这里写死 hex；改色改设计令牌层，两边一起变。
      ⛔ 也别把这五档并进 `#pms-bl-body` —— `tests/pms-brand-lines-views.test.js`
         锁着那三个必须是 `var(--color-tone-*-fg)` **直接**引用。
   ──────────────────────────────────────────────────────────────────────────
   ⚠️⚠️ 定义在 `:root` 上（**不是** `.pms-page`）：2026-10-11 第 46 批实测 ——
      写在 `.pms-page` 上时真机取不到值（层级色全落成默认灰、左边框整条失效），
      而 `--color-tone-*-fg` 本身是 `:root` 级的 ⇒ 直接挂同一层最省事、也最可靠。
      （页面板块外壳各不相同，挂 `:root` 就不依赖"页面根叫什么类"。） */
:root {
  --pms-k-brand: var(--color-tone-blue-fg);
  --pms-k-partner: var(--color-tone-green-fg);
  --pms-k-chan: var(--color-tone-teal-fg);
  --pms-k-link: var(--color-tone-purple-fg);
  --pms-k-product: var(--color-tone-orange-fg);
  --pms-k-brand-bg: var(--color-tone-blue-bg);
  --pms-k-partner-bg: var(--color-tone-green-bg);
  --pms-k-chan-bg: var(--color-tone-teal-bg);
  --pms-k-link-bg: var(--color-tone-purple-bg);
  --pms-k-product-bg: var(--color-tone-orange-bg);
}
/* 带色板块：JS 给分组传 `tone` ⇒ 类名 `pms-group--k-<tone>`。
   ⚠️ 默认值给在**基类** `.pms-group` 上、由子类覆盖：
      这样"没传 tone 的板块"（合同 / 报销…的表单分组）也有中性值，
      不会因为 `var(--pms-k-now)` 悬空把整块渲染成透明/黑。
      ⛔ 别把默认值写到 `.pms-chips` / `.pms-chip` 上 —— 元素自身的声明会盖掉
         祖先板块传来的层级色（就近优先），选中色就全变主色了。 */
.pms-group {
  --pms-k-now: var(--color-primary-500);
  --pms-k-now-bg: var(--color-surface-2);
}
/* ⭐⭐ 第 54 批（乐哥 2026-10-12）：「**去掉那个色块**」—— 指模块标题左边那条彩色竖条。
   ⇒ `.pms-group--k` 现在**只剩"标记 + 传递层级色变量"两个用途**，本身**不产生任何视觉**：
      · 它给组内的胶囊组（`.pms-chips` / `.pms-chip`）与脑图提供 `--pms-k-now`；
      · 组标题**不再**上层级色（与"去掉色块"同批一起去掉）。
   ⛔ 别再给 `.pms-group--k` 加 `border-left` / `background` / 标题色 ——
      第 46~53 批那几版"左色条 / 标题同色 / 整块浅底"都已被乐哥否掉。
   ⚠️ 层级色仍在**小面积**上出现（脑图格子边、胶囊选中底），大块一律白底。 */
.pms-group--k-brand { --pms-k-now: var(--pms-k-brand); --pms-k-now-bg: var(--pms-k-brand-bg); }
.pms-group--k-partner { --pms-k-now: var(--pms-k-partner); --pms-k-now-bg: var(--pms-k-partner-bg); }
.pms-group--k-chan { --pms-k-now: var(--pms-k-chan); --pms-k-now-bg: var(--pms-k-chan-bg); }
.pms-group--k-link { --pms-k-now: var(--pms-k-link); --pms-k-now-bg: var(--pms-k-link-bg); }
.pms-group--k-product { --pms-k-now: var(--pms-k-product); --pms-k-now-bg: var(--pms-k-product-bg); }
/* 板块外观的**演变史**（都别再摆回去）：
     第 46~49 批：整块浅色底 + 块内 6 层白盒套娃（每层 1px 边框 + 3px 左色条）
                ⇒ 乐哥：「这乱七八糟的ui，能不能重新做？」
     第 50 批：去掉整块浅底，只留 **3px 左色条 + 标题同色**
     第 52 批：每个大模块一个**模块框**（白底 + 1px 描边 + 圆角 16 + 轻阴影）
     第 54 批：**连左色条与标题色也去掉** —— 乐哥：「去掉那个色块」
   ⇒ 现行 = 模块框四条边一律 1px 中性色，层级色只出现在**脑图格子边 / 胶囊选中底**这类小面积上。 */

/* ⭐⭐ 第 52 批（乐哥 2026-10-12）：「就是品牌 + 投放线 + 项目名称与属性 + 签约，这几个
   每一个属于一个大模块，按照我们设计标准会有自己的模块框」。
   ⇒ 每个大模块 = 一个**模块框**：白底 + 1px 描边 + 圆角 16 + 轻阴影 —— 与全站 `.card`
     同一套令牌（`--color-frame-border` / `--radius-lg` / `--shadow-card`）。
   ⚠️ 只挂在**本表单**的组上（JS 传 `box: true` ⇒ 类名 `pms-group--box`）⇒
      别的表单分组**逐字不变**（`formModal` 是全站共用件，勿动基类 `.pms-group`）。
   ⚠️ 四条边**一律 1px 中性色**（第 54 批：乐哥要求去掉左边那条层级色彩条）——
      所以这里**不再**有任何 `border-left` 加宽/上色。 */
.pms-group--box {
  padding: var(--space-5);
  background: var(--color-surface);
  border: 1px solid var(--color-frame-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
}

/* ── 胶囊组（字段类型 `chips`，第 46 批）───────────────────────────────────
   用途：把"这一层能选的都摆出来、点一下即选中"（乐哥：「按用户直觉，一级级填」）。
   ⚠️ 结构固定两层：外层**竖排**（一行一个"组"），内层 `__grp` **横排**（胶囊换行）。
      这样"星盟下：ADQ / 千川"这种**分组**才表达得出来（组头一行 + 该组胶囊一行）。
      单组时只有一个 `__grp` ⇒ 视觉上就是横排一排，不会多出空白行。
   ⚠️ 选中色 = 所在板块的层级色（`--pms-k-now`，由板块类提供）。
   ⚠️ `--d` = 层级深度（行内给），做链路那种多级下钻的缩进。 */
.pms-chips { display: flex; flex-direction: column; gap: 6px; }
.pms-chips__grp { display: flex; flex-wrap: wrap; gap: 6px; }
.pms-chips__ghead { font-size: var(--font-size-xs); color: var(--color-muted); }
.pms-chips__hint { font-size: var(--font-size-xs); color: var(--color-muted); }
.pms-chip,
.pjt-chip {
  font: inherit;
  font-size: var(--font-size-sm);
  line-height: 1.4;
  text-align: left;
  padding: 5px 12px;
  border-radius: var(--radius-full);
  border: 1px solid var(--color-border);
  background: var(--color-surface);
  color: var(--color-fg);
  cursor: pointer;
  transition: border-color var(--motion-fast) var(--motion-ease-standard),
    background var(--motion-fast) var(--motion-ease-standard);
}
.pms-chip:hover,
.pjt-chip:hover { border-color: var(--pms-k-now); }
.pms-chip.is-on,
.pjt-chip.is-on {
  background: var(--pms-k-now);
  border-color: var(--pms-k-now);
  color: var(--color-accent-on);
}
/* 末项「＋ 新建…」：虚线框、文字用层级色 —— 与"已有记录"区分得开。 */
.pms-chip--new,
.pjt-chip--new { border-style: dashed; color: var(--pms-k-now); }
/* 多级下钻的缩进：`--d` 由 JS 按链路深度给（顶层 0 ⇒ 不缩进）。 */
.pms-chip--sub { margin-left: calc(var(--d) * 14px); }

/* ── 「将要建出的项目」名单（第 46 批）────────────────────────────────────
   挂在「项目名称与属性」板块里（走 `formModal` 的 `g.html`）——它随联动**实时重画**，
   用字段装不下（字段只能承载一个值）。
   用途：新建项目一次可能建出好几条 ⇒ 下单前让人**看得见到底要建哪些**。 */
.pms-newlist { margin-top: 10px; }
.pms-newlist__hd { font-size: var(--font-size-xs); color: var(--color-muted); margin-bottom: 4px; }
.pms-newlist__ul { margin: 0; padding-left: 18px; max-height: 132px; overflow: auto; }
.pms-newlist__ul li { font-size: var(--font-size-sm); line-height: 1.7; color: var(--color-muted); }

/* ── 项目列表：**表头的环节层级色**（第 46 批）────────────────────────────
   乐哥：「项目列表…每一个板块列上面，也有自己这个层级的语义颜色」。
   与「新建项目」表单的板块、脑图节点的类型色**同一档**（都取 --pms-k-*）。
   ⚠️ 只上色，不改权重 / 字号：表头整体仍是全站那套（别在列上另起一套字号）。
   ⚠️⚠️ 前缀不能省（三处都省不得）：全站有 .table thead th 那条（"全列居中"那套），
      还有 table.css 里的 .table > thead > tr > th:first-child；光写 .pms-th--k-brand
      会被它们盖住 —— 真机实测过：只写类名时**五列全灰**，加了 .table thead th 之后
      只剩第一列（品牌）还是灰的（那条 first-child 规则同特异性、后加载）。
   ⚠️⚠️⚠️ 本注释必须在**同一块**里闭合（第 46 批踩过）：上一版把闭合符写进了说明文字里，
      后面几行中文就成了 CSS 里的"野语句" ⇒ **紧跟着的那条规则会被整条吞掉**
      （症状：五条规则里只有第一条 .pms-th--k-brand 不生效，怎么抬特异性都没用）。
      这正是门禁「顶层不得出现非 ASCII 选择器」在守的那件事。 */
.pms-table-wrap .table thead th.pms-th--k-brand { color: var(--pms-k-brand); }
.pms-table-wrap .table thead th.pms-th--k-partner { color: var(--pms-k-partner); }
.pms-table-wrap .table thead th.pms-th--k-chan { color: var(--pms-k-chan); }
.pms-table-wrap .table thead th.pms-th--k-link { color: var(--pms-k-link); }
.pms-table-wrap .table thead th.pms-th--k-product { color: var(--pms-k-product); }

/* ── 纵向脑图（「新建项目」表单 · 第 51 批）────────────────────────────────
   乐哥拍板：品牌在顶居中 → 服务商端口 → 投放平台 → 投放链路(可多级) → 产品；
   格子 + **折线**；左边一列标出「这一层是什么」，每层一条铺满的浅色带。
   ⚠️ 布局是 **JS 算坐标 + 绝对定位**（见 `pjmLayout`/`pjmHtml`）——
      ⛔ 不用嵌套 ul/li 的 org-chart CSS：那样层带没法等高、链路多级也没法撑高。
   ⚠️ 居中靠 `.pjm__inner{margin:0 auto}`（画布 `width:max-content;min-width:100%`）——
      ⛔ 别在 JS 里算"可视宽减内容宽"（表单首次渲染时量不到容器宽）。 */
.pjm { display: flex; border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow: hidden; }
.pjm__layers { flex: 0 0 104px; background: var(--color-surface-2); border-right: 1px solid var(--color-border); }
.pjm-lay { display: flex; align-items: center; justify-content: flex-end; padding-right: var(--space-3);
  border-top: 1px solid var(--color-border); font-size: var(--font-size-xs); color: var(--color-muted); }
.pjm-lay:first-child { border-top: 0; }
.pjm__scroll { flex: 1 1 auto; overflow-x: auto; }
.pjm__canvas { position: relative; width: max-content; min-width: 100%; }
.pjm__inner { position: relative; margin: 0 auto; }
.pjm-band { position: absolute; left: 0; right: 0; border-top: 1px solid var(--color-border); }
.pjm-band:first-child { border-top: 0; }
.pjm__svg { position: absolute; left: 0; top: 0; color: var(--color-border); pointer-events: none; }

/* 格子：白底 + 2px 层级色边（层级色走行内的 `--pjm-c`） */
.pjm-nd { --pjm-c: var(--color-primary-500);
  position: absolute; display: flex; align-items: center; justify-content: center;
  padding: 0 var(--space-2); border-radius: var(--radius-md); background: var(--color-surface);
  border: 2px solid var(--pjm-c); font-size: var(--font-size-sm); font-weight: var(--font-weight-medium);
  color: var(--color-fg); cursor: pointer; user-select: none; white-space: nowrap; overflow: hidden;
  transition: background var(--motion-fast) var(--motion-ease-standard); }
.pjm-nd:hover { background: var(--pms-k-now-bg); }
.pjm-nd__nm { overflow: hidden; text-overflow: ellipsis; }
.pjm-nd.is-root { background: var(--pms-k-brand-bg); }
/* 格子右上角的 ✕ —— 点它**直接从这条线移出**（不删档案）。
   ⚠️ 第 103 批（乐哥 2026-10-14：「直接移出，别问了」）：第 100/102 批那套
      "点 ✕ 开二选一弹窗（移出 / 删档案）"整条撤掉 ⇒ 它是纯装饰性的 `<span>`，
      不需要 `<button>` 的键盘可达（没有弹窗要开）。
   ⚠️ 档案本身的删除在「渠道信息」页；⛔ 别在脑图上加"删档案"（会连累别的项目）。 */
.pjm-nd__x { position: absolute; right: 3px; top: 1px; font-size: 11px; line-height: 1;
  color: var(--color-border); cursor: pointer; opacity: 0; }
.pjm-nd:hover .pjm-nd__x { opacity: 1; }
.pjm-nd__x:hover { color: var(--color-danger); }
/* ⛔ 第 100 批**删掉了** `.pjm-nd__del`（左上角那枚垃圾桶）——
   它的行为已并入右上角的 ✕（弹窗里选"删除档案"）。别再把它加回来：
   两枚图标各管一半语义正是乐哥这次报"某个环节没法直接删除"的根因（见 project.js 的说明）。 */

/* ⭐ 「＋」常驻不显示，**鼠标移到格子上才浮出**，且挂在格子**正下方**（＝加下一级，像 XMind） */
.pjm-add { --pjm-c: var(--color-primary-500);
  position: absolute; width: 22px; height: 22px; padding: 0; border-radius: var(--radius-full);
  border: 1.5px solid var(--pjm-c); background: var(--color-surface); color: var(--pjm-c);
  font: inherit; font-size: 14px; line-height: 1; display: flex; align-items: center;
  justify-content: center; cursor: pointer; opacity: 0; z-index: 3;
  transition: opacity var(--motion-fast) var(--motion-ease-standard); }
.pjm-nd:hover + .pjm-add, .pjm-add:hover, .pjm-add.is-open { opacity: 1; }
.pjm-add:hover { background: var(--pms-k-now-bg); }

/* 候选浮层 */
.pjm-pop { position: absolute; z-index: 9; background: var(--color-surface); border: 1px solid var(--color-border);
  border-radius: var(--radius-md); box-shadow: 0 6px 20px rgba(28, 30, 33, .10); padding: 10px 12px; min-width: 210px; }
/* ⭐ 第 52 批（乐哥：「这个窗口没法关闭」）：浮层自带一个 ✕。
   之前只能"再点一次同一个 ＋"才能收起 —— 用户根本发现不了，读起来就是"关不掉"。 */
.pjm-pop__x { position: absolute; right: 3px; top: 3px; width: 20px; height: 20px; padding: 0;
  border: 0; background: transparent; color: var(--color-muted); cursor: pointer; font: inherit;
  font-size: 12px; line-height: 1; border-radius: var(--radius-full);
  display: flex; align-items: center; justify-content: center; }
.pjm-pop__x:hover { background: var(--color-surface-2); color: var(--color-fg); }
/* ⚠️ 标题要给 ✕ 让位（`padding-right: 18`），否则长标题会被那颗 ✕ 压住。 */
.pjm-pop__hd { font-size: var(--font-size-xs); color: var(--color-muted); margin-bottom: var(--space-2);
  padding-right: 18px; }
.pjm-pop__bd { display: flex; flex-wrap: wrap; gap: 7px; }
.pjm-pop__bd--col { flex-direction: column; align-items: stretch; }
.pjm-pop__empty { font-size: var(--font-size-xs); color: var(--color-muted); }
.pjm-cand { display: inline-flex; align-items: center; padding: 5px 11px; border-radius: var(--radius-md);
  border: 1.5px dashed currentColor; background: var(--color-surface); color: var(--color-fg);
  font: inherit; font-size: var(--font-size-sm); cursor: pointer; text-align: left; }
.pjm-cand:hover { background: var(--color-surface-2); }
.pjm-cand--new { border-style: dotted; color: var(--color-muted); }

/* 缩放/动效偏好：全站统一由 `--motion-*` 令牌兜底，这里不另写 media 查询。 */

/* ── ① 页面竖向节奏 ──────────────────────────────────────────────────────────
   ⚠️ 与 kq / finance 板块保持同一套节奏（16px 起的元素间距、卡片间距 20px）。
      这里只给"本板块特有的元素"设间距，公共区块（.kpi-row / .toolbar / .card）
      的默认间距由公共件提供，**不重复声明** —— 否则改公共间距时这里不跟随（双源）。 */
.pms-page .pms-body > * + * {
  margin-top: var(--space-5);
}

/* 「数据说明」弹窗里的**页签行**（合并页专用：三个页签各一行，靠页签名加粗定位）。
   ⚠️ 弹窗主体复用全站的 `.help-section / .help-label / .help-text`（定义在 layout.css）——
      本类只负责"页签名 + 说明"这一行的排版，⛔ 不另起一套面板样式。
   ⚠️ 用 `--color-fg-2`（不是 --color-fg）：页签名是**索引**，正文说明才是主角。 */
.pms-help-tab { margin-bottom: var(--space-2); }
.pms-help-tab:last-child { margin-bottom: 0; }
.pms-help-tab > b { color: var(--color-fg-2); font-weight: 500; }

/* ⭐⭐ 2026-10-10：页面说明条 `.pms-help` **已删** —— 说明改收进页头右端的「?」
   （乐哥：「注释按照设计规范，都收到 tab 栏里」）
   ────────────────────────────────────────────────────────────────────────────
   ⚠️ 这里原来写着"**必要前提留在页面上、算式与长解释才收进「?」**"，
      并据此认为本板块的说明条该留着 —— 那是 2026-10-09 的判断。
      **2026-10-10 被推翻**：他明确要求"都收到 tab 栏里"（= 设计规范那条
      「页面说明 → 收进「?」面板」，见 docs/DESIGN-SPEC.md 的骨架合规表）。
   📌 反转留痕（本项目惯例）：上一版那句推导本身没错，只是**权衡结果被他改了**——
      他更看重"每页白占一行 + 切 tab 时条上讲的还是整页的事"这个代价。
   ⛔ 别再把它加回来。要改说明文案请改 `project.js` 的 `HELP` 表。
   ▶ 实现见 `project.js` 的 `helpBtnHtml()`（按钮落点）/ `openHelp()`（弹窗）。 */

/* ── ② 表格里的双行单元格 ────────────────────────────────────────────────────
   主文本（品牌名 / 合同编号）+ 次文本（备注 / 合同名）。
   ⚠️ 次文本用 `--color-muted`（次级信息）而不是 meta：
      两行都是要读的内容，不是"可选提示"，对比度要够（meta 在白底只有 3.07:1，不达标）。 */
.pms-cell-main {
  color: var(--color-fg);
  font-weight: var(--font-weight-medium);
}
.pms-cell-sub {
  margin-top: 2px;
  color: var(--color-muted);
  font-size: var(--font-size-xs);
  line-height: 1.4;
}

/* 操作列：行内按钮右对齐、窄间距。
   ⚠️ 用 flex + gap 而不是给按钮加 margin：后者会在按钮被权限隐藏时**留下空隙**
      （本项目在"按钮消失但位置还在"上栽过）。
   ⚠️ 按钮本身是 `.icon-btn[data-edit]` / `[data-del]` —— **中文灰 / 红字的样式来自
      table.css**（那里有 `.table td .icon-btn[data-edit] { color: muted }` 与
      `[data-del] { color: danger }`）。这里**不要**再给颜色，否则就是"同一件事两处实现"。
      本项目实测过的坑：`.table td .btn--ghost` 会被 table.css 统一成"软蓝实心"，
      所以行内操作**不能用文字按钮**（会整列蓝块），这也是这里选图标按钮的原因之一。 */
/* ⭐ 2026-10-16：**数字列**（金额/数量）—— 右对齐但**不压宽度**。
   ⚠️⚠️ 别拿 `.pms-col-op` 当数字列用：它是**操作列**的类，带 `width: 1%`（把列压到内容宽），
   用到"账户数 / 总余额"这种列上 ⇒ 列被挤窄、与相邻列之间出现肉眼可见的**断层/竖线**
   （乐哥 2026-10-16 截图实报：「金额那块和其他的有断层」「账户数两边的竖线不要」）。
   操作列要 `width:1%`（收缩到内容宽）是**对的**，但那只适用于"只有几个按钮"的最后一列。 */
.pms-col-num { white-space: nowrap; }
/* ⚠️⚠️ 右对齐必须写成**表/单元格层级**（与上面 `.pms-col-op` 同一套写法）：
   全站 `table.css` 有一条「全列居中」`.table:not(:where(td .table)) > tbody > tr > td`
   （特异性 0,1,3）—— 裸 `.pms-col-num`（0,1,0）**压不过它**，会**静默不生效**
   （本轮实测：列宽改对了、数字却还在中间）。写到这里拿 (0,2,2) 才赢得过。 */
.table thead th.pms-col-num,
.table tbody td.pms-col-num,
.table tfoot td.pms-col-num { text-align: right; }
.pms-col-op { white-space: nowrap; width: 1%; }
/* ⭐ 2026-10-10：操作列**收缩到内容宽**（`width:1%` + 上面的 `nowrap` 是标准技巧）。
   ⛔ 不收缩的后果（实测）：表格有 `table-layout:fixed`（JS 按列数加的下限），
      列宽由首行决定 ⇒ 最后一列会**吃掉全部剩余空间**：
        品牌表操作列 276px / 服务商端口页 345px / 品牌渠道勾选页 —— 而内容只有 72px（两个图标）
      ⇒ 右侧一大片空白，叠加"表头右对齐"后看着更像没对齐。
   ⚠️ 只对 `.pms-col-op` 生效（操作列），不动其它列 —— 其它列的内容宽度差异大，
      给它们设 `width:1%` 会让长文本列被压窄。 */
.pms-rowbtn {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  justify-content: flex-end;
}

/* ── ④ 投放链路树（2026-10-10 第 8 批：一行一条一级链路 + 格内缩进的树）─────────────
   乐哥：「这个 ui 还是很奇怪，我觉得是引导线的问题」+「一个一级投放链路一行，
   然后新增下级也不按照行隔开」→ 选「一级一行、下级在格内缩进」。
   ⛔ 旧版每级各占一行，把 `border-left` 画在**内容块**上当"色条"—— 那根线的高度只跟文字走、
      上下都不接任何东西，真机上就是一根**断头线**（乐哥截图里指的就是它）；
      名字前的 `└─` 拐角上方也没有 `│` 接过来。
   ⭐ 现在：导轨 = 子块自己的 `border-left`（从父级名字正下方一路连到最后一个子级），
      横线 = 每行自己的 `::before`（贴到导轨上）⇒ 是一根**真连起来的**引导线。
   ⚠️ `.pms-tree` 是 flex（箭头 + 名字 + 小字 + 入口同一行、垂直居中）。
      全站「全列居中」（table.css 的 `.table:not(:where(td .table)) > tbody > tr > td`，
      特异性 0,1,3）管的是单元格本身，不影响这里 flex 内部的排列。 */
.pms-tree {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  /* 同一格里要叠好几行（一行一个节点）⇒ 上下留一点，否则相邻两行的名字贴在一起。 */
  padding: 2px 0;
}
/* ── 树线 = 每级一条**贯穿整组**的竖导轨（2026-10-10 第 14 批）────────────────────
   乐哥（截图 + 参考图）：「这个ui还是得优化，**一级一级不直观**」
     ＋「**像这种就挺好看的一级一级也直观**」（贴了一张侧栏导航树的截图）。
   参考图实测（560×1384、2x）：缩进步长 42px(=CSS 21px)｜竖线 **1px 中性浅灰 `#E9E9E9`**｜
     **没有横笔**｜四级文字**同一个颜色**（主色 `#3D3D3D` 占 9012 像素、次高只有 393）
     ⇒ 它的层级完全交给「**缩进 + 竖导轨**」两件事。
   ⭐ 于是把第 13 批的「肘形拐角（竖笔 + 横笔 + 圆角）」换成**贯穿式竖导轨**：
      · 线画在**子块**上 ⇒ 从第一个子级一直连到整棵子树的底边，**一条线说明一组**；
      · **不再有横笔** ⇒ 名字直接从线右边开始，行里没有那些小钩子，整块干净。
   ⛔⛔ 第 13 批在这里写过"线绝不能再画回子块上"—— 那条结论**只在"还有横笔"时成立**：
      横笔画在行的中间，而子块的下限在行的底边 ⇒ 中间到行底那一截必然多出来（结构性拖尾）。
      **横笔删掉之后，子块的下限 == 最后一个子级的底边**，导轨末端正好收在最后一个子级上，
      既不拖尾、也不"多延展一段"。⇒ 若哪天要把横笔加回来，必须同时把线改回"画在行上"。
   ⚠️ `margin-left: 9px` = 折叠箭头宽的一半（箭头中心）⇒ 导轨正好从箭头底下长出来。
   ⚠️ 缩进步长 = 9 + 1(导轨) + 15 = **25px**，与改造前逐值一致 ⇒ 六列横向对齐那套不用动。

   ⭐ 第 16 批（乐哥：「**引导线显示，看能不能优化**」）—— 实测出两处"两端都伸过头"：
     ① **顶端悬空**：导轨从子块顶边开始，而子块顶边 = 父行**底边**下方 1px ⇒
        离父级名字中线有 **18px 空隙**（父行 34px、名字居中）⇒ 看着"线跟父级断开了"。
     ② **底端多伸半行**：导轨画到子块底边（= 最后一行**底边**）⇒ 比最后一个子级的**中线**多出 **17px**；
        而且同一棵树的每一级都伸到同一条底线上 ⇒ 末端像一排**梳齿**。
     ⇒ 改成用 `::before` 画线（不再用 `border-left`），两端各收一次：
        · `top: -11px` —— 从"父行中线再往下一半图标高"起笔，正好落在**折叠图标的下沿**（不压图标）；
        · `bottom: 17px` —— 停在**最后一个子级行的中线**上（17 = 行高 34 的一半）。
     ⚠️ 两条偏移都是按"行高 34 / 图标 14"推导的，**改行高令牌必须同时改这两个数**（门禁 A15 锁这个关系）。
     ⚠️ 用 `::before` 之后要自己补回那 1px：原来 `border-left(1) + padding(15)`，现在 `padding-left: 16px`
        ⇒ 步长仍是 25px、子级内容位置一个像素都不动。
   ⭐ 顺带加了「**悬停整条链高亮**」：鼠标停在某个节点上，它所在路径上的每一级导轨一起变主色 ——
     "这条链路挂在谁下面"不用再顺着线往上找。用 `:has()` 实现（纯渐进增强，老浏览器只是没这个效果，
     不影响任何布局）。 */
.pms-tree__kids {
  /* ⚠️ `margin-left: 9px` **不是随手写的**：折叠箭头宽 18px、图标 14px 居中 ⇒
     箭头中心正好在 **9px**。导轨落在 9px 上，看上去就是"从那个箭头底下长出来的"。
     ⛔ 若改了 `.pms-tree__fold` 的宽度，这个 9 要跟着改回"宽度的一半"。 */
  margin: 1px 0 2px 9px;
  position: relative;
  /* 缩进步长 = 9(margin) + 1(导轨) + 16(padding) = **25px**（= 原来 border 1 + padding 15）。 */
  padding-left: 16px;
}
/* 导轨本体（绝对定位，两端各收一次，见上面第 16 批的说明）。
   ⚠️ 线宽 1px、色 `--color-border`（`#E1E8F2`，与参考图的 `#E9E9E9` 同档）——
      中性灰而不是主色：主色线在密集表格里会跟"可点/链接"的语义打架。 */
.pms-tree__kids::before {
  content: '';
  position: absolute;
  left: 0;
  /* 顶端：父行高 34、图标 14 居中 ⇒ 图标下沿在行顶 +24；块顶在父行底下 1px
     ⇒ 相对块顶 = -(34 - 24) - 1 = **-11px**。 */
  top: -11px;
  /* 底端：末行高 34 ⇒ 收在它的中线上 = 行底往上 17px（= 34 的一半）。 */
  bottom: 17px;
  border-left: 1px solid var(--color-border);
}
/* ⭐ 悬停整条链：鼠标停在任一节点上 ⇒ 包着它的**每一级**导轨一起变主色（= 它的完整路径）。
   用 `:has()`：`.pms-tree__kids` 会被"它内部有 hover 的节点"这个条件命中，祖先层层命中。
   ⚠️ 这是**纯增量**效果，老浏览器不支持 `:has()` 时只是没高亮，布局与信息一个字都不少。 */
.pms-tree__kids:has(.pms-tree:hover)::before { border-left-color: var(--color-primary-400); }
.pms-tree:hover > .pms-tree__name { color: var(--color-accent-text); }
/* 折叠箭头：18×18 的点击区（图标本身 14，留 2px 余量好点）。
   ⚠️ 没下级的行用等宽占位（`.pms-tree__fold-ph`）—— 否则同层名字左右错开一格。 */
.pms-tree__fold {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  padding: 0;
  border: 0;
  background: transparent;
  border-radius: var(--radius-sm);
  color: var(--color-muted);
  cursor: pointer;
  transition: color var(--motion-fast) var(--motion-ease-standard),
              background-color var(--motion-fast) var(--motion-ease-standard);
}
.pms-tree__fold:hover { color: var(--color-accent-text); background: var(--color-accent-bg); }
.pms-tree__fold-ph { flex: none; width: 18px; }
.pms-tree__name {
  flex: none;
  color: var(--color-fg);
  font-weight: var(--font-weight-medium);
}
/* ── 层级区分（2026-10-10 第 8 批起；第 14 批补第四档）──────────────────────────
   ⛔ 第 6 批的「行底色（`.pms-tree-row--lv2/3`）+ 内容块色条（`.pms-tree--lv2/3`）」
      已**全部作废** —— 一行里装的是一整棵树，表格行没有"某一级"这个粒度；
      而那个色条恰恰就是乐哥说的"断头线"（高度只跟文字走、上下都不接）。
   ⭐ 层级现在由三个通道一起说：**缩进**（子块给的 25px/级）+ **导轨**（贯穿式竖线）
      + **字阶**（下面这四档）。
   ⚠️ 四级往下**不再变小、也不再变淡**：字号夹在 12px（再小读不动）、
      字色夹在 `--color-muted`（再淡对比度只剩 3.0:1，达不到正文 4.5:1 的门槛）——
      更深的层级交给**缩进**继续说（缩进是唯一能无限延伸的通道）。
      ⛔ 别为了"层级更多"把第四档也调到 `--color-fg-3`：它在白底只有 3.0:1。
   ⚠️ 颜色一律用现成令牌、不写裸 hex（`ui-token-standards` 门禁会红）。 */
/* 字阶四档：14/600/主色 → 13/500/正文 → 12/500/次级 → 12/400/次级 */
.pms-tree--lv1 .pms-tree__name { font-size: 14px; font-weight: 600; color: var(--color-fg); }
.pms-tree--lv2 .pms-tree__name { font-size: 13px; font-weight: 500; color: var(--color-fg-2); }
.pms-tree--lv3 .pms-tree__name { font-size: 12px; font-weight: 500; color: var(--color-muted); }
.pms-tree--lv4 .pms-tree__name { font-size: 12px; font-weight: 400; color: var(--color-muted); }
/* ⚠️ 一行装一棵树 ⇒ 行会被撑高：右边那几列**顶对齐**
   （每条一级链路的"第一行"就是它自己那一行，各列的栈都从格子顶开始 ⇒ 天然齐平）。
   ⚠️ 特异性要压过 table.css 的"全列居中"（`.table:not(:where(td .table)) > tbody > tr > td`，
   0,1,3）⇒ 借 `#pms-links-list` 这个 id 抬上去。 */
#pms-links-list .pms-tree-row > td { vertical-align: top; }
/* 名字右侧的小字（`.pms-tree__mini`）。
   ⚠️⚠️ **链路列表已经不用它了**（第 13 批：状态 / 项目数都在各自的列里逐节点占一行，
      名字旁再来一份就是"一个值两个出口"）。
   但它**还在被「业务线总览」页用**（`pms-bl` 的链路节点下面挂产品名，见 `project.js` 的
   `n.products` 那处）⇒ 这条规则**不能删**（第一版顺手删了，会让那边的产品小字掉样式）。
   ⛔ 链路列表里别再引用它。 */
.pms-tree__mini {
  flex: none;
  margin-left: 6px;
  font-size: var(--font-size-xs);
  color: var(--color-muted);
  white-space: nowrap;
}
.pms-tree__mini--off { color: var(--color-danger); }
/* 名字右侧的小字：链路说明（remark）。宽度富余时占满，不够则省略号。 */
.pms-tree__path {
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--font-size-xs);
  color: var(--color-muted);
}
/* ⛔ 已删除：`.pms-tree__dash`（下级在「端口 / 平台」列留的 `—` 占位符）。
   第 8 批起下级不再占行、也就没有那两格了 —— 右边那几列只写一级链路。
   别再把它加回来：一屏破折号正是乐哥说"ui 奇怪"的一部分。 */
/* ⭐「＋ 加下级」入口：常显但很淡（乐哥的痛点就是"看不出这儿能往下长一层"，
   藏进 hover 等于没解决），hover 才变主色。
   ⚠️ 紧跟名字右侧、**不推到行尾**（第一版用了 `margin-left:auto`，真机上它与链路名
      隔了 400px 空白，看着像"另一列的内容"，反而更散）。
   ⛔ 不能换用 `.btn--ghost`：table.css 会把表格里的 ghost 按钮统一成"软蓝实心"
      （本项目在行内操作上踩过，整列会变蓝块）。 */
.pms-tree__add {
  flex: none;
  margin-left: 8px;
  padding: 2px 6px;
  border: 0;
  background: transparent;
  border-radius: var(--radius-sm);
  color: var(--color-fg-3);
  font-family: inherit;
  font-size: var(--font-size-xs);
  white-space: nowrap;
  cursor: pointer;
  transition: color var(--motion-fast) var(--motion-ease-standard),
              background-color var(--motion-fast) var(--motion-ease-standard);
}
.pms-tree__add:hover { color: var(--color-accent-text); background: var(--color-accent-bg); }

/* ── 每行的统一高度（2026-10-10 第 11 批）─────────────────────────────────────
   第一格的树行 与 操作列的按钮行**共用这一个数** —— 它们一左一右描述同一个节点，
   错 1px 就会越往下越歪（到第 3 级差一整个人高）。
   ⛔ 不许两处各写一个数（本项目铁律第 7 条：要"相接 / 对齐"的量由一处给出）。
   ⚠️ 为什么不用"内容高"：树每级字号不同（14 / 13 / 12），而操作按钮是固定高 ⇒
      两边各自跟内容走的话**每一行都会差一点点**，累积起来就是错位。
   ⚠️ 取值 **34px = 全站控件「行内档」高度**（`--control-height-sm`：工具栏 / 表格行内的控件）
      —— 操作列里那两个 `.icon-btn` 就是 34px 高，行高若不是 34，按钮会比树行高、
      每行差 6px、往下累积成整列错位（实测：3 级树第 1 行差 3px、第 3 行差 15px）。
   ⛔ 别为了"看着紧凑"把这里调回 28 —— 那就得同时把 `.icon-btn` 压到 28，
      等于自造"第三档控件高度"（本项目控件高度只有 44 / 34 两档，见 DESIGN-SPEC §5）。
   ⚠️ 变量挂在 `<tr>`（两格的共同祖先）上，且只作用于链路清单 ——
      业务线总览页的树是另一种排布（节点下面挂着项目胶囊），不受影响。 */
#pms-links-list .pms-tree-row { --pms-lk-row-h: 34px; }
#pms-links-list .pms-tree { min-height: var(--pms-lk-row-h); }
#pms-links-list .pms-cellrow { min-height: var(--pms-lk-row-h); }

/* ── 「列内栈」：一条一级链路 = 一个 `<tr>`，**每一列**里都是"这棵树每个节点一行的栈"──
   （2026-10-10 第 13 批从"只有操作列这样"扩到**六列都这样**）
   乐哥：「我觉得不好看，还有个原因是**引导线**，你看下要不换个方式，**不要引导线**」
        +「另外二三级链路挂靠了一级链路，**归属应该和一级链路一样吧**」
   ⭐ 这两句合起来就是"每个节点都是横跨所有列的一整行"：
      · 不用引导线了 —— 整行对齐 + 每级缩进本身就说明了层级（导轨成了多余装饰）；
      · 归属（端口 / 平台）每级都写出来 —— 服务端本来就是从顶层推的，二三级那份**就是**一级那份。
   ⚠️ 对齐靠两件事，缺一不可：
      ① 行高同值（`--pms-lk-row-h`）：`.pms-tree` 与 `.pms-cellrow` 读同一个变量；
      ② **子块的上下 margin 与 `.pms-tree__kids` 逐值相同**（1px / 2px）——
         差 1px 会让"父行之后的每一行"整体下移（且**不报错**）。
   ⚠️ 左右**不缩进**：缩进是"谁挂在谁下面"的视觉答案，只有第一列需要；
      右侧几列都从同一左端开始 ⇒ 竖着扫一列时值是齐的（树形表格的标准样子）。
   ⛔ 按钮本身仍是全站 `.icon-btn`（编辑 = 中性灰、删除 = 红，颜色来自 table.css）——
      这里**不要**再给颜色（同一件事两处实现，本项目踩过）。 */
.pms-cellrow {
  display: flex;
  align-items: center;
  min-height: var(--pms-lk-row-h);
}
/* ── 表头与数据**同一条竖轴**（2026-10-13 第 90 批 · 乐哥：「字段和表头没对齐」）────
   ⚠️⚠️ 为什么这张表要专门声明对齐：**数据格是 flex 容器**（`.pms-cellrow`，列内栈靠它
      对齐行高）—— 而 flex 项目**不受 `text-align` 影响** ⇒ 全站那套「默认居中 +
      `col-align-left`」（见 table.css 的《表格列对齐规范》）**对这张表完全无效**：
      表头按规范居中、数据一律贴左。第一列被撑到 651px 时偏差已 ≈300px。
   ⇒ 对齐必须由 flex 的 `justify-content` 表达，且**与表头同档**。档位照《表格列对齐规范》
      （乐哥 2026-09-14 定稿）：**长文本列靠左 / 其余居中 / 数值列靠右**。
   ⚠️ 把 `.col-align-left` / `.is-num` 一并接管：它们是 `app.js` 的 `fixColAlign()` 与
      数值列**事后**打上的全站类，只改 `text-align`（对 flex 无效）⇒ 不接管的话，
      某天表头被判成长文本列（th 靠左）而数据仍居中，**同一件事两个出口**又会错位。 */
#pms-links-list .pms-cellrow { justify-content: center; }
/* 长文本列（链路名 / 树）—— ⛔ 别改成居中：这一列是一整棵缩进树，居中会让父子左缘错开、
   层级读不出来。类名与表头**同一个**（`columns` 的 `align: 'left'` ⇒ th.pms-col-left）。 */
#pms-links-list td.pms-col-left .pms-cellrow,
#pms-links-list td.col-align-left .pms-cellrow { justify-content: flex-start; }
/* 操作列靠右（与 th/td 的 `.pms-col-op { text-align: right }` 同档）。 */
#pms-links-list td.pms-col-op .pms-cellrow,
#pms-links-list td.is-num .pms-cellrow,
#pms-links-list td.num .pms-cellrow,
#pms-links-list .pms-cellrow--right { justify-content: flex-end; }
/* 表头同档：`pms-col-left` 由 `tableCard` 的 `align: 'left'` 挂到 th 上（见 pages/project.js）。
   ⚠️ 全站默认是「全列居中」（table.css），这里用带 `.pms-table-wrap` 的一条压过它 ——
      与旁边 `.pms-col-op` 那条同一个写法（板块内联、⛔ 不动全站 table.css）。 */
.pms-table-wrap .table thead th.pms-col-left,
.pms-table-wrap .table tbody td.pms-col-left { text-align: left; }
/* 项目数列：栈行**竖排**（数字一行、「其中下级 N」第二行）——
   ⚠️ `.pms-cellrow` 默认是横向 flex，不特别处理的话那行小字会跟数字并排。
   ⚠️⚠️ 这里**必须把行高压进 34 以内**：这一列是六列里唯一"一行可能要放两行字"的，
      两行默认行高加起来 ≈ 36px ⇒ 那一行被撑高 3px、**它下面的每一行跟着下移**
      （实测第 1 行差 3px、第 2~4 行差 6px）。⇒ 两行的 line-height 都压到 1.15、
      `.pms-cell-sub` 的上边距压到 1px（13×1.15 + 12×1.15 + 1 ≈ 30 < 34）。 */
.pms-col-cnt .pms-cellrow {
  flex-direction: column;
  /* ⚠️⚠️ `column` 方向下**横向**对齐由 `align-items` 决定（`justify-content` 管的是纵向）
     ⇒ 第 90 批修表头/数据错位时，这一列必须在这里改，⛔ 改上面那条 `justify-content` 没用
     （那正是它当时明明写了 `justify-content: center` 却仍贴左的原因）。
     值与表头同档 = **居中**（《表格列对齐规范》：其余居中）。 */
  align-items: center;
  justify-content: center;
  line-height: 1.15;
}
.pms-col-cnt .pms-cell-sub { margin-top: 1px; line-height: 1.15; }
/* ⭐ 下级的数（含"其中下级 N"）是**从上级推出来的同一个数的前缀**，不是它自己的值 ⇒
   淡一档，与「端口 / 平台 / 状态」三列的继承值保持一致。
   ⚠️ 一级链路自己那一行**不淡**（它是这条链路真实的项目数）。 */
.pms-col-cnt .pms-cellkids .pms-cellrow { color: var(--color-muted); }
/* ⛔ 与 `.pms-tree__kids` 的 `margin: 1px 0 2px 6px` **上下必须逐值相同**
   （左右可以不同：左边那 6px 是缩进，这几列不缩进）。 */
.pms-cellkids { margin: 1px 0 2px; }

/* ── 继承值（下级链路的端口 / 平台 / 状态）──────────────────────────────────────
   ⭐ 下级不自己存端口/平台（服务端从顶层推，见 `store.js` 的 `root_partner_id`），
      所以写出来的值**是同一份**。视觉上必须让人看出"这不是它自己设的、是跟上级的"：
      · 淡一档（`--color-muted`）而不是正文色；
      · `title` 写明"跟随一级投放链路「UDS」"（悬停即知，不占版面）。
   ⚠️ 一级是「未挂靠」时下级**留空**（`ownerCell` 里判的）—— 同一件事标 N 遍橙 badge
      只会把整列染花（第 8 批删掉 `—` 占位就是为了这个）。
   ⛔ 不要做成 badge：badge 是"有状态要表态"，继承值是"同一个值再写一遍"，两者语义不同。 */
.pms-inherit { color: var(--color-muted); }
/* 下级「已停用」仍要能一眼看到 ⇒ 保留红字（这是"有话说"的那一种，不是重复）。 */
.pms-inherit--off { color: var(--color-danger); }

/* ── ⑤ 项目详情：「业务要素关系」链条（2026-10-10 第 9 批）────────────────────────
   乐哥：「最终看要不要做一个整体的，就是品牌，服务商端口，投放平台，链路，产品
   那种的关联关系图，这样更直观」→ 三选一里选了**放在项目里**
   （项目就是这五要素的交叉点：项目名 = 品牌 · 端口 · 链路全路径）。
   ⚠️ 这些格子是 `<button>`（能键盘聚焦、点一下跳页），⛔ 别套 `.btn` / `.btn--ghost`：
      table.css 会把表格里的 ghost 按钮统一成"软蓝实心"（本项目在行内操作上踩过），
      而套 `.btn` 会得到一个大号实心按钮，把一条链撑得又高又花。
   ⚠️ 颜色一律用现成令牌、不写裸 hex（`ui-token-standards` 门禁会红）。 */
.pms-rel__head {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 10px;
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  color: var(--color-fg);
}
.pms-rel__hint {
  font-size: var(--font-size-xs);
  font-weight: 400;
  color: var(--color-fg-3);
}
/* ⭐⭐ 2026-10-22 十七改（乐哥：「这个页面要重做，按照设计规范」）：
   原来五格是 `min-width: 96px` + `flex-wrap` ⇒ 宽度各随内容、越往右越窄，
   一屏里五格参差不齐（截图里最明显的问题）。
   ✅ 改成**等分**（`flex: 1 1 0`）+ 不换行（宽屏），格宽一致、箭头居中当分隔。
   ⚠️ 窄屏（<900px）放开换行，否则五格会被压成一条条。 */
.pms-rel__chain {
  display: flex;
  align-items: stretch;
  gap: 8px;
  flex-wrap: nowrap;
}
@media (max-width: 900px) {
  .pms-rel__chain { flex-wrap: wrap; }
}
.pms-rel__node {
  display: flex;
  flex-direction: column;
  gap: 4px;
  flex: 1 1 0;
  min-width: 0;
  padding: 10px 12px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  font-family: inherit;
  text-align: left;
  cursor: pointer;
  transition: border-color var(--motion-fast) var(--motion-ease-standard),
              background-color var(--motion-fast) var(--motion-ease-standard);
}
.pms-rel__node:hover { border-color: var(--color-primary-300); background: var(--color-accent-bg); }
.pms-rel__node:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; }
.pms-rel__label {
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: var(--font-size-xs);
  color: var(--color-muted);
}
.pms-rel__value {
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  color: var(--color-fg);
}
/* ⭐「投放平台」是**推导值**（项目自己不存平台，从链路所属推）⇒ 虚线 + 淡底，
   ⛔ 不能让它长得跟别的格子一样 —— 那会让人以为能在这儿改它。 */
.pms-rel__node--derived { border-style: dashed; background: var(--color-surface-2); }
.pms-rel__node--derived .pms-rel__value { color: var(--color-fg-2); }
.pms-rel__tag {
  font-size: var(--font-size-xs);
  color: var(--color-fg-3);
}
/* 空格子：更淡，但**照样可点**（点过去配）—— 否则看到"未设置"却不知道去哪设。 */
.pms-rel__node--empty .pms-rel__value { color: var(--color-fg-3); font-weight: 400; }
.pms-rel__arrow {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 14px;
  color: var(--color-fg-3);
  font-size: var(--font-size-xs);
  user-select: none;
}
.pms-rel__sub {
  margin-top: 10px;
  font-size: var(--font-size-xs);
  color: var(--color-muted);
}

/* ── ⑤ 业务线总览（2026-10-10 第 10 批）───────────────────────────────────────
   乐哥：「就是品牌，服务商端口，投放平台，链路，产品那种的关联关系图，这样更直观」
     → 三选一里选了「C：品牌视角的业务线总览」。
   ⚠️ 树本身**复用** `.pms-tree` / `.pms-tree__kids`（与链路页同一套导轨 + 层级字号）——
      这里只补"这一页独有"的三样：品牌选择行、平台块头、项目小胶囊。
   ⭐ 第 15 批（乐哥：「业务线总览，也按照上面展示的这样做吧」）：
     这一页的树原来**偏紧、而且导轨压在一级名字里**（实测行高 25/23.5/22，导轨落在名字第 9px）。
      ① 行高：链路页那套 `--pms-lk-row-h: 34px` 是挂在 `#pms-links-list` 上的
         （借 id 压过 table.css 的"全列居中"）⇒ 这一页不在表格里、拿不到 ⇒ **这里也给它一份**。
         ⛔ 值必须与链路页**同一个数**（34px = 控件紧凑档）；两处各写一个数就会又分叉。
      ② 导轨压字：前端给每个节点补了"折叠箭头等宽占位"（`.pms-tree__fold-ph`）⇒
         名字从节点 24px 开始、导轨回到占位正中 —— 与链路页**逐值一致**。 */
#pms-bl-body {
  --pms-lk-row-h: 34px;
  /* ⭐⭐ 第 23 批（乐哥：「用彩色边框，白色填充」）：三类节点的**类型色**（脑图里作 1px 边框）。
     ⭐⭐ 第 24 批起就挂在 `#pms-bl-body` 上 —— 业务线脑图的格子、面包屑、标签都要用
        **同一套环节色**；（⛔ 别在某个具体的脑图容器里再留一份，两份会各走各的。）
        ⭐ 第 65 批：格子那一层改用 `--pms-k-*`（与「新建项目」脑图同一套），
        这三个别名仍被 `.pms-bl-ln__seg` 等旧件引用，保留。
     取值 = 既有 `--color-tone-*-fg` 那一族（白底上的对比度都在 4.3:1 以上，三档深浅一致
     ⇒ 摆在一起不会一个抢一个）。 */
  --pms-node-brand: var(--color-tone-blue-fg);   /* 品牌 = 钴蓝（根） */
  --pms-node-chan: var(--color-tone-teal-fg);    /* 投放平台 = 青绿 */
  --pms-node-link: var(--color-tone-purple-fg);  /* 链路 = 紫（数量最多的一档） */
}
#pms-bl-body .pms-tree { min-height: var(--pms-lk-row-h); }
.pms-bl__head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.pms-bl__label {
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  color: var(--color-fg);
}
.pms-bl__sel { width: 220px; }
.pms-bl__hint {
  flex: 1 1 260px;
  min-width: 0;
  font-size: var(--font-size-xs);
  color: var(--color-muted);
}
.pms-bl__chhead {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  border-bottom: 1px solid var(--color-border-soft);
}
.pms-bl__chname {
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  color: var(--color-fg);
}
.pms-bl__ports { font-size: var(--font-size-xs); color: var(--color-muted); }
.pms-bl__warn { color: var(--color-danger); }
.pms-bl__tree { padding-top: 6px; }
.pms-bl__none { font-size: var(--font-size-xs); color: var(--color-fg-3); }
/* 「这条线上的项目」小胶囊：点一下进项目详情。
   ⛔ 不套 `.btn`（会变成大号实心按钮，把一行撑高）；存在感对齐 `.pms-tree__add`。 */
.pms-bl__pj {
  flex: none;
  margin-left: 8px;
  padding: 1px 8px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-pill);
  background: var(--color-surface);
  color: var(--color-accent-text);
  font-family: inherit;
  font-size: var(--font-size-xs);
  white-space: nowrap;
  cursor: pointer;
  transition: border-color var(--motion-fast) var(--motion-ease-standard),
              background-color var(--motion-fast) var(--motion-ease-standard);
}
.pms-bl__pj:hover { border-color: var(--color-primary-300); background: var(--color-accent-bg); }
.pms-bl__pj:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; }

/* ⭐ 第 17 批（乐哥 2026-10-11：「丙·每条业务线一行 和乙都做，两种方式我们可以自己切换」）：
   业务线总览的**展示方式开关**。放品牌筛选那一行的末尾 —— `hint` 是 `flex: 1 1 260px`
   ⇒ 它会被推到最右边、与「品牌 [下拉]」同排（放得下就一行，放不下自动换行）。
   ⛔ 不给它加 `.table-tabs`：那个类会被 app.js 的页头布局逻辑搬进页头，而这里要它留在卡内。 */
.pms-bl__views { flex: none; }

/* ── ⭐ 第 18 批：「每个环节都能点」（乐哥：「每个环节能不能点啊」）─────────────────
   语义与项目详情页「业务要素关系」的格子（`.pms-rel__node`）**同一套**：
   点一下 → 去对应页面维护（品牌 → 合作品牌；平台 / 端口 / 产品 → 渠道信息对应 tab；
   链路 → 投放链路 tab）。落点在 JS 的 `GO` 常量里，这里只管"看起来能点、点得着"。
   ⚠️ 这些元素原本是 `<span>`，现在换成 `<button>`（键盘可达、语义正确）⇒
      先把 button 的**默认外观**抹掉（padding / border / background / font-family / text-align）。
   ⛔⛔ **这里故意不写 `font-size` / `font-weight` / `color`，也不写 `font: inherit`** ——
      `button.pms-xxx`（特异性 0,1,1）会**盖掉**管字号的那些类（`.pms-bl-ln__seg--lv1` = 0,1,0），
      写成 `font: inherit` 会让四档字号全部失效。UA 的 `button { font: … }` 是**元素选择器**（0,0,1），
      天然输给类 ⇒ 不重置也不会用错字号。 */
button.pms-tree__name,
button.pms-bl__chname, button.pms-bl__ports,
button.pms-bl-ln__seg, button.pms-bl-ln__tag,
button.pblm-nd__nm {
  padding: 0;
  border: 0;
  background: none;
  font-family: inherit;
  line-height: inherit;
  text-align: left;
  cursor: pointer;
}
/* 悬停反馈：变主色 **+ 下划线** —— 光变色不够，"这里能点"要一眼看得出来
   （它们原本是纯文字，没有任何"按钮长相"；下划线是最省地方的提示）。 */
button.pms-tree__name:hover,
button.pms-bl__chname:hover, button.pms-bl__ports:hover,
button.pms-bl-ln__seg:hover, button.pms-bl-ln__tag:hover,
button.pblm-nd__nm:hover {
  color: var(--color-accent-text);
  text-decoration: underline;
}
/* 键盘聚焦可见（与全站同款） */
button.pms-tree__name:focus-visible,
button.pms-bl__chname:focus-visible, button.pms-bl__ports:focus-visible,
button.pms-bl-ln__seg:focus-visible, button.pms-bl-ln__tag:focus-visible,
button.pblm-nd__nm:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* ── 形态「每条业务线一行」────────────────────────────────────────────────────
   一行 = 一条**走到底**的业务线（叶子链路，或自己就挂着项目的中间链路）。
   左：面包屑全路径 ｜ 中：端口 / 产品 ｜ 右：项目胶囊。
   ⚠️ 逐段的字阶**与「树 / 脑图」同一套四档**（14/600 → 13/500 → 12/500 → 12/400）——
      三处看的是同一件事，各写一份字号就会分叉（与第 14 批给树定档同一条纪律）。
      ⛔ 别改成 `--font-size-sm` 这类令牌：树那四条是**硬写的 px**（窄屏不断档），
         这里若用令牌，窄屏下会缩到 13px、两处就不一样了。 */
/* ⭐⭐ 第 18 批（乐哥：「有点挤，太挤了放宽」）：行高 34 → 46、内边距与段间距一起放大。
   ⚠️ 这一页是**阅读型**列表（一行 = 一条业务线，一屏就看这几行），不是链路列表那种
      "六列数据要塞进一行" ⇒ 它没有理由沿用紧凑档 34。
   ⚠️ 改这里**不影响**链路列表：那个 34 挂在 `#pms-links-list` 上，是另一份令牌。 */
.pms-bl-ln { padding-top: 6px; padding-bottom: 8px; }
.pms-bl-ln__row {
  display: flex;
  align-items: center;
  gap: 14px;
  min-height: 46px;
  padding: 8px 10px;
  margin: 0 -10px;
  border-radius: var(--radius-sm);
  border-bottom: 1px solid var(--color-border-soft);
}
.pms-bl-ln__row:last-child { border-bottom: 0; }
.pms-bl-ln__row:hover { background: var(--color-surface-2); }
/* 左段：面包屑。`flex: 1 1 auto` + `min-width: 0` ⇒ 路径长了先挤它，不挤右边的项目胶囊 */
.pms-bl-ln__path {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 6px;
}
/* ⭐ 第 24 批（乐哥：「这个链路，**不同环节用不同颜色文字**，就按照脑图那个定义的
   颜色语义」）：这一串面包屑**每一段都是「链路」这个环节** ⇒ 统一走 `--pms-node-link`
   （= 脑图里"链路"格边框的那个紫）。
   ⚠️ 层级深浅**改由字号 / 字重**表达（lv1 最大最重 → lv4 最小最细）——
      ⛔ 别把 `color` 加回 `--lvN`：颜色已经被"环节色"占用，两者**特异性相同**、
         只能靠书写顺序决胜 ⇒ 必然出现"改了一条、另一条不生效"的静默故障。
   ⚠️ 白底对比度：紫 `#5346B7` ≈ 8.2:1，过正文线（4.5:1）。 */
.pms-bl-ln__seg { flex: none; color: var(--pms-node-link); }
.pms-bl-ln__seg--lv1 { font-size: 14px; font-weight: 600; }
.pms-bl-ln__seg--lv2 { font-size: 13px; font-weight: 500; }
.pms-bl-ln__seg--lv3 { font-size: 12px; font-weight: 500; }
.pms-bl-ln__seg--lv4 { font-size: 12px; font-weight: 400; }
/* 终点那一段（这条线实际落在哪条链路上）：比它所在档再重一档 —— 一行扫下来先看到它 */
.pms-bl-ln__seg.is-end { font-weight: 600; }
.pms-bl-ln__sep { flex: none; color: var(--color-fg-3); font-size: 12px; user-select: none; }
.pms-bl-ln__tags {
  flex: 0 1 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 10px;
  overflow: hidden;
}
.pms-bl-ln__tag {
  font-size: var(--font-size-xs);
  color: var(--color-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* ⭐ 第 24 批：标签按**维度**上色（与脑图框内小字、脑图节点类型色**同一套**）——
   扫一行就知道哪段是「端口」、哪段是「产品」。
   ⚠️ 写在 `.pms-bl-ln__tag` **之后**：修饰类与它**特异性相同**（0,1,0），同分靠书写顺序决胜。
   ⚠️ hover 仍由 `button.pms-bl-ln__tag:hover`（0,2,1）接管变主色 —— 可点的语义不丢。 */
.pms-bl-ln__tag--partner { color: var(--color-tone-teal-fg); }
.pms-bl-ln__tag--product { color: var(--color-tone-orange-fg); }
.pms-bl-ln__pjs {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  flex-wrap: wrap;
  gap: 6px;
  max-width: 40%;
}
.pms-bl-ln__pjs .pms-bl__pj { margin-left: 0; }

/* ── 形态「脑图」（第 65 批：**横向五层** = 品牌 › 服务商端口 › 投放平台 › 投放链路 › 产品）──
   ⭐⭐ 乐哥 2026-10-12：「业务线这个脑图，要按照我们新增项目里面的脑图那样**一层一层的展示**
   （不过是横向的），这样更直观，**当前这个缺环节，会很奇怪**」。
   ▸ 布局与「新建项目」脑图（`.pjm`）**同一套做法**：JS 算坐标 + 绝对定位格子 + SVG 折线。
     那边是纵向（层 = 行、左边一列层名），这边是横向（层 = 列、**顶上一行层名**）。
   ▸ ⛔ 别再退回"嵌套 ul/li 的 org-chart"（那是本节改前的实现）：它每加一级都要真嵌一层 `ul`，
     而端口 / 产品当时只能挤在格子里当小字 —— 正是"缺环节"的来源。
   ⚠️ 层名行的高度由 JS 给（`--pblm-hdr`，与 `BLM.HDR` 同一个数）——
      两处各写一个数就会分叉（层名行与格子的纵向起点错开）。 */
.pblm-wrap { padding: 14px 16px; }
.pblm { border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow: hidden; }
.pblm__scroll { overflow-x: auto; }
.pblm__canvas { position: relative; width: max-content; min-width: 100%; }
.pblm__inner { position: relative; }
.pblm-lay { position: absolute; top: 0; height: var(--pblm-hdr);
  display: flex; align-items: center; justify-content: center;
  font-size: var(--font-size-xs); color: var(--color-muted); }
.pblm__hdline { position: absolute; left: 0; right: 0; border-top: 1px solid var(--color-border); }
/* 层带 = 列与列之间的 1px 竖线（横向脑图的"层"是列 ⇒ 分隔线也竖着走） */
.pblm-band { position: absolute; top: 0; bottom: 0; border-left: 1px solid var(--color-border); }
.pblm__svg { position: absolute; left: 0; top: 0; color: var(--color-border); pointer-events: none; }

/* 格子：白底 + 2px 层级色边（层级色走行内的 `--pbl-c`）—— 与 `.pjm-nd` 同一档观感。
   ⚠️ 圆角走 `--radius-lg` 令牌（`tests/radius-tokens.test.js` 守着"不许写死 px/%"）。 */
.pblm-nd { --pbl-c: var(--color-primary-500);
  position: absolute; box-sizing: border-box; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 3px; padding: 4px var(--space-2);
  border-radius: var(--radius-lg); background: var(--color-surface);
  border: 2px solid var(--pbl-c); font-size: var(--font-size-sm); color: var(--color-fg);
  transition: background var(--motion-fast) var(--motion-ease-standard); }
.pblm-nd.is-root { background: var(--pms-k-brand-bg); }
.pblm-nd__nm { max-width: 100%; padding: 0; border: 0; background: none; font-family: inherit;
  font-size: var(--font-size-sm); font-weight: var(--font-weight-medium); color: var(--color-fg);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
button.pblm-nd__nm { cursor: pointer; }
button.pblm-nd__nm:hover { color: var(--color-accent-text); }
/* 链路格第二行：项目胶囊 / 「还没建项目」 */
.pblm-nd__row { display: flex; align-items: center; gap: 4px; max-width: 100%; }
.pblm-nd__none { font-size: var(--font-size-xs); color: var(--color-fg-3); }
/* ⋯ 钉在格子**右上角内侧**；＋ 钉在"右缘垂直居中"（= 出线起点）⇒ 两颗分开摆，
   ⛔ 别挨在一起（本项目对"两个入口看着像一件事"零容忍）。 */
.pblm-nd__acts { position: absolute; right: 1px; top: 1px; display: flex; }
/* ⭐ ＋：**搬出文档流**、钉在这一格的右缘垂直居中（＝延伸下一个环节的位置）。
   ⚠️ 它是盒子的**兄弟**（盒子 `overflow: hidden` 装不下它）⇒ 用相邻选择器跟 hover 联动；
      这样它既不占文档流、也不会把格子撑高（第 41 批定的做法，`pjm-add` 同款）。 */
.pblm-add { --pbl-c: var(--color-primary-500);
  position: absolute; width: 22px; height: 22px; padding: 0; border-radius: var(--radius-full);
  border: 1.5px solid var(--pbl-c); background: var(--color-surface); color: var(--pbl-c);
  font: inherit; font-size: 14px; line-height: 1; display: flex; align-items: center;
  justify-content: center; cursor: pointer; opacity: 0; z-index: 3;
  transition: opacity var(--motion-fast) var(--motion-ease-standard); }
.pblm-nd:hover + .pblm-add, .pblm-add:hover, .pblm-add:focus-visible, .pblm-add.is-open { opacity: 1; }
.pblm-add:hover { background: var(--pbl-c-bg); }
@media (hover: none) { .pblm-add { opacity: .55; } }
/* 浮层底部那行"为什么这里能加的是平台"的说明（第 65 批：品牌 / 端口两格要解释口径） */
.pblm-pop__note { margin-top: var(--space-2); padding-top: var(--space-2);
  border-top: 1px solid var(--color-border-soft);
  font-size: var(--font-size-xs); color: var(--color-muted); max-width: 260px; }

/* 节点上的就地操作（`project.js` 的 `menuBtnHtml`）——
   ▸ 默认 `opacity: 0`、**鼠标移到格子上才浮出**；`:focus-visible` 也要露（键盘 tab 过去看得见）。
   ▸ 圆角走 `--radius-full` 令牌（`tests/radius-tokens.test.js` 守着"不写死 50%/999px"）。 */
.pms-nodemenu {
  flex: none;
  width: 18px;
  height: 18px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-full);
  background: var(--color-surface);
  color: var(--color-muted);
  cursor: pointer;
  opacity: 0;
  transition: opacity var(--motion-fast) ease, color var(--motion-fast) ease,
    border-color var(--motion-fast) ease;
}
.pblm-nd:hover .pms-nodemenu,
.pms-nodemenu:focus-visible { opacity: 1; }
.pms-nodemenu:hover { color: var(--color-primary-500); border-color: var(--color-primary-500); }
/* 触屏 / 没有 hover 的设备：一律常显（否则这个入口在那儿根本摸不到） */
@media (hover: none) { .pms-nodemenu { opacity: .55; } }

/* ── 「业务线 / 项目列表」视图切换（2026-10-11 合并成一页后新增）────────────────
   ⚠️ 用全站通用的 `.seg`（与本页上面那排"脑图 / 每线一行 / 树"同款），只是外面留间距。
   ⛔ 不给它加 `.table-tabs` —— app.js 的页头布局会把那个类搬走（它是卡内控件）。 */
/* ⭐⭐ 第 30 批：「视图 tab（左） + 新建项目（右）」= **一条 tab 行**。
   乐哥：「这俩放悬浮 tab 栏里面」「新增项目这个按钮，应该放在悬浮 tab 里」。
   ⚠️ 这一行**不是** app.js 那套"页头吸顶 TAB"（那是 `.table-tabs` 的活）——
      项目板块是**独立外壳**，不走 `applyFilterBarLayout`，它是页面内的控件行。
      ⛔ 别给它加 `.table-tabs`：那会被 app.js 的页头布局搬走。
   ⚠️ `space-between` 把按钮推到右端；按钮**只在有 `project:list:manage` 时渲染**，
      没权限时左边 seg 照旧靠左、右侧空着 —— 不需要额外处理。 */
/* ⚠️⚠️ `#pms-pj-tabs` 是上面那条 tab 行的**渲染槽**（`paintViewTabs` 往它里面写
   `.pms-pj__tabbar`；切视图时它**不重建**，只重画 `#pms-pj-view`）。
   它是普通 div ⇒ 高度**恰好等于** tab 行本身（实测 64 = 64）。
   ⛔⛔ 作为 sticky 元素的**包含块**时，这样的盒子 = **零行程**：
   `position: sticky` 一点作用都没有（真机实测：滚到底 tab 行 `rect.top = −627`、
   `is-stuck` 恒 false —— **不报错，就是没吸顶**，本项目最怕的静默失效）。
   ✅ `display: contents` 让它**不产生盒子** ⇒ tab 行的包含块变成 `.pms-body`（很高）
   ⇒ sticky 恢复行程。（渲染槽照旧可用：`display:contents` 只影响布局、不影响 DOM 与 innerHTML。）
   ⛔ 别改成"给它写个高度"：那会凭空多出一块空白，而且高度写多少都是拍脑袋。
   📌 这条也是第 86 批真机复查才发现的 —— 第 85 批那轮验证被探针掩盖了：
      当时把克隆内容塞进了 **这个包裹层**（正好把它撑高、临时给了行程），
      于是"吸顶正常"是**探针造出来的**。验证必须把内容塞进**真实的列表容器**。 */
#pms-pj-tabs { display: contents; }

.pms-pj__tabbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  /* ⭐⭐⭐ 2026-10-13 第 86 批：**这一行 = 全站标准的"含 tab 的页头卡"**
     （乐哥：「项目管理页面的 tab，表格这些也按照设计规范做」→
       「我发的那个样式是和全站标准一样，但是细节我不清楚，**细节要按照全站标准做**」）。
     ──────────────────────────────────────────────────────────────────────────
     ▸ **逐值同源** = `layout.css` 的 `.page-header--has-tabs`（全站"含 tab 的一级页头"）。
       ⛔ 下面这些声明**一处都不许自己写值**（写死值 = 造出"同一种东西两套数值"，
          本项目对此零容忍；要改先改全站那一处、两处一起动）：
            background / border / border-radius / box-shadow / padding / margin-bottom /
            position / top / z-index / transition
     ▸ 为什么是"白底圆角卡"：全站那行是 2026-09-28 乐哥拍板的「页头包盒」——
       页头从「裸放」收进白底圆角卡，解决"内容区上面太空 / 左侧菜单显高"。
     ⚠️⚠️ 第 85 批只做了**吸顶那一半**（sticky + 贴顶变玻璃），常态还是**裸放透明**
        ⇒ 本次把常态补齐（与全站同款白卡），两半这才凑成完整一套。
     ⚠️ `margin-bottom: var(--space-5)`(20) 看着比原来的 12 大，但**视觉间距一点没变**：
        它与下方 `#pms-pj-view` 自带的 `margin-top: 20px` 会 **margin-collapse**，
        取 max(12,20) = max(20,20) = **20**（改前后实测都是 20）⇒ 纯粹是"数值也对齐全站"。
     ▸ `top: var(--tabs-bar-height)`（= 顶栏 60）—— ⛔ 别加 `--frame-gap`、也别写 `+4`：
       规范里为此踩过两次（会悬在顶栏下方一截）。
     ⚠️⚠️ 项目板块是**独立外壳**，`app.js` 的 `updateStuck()` 扫描清单里原本没有本类
        ⇒ 那一行必须**同时**加（否则 `is-stuck` 永远不挂上 = 玻璃静默失效，改 CSS 也白改）。 */
  background: var(--color-surface);
  border: 1px solid var(--color-frame-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
  padding: 10px;
  margin-bottom: var(--space-5);
  position: sticky;
  top: var(--tabs-bar-height);
  z-index: 60;
  transition: background-color var(--motion-base) var(--motion-ease-standard),
              border-color var(--motion-base) var(--motion-ease-standard),
              box-shadow var(--motion-base) var(--motion-ease-standard);
}

/* 吸顶态 = **只换材质**（白卡 → 液态玻璃），几何**一点不动**。
   与 `.page-header--has-tabs.is-stuck` **逐值同源**：只动 background / border-color /
   box-shadow ＋ 上毛玻璃；⛔ **不动** padding / margin / border-radius（全站那处也不动）。
   ⚠️⚠️ 第 85 批这里是「padding 外扩 + 等量负 margin」（那时底子是**透明裸行**，
      玻璃得往外铺一圈才盖得住内容区两侧）。现在底子已经是**白卡**（与全站同源）
      ⇒ 那套外扩**必须撤掉**：
        ① 留着 = "卡里再套一个玻璃块"（两层底），与全站形态不同；
        ② 少一对互相抵消的数值 ⇒ 少一处"吸顶那一刻布局跳 8px"的隐患。
      （两态几何完全一致 ⇒ 贴顶时**不可能**有跳动，这是比"抵消得刚好"更稳的做法。） */
.pms-pj__tabbar.is-stuck {
  background: var(--liquid-bg);                 /* 135deg 斜向渐变（液态玻璃的走向） */
  -webkit-backdrop-filter: var(--liquid-blur);
  backdrop-filter: var(--liquid-blur);
  border-color: var(--liquid-edge);
  box-shadow: var(--liquid-shadow);
}

/* ⭐ 第 44 批：视图切换行**右端**那一组（「?」说明 + 「＋新建项目」）。
   ⚠️ 必须包一层：`.pms-pj__tabbar` 是 `space-between`，直接多塞一个子元素会被推到**正中间**。 */
.pms-pj__right {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}
/* ⭐⭐⭐ 2026-10-17 全站 UI 统一（乐哥：「全局检查下系统的 ui，包括按钮宽高样式、
   模块的样式等等，全部统一」）—— **页头主操作按钮 = 42，与左侧 tab 托盘同高**。
   ──────────────────────────────────────────────────────────────────────────────
   ▸ 实测病根（1680 视口 / dpr1，真机读 rect）：
       `.seg.pms-pj__views` 托盘 **42** ｜ `.pms-pj__right .btn` **34**
       ⇒ 同一行里右侧主按钮比左侧托盘矮 **8px**，肉眼可见的错位。
   ▸ 为什么是 42 而不是 44/34：这不是新定的档，是**把已有的口径补齐**——
       达播（`.page-actions`）与财务（`.page-actions`）在"页头带 tab"时**本来就是 42**
       （`layout.css` 2026-10-07 乐哥拍板「其他控件升到 42」与托盘同高，令牌
        `--control-height-row`）；项目板块当时漏了 ⇒ 这里只补这一处，⛔ 不新造数值。
   ▸ 为什么不改 `.btn--sm` 本身：全站 34 是表格行内/工具栏的主流（400+ 处引用），
     只有"页头这一行"需要与托盘配平 ⇒ 作用域限定在 `.pms-pj__right` 内。
   ⚠️ 排除 `.page-help-btn`（那颗「?」是 24px 圆钮，不是按钮族，不能被拉到 42）。
   ⚠️ 容器 `.pms-pj__tabbar` 高 64 = padding 10×2 + 托盘 42 + 边框 1×2 ⇒ 按钮 42
      仍在行内（不撑高、不溢出），已真机复核。 */
.pms-pj__right .btn:not(.page-help-btn) {
  height: var(--control-height-row);
  min-height: var(--control-height-row);
  /* 与达播 / 财务的页头主操作**逐值同源**（含内边距）——见 layout.css 同名那组规则 */
  padding-left: var(--space-4);
  padding-right: var(--space-4);
}
/* 间距上移到 `.pms-pj__tabbar`（它才是这一行的容器） */
/* ⭐ 2026-10-16（乐哥：「搜索框…应该**在 tab 右边**」）：
   `.pms-pj__tabbar` 是 `space-between` —— 左端要放「签 + 搜索框」两件东西时必须**包一层**，
   否则第二个子元素会被推到**正中间**（本项目在别处踩过同一坑，见 `.pms-pj__right` 的注释）。
   ⚠️ 间距用全站令牌，⛔ 不写死像素；`min-width: 0` 让窄屏时搜索框能被压缩而不是把行撑破。 */
.pms-pj__left {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-width: 0;
}
/* ⛔ 2026-10-16：开户管理那张表的「大类分隔竖线」加过一次、当天又被乐哥撤掉
   （「竖线不要，去掉」）⇒ 规则已删。这里记一笔是为了**别再加回来**：
   全站对竖线的口径见 `table.css` 里那段（乐哥 2026-09-14：「不用所有表格都加竖线，
   微信小店这种大类加竖线的就很好」）—— 那是**全站标准**，要动先动那里。 */
.pms-pj__views { margin-bottom: 0; }

/* ⭐ 2026-10-13「全平台」签下「新增开户」第一步：选平台（里面的按钮排成一行）。
   ⚠️ 用 `grid` + `auto-fit`（⛔ 不写死列数）：平台会从 4 个长到十几个，
      写死 4 列到时换行会挤成一坨。 */
.pms-a-pick {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: var(--space-2);
}
/* ⭐⭐ 第 95 批（乐哥：「这个导入的 ui 还有方式，**按照系统标准做**」）：
   投放账户批量导入 —— 上传件改用**全站标准** `.upload-drop`（样式在 `components.css`，
   与「消耗明细导入」「协议酒店账单导入」「合同识别」同一件），这里只补"图标 + 提示"的排版。
   ⛔ 原来这里摆的是**原生 `<input type="file">`**（"选择文件｜未选择任何文件"那个灰框）——
      与全站控件风格两回事。`contract-extract-ui.js` 第 91 批已经因同一个写法改过一次，
      注释里写着「现改为复用全站标准上传件 `.upload-drop`」。
   ⚠️ ⛔ **不要再往回到原生 input**（门禁 `pms-account-import` A5 锁着这一条）。 */
.pms-imp__drop-in { text-align: center; }
.pms-imp__drop-tip { margin-top: 8px; color: var(--color-fg-2); }
/* ⭐⭐ 第 96 批（乐哥 2026-10-14 截图：「这个**间隔太小了**，不对」）：
   弹窗里「两组」之间的间距**由全站容器 `.form-grid` 的 `gap`（--space-5 = 20px）给** ——
   JS 那边已把两组改成同一个 `.form-grid` 里的两个 `.span-2`（见 accountImportDialog）。
   ⛔ 这里**不补**任何"组间 margin"：一旦在这写，就变成第二处间距定义，改一处漏一处。
   本段只管**结果区与上传区之间**那一段（它不在 `.form-grid` 里）。 */
.pms-imp__result { margin-top: var(--space-4); }
/* ⚠️ 还没预检时结果是空的 ⇒ `display:none` 把那段 margin 也一起消掉，
   否则弹窗底部会凭空多出 16px 空白（空盒子有 margin 没内容）。 */
.pms-imp__result:empty { display: none; }
/* 结果区里可能有两张卡（结果卡 + 碰重卡）—— 卡与卡之间同样隔一段。 */
.pms-imp__result > * + * { margin-top: var(--space-4); }
/* 结果卡里那一行动作 = 「确认导入」+ 一句"确认后会写什么"
   （照「消耗明细导入」把确认按钮放**卡内**，⛔ 不摆到弹窗 footer 上）。 */
.pms-imp__foot {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
  margin-top: var(--space-3);
}
/* ⭐ 第 23 批：格子改成**白底**之后，原本"白底胶囊"在白框里就看不出来了
   ⇒ 脑图内的胶囊换成浅灰底（`--color-surface-2`）。
   ⚠️ 只作用于**脑图内**（限定 `.pblm-nd__row`）—— 表格里的胶囊还在白底上，保持原样。 */
.pblm-nd__row .pms-bl__pj { margin-left: 0; max-width: 100%; overflow: hidden; text-overflow: ellipsis; background: var(--color-surface-2); }
/* ⚠️ 必须补一条脑图内的 hover：上面那条与全站的 `.pms-bl__pj:hover` **特异性相同**（0,2,0），
   而它写在后面 ⇒ 不补的话胶囊 hover 时底色不会变（看着"点不动"）。 */
.pblm-nd__row .pms-bl__pj:hover { background: var(--color-accent-bg); }

/* 表格卡：卡片内表格贴边（与全站列表页一致 —— 表头那行的 20px 留白由 table.css 给） */
.pms-card__body { padding: 0; }

/* 合同链接：与表格里其他链接同款（主色 + 无下划线，hover 才出现） */
.pms-link {
  color: var(--color-accent-text);
  text-decoration: none;
}
.pms-link:hover { text-decoration: underline; }

/* ── ③ 品牌 × 渠道 勾选矩阵 ──────────────────────────────────────────────────
   横向可滚动（渠道数量会增长到十几个），首列品牌名**吸左**（sticky）——
   否则横向滚到右边会不知道这一行是哪个品牌（本项目在宽表上踩过同类问题）。
   ⚠️ 每个单元格固定宽度，不按内容自适应：勾选框宽窄不一会让"哪一列是哪个渠道"
      读起来错位（表头与列必须严格对齐）。 */
/* ⭐ 第 43 批：卡头右侧的"动作按钮 + 共 N 条"包裹层（见 `tableCard` 的 `headerAction`）。
   ⚠️ 必须包一层再塞进 `.card__header`：那个容器是 `justify-content: space-between`，
      直接多塞一个子元素会被推到**正中间**（既不靠左也不靠右）。
   ⚠️ 用 `flex-wrap: wrap`：窄屏时按钮与计数宁可换行，也别把卡标题挤成两行。 */
.pms-card-head {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-3);
}

.pms-links {
  overflow-x: auto;
  padding-bottom: var(--space-2);
}
.pms-links__row {
  display: flex;
  align-items: center;
  min-height: 44px;
  border-bottom: 1px solid var(--color-border-soft);
  /* ⚠️ 2026-10-09 修（乐哥：「我框起来的这些文字位置不对」）：
     **右端留 24** —— 与 `.card__header` 的左右 padding（`--space-6`）对齐。
     漏了的现象：最后一列勾选框紧贴卡片右边缘（贴到 1px 边框上），
     与上方卡片头的说明文字不在同一条竖线上。 */
  padding-right: var(--space-6);
}
.pms-links__row:last-child { border-bottom: 0; }
.pms-links__row--head {
  min-height: 38px;
  color: var(--color-muted);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-medium);
}
.pms-links__brand {
  flex: 0 0 200px;
  position: sticky;
  left: 0;
  /* ⚠️ sticky 列必须有**不透明背景**，否则横向滚动时下层内容会透出来 */
  background: var(--color-surface);
  /* ⚠️⚠️ 2026-10-09 修（乐哥报障）：**左内边距必须与卡片头一致（24 = `--space-6`）**。
     原为 0 ⇒ 品牌列文字比卡片标题「品牌渠道配置」**戳出 24px**，两者左边界不齐
     （实测：标题文字 x=297，表格首列文字 x=273）。
     ⛔ 不能改成给 `.pms-links__row` 加 `padding-left`：品牌列是 `position:sticky; left:0`，
        行的左内边距会让"粘住"的位置停在 padding 之外 ⇒ 横向滚动时后面的渠道列
        会从那条 24px 缝隙里**钻出来**。必须加在**品牌列自己**身上。
     ⚠️ 品牌列总宽仍是 200：左 24 + 内容 + 右 16。 */
  padding-left: var(--space-6);
  padding-right: var(--space-4);
  z-index: 1;
}
.pms-links__cell {
  flex: 0 0 96px;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* 自造复选框：藏原生控件、画一个方框。
   ⚠️ 为什么不用 `<input type="checkbox">` 原样：浏览器原生勾选框在各平台样式不一，
      与全站「柔和圆角」的语言不搭（本项目在开关/单选上已有统一自造件的先例）。
   ⚠️ 但**原生控件必须留在 DOM 里**（只做视觉隐藏，`opacity:0`）——
      否则键盘可达性与 `change` 事件都没了（本项目在"自造控件丢了键盘操作"上栽过）。 */
.pms-check {
  position: relative;
  display: inline-flex;
  cursor: pointer;
}
.pms-check input {
  position: absolute;
  inset: 0;
  opacity: 0;
  cursor: pointer;
  margin: 0;
}
.pms-check__box {
  width: 20px;
  height: 20px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  transition: background-color var(--motion-fast) var(--motion-ease-standard),
              border-color var(--motion-fast) var(--motion-ease-standard);
}
.pms-check input:checked + .pms-check__box {
  background: var(--color-accent);
  border-color: var(--color-accent);
}
/* 勾：用 CSS 画（不引图标 —— 勾在小尺寸下用 SVG 反而看不清；这里两笔画最清楚） */
.pms-check input:checked + .pms-check__box::after {
  content: "";
  position: absolute;
  left: 7px;
  top: 3px;
  width: 5px;
  height: 10px;
  border: 2px solid #fff;   /* ⚠️ #fff 是全站唯一允许的裸色（规范明文例外）；深蓝底上画白勾 */
  border-top: 0;
  border-left: 0;
  transform: rotate(45deg);
}
/* 禁用态（没有管理权限时）：整体降透明度，仍可读 */
.pms-check input:disabled { cursor: not-allowed; }

/* ── 表单里的「多选」（2026-10-10，目前只有「投放链路 › 适用平台」用它）──────────
   勾选框外观**复用** `.pms-check__box`（见上），这里只补"一行能排几个 + 带文字标签"。
   ⛔ 不要改 `.pms-check` 本身：品牌渠道勾选矩阵靠它做**纯方块**（无文字），
      加内边距/文字样式会把那个矩阵的列宽撑歪。 */
.pms-multi {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3) var(--space-4);
  padding: 4px 0 2px;
}
.pms-multi .pms-check { align-items: center; gap: 6px; }
/* ⚠️ 勾选框是 20px 方块，文字基线要对齐方块中心 ⇒ 用 flex 居中（上面那条 gap 已给间距），
   不再单独调 line-height（那是补偿中文字面的老写法，这里不需要）。 */
.pms-multi__label {
  font-size: var(--font-size-sm);
  /* ⚠️ 正文色令牌在本项目叫 **`--color-fg`**（不是 `--color-text` / `--color-text-primary`，
     那两个全站**没有定义** ⇒ 整条声明被浏览器丢弃、静默不生效；
     门禁 `css-var-defined` 会拦）。次级色用 `--color-muted`。 */
  color: var(--color-fg);
}
/* ── ⭐⭐ 第 26 批：多选的**两级树**（「产品 › 挂在哪些链路」用它）─────────────────────
   乐哥：「这个产品选择链路的方式感觉不对，**就是完全没分类**」⇒ 按**一级链路**分组：
   组头 = 一级链路**自己**（**它也可勾**）+ 右侧「平台 · 端口」小字；下级缩进一行一行排。
   ⚠️ 只加 `--tree` 修饰类，⛔ **不动 `.pms-multi` 本身** —— 它还被「端口可开平台」「平台可开端口」
      两处用着（那两处是**横向**排列的扁平勾选，一改就把它们挤歪）。
   ⚠️ 行上必须保留 `.pms-check`：勾选框本体（20px 方块 + 那笔画出来的勾）靠它定位，⛔ 别去掉。 */
.pms-multi--tree {
  display: block;                          /* 覆盖父级的横向 flex（树是**纵向**的） */
  max-height: 288px;                       /* 链路只会越来越多 ⇒ 控件内滚动，别把弹窗撑到顶天 */
  overflow: auto;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: 4px 0;
}
/* 组与组之间一条浅线（一眼数得出"几条一级链路"） */
.pms-multi--tree .pms-multi__grp + .pms-multi__grp { border-top: 1px solid var(--color-border-soft); }
.pms-multi__row {
  /* 层级档位：**默认 0**，由 JS 的行内 `style="--d:N"` 覆盖（N = 缩进档）。
     ⚠️ 这里给默认值 + 用 `var(--d)`（**不写兜底**）—— 全站禁 `var(--令牌, 兜底值)`
        （`ui-token-standards` 门禁②，兜底会把拼错的令牌名静默掩掉）；
        而"必须有定义"那条（`css-var-defined`）靠**这一行**就满足了。 */
  --d: 0;
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 30px;
  /* 缩进 = 10px 左留白 + `--d` 档 × 20px（一档 ≈ 一个勾选框宽，层级一眼可数） */
  padding: 0 10px 0 calc(10px + var(--d) * 20px);
}
.pms-multi__row:hover { background: var(--color-surface-2); }
/* 组头：**有下级**的才给浅底条（叶子一级链路就是普通一行 —— 渲染侧只给有下级的打 `--head`）
   ⛔ 别在 CSS 里用 `:has()` 判"组里行数 > 1"：层级深了"行数"与"是不是组头"不再等价。 */
.pms-multi__row--head { background: var(--color-surface-2); }
.pms-multi__row--head .pms-multi__label { font-weight: var(--font-weight-medium); }
/* 右侧的「平台 · 端口」（只有组头有；`margin-left:auto` 把它推到最右） */
.pms-multi__meta {
  margin-left: auto;
  flex: none;
  font-size: var(--font-size-xs);
  color: var(--color-muted);
  white-space: nowrap;
}
.pms-check input:disabled + .pms-check__box { opacity: 0.45; }
/* 键盘聚焦：原生控件是透明的，焦点环要画在自造方框上
   （漏了这一步就会出现"Tab 能走到但看不出焦点在哪"，是无障碍硬伤） */
.pms-check input:focus-visible + .pms-check__box {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

/* ── ④ 表单弹窗内的两列网格（PMS 模型）────────────────────────────────────────
   ⚠️ `.form-row` 本身是"标签在左 + 控件占满"的 flex 一行；这里把它放进 2 列网格，
      每行两个字段 ⇒ 控件有富余宽度（乐哥 2026-09-27 参考图的第 4 条特征）。
   ⚠️ 标签列宽 96px（不是整页表单的 112px）：弹窗总宽 880px 减去内边距，
      两列各约 380px；112px 标签 + 380px 控件在窄屏会挤 ⇒ 一页两列用 96px 足够放下
      本板块的标签（最长「统一社会信用代码」8 字 = 112px ✗）——
      ⇒ **不写死**，让标签按内容宽度自然撑开、`min-width` 120px 保底（见下方）。 */
/* ⚠️ 2026-10-09 表单统一（第二批）起，`.pms-grid` / `.pms-span2` **已无用户** ——
   项目的表单已改走**全站标准** `.form-grid` / `.span-2`（乐哥当天定「全站一律标签在上」）。
   下面这几条保留一版以防漏网（本项目有过"以为没人用、其实动态拼类名"的先例），
   确认无引用后可删。⛔ 新代码不要再用它们。 */
.pms-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-4) var(--space-6);
  align-items: start;
}
/* 表单分组（① 基础信息 / ② 业务与计费）：标题 + 网格。
   ⚠️ 分组编号用**主色浅底小方块**（不是 3px 竖条）—— 与达人编辑页的分区编号同款
      （那是乐哥认可的形态，见 DESIGN-SPEC §8 的「分区编号」条目）。 */
.pms-group + .pms-group { margin-top: var(--space-6); }
/* ⚠️ 2026-10-09 表单统一（第一批）：分区头的**外观**（字号/字重/字色/横排/序徽标）
   已全部并入 overlays.css 的 `.form-sec` 别名组（`.pms-group__title` 也在那组多选择器里）
   ⇒ 这里**只留间距**。⛔ 不许再把 font-size / color / width / border-radius 之类加回来
   （那会变成"两处定义"，改一处漏一处 —— 门禁 `tests/form-unify.test.js ①` 会红）。 */
.pms-group__title {
  margin-bottom: var(--space-4);
}
/* .pms-group__no：外观见 overlays.css 的 `.form-sec__no`（20×20 · r6 · 浅蓝底 · 主色字） */

/* ⭐ 2026-10-11：分组**可折叠** —— `formModal` 的 `groups[].collapsed` ⇒ 原生 `<details>`。
   用途：「新建项目」把"跟这条线无关的项目属性"（主体/合同/事业部/周期/备注）默认收起来，
   一眼只剩 5~6 项（原来 13 项）。缘起（乐哥）：「现在是整个项目所有信息，很冗余」。
   ⚠️ 只有传了 `collapsed` 的分组才命中这几条 —— 其余分组的渲染与样式**逐字不变**
      （`formModal` 是全板块共用组件，零外溢是这里的红线）。
   ⚠️⚠️ 选择器必须写成 `summary.pms-group__title` 这种**复合形**，⛔ 不要单独写
      `.pms-group__title { … }`：门禁 `tests/form-unify.test.js ①` 按**精确选择器**
      取块、并禁止里面出现"外观"（`display:flex` 算外观）—— 它锁的是"分区头外观
      只能有一处定义"。复合选择器不在它的名单里，所以安全。
   ⚠️ 这里也**不写** font-size / color 之类：标题外观归 overlays.css 的 `.form-sec` 别名组。 */
.pms-group--fold > summary.pms-group__title {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  cursor: pointer;
  list-style: none;
  user-select: none;
}
.pms-group--fold > summary.pms-group__title::-webkit-details-marker { display: none; }
.pms-group--fold > summary.pms-group__title::marker { content: ''; }
.pms-group--fold .pms-group__caret {
  display: inline-flex;
  margin-left: auto;
  color: var(--color-muted);
  transition: transform var(--motion-fast) var(--motion-ease-standard);
}
.pms-group--fold[open] .pms-group__caret { transform: rotate(180deg); }

/* ⭐ 2026-10-11：表单顶部的**只读摘要**「基于：品牌 · 平台 · 端口 · 链路」——
   只在"从脑图节点点 ＋ 打开"的新建表单里出现（见 `projectForm()` 的 `fromBanner()`；
   从列表页新建、或编辑时，`bannerHtml` 是空串 ⇒ 这条样式不出现）。
   它回答的是"这个项目建在哪条线上"，所以用**主色浅底**与下面的输入区区分开。 */
.pms-fromline {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  margin-bottom: var(--space-5);
  background: var(--color-primary-50);
  border-radius: var(--radius-md);
  font-size: var(--font-size-sm);
  color: var(--color-fg-2);
}
.pms-fromline__icon { display: inline-flex; color: var(--color-accent-text); }
/* ⭐ 第 30 批：摘要改成**路径式**（`品牌 › 平台 › 端口 › 链路`）——
   乐哥：「上面的那些已经有的环节，应该**路径方式**或者咋展示」。
   ⚠️ 分隔符用 `<i class="pms-fromline__sep">›</i>`（行内、`font-style: normal`）——
      ⛔ 别改成 `::after { content: '›' }`：那样它会被复制/朗读带进去，而且换行时会孤立在行尾。 */
.pms-fromline__path { display: inline-flex; align-items: center; flex-wrap: wrap; gap: var(--space-1); }
/* ⭐⭐ 第 36 批：摘要里每一节从 `<b>` 改成**可点的 `<button>`**（乐哥：「维护这个页面的时候…
   就不能放在前面那个环节维护吗，这样才符合直觉吧」）⇒ 点它就地打开那个环节的编辑窗。
   ⚠️ 按钮 reset 只重置 padding / border / background / font-family，⛔ **不写 `font: inherit`**：
      那是个简写、会把字号字重一起重置，而本项目的按钮字号靠类覆盖（第 18 批栽过这个坑）。
   ⚠️ 下划线用 `border-bottom: dashed` 而不是 `text-decoration` —— 后者在中文里会压笔画。 */
.pms-fromline__seg {
  padding: 0 2px;
  border: 0;
  border-bottom: 1px dashed var(--color-accent-text);
  background: transparent;
  font-family: inherit;
  font-size: inherit;
  font-weight: 600;
  color: var(--color-accent-text);
  cursor: pointer;
}
.pms-fromline__seg:hover {
  color: var(--color-primary-500);
  border-bottom-color: var(--color-primary-500);
}

/* ⭐⭐ 2026-10-11 第 36 批：「新建项目」**二级页**（`#/projects/new`）+ 两处内联外壳 ──────
   乐哥：「这个新增就别窗口了，直接二级页面，因为内容不少」。
   ▸ `.pms-embed` = `formModal` 在 `opts.host` 模式下的壳（body + footer）—— 视觉与弹窗里那套
     一致（同一份字段渲染），只是不再套 `.modal`；⛔ 别在这里另写一套字段样式（会漂移）。
   ▸ `.pms-flow-wrap / __host / __foot` = 逐级定位（引导流）的内联壳，同上。 */
.pms-embed__foot,
.pms-flow-foot {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-2);
  padding-top: var(--space-4);
  margin-top: var(--space-4);
  border-top: 1px solid var(--color-border);
}
.pms-flow-wrap { padding-top: var(--space-2); }

/* ⭐⭐ 2026-10-12 第 59 批：项目板块**二级页**的宽度档（乐哥：「新增项目的列宽，
   按照我们约定的二级页面的做」）。
   ──────────────────────────────────────────────────────────────────────────
   口径 = 全站二级页那一档，**唯一来源是令牌 `--form-max-width`**（现 1040 逻辑 ⇒
   1920 屏 + zoom 0.85 下视觉 **884**）—— 与「新增达人」「新增货品」实测逐像素同宽。
   ⛔ 不写魔数（他日后调 `--form-max-width`，这里必须自动跟）。
   ⚠️ 包裹容器里同时装着**页头**（`UI.headNav`）与内容：只收内容不收页头会出现
      「标题行宽、内容卡窄」（2026-09-28 乐哥当场否过的形态）。
   ⚠️ 用包裹容器而不是给 `#page-root` 加档位类 —— 那是五个板块共用的节点，
      加类会在切页时残留（别的页跟着变窄、零报错）。
   ⚠️ 一级列表页（`.pms-page`）⛔ 不许套这个类：表格宽度是列数实打实用掉的，
      收窄会横向滚动（设计规范「两档制」的判据：主体是不是表格）。 */
.pms-subpage {
  max-width: var(--form-max-width);
  margin-left: auto;
  margin-right: auto;
}

.pms-new { display: flex; flex-direction: column; gap: var(--space-4); }
.pms-new__card { margin: 0; }
.pms-new__h {
  font-size: var(--font-size-base);
  font-weight: 600;
  color: var(--color-fg);
  margin-bottom: var(--space-1);
}
.pms-new__no { color: var(--color-primary-500); margin-right: 4px; }
.pms-new__tip {
  margin: 0 0 var(--space-3);
  font-size: var(--font-size-sm);
  color: var(--color-muted);
  line-height: 1.6;
}
.pms-fromline__sep { color: var(--color-muted); font-style: normal; padding: 0 2px; }
.pms-fromline__hint { color: var(--color-muted); font-size: var(--font-size-xs); }
/* 跨两列（备注这类整行字段） */
.pms-span2 { grid-column: 1 / -1; }

/* 弹窗内表单：标签按内容自然宽（至少 96px），避免长标签换行挤掉控件 */
.pms-grid .field-label {
  width: auto;
  min-width: 96px;
  max-width: 140px;
}

/* 操作列整体右对齐（表头 + 单元格）—— 与 .pms-rowbtn 的 justify-content: flex-end 一致。
   ⚠️⚠️ 选择器**不能简写成 `.pms-col-op`**（2026-10-10 实测修）：
     全站 `table.css` 有一条「全列居中」（乐哥 2026-09-14 定稿）
        `.table:not(:where(td .table)) > tbody > tr > td { text-align: center }`（特异性 **0,1,3**），
     而裸 `.pms-col-op` 只有 **0,1,0** ⇒ **压不过它** ⇒
       · th 走 `tableCard` 输出的 inline `style="text-align:right"` ⇒ **表头在右**
       · td 被上面那条强制居中 ⇒ **按钮在中间**
       ⇒ 「操作」列表头与内容**错位**（实测：品牌表差 95px、项目列表差 40px —— 列越宽越明显）。
     ⇒ 显式写到 `tbody td` 层级拿 **(0,2,2)**，与 `table.css` 里 `.is-num`（数值列）**同一套写法**
       （那边注释也记着同一个坑：「只把 center 改成 right 会静默不生效」）。 */
.table thead th.pms-col-op,
.table tbody td.pms-col-op,
.table tfoot td.pms-col-op { text-align: right; }

/* ── 窄屏：2 列退回 1 列 ─────────────────────────────────────────────────────
   ⚠️ 与全站断点一致（900px 处 `.form-row` 本来也会退回"标签在上"，见 pages-influencer.css）。 */
@media (max-width: 900px) {
  .pms-grid { grid-template-columns: minmax(0, 1fr); }
  .pms-links__brand { flex-basis: 140px; }
  .pms-links__cell { flex-basis: 76px; }
}

/* ══════════════════════════════════════════════════════════════════════════
   批次 2（2026-09-30）：项目总览 / 项目列表 / 详情 / 充值与审批 / 收付款明细
   ──────────────────────────────────────────────────────────────────────────
   ⚠️ 只写"本板块独有"的东西。能用公共件的**一律用公共件**：
     · 表格 = `.card` + `.table`（tableCard 组件）
     · 徽章 = `.badge--success|warning|info|neutral`（UI.badge，**没有** green/gray 这些类）
     · 表单 = `.form-row` / `.field-label` / `.form-row__ctrl` / `.field-hint`
     · tab  = `.tabs` / `.tab`（全站胶囊，定义在 pages-influencer.css）
     · 金额 = `.num`（base.css，等宽数字）
   本批新增的类都在下面，每条都写了"为什么需要它"。
   ══════════════════════════════════════════════════════════════════════════ */

/* ── 项目名（表格里那列的主文案）：加粗 + 主色，与品牌/渠道页同款 ────────────── */
.pms-name { font-weight: var(--font-weight-medium); color: var(--color-fg); }

/* ── 可点整行（总览的"最近项目"）：hover 给底色暗示"这行能点" ────────────────
   ⚠️ 只有**真的绑了点击**的行才加这个类 —— 加了不绑就是"看着能点、点了没反应"。 */
.pms-row-link { cursor: pointer; }
.pms-row-link:hover { background: var(--color-surface-2); }

/* ── 说明块（"当前没有待办"这类一句话说明）───────────────────────────────────
   与空态（UI.emptyState）分工：空态用在"整块内容没有"，这里用在
   "卡片里的一小段说明"——后者画个大空态会把卡片撑得很高（看着像页面坏了）。 */
/* ── 「只看未使用」筛选（第 55 批）──────────────────────────────────────────
   用途：渠道信息页的**清理线索** —— 筛出没有任何引用的档案（端口 / 链路 / 产品看
   有没有项目在用，平台看有没有品牌勾选）。
   ⚠️ 为什么非带个 label 不可：本页的搜索框会被 `applyFilterBarLayout` 搬进页头
     （全站统一），工具行里往往只剩这一个控件 ⇒ 不加 label 就是"一条白卡里
     孤零零一个下拉"（第 55 批真机截图看到的）。
   ⚠️ 它是**筛选项不是表单控件** ⇒ 前缀用「只看」，整体读作「只看：全部 / 仅未使用」。 */
.pms-unused { display: inline-flex; align-items: center; gap: var(--space-2); }
.pms-unused__lb { font-size: var(--font-size-sm); color: var(--color-muted); }

.pms-note {
  padding: var(--space-4) 0;
  color: var(--color-muted);
  font-size: var(--font-size-sm);
  line-height: var(--font-line-height-body);
}

/* ── 待办提醒（总览第一张卡）─────────────────────────────────────────────────
   ⚠️ 用 `<a>` 整块可点（带 hover 位移暗示）—— 待办的本质是"点进去处理"，
      只显示文字不给出入口的话，用户还得自己去找那一页。 */
.pms-todo {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-md);
  margin-bottom: var(--space-3);
  color: inherit;
  text-decoration: none;
  transition: background-color var(--motion-fast) var(--motion-ease-standard);
}
.pms-todo:last-child { margin-bottom: 0; }
.pms-todo:hover { background: var(--color-surface-2); }
.pms-todo__icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; flex: none;
  border-radius: var(--radius-sm);
  background: var(--color-accent-bg);
  color: var(--color-accent-text);
}
.pms-todo__body { display: flex; flex-direction: column; gap: 2px; flex: 1; min-width: 0; }
.pms-todo__text { font-weight: var(--font-weight-medium); color: var(--color-fg); }
.pms-todo__sub { font-size: var(--font-size-sm); color: var(--color-muted); }
.pms-todo__go { color: var(--color-muted); flex: none; }

/* ── 描述列表（详情页的"字段 → 值"两列）─────────────────────────────────────
   ⚠️ 用 `<dl>` 语义标签（不是一堆 div）：它是"名词-释义"关系，屏幕阅读器靠它理解。
   两列自适应：窄屏自动退回一列（不写媒体查询 —— `auto-fit` 本来就够）。 */
/* ⭐ 2026-10-22 十七改：`auto-fit 280px` 在 1500px 卡里会摊成 5 列，
   每格 300px、label 与值离得老远，右半边像没排完版。
   ✅ 固定 **3 列**（一屏 3 项、8 项正好三行），列宽随卡伸缩。 */
.pms-dl {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-3) var(--space-5);
  margin: 0;
}
@media (max-width: 900px) {
  .pms-dl { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 600px) {
  .pms-dl { grid-template-columns: 1fr; }
}
/* 卡内小标题（三张卡各自的一块，不用 card__head 是为了保持"同一张卡里分节"的形态） */
.pms-dl__hd {
  margin-bottom: var(--space-3);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  color: var(--color-fg);
}
.pms-dl__row {
  display: flex;
  gap: var(--space-3);
  font-size: var(--font-size-sm);
  line-height: var(--font-line-height-body);
}
.pms-dl__row dt { flex: none; width: 96px; color: var(--color-muted); }
.pms-dl__row dd { margin: 0; color: var(--color-fg); word-break: break-word; }

/* ── 流转记录（充值单的时间线）───────────────────────────────────────────────
   ⚠️ 左侧竖线 + 圆点：让"谁在第几步做了什么"一眼看出先后（纯文字列表读不出时序）。 */
.pms-tl { display: flex; flex-direction: column; gap: var(--space-3); }
.pms-tl__item {
  position: relative;
  padding-left: var(--space-5);
  border-left: 1px solid var(--color-border-soft);
  padding-bottom: var(--space-3);
}
.pms-tl__item:last-child { padding-bottom: 0; }
.pms-tl__item::before {
  content: '';
  position: absolute;
  left: -4px; top: 6px;
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--color-accent);
}
.pms-tl__head { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-2); }
.pms-tl__act { font-weight: var(--font-weight-medium); color: var(--color-fg); }
.pms-tl__who { font-size: var(--font-size-sm); color: var(--color-fg-2); }
.pms-tl__at { font-size: var(--font-size-xs); color: var(--color-muted); }
.pms-tl__body { margin-top: 2px; font-size: var(--font-size-sm); color: var(--color-fg-2); }
.pms-tl__note { margin-top: 2px; font-size: var(--font-size-xs); color: var(--color-muted); }

/* ── 行内复选框标签（充值页的「只看我提交的」）───────────────────────────────
   ⚠️ 不用 `.pms-check`（那是品牌×渠道矩阵用的三态块，形态完全不同）。 */
.pms-check-inline {
  display: inline-flex; align-items: center; gap: var(--space-2);
  padding: 0 var(--space-2);
  font-size: var(--font-size-sm);
  color: var(--color-fg-2);
  white-space: nowrap;
  cursor: pointer;
}
.pms-check-inline input { cursor: pointer; }

/* ── 详情页的 tab 与下方内容之间的间距 ──────────────────────────────────────
   ⚠️ `.tabs` 自带 `margin-bottom`（全站值），详情页上面是 `UI.headNav` 的白卡
      ⇒ 白卡与 tab 之间也要有一致的间距，否则 tab 会贴着页头（看着像挤在一起）。 */
.pms-page .tabs, .pms-body .tabs { margin-top: var(--space-4); }

/* ── 不换行（宽表里那些"短且不该断"的列）───────────────────────────────────
   ⚠️ 必须配合 `minWidth` 用：只加 nowrap 会让表格更宽（内容撑开），
      配合下限才能保证"列宽可控 + 内容不断行"。
   ⚠️ 用在：单号、金额、比率这类**短且断行会读错**的值上；
      ⛔ 不要用在项目名这种长文本上（那会被撑得很宽，把别的列挤掉）。 */
.pms-nowrap { white-space: nowrap; }

/* ── 操作列吸右（窄屏/小窗口下横向滚动时，操作按钮始终可见）────────────────────
   ⚠️⚠️ 为什么需要：本板块的表是宽表（充值单 8 列 / 流水 9 列）。
      窗口一窄（笔记本 1280、手机 390）表格就会横向滚动 ——
      而**操作列在最右边**，会被滚出视野 ⇒ 用户看不到"审批/编辑"按钮，
      那些功能在窄屏上等于不存在（不报错、只是"找不到"）。
   ⚠️ 只在 `.pms-table-wrap` 内生效：不动公共 table.css（那是全站几十张表共用的，
      改它影响面不可控 —— 本项目对"改公共代码前先看被谁复用"有明确纪律）。
   ⚠️ sticky 单元格**必须有不透明背景**，否则滚动时下层内容会透出来（同矩阵的首列吸左）。
      背景色要与整行一致：默认白，行 hover 时跟着变 —— 不同步的话
      "鼠标所在整行变色、只有操作列还是白的"，看着像那一格没选中。
   ⚠️ `border-collapse: collapse` 下 sticky 单元格的边框会随滚动错位，
      所以左边界线用 `box-shadow: inset` 画（不依赖 border）。 */
.pms-table-wrap td.pms-col-op,
.pms-table-wrap th.pms-col-op {
  position: sticky;
  right: 0;
  z-index: 1;
  background: var(--color-surface);
  box-shadow: inset 1px 0 0 var(--color-border-soft);
}
.pms-table-wrap .table tbody tr:hover td.pms-col-op { background: var(--table-row-hover); }

/* 「上传合同 PDF 自动识别」块的样式已提到共享层（public/css/components.css 的 `.ct-extract*`）。
   ⭐ 2026-10-10 第 11 批：签约公司表单（influencer-company-form.js）也要用同一块
   ⇒ 从本文件迁走，⛔ 不在这里留第二份（两个使用方各一份样式必然漂移）。 */

/* ══════════════════════════════════════════════════════════════════════════
   「逐级新建引导流」的时间线（2026-10-11 第 33 批）
   ──────────────────────────────────────────────────────────────────────────
   乐哥：「新建项目，我们应该和达播工作台排期一样，**一层层往下新建，然后有引导线**，
   这样才直观」→ 形态照搬排期「行程排期」（`pages-schedule.css` 的 `.tl`：竖导轨 + 圆点 + 卡片）。

   ⛔ 不直接复用 `.tl` 的类名：两个板块的样式是**按页加载**的，跨板块引类名会时有时无。
   ⚠️ 与 `.tl` 的一处**不同（是改进）**：那套要 JS（`adjustLine`）从真实 DOM 量出竖线的
      top/height —— 因为它的节点间有"新增其他事项"行、高度不定。这里四级是**固定的**，
      ⇒ 每个 item 自己画一段贯穿整项的竖线，**首尾各收一次**（`:first-child` / `:last-child`），
      **纯 CSS 就连成一条线**，不依赖任何测量（少了"线悬空/多伸一截"那一类故障）。
   ⚠️ 圆点的 `margin-top` 要与 `.pms-flow__head` 的文字行对齐；改字号要一起重算。 */
.pms-flow { display: block; }
.pms-flow__item { display: flex; gap: var(--space-3); }
.pms-flow__rail { position: relative; width: 20px; flex: none; }
.pms-flow__rail::before {
  content: '';
  position: absolute;
  left: 9px;
  top: 0;
  bottom: 0;
  width: 2px;
  background: var(--color-border);
}
/* ① 首项：从圆点起（上半段不画）② 末项：到圆点止（下半段不画）—— 两端自然收口 */
.pms-flow__item:first-child .pms-flow__rail::before { top: 14px; }
.pms-flow__item:last-child .pms-flow__rail::before { bottom: auto; height: 14px; }
.pms-flow__dot {
  position: relative;
  z-index: 1;
  display: block;
  width: 12px;
  height: 12px;
  margin: 7px auto 0;
  border-radius: var(--radius-full);
  background: var(--color-border);
}
/* 这一级已选定 ⇒ 圆点亮起（一眼看出走到哪了） */
.pms-flow__item.is-set .pms-flow__dot { background: var(--color-primary-500); }
.pms-flow__body { flex: 1; min-width: 0; padding-bottom: var(--space-4); }
.pms-flow__head { font-size: var(--font-size-sm); color: var(--color-fg-2); margin-bottom: 6px; }
.pms-flow__ctrl { display: flex; align-items: center; gap: var(--space-2); }
.pms-flow__ctrl .input { max-width: 340px; }
.pms-flow__wait { font-size: var(--font-size-sm); color: var(--color-muted); }
.pms-flow__hint { margin-top: var(--space-2); font-size: var(--font-size-sm); color: var(--color-muted); }

/* ⭐⭐ 2026-10-11 第 34 批 ─────────────────────────────────────────────────────
   ① 「改端口 / 平台」弹窗（`project.js` 的 `editLinkOwner`）—— 只要两个下拉，
      所以自建一个极小的布局，⛔ 不复用 formModal（那会带出 6 个字段）。
   ② 业务线脑图的**聚焦条**（`focusBar`）：`品牌 › 平台 › 当前链路`，上游收成一行。 */
.pms-owner__row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-3);
}
.pms-owner__k {
  flex: none;
  width: 88px;
  font-size: var(--font-size-sm);
  color: var(--color-fg-2);
}
.pms-owner__row .input { flex: 1; min-width: 0; }
.pms-owner__hint {
  margin-top: var(--space-2);
  font-size: var(--font-size-sm);
  color: var(--color-muted);
  line-height: 1.6;
}

.pms-focus { margin-bottom: var(--space-3); }
.pms-focus__bar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 2px;
}
.pms-focus__label {
  margin-right: var(--space-2);
  font-size: var(--font-size-sm);
  color: var(--color-muted);
}
/* 面包屑每一节：可点的走 `[data-go]`（与脑图里的环节同一套落点），当前节点是纯文字 */
.pms-focus__c {
  padding: 2px 6px;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  font-size: var(--font-size-sm);
  color: var(--color-fg-2);
  cursor: pointer;
}
.pms-focus__c:hover { color: var(--color-primary-500); background: var(--color-surface-2); }
.pms-focus__c.is-cur { color: var(--color-fg); font-weight: 600; cursor: default; }
.pms-focus__c.is-cur:hover { color: var(--color-fg); background: transparent; }
.pms-focus__sep { display: inline-flex; align-items: center; color: var(--color-muted); }
/* 右侧动作推到最右（面包屑短的时候也不会跟着左边跑） */
.pms-focus__sp { flex: 1; }

/* ── 业务分组管理弹窗（第 67 批 · 乐哥：「链路聚合…勾选属于同一个业务属性的链路」）──
   左 = 分组清单（点一个编辑 / ＋新建），右 = 组名 + 说明 + 链路勾选树。
   ⚠️ 全部走设计令牌（⛔ 不写死颜色/圆角/间距 —— 有 `css-var-defined` 门禁盯着）。 */
.pms-lkg { display: flex; gap: var(--space-4); align-items: stretch; min-height: 380px; }
.pms-lkg__side { width: 220px; flex: none; display: flex; flex-direction: column; gap: var(--space-3); }
.pms-lkg__list { display: flex; flex-direction: column; gap: 2px; overflow: auto; }
.pms-lkg__item { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2);
  width: 100%; padding: 8px 10px; border: 1px solid transparent; border-radius: var(--radius-md);
  background: var(--color-surface-2); cursor: pointer; font-size: 13px; text-align: left; }
.pms-lkg__item:hover { border-color: var(--color-border); }
.pms-lkg__item.is-on { border-color: var(--color-accent-border); background: var(--color-accent-bg); }
.pms-lkg__nm { font-weight: 500; color: var(--color-fg); }
.pms-lkg__cnt { font-size: 12px; color: var(--color-fg-3); }
.pms-lkg__main { flex: 1 1 auto; display: flex; flex-direction: column; gap: var(--space-4); min-width: 0; }
.pms-lkg__fld { display: flex; flex-direction: column; gap: 6px; }
.pms-lkg__tree { border: 1px solid var(--color-border); border-radius: var(--radius-md);
  padding: var(--space-2) var(--space-3); max-height: 260px; overflow: auto; }
.pms-lkg__row { display: flex; align-items: center; gap: var(--space-2); padding: 3px 0; }
.pms-lkg__chk { display: flex; align-items: center; gap: 8px; cursor: pointer; font-size: 13px; color: var(--color-fg); }
.pms-lkg__kids { font-size: 12px; color: var(--color-fg-3); }
.pms-lkg__empty { padding: var(--space-3); font-size: 12px; color: var(--color-fg-3); }
.pms-lkg__sug { display: flex; align-items: center; gap: 6px; min-height: 20px; font-size: 12px; color: var(--color-fg-3); }
.pms-lkg__sugbtn { border: 1px dashed var(--color-border); border-radius: var(--radius-sm);
  background: transparent; padding: 1px 8px; font-size: 12px; cursor: pointer; color: var(--color-accent-text); }
.pms-lkg__sugbtn:hover { border-color: var(--color-accent-border); }
.pms-lkg__foot { margin-left: auto; display: flex; gap: var(--space-2); }


/* ── 项目表单「签约」多行编辑器（第 72 批）────────────────────────────────────
   一行 = 一家签约主体 + 它自己的合同（合同可留空＝还没签）。
   ⚠️ 两列都 `flex:1 1 0` + `min-width:0` —— 不写 min-width 时 select 会被内容
      （企业全名很长）撑开、把删除按钮挤出可视区（本项目在别的行式布局踩过）。 */
.pjt-sign__rows {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
}
.pjt-sign__row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
.pjt-sign__row .select { flex: 1 1 0; min-width: 0; }
.pjt-sign__row .icon-btn { flex: none; }


/* ── 品牌页 / 企业页（下钻链的二级 / 三级页 · 第 73 批）────────────────────
   一行 = 一个品牌/企业的身份卡（名字 + 元信息 + 右端「编辑信息」）。
   ⚠️ 这两页**不套** `.pms-subpage`（那是表单型的 1040 宽）—— 两页都有表格，
      而表格宽度是列数用掉的，收窄会横向滚动（与"一级列表页不许套窄"同一条判据）。
   ⚠️ 两块内容竖排（签约在上、投放在下）：它们是**两条线**，横排会让人以为是主从。 */
.pms-drill__hero {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-4);
}
.pms-drill__name {
  font-size: var(--font-size-lg);
  font-weight: 500;
}
.pms-drill__meta {
  margin-top: var(--space-1);
  color: var(--color-muted);
  font-size: var(--font-size-sm);
}
.pms-drill__grid {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  margin-top: var(--space-4);
}


/* ── 整行可点（第 74 批）：品牌列表点**任意位置**进品牌页 ──────────────────────
   ⚠️ 只给 cursor + hover 底色 —— ⛔ 不给整行套 `<a>`（那会把整行文字全变成链接色，
      还会跟行内的编辑/删除按钮抢点击）。点击由 `#pms-brand-list` 上的委托处理，
      行内的 `[data-edit]` / `[data-del]` / `<a>` 会让路（见 paintBrands）。 */
.pms-row--go { cursor: pointer; }
.pms-row--go:hover { background: var(--color-surface-2); }

/* 品牌页「签约」块的**维度切换**（第 82 批起并进卡头）。
   ⛔ `.pms-drill__seg` 那条规则**已删** —— 托盘不再单独占一行，而是作为 `headerTabs`
      进 `.card__header`（见 `paintBrandPage` 的 `signTabs`）。别再加回来。
   ⚠️ 卡头里"标题 + tab"必须先包一层左组：`.card__header` 是 `justify-content: space-between`
      ⇒ 直接塞第三个平级子元素，**中间那个会被推到几何正中**（tab 会飘到卡片中间、与标题脱开）。 */
.pms-card-head--left {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  min-width: 0;
}
.pms-card-head--left .seg.table-tabs { flex: 0 0 auto; }   /* 托盘不许被拉伸（灰底只到内容宽） */

/* ⚠️⚠️ 窄屏必须放行换行 —— 真机实测（390px）：卡头一行塞不下"标题 + 184 的托盘 + 右侧动作"
   ⇒ flex 会把**卡标题压成一根竖排单字**（"签/约/·/这/个/品/牌…"），
   而且托盘与「＋新增合同」**互相重叠**（实测 trayRight 253 > 右侧组 x 234）。
   ✅ 三档：① 左组占满一行；② 左组内部允许换行 ⇒ 托盘掉到标题下一行；
      ③ 右侧动作组跟着换到第二/三行（`.card__header` 本身要允许 wrap）。
   ⚠️ 只对**带 tab 的卡头**生效（`:has`）—— 其余卡头保持一行两端对齐的原样。 */
.card__header:has(.pms-card-head--left) { flex-wrap: wrap; row-gap: var(--space-2); }
@media (max-width: 1024px) {
  .pms-card-head--left { flex: 1 1 100%; flex-wrap: wrap; gap: var(--space-2) var(--space-3); }
}

/* ── 品牌页「品牌投放」卡的两条 fact（第 80 批 · `投放平台` / `投放链路`）────────
   标签走**环节层级色**（`--pms-k-chan` 青绿 / `--pms-k-link` 紫）——与项目列表的表头、
   新建项目脑图的节点是**同一套语义色**（第 46 批定的），一眼看出"这一段是链路那一段"。
   ⛔ 只上到**标签**这一层；第 54 批已把"板块标题上层级色 / 左边色条"否掉
      （`.pms-group--k` 只剩传色变量的作用），别在这里把大块底再染色。 */
/* ── 品牌页「品牌信息」卡的**属性条带**（第 81 批重排 · 乐哥：「排版乱七八糟的」）────
   真机量出的病根：原先是**单列 label/value**（`.pms-dl--stack`）—— 卡片 1561px 宽，
   内容只用到左边 **496px（13%）**，右边 1065px 全是白 ⇒ 像"没排完版"。
   ✅ 现用 `auto-fit` 等分铺满（1559 ⇒ 3 条时每格约 483、4 条时约 363）：
      label 在上（12px 灰 + 环节色）、值在下 ⇒ 与全站 `.kpi-card`（标签上/值下）同一套语言。
   ⛔ 别退回 flex 单行（`.pms-facts` 是 flex 项时会被缩到内容宽 —— 本轮实测踩到，右边照样空）；
      ⛔ 也别给它加固定 `width`，宽度要跟着卡片走。 */
.pms-facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 18px 32px;
  align-items: start;
  /* ⚠️ **必须自己给内距**：`.pms-card__body { padding: 0 }` 是板块级覆盖
     （表格要通铺到卡边）⇒ 不写的话属性带**贴卡边**，而上面卡头标题缩进 24px
     ⇒ 上下左缘错开一格（真机实测：标题 316 / 属性带 292，差 24）。
     ⚠️ 左右用 `--space-6`（24，与卡头 padding 同值）、底 20（`--space-5`）——
     ⛔ 别手写 `24px/20px`（那是令牌的活）。 */
  padding: 0 var(--space-6) var(--space-5);
}
.pms-facts__c { min-width: 0; }
.pms-facts__k {
  margin-bottom: 7px;
  color: var(--color-muted);
  font-size: var(--font-size-xs);
  line-height: 1;
}
/* 环节层级色只上到**标签**这一层（与项目列表表头、新建项目脑图同一套语义色）。 */
.pms-facts__k--chan { color: var(--pms-k-chan); }
.pms-facts__k--link { color: var(--pms-k-link); }
.pms-facts__v {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  min-width: 0;
  color: var(--color-fg);
  font-size: var(--font-size-sm);
  line-height: 1.7;
  word-break: break-word;
}
/* 多平台时每平台一行（单平台只有一行，看不出差别）。 */
.pms-facts__ln { display: block; }
.pms-facts__sep { color: var(--color-fg-3); }

/* 环节 chip（平台 / 链路）：与全站 `.tag` 同形态，但颜色走**令牌**（⛔ 不写裸 hex）——
   `.tag` 的配色是由 JS 内联 `--tag-fg/bg` 注入的（`UI.tag()` 要 6 位 hex），
   而这两个色是**设计令牌**，写成类才能"改令牌层、全站跟着变"。 */
.pms-chip {
  display: inline-flex;
  align-items: center;
  height: 20px;
  padding: 1px 6px 0;
  border-radius: var(--radius-seg);
  font-size: var(--font-size-xs);
  line-height: 1;
  white-space: nowrap;
}
.pms-chip--chan { color: var(--pms-k-chan); background: var(--pms-k-chan-bg); }
.pms-chip--link { color: var(--pms-k-link); background: var(--pms-k-link-bg); }
.pms-put__none { color: var(--color-muted); }

/* ⛔ 第 77 批：`.pms-link-top` **已删** —— 第 76 批为"自己写的那个链接输入框"加的，
   第 77 批顶部改回共享组件 `ContractExtractUI`（`linkOnly` 模式，它自带 `.ct-extract` 样式）
   ⇒ 这组规则成了死代码。⛔ 别再加回来：同一块地方两块样式会打架。 */

/* ⭐⭐ 2026-10-18「本月对账」条（充值提需页，KPI 行下方一行小字）
   ──────────────────────────────────────────────────────────────────────────
   用途：把「本月提需（单据）」与「本月平台实际到账（日结）」并排放 ——
        乐哥要的是"提需和趋势对一眼"，不是一张新卡片（塞第 5 张 KPI 卡会把
        两个金额挤进一个 value 里，读不出来）。
   ⚠️ 视觉刻意做**轻**：浅底 + 12/14px + 无边框，它是"注释级的对照"，
      不能和上面 4 张 KPI 卡抢权重（本项目反复强调"别让统计行变成第二排卡片"）。
   ⚠️ 差额只有**语义色**（`--color-danger`），不用红底块：这是"提示差异"，不是"出错"。
   ⛔ 不要给它加 `box-shadow` / 边框 —— 那会读成一张卡。 */
.pms-recon {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-4);
  margin-top: var(--space-3);
  padding: var(--space-2) var(--space-4);
  background: var(--color-surface-2);
  border-radius: var(--radius-md);
  font-size: var(--font-size-sm);
  color: var(--color-fg-2);
}
.pms-recon b {
  font-weight: var(--font-weight-semibold);
  color: var(--color-fg);
  font-variant-numeric: tabular-nums;
}
.pms-recon__diff.is-warn b { color: var(--color-danger); }
.pms-recon__note { font-size: var(--font-size-xs); color: var(--color-muted); }

/* ══ 充值单详情弹窗（2026-10-18 按「财务报销审批」那一套重排）══════════════════
   乐哥：「充值单的 ui 好好做，你做的这是啥，看看财务报销审批的那个弹窗 ui」
   ──────────────────────────────────────────────────────────────────────────
   改前：13 行 `dl` 平铺 + 两个裸 `<h3>`，一个弹窗里混了三种语言
        （定义列表 / 浅底条 / 裸标题）—— 与「报销审批」那种"每块信息一张卡"不是一套。
   现在：顶部摘要 + 5 张 `.module-card`（充值信息 / 服务费与返点 / 平台实际到账 /
        流转记录 / 留言），与报销审批同语言。
   ⚠️ 只用**公共件** `.module-card`，⛔ 没有再自造一套"弹窗专用卡"
      —— 那正是"每个弹窗长得不一样"的来源。 */
.pms-detail { display: block; }
.pms-detail__head { margin-bottom: var(--space-4); }
.pms-detail__no-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-1);
}
/* 单号用等宽字：CZ000123 这种"字母+数字"在比例字里长短不齐，等宽才好扫读 */
.pms-detail__no {
  font-family: var(--font-family-mono);
  font-size: var(--font-size-lg);
  font-weight: var(--font-weight-semibold);
  color: var(--color-fg);
}
.pms-detail__meta { color: var(--color-muted); font-size: var(--font-size-sm); }
.pms-detail__meta b { color: var(--color-fg); font-weight: var(--font-weight-medium); }

/* 弹窗内的模块卡：内边距 24 → **16**。
   ▸ 为什么必须是 16：`.module-card__head` 自带 `margin: -16 -16 12` 的负边距，
     只有父级 padding = 16 时它才刚好贴到卡边、head 自己的 16 内距再给回视觉留白
     ⇒ 标题与正文落在**同一条左基准线**上（24 时标题会比正文多缩进 8px，肉眼可见的错位）。
   ▸ 卡间距收到 12（页面级是 16）：弹窗比页面紧一档。 */
.pms-mod { padding: var(--space-4); }
.pms-mod + .pms-mod,
.pms-detail > #pms-rch-plat + .pms-mod,
.pms-mod + #pms-rch-plat { margin-top: var(--space-3); }
/* 「平台实际到账」是**异步插进壳**里的 ⇒ 与上一张卡之间隔着壳，
   `.pms-mod + .pms-mod` 匹配不到（财务 #ap-cmts 踩过同一个坑），给壳补间距。 */
.pms-detail > #pms-rch-plat { margin-top: var(--space-3); }
/* 弹窗里字段固定两列（`auto-fit` 在 lg 宽度下会算成 3 列，13 个字段被切得零碎） */
.pms-mod .pms-dl {
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-3) var(--space-5);
}
.pms-mod .pms-dl__row dt { width: 96px; white-space: nowrap; }
/* 卡内的对照条不再铺浅底：卡本身是白的，里面再垫一层浅底就是"卡中卡"（规范反模式） */
.pms-mod .pms-recon {
  margin-top: 0;
  padding: 0;
  background: transparent;
}
.pms-mod .pms-recon__note { display: block; margin-top: var(--space-2); }
/* ⚠️ textarea 默认宽度是 `cols`（20 字 ≈ 200px）——不在 grid 里就不会撑满，
   截图里那个只有 200px 宽的留言框就是这么来的。卡内的 textarea 一律占满。 */
.pms-mod .textarea {
  width: 100%;
  min-height: 72px;
  box-sizing: border-box;
}

/* ── 充值单详情：状态进度条（照「报销审批」`.ap-steps` 的形态）───────────────────
   三档：实心绿 = 已完成 / 空心蓝描边 = 当前 / 灰 = 未到；驳回时第 2 段变红。
   ⚠️ 类名归本板块（`.pms-step*`）：财务那套 `.ap-steps` 还带"审批人头像胶囊"，
      硬套会把不属于充值的语义带进来。两边将来要共用再抽公共件。 */
.pms-steps {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  margin: var(--space-1) 0 var(--space-4);
}
.pms-step {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 12px 3px 4px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-pill);
  background: var(--color-surface);
  font-size: var(--font-size-sm);
  color: var(--color-meta);
}
.pms-step i {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--color-surface-2);
  font-style: normal;
  font-size: 11px;
}
.pms-step--done {
  background: var(--color-success);
  border-color: var(--color-success);
  color: #fff;
}
.pms-step--done i { background: var(--color-surface); color: var(--color-success); }
.pms-step--current {
  border-color: var(--color-accent);
  color: var(--color-accent-text);
  font-weight: var(--font-weight-semibold);
}
.pms-step--current i { background: var(--color-accent); color: #fff; }
.pms-step--bad {
  border-color: var(--color-danger);
  color: var(--color-danger);
  font-weight: var(--font-weight-semibold);
}
.pms-step--bad i { background: var(--color-danger); color: #fff; }
/* 段与段之间的短横线（不用箭头：这是"进度"不是"流向"，箭头会读成跳转） */
.pms-step__gap {
  width: 14px;
  height: 1px;
  background: var(--color-border);
}
/* 卡内的「平台实际到账」对照行：与上方字段用一条 hairline 隔开（它是附加对照，不是字段）。
   ⚠️ 不用浅底块（卡内不再垫一层底）、也不用边框盒（会读成"卡里又一张卡"）。 */
.pms-recon--sep {
  border-top: 1px solid var(--color-border-soft);
  margin-top: var(--space-4);
  padding-top: var(--space-3);
}
/* 对照行的小标题：与 dt 同款灰字（不抢"充值信息"这个卡标题） */
.pms-recon--sep .pms-recon__cap {
  display: flex;
  align-items: center;
  gap: 4px;
  margin-bottom: var(--space-2);
  font-size: var(--font-size-xs);
  color: var(--color-muted);
}
.pms-recon--sep .pms-recon__row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  font-size: var(--font-size-sm);
}

/* ⚠️⚠️ `.pms-recon` 本体是 flex —— 卡内这版有"标题行 + 数据行"**两行**，
   继承 flex 会把它们横着排（截图里"平台实际到账"和"账户 888…"挤在那一条上）。
   ⇒ 必须显式 block。 */
.pms-recon--sep { display: block; }
/* 弹窗内的整体压缩：目标是把 4 张卡 + 摘要 + 进度条压进 `modal__body` 的可视高度，
   让人**一屏看到「提交留言」按钮**（实测不压缩时 scrollHeight 840 / clientHeight 766，
   按钮正好被切在滚动区外，看着像"没有提交按钮"）。
   ⚠️ 这几条只作用于详情弹窗（`.pms-mod` / `.pms-detail`），⛔ 不动页面级间距。 */
.pms-mod { padding: 14px; }
.pms-mod .pms-dl { gap: 8px 20px; }
.pms-mod .textarea { min-height: 56px; }
.pms-detail__head { margin-bottom: var(--space-3); }
.pms-steps { margin: 6px 0 var(--space-3); }
.pms-recon--sep { margin-top: var(--space-3); padding-top: var(--space-2); }

/* 进度条上的"谁"：竖线分隔 + 小头像 + 名字（照财务 `.ap-step__who` 的形态）
   ⚠️ 两种底色要分开写：
     · 已完成段（绿实底）→ 白半透明竖线 + 白底彩字头像
     · 当前段 / 未到段（白底）→ 中性竖线 + 浅蓝底蓝字头像
   ⛔ 只写一套的话，绿底上的白线会消失、白底上的白线也看不见（财务踩过）。 */
.pms-step__who {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-left: 6px;
  padding-left: 6px;
  border-left: 1px solid var(--color-border);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-regular);
}
.pms-step--done .pms-step__who { border-left-color: rgba(255, 255, 255, .55); }
.pms-step__who .cell-person__avatar {
  width: 16px;
  height: 16px;
  font-size: 9px;
  border: 0;
  background: var(--color-accent-bg);
  color: var(--color-accent-text);
}
.pms-step--done .pms-step__who .cell-person__avatar {
  background: rgba(255, 255, 255, .9);
  color: var(--color-success);
}

/* ⚠️ 资金单据弹窗：类型专属段是**动态塞进壳里的** → 它与上一段 `.form-sec` 之间隔着
   `#pms-fo-extra` 这个壳，`.form-sec + .form-sec`（相邻兄弟）匹配不到 ⇒ 分段间距为 0
   （截图里"主体/申请事由"那行直接贴着"2 退款信息"标题）。
   同款坑在财务的 `#ap-cmts` 也踩过 —— 给壳补上同样的间距。 */
#pms-fo-extra { margin-top: var(--space-6); }

/* 资金单据页的类型 tab（充值 / 转账 / 退款）—— 用项目板块既有的 `seg table-tabs` 那一套，
   只补两处：卡内左对齐的外边距、数量角标的样式。
   ⚠️ 数量角标刻意做**轻**（灰底小字），它是"辅助信息"，不能和 tab 文字抢。 */
.pms-fo-tabs { margin: 0 0 var(--space-3); }
.pms-fo-tabs__n {
  margin-left: 6px;
  padding: 0 5px;
  border-radius: var(--radius-pill);
  background: var(--color-surface-2);
  color: var(--color-muted);
  font-size: var(--font-size-xs);
  font-variant-numeric: tabular-nums;
}
.pms-fo-tabs .seg__btn.is-active .pms-fo-tabs__n {
  background: var(--color-primary-50);
  color: var(--color-accent-text);
}

/* ==========================================================================
   ⭐ 2026-10-18 店铺账户 › 明细抽屉（`.pms-shd-table`）
   --------------------------------------------------------------------------
   乐哥：「抽屉展开的列宽一点吧，当前的不行，信息没一次性展示全」。

   两层原因，两层都改：
     ① **抽屉本身太窄**（560px）⇒ 开宽档 `.drawer--wide`（1000px，见 overlays.css）；
     ② **单元格会折行** ⇒ 19 位账户 ID、公司主体这类长串一旦换行，一个账户就占两行高、
        一屏只看到几户；再加列宽被压扁，"信息没一次性展示全"就是它。
        ⇒ 本文件这条：该抽屉的表**一律不折行**，放不下时由 `.table-wrap` 横向滚（不压列）。

   ⚠️ 只作用于这一张表（`.pms-shd-table`）—— ⛔ 别挂到全局 `.table` 上：
      全站很多表的长文本本来就该折行，一刀切会把别的页撑到必须横滚。
   ========================================================================== */
.pms-shd-table th,
.pms-shd-table td {
  white-space: nowrap;
}

/* ==========================================================================
   ⭐ 2026-10-19 多值标签容器（`.pms-tags`）—— 店铺账户的「代理商 / 客户」列
   --------------------------------------------------------------------------
   一家店可能挂多个代理商/客户 ⇒ 一格里出多个胶囊。
   ▸ `inline-flex + gap`：胶囊之间靠**容器间距**隔开，⛔ 别在标签里塞空格/顿号
     （那会让「星盟、微盟」变成一个带顿号的胶囊，颜色含义就没了）。
   ▸ `nowrap`：这几个名字都短，宁可整格横向滚，也不要一半在上一行一半在下一行。
   ========================================================================== */
.pms-tags {
  display: inline-flex;
  align-items: center;
  /* ⚠️ 间距走令牌（`--space-1` = 4px）—— 全站间距只认令牌，不写字面量 */
  gap: var(--space-1);
  flex-wrap: nowrap;
}

/* ==========================================================================
   ⭐ 2026-10-18 店铺弹窗「关联投放账户」候选行（`.pms-accpick`）
   --------------------------------------------------------------------------
   乐哥截图：「下面账户信息感觉有点少，只是编码，很多信息没法直接看到」。

   ▸ 原来一行只有 `账户ID（现属 X）` —— 一家店 71 个户、主体只几种，光看号挑不出来。
     现在一行两排：
       ① 账户 ID（加粗等宽） + **主体/名称** + **余额**（右对齐）
       ② 店铺归属（本店/现属 X） + 归属项目 + 代理商 + 停用标记
   ▸ 为什么用 flex 两层而不是表格：这是个可滚动的勾选清单（每项带 checkbox），
     表格会把 checkbox 列和文字列绑在一起、窄弹窗里更容易错位。
   ========================================================================== */
.pms-accpick {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  padding: 6px 4px;
  /* ⚠️ 必须走令牌（`--radius-seg` = 6）：`radius-tokens` 门禁禁字面量 6px */
  border-radius: var(--radius-seg);
  cursor: pointer;
}
.pms-accpick:hover { background: var(--color-surface-2); }
/* checkbox 跟第一排文字对齐（列表项是两排，不给 margin-top 会顶到中间） */
.pms-accpick > input { margin-top: 2px; flex: 0 0 auto; }
.pms-accpick__body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.pms-accpick__l1 { display: flex; align-items: baseline; gap: 8px; min-width: 0; }
.pms-accpick__id {
  flex: 0 0 auto;
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  font-variant-numeric: tabular-nums;   /* 账户号等宽：上下行能对齐看位数 */
}
.pms-accpick__nm {
  flex: 1 1 auto; min-width: 0;
  font-size: var(--font-size-sm);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pms-accpick__bal {
  flex: 0 0 auto;
  font-size: var(--font-size-sm);
  font-variant-numeric: tabular-nums;
}
.pms-accpick__l2 {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  font-size: var(--font-size-xs);
  color: var(--color-muted);
}
.pms-accpick__off { color: var(--color-danger); }

/* ==========================================================================
   ⭐ 2026-10-19 服务商端口 ›「可开平台」列（`.pms-pachip`）
   --------------------------------------------------------------------------
   乐哥：「这个页面的可开平台，感觉可以整合到上面可开投放平台（下方勾）里面」
   ⇒ 下方那张「端口 × 平台」矩阵撤掉，勾选直接做进这一列：**点标签即切换**。

   形态对齐全站标签（`.tag--sm`：高 18~20、字号 xs、圆角 --radius-seg）：
     · 已开 = 实色描边 + 浅底 + ✓（配色走 `UI.tagColor()`，与账户档案/店铺账户同一个色）
     · 未开 = 虚线灰底 + ＋（⛔ 未开的也必须渲染：新端口要有入口开通第一个平台）

   ⚠️ 用 `<button>` 而不是 `<span>`：它是有行为的控件（点一下写库），
      键盘 Tab 能到、回车能触发 —— 用 span 要自己补一堆 role/tabindex，还容易漏。
   ⚠️ `transition: none`（与表格行 hover 同一条纪律）：勾选是即时反馈，渐变会显得"慢半拍"。
   ⚠️ 换行（`flex-wrap`）而不是横向滚动：一行最长 6~7 个平台（约 380px），
      超出时让它在格子里换行，比给整张表加横向滚动条好读。
   ========================================================================== */
.pms-pachips {
  display: flex;
  /* ⭐ 2026-10-19 二改（乐哥：「列很宽，这个一行展示，没必要分行」）——
     **不换行**：列宽由标签内容撑开（实测 6 个平台约 380px，整表仍在屏内）。
     ⛔ 别再设 `max-width` + `flex-wrap: wrap`：那会把最后一个平台挤到第二行
        （截图里就是「巨量AD」单独一行，行高翻倍、整列看着很宽）。
     ⚠️ 平台多到一屏放不下时，由外层 `.table-wrap` 横向滚动兜底，不在这里折行。 */
  flex-wrap: nowrap;
  gap: 6px;
  /* ⭐ 2026-10-19 三改（乐哥截图红框：「居中展示吧」）——
     这一列比标签组宽（表头两行文字 + 均分剩余宽度），左对齐会在右侧留一大片空白；
     居中后左右留白对称，六枚标签像"这一格的内容"而不是"贴着左边的一串"。
     ⚠️ 只影响这一格（`.pms-pachips` 是专用容器），不会改全站表格的居中规则。 */
  justify-content: center;
}

/* 列头第二行的小字（「亮色 = 已开 · 灰色 = 未开 · 点一下切换」）。
   ⚠️ `display:block` 才能另起一行；字号必须比表头小（否则两行一样重，看着像两列）。 */
.pms-th-sub {
  display: block;
  margin-top: 2px;
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-regular);
  color: var(--color-muted);
}
.pms-pachip {
  display: inline-flex;
  align-items: center;
  height: 20px;
  padding: 0 8px;
  border: 1px dashed var(--color-border-soft);
  border-radius: var(--radius-seg);
  background: transparent;
  color: var(--color-muted);
  font-family: inherit;
  font-size: var(--font-size-xs);
  line-height: 1;
  white-space: nowrap;
  cursor: pointer;
  transition: none;
}
.pms-pachip:hover {
  border-color: var(--color-muted);
  color: var(--color-fg);
}
.pms-pachip.is-on {
  border-style: solid;
  /* 三个变量由 JS 内联（`--tag-fg` / `--tag-bg` / `--tag-line`），全部来自
     `UI.tagFg/tagBg/tagLine` —— **全站唯一色板**（见 ui.js）。
     ⛔ 别在这里写死颜色，否则同一个平台会和账户档案页不同色。
     ⚠️ 边框用 `--tag-line`（主色混白 62%）而不是 `--tag-fg`：
        实色描边 + 实色字 + 浅底，三个饱和色叠在一起会显重（这是上一版"看着奇怪"的一部分）。 */
  border-color: var(--tag-line);
  background: var(--tag-bg);
  color: var(--tag-fg);
}
.pms-pachip.is-on:hover { color: var(--tag-fg); }
.pms-pachip[disabled] {
  cursor: default;
  opacity: 0.55;
}
.pms-pachip[disabled]:hover {
  border-color: var(--color-border-soft);
  color: var(--color-muted);
}
/* ⛔ 2026-10-19：`.pms-pachip__mark`（标签前的 ✓ / ＋）**已删除** ——
   乐哥：「每一个平台前，不要留 + 这个符号…选中也不要对号」。
   状态现在只靠"实色浅底 + 深字（已开）" vs "虚线灰框 + 灰字（未开）"表达。
   ⛔ 别再给它加回来：六枚标签并排时符号比名字还抢眼，且那列会再宽 ~60px。 */

/* ══════════════════════════════════════════════════════════════════════════
   账户档案 · 批量条（2026-10-22）
   --------------------------------------------------------------------------
   乐哥（截图圈住「已选 1 条 / 批量归属到项目」）：「多选后的这个按钮位置还有样式不对吧」
   ▸ 病根不在样式类，在**位置**：它被塞进工具栏的右槽（flex 行里），于是和筛选挤在同一行、
     还把右端那串「共 N 条 · 已归属 / 未归属」统计压掉了。
   ▸ 现在它已经是工具栏的**兄弟节点**（独立一行）—— 这里只补两件小事：
     与工具栏之间的间距、以及与「达人列表」同一个 `.batch-bar` 语义（⛔ 不另造颜色）。
   ⚠️ 选择器带 `#pms-a-batchbar` 前缀，只影响本页 —— 全局 `.batch-bar`
     还被达人列表 / 费用管理用着，⛔ 别在这儿改那个类。
   ══════════════════════════════════════════════════════════════════════════ */
#pms-a-batchbar {
  margin-top: var(--space-2);
  /* 与卡片左边缘对齐（工具栏本身也是这个内距，肉眼才是一条竖线） */
  margin-left: 0;
  margin-right: 0;
}
/* 批量条里的动作按钮**不要被拉伸**：flex 行默认 stretch/居中，按钮在长条里会显得矮胖 */
#pms-a-batchbar .btn {
  flex: 0 0 auto;
}



/* ══════════════════════════════════════════════════════════════════════════
   项目详情 · 归属链路弹窗里的「＋ 新建一条链路」（2026-10-22 五改）
   --------------------------------------------------------------------------
   乐哥：「还是得在项目下建，然后一个项目可以建多个链路」——
   弹窗底部给一个就地新建入口，建完自动追加归属（不影响已归属的）。
   ══════════════════════════════════════════════════════════════════════════ */
.pms-linknew {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  border-top: 1px dashed var(--color-border-soft);
}
.pms-linknew__hint {
  color: var(--color-muted);
  font-size: var(--font-size-xs);
}

/* ══════════════════════════════════════════════════════════════════════════
   项目详情 · 投放组合（项目 × 链路 × 产品，2026-10-22 六改）
   --------------------------------------------------------------------------
   一行 = 这条链路投哪些产品（`链路名` 弱化 + 产品名正常字重）。
   多条链路时竖排；没有产品的那条明说"未选产品"，不留空（留空看着像没加载）。
   ══════════════════════════════════════════════════════════════════════════ */
.pms-combo {
  display: inline-flex;
  flex-direction: column;
  gap: 2px;
  vertical-align: top;
}
.pms-combo__line i {
  font-style: normal;
  color: var(--color-muted);
  margin-right: var(--space-2);
}
.pms-combo__line i::after {
  content: '：';
}

/* ══════════════════════════════════════════════════════════════════════════
   项目详情 · 投放组合区块（2026-10-22 八改：从弹窗搬到页面）
   --------------------------------------------------------------------------
   乐哥：「项目选了链路之后，就会自动生成一个链路行，在这个下面再去选对应的产品」
   ⇒ 一条已归属链路一行：左边链路名（弱化为标签），右边搜索多选产品。
   行的分隔用虚线（不是表格线）—— 它不是数据表，是"每条链路配一次"的清单。
   ══════════════════════════════════════════════════════════════════════════ */
.pms-cb {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.pms-cbhead__sub {
  margin-left: var(--space-2);
  color: var(--color-muted);
  font-size: var(--font-size-xs);
  font-weight: 400;
}
.pms-cbrow {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding-bottom: var(--space-3);
  border-bottom: 1px dashed var(--color-border-soft);
}
.pms-cbrow:last-child {
  padding-bottom: 0;
  border-bottom: 0;
}
.pms-cbrow__link {
  flex: 0 0 200px;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 32px;
}
.pms-cbrow__name {
  color: var(--color-fg);
  font-size: var(--font-size-sm);
  font-weight: 600;
  word-break: break-all;
}
.pms-cbrow__hint {
  color: var(--color-meta);
  font-size: var(--font-size-xs);
  flex: 0 0 auto;
}
.pms-cbrow__pick {
  flex: 1 1 auto;
  min-width: 0;
}
.pms-cbempty {
  color: var(--color-muted);
  font-size: var(--font-size-sm);
  line-height: 1.7;
}

/* ══════════════════════════════════════════════════════════════════════════
   链路详情（2026-10-22 九改）—— 链条页点链路名打开
   --------------------------------------------------------------------------
   四块：基本信息 / 关联项目 / 关联产品（按项目分组，只读）/ 关联账户。
   只读清单用行式排布（不是表格）：一行一个对象，右侧小字是归属信息。
   ══════════════════════════════════════════════════════════════════════════ */
.pms-tree__name--open {
  cursor: pointer;
}
.pms-tree__name--open:hover {
  color: var(--color-accent);
  text-decoration: underline;
}
.pms-ld__hd {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: var(--space-4) 0 var(--space-2);
  font-size: var(--font-size-sm);
  font-weight: 600;
  color: var(--color-fg);
}
.pms-ld__n {
  min-width: 20px;
  padding: 0 var(--space-1);
  border-radius: var(--radius-pill);
  background: var(--color-surface-2);
  color: var(--color-muted);
  font-size: var(--font-size-xs);
  font-weight: 400;
  text-align: center;
}
.pms-ld__list {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}
.pms-ld__row {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  width: 100%;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  background: var(--color-surface-2);
  text-align: left;
}
.pms-ld__row--link {
  cursor: pointer;
  border: 0;
  font: inherit;
  color: inherit;
}
.pms-ld__row--link:hover {
  background: var(--color-surface-2);
  outline: 1px solid var(--color-border-soft);
}
.pms-ld__main {
  flex: 1 1 auto;
  min-width: 0;
  font-size: var(--font-size-sm);
  color: var(--color-fg);
  word-break: break-all;
}
.pms-ld__sub {
  flex: 0 0 auto;
  color: var(--color-muted);
  font-size: var(--font-size-xs);
}
.pms-ld__tag {
  flex: 0 0 auto;
  color: var(--color-meta);
  font-size: var(--font-size-xs);
}
.pms-ld__prodrow {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  background: var(--color-surface-2);
}
.pms-ld__prodproj {
  flex: 0 0 220px;
  border: 0;
  background: none;
  padding: 0;
  font: inherit;
  font-size: var(--font-size-sm);
  color: var(--color-accent);
  cursor: pointer;
  text-align: left;
  word-break: break-all;
}
.pms-ld__prodproj:hover {
  text-decoration: underline;
}
.pms-ld__prods {
  flex: 1 1 auto;
  min-width: 0;
  font-size: var(--font-size-sm);
  color: var(--color-fg);
}
.pms-ld__empty {
  padding: var(--space-3);
  border: 1px dashed var(--color-border-soft);
  border-radius: var(--radius-sm);
  color: var(--color-muted);
  font-size: var(--font-size-xs);
  line-height: 1.7;
}
.pms-ld__note {
  margin-top: var(--space-4);
  color: var(--color-meta);
  font-size: var(--font-size-xs);
  line-height: 1.7;
}

/* ══════════════════════════════════════════════════════════════════════════
   项目详情 › 投放链路（三级明细页，2026-10-22 十改）
   --------------------------------------------------------------------------
   乐哥：「一个项目 + 1 个链路一行，放在项目下面（三级页面）；
   这个链路和产品、投放账户再做关联；**配置入口不能和明细展示放一起**」
   ⇒ 页面主体是只读表格，配置全在行内「配置」弹窗里。
   ══════════════════════════════════════════════════════════════════════════ */
.pms-lr__name {
  font-size: var(--font-size-sm);
  color: var(--color-fg);
  font-weight: 600;
  word-break: break-all;
}
.pms-lr__prods,
.pms-lr__acc {
  font-size: var(--font-size-sm);
  color: var(--color-fg);
}
.pms-lr__note {
  margin-top: var(--space-3);
  padding: var(--space-3);
  border: 1px dashed var(--color-border-soft);
  border-radius: var(--radius-sm);
  color: var(--color-muted);
  font-size: var(--font-size-xs);
  line-height: 1.7;
}
.pms-lr-config__hd {
  margin: var(--space-3) 0 var(--space-2);
  font-size: var(--font-size-sm);
  font-weight: 600;
  color: var(--color-fg);
}
.pms-lr-config__hd:first-child {
  margin-top: 0;
}
.pms-lr-config__note {
  margin-top: var(--space-3);
  color: var(--color-meta);
  font-size: var(--font-size-xs);
  line-height: 1.7;
}

/* ══════════════════════════════════════════════════════════════════════════
   新建/编辑项目 · 顶部「项目名」（2026-10-22 十一改 → 十二改修布局）
   --------------------------------------------------------------------------
   乐哥：「留意下 ui 布局，还有注释提醒，现在这个乱七八糟的」
   ⇒ 两个**等高**的框并排：左 = 公式前缀（只读），右 = 自定义文字（可填）。
     原来占位长句 + 输入框挤在一行、下面再压一整行说明 ⇒ 全删，只留框本身。
   ══════════════════════════════════════════════════════════════════════════ */
.pms-namebar {
  display: flex;
  align-items: stretch;
  gap: var(--space-3);
  flex-wrap: wrap;
}
/* ⭐ 2026-10-22 十三改（乐哥：「这俩框 55 分吧」）：
   两个框**各占一半**（`flex: 1 1 0` + gap），⛔ 不再给左边 320 起、右边固定 240 ——
   那样左边吃掉大半行、右边剩一条窄缝，看着不平衡。 */
.pms-namebar__pre {
  display: flex;
  align-items: center;
  flex: 1 1 0;
  min-width: 0;
  height: var(--control-height);
  padding: 0 var(--space-4);
  border: 1px dashed var(--color-border-soft);
  border-radius: var(--radius-md);
  background: var(--color-surface-2);
  color: var(--color-fg);
  font-size: var(--font-size-base);
  font-weight: 600;
  word-break: break-all;
}
.pms-namepre__ph {
  color: var(--color-meta);
  font-weight: 400;
}
/* 「保留现有项目名」是**状态标签**，不是名字的一部分 ⇒ 小字 + 浅底，与原名字用竖线隔开。 */
.pms-namepre__keep {
  margin-right: var(--space-2);
  padding: 0 var(--space-2);
  border-radius: var(--radius-sm);
  background: var(--color-bg);
  color: var(--color-muted);
  font-size: var(--font-size-xs);
  font-weight: 400;
  white-space: nowrap;
}
.pms-namebar__sfx {
  flex: 1 1 0;
  min-width: 0;
  height: var(--control-height);
}

/* 签约区空态：一行说明 + 右侧主按钮（原来三段纵向堆，空白太大） */
.pjt-sign__empty {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  flex-wrap: wrap;
  padding: var(--space-3) var(--space-4);
  border: 1px dashed var(--color-border-soft);
  border-radius: var(--radius-sm);
  background: var(--color-surface-2);
}
.pjt-sign__emptytxt {
  color: var(--color-muted);
  font-size: var(--font-size-sm);
}

/* 表单里"主数据清单为空"的明确提示（2026-10-22 十一改：不许只留一个空白下拉） */
.pms-fieldwarn {
  margin-top: var(--space-1);
  color: var(--color-warning-text);
  font-size: var(--font-size-xs);
  line-height: 1.6;
}

/* 项目列表 · 「投放链路」「产品」两列的**数量**（2026-10-22 十六改）
   --------------------------------------------------------------------------
   乐哥：「这两列，我觉得现在得改成数量」—— 名字会把列撑爆（"UDS腾讯 等 2 条"）。
   数字实心、单位弱化；完整名单在 title 里，悬浮可见。
   ══════════════════════════════════════════════════════════════════════════ */
.pms-count {
  font-size: var(--font-size-sm);
  color: var(--color-fg);
  white-space: nowrap;
}
.pms-count i {
  margin-left: 2px;
  font-style: normal;
  font-size: var(--font-size-xs);
  color: var(--color-muted);
}

/* 卡 2「签约」：一行 = 一家主体 + 它自己的合同（表格式两列，不是散着的两行） */
.pms-signrows {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.pms-signrow {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  padding: 10px 12px;
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-md);
  background: var(--color-surface-2);
}
.pms-signrow__ent {
  flex: 0 0 280px;
  min-width: 0;
  font-size: var(--font-size-sm);
  color: var(--color-fg);
  word-break: break-all;
}
.pms-signrow__ct {
  flex: 1 1 auto;
  min-width: 0;
  font-size: var(--font-size-sm);
  color: var(--color-fg-2);
  word-break: break-all;
}
.pms-signrow--empty {
  display: block;
  color: var(--color-muted);
  font-size: var(--font-size-sm);
  background: var(--color-surface);
  border-style: dashed;
}

/* 卡 1 头部右侧的「调整链路」（十七改：入口从字段表挪到这里） */
.pms-rel__act {
  margin-left: auto;
}


/* ==== /css/pages-shop.css ==== */
/* ==========================================================================
   店铺运营中心 —— 板块样式（public/css/pages-shop.css）
   --------------------------------------------------------------------------
   2026-10-23 建立。第 6 个板块。

   ⭐⭐ 本文件**刻意很薄**（与 pages-project.css 同一条纪律）：能用公共件的一律不重写。
     页面骨架   `.page` / `.page-header` / `.page-title` / `.page-actions`   ← layout.css / pages.css
     KPI        `.kpi-row` / `.kpi-card` / `.kpi-card--accent` / `.kpi-card--tone-*` ← pages.css
     卡片       `.card`                                                      ← components.css
     表格       `.table` / `.table-wrap` / `.table-toolbar__total`            ← table.css
     控件       `.input` / `.select` / `.btn`（两档制靠全局兜底）              ← components.css
     空态       `UI.emptyState`                                              ← components.css
     侧栏       `.nav-group` / `.nav-item--sub` 等（与其余四板块共用）          ← layout.css

   ⇒ 只写**本板块独有的三样**：
     ① 利润拆解块（`.shop-calc*`）—— 把算式逐行摆出来，别处没有这种形态
     ② 表格数字列对齐（`.shop-num` / `.shop-num--right`）
     ③ 凭证表单（`.shop-form*`）+ 分页条（`.shop-pager*`）

   ⚠️ 三条硬约束（DESIGN-SPEC 门禁会拦）：
     · **禁裸 hex**，颜色一律走 `--color-*` 令牌；
     · **禁 `var(--令牌, 兜底值)`** —— 兜底会掩盖令牌名拼错，变成静默失效；
     · 过渡/动画时长一律引 `--motion-*`，⛔ 不许裸写 `.15s`。
   ========================================================================== */

/* ══════════════════════════════════════════════════════════════════════════
   ① 让位三件套（⭐ 不是可选项）
   ──────────────────────────────────────────────────────────────────────────
   本板块是「独立外壳 + 自带侧栏 + 显示标签栏」形态。`.is-standalone` 默认把侧栏
   与标签栏整个 `display:none`，且 `.app-main` 不给自己留侧栏的位置。
   ⇒ 三件缺任何一件的症状都**不报错**：
      · 漏 ① ⇒ 侧栏 DOM 全对、屏幕上一片空白（本项目在项目板块踩过，查了很久）
      · 漏 ② ⇒ 标签栏不见了
      · 漏 ③ ⇒ 内容区被侧栏盖住 / 左侧空出 240px
   ⚠️ `margin-left` 必须是 `sidebar-width + shell-inset`（含那条缝）——
      只写 `sidebar-width` 会让内容屏与侧栏**贴死**（缝 = 0），与其他板块不一致。
   ⚠️ 768 断点必须把 margin 收回 0：手机上侧栏是抽屉（覆盖式），
      不收的话内容只剩 ~150px（本项目实测过）。 */
.app-shell.is-standalone.is-shop .sidebar { display: flex !important; }
.app-shell.is-standalone.is-shop .page-tabs-bar { display: flex !important; }
.app-shell.is-standalone.is-shop .app-main {
  margin-left: calc(var(--sidebar-width) + var(--shell-inset));
  width: auto;
}
@media (max-width: 768px) {
  .app-shell.is-standalone.is-shop .app-main {
    margin-left: 0;
    width: auto;
  }
}

/* 页头说明（替代 `.page-desc` —— 那个类有白名单门禁，新板块用自己的类） */
.shop-pagehint {
  margin-top: var(--space-2);
  font-size: var(--font-size-sm);
  line-height: 1.6;
  color: var(--color-muted);
}

/* ══════════════════════════════════════════════════════════════════════════
   ② 利润拆解块
   ──────────────────────────────────────────────────────────────────────────
   乐哥的诉求是「我就能估算出利润」，所以要让他**看得见算式**、能自己复算，
   而不是只给一个利润数字（那种数没法核对，等于让他信系统）。
   ⇒ 三行输入 + 一行结果，逐行左标签右数值，等宽数字对齐。 */
.shop-calc__title {
  font-size: var(--font-size-lg);
  font-weight: 500;
  color: var(--color-fg);
}
.shop-calc__body {
  padding: 0 var(--space-5) var(--space-5);
}
.shop-calc__line {
  display: flex;
  align-items: baseline;
  gap: var(--space-4);
  padding: var(--space-3) 0;
  border-bottom: 1px solid var(--color-border-soft);
  font-size: var(--font-size-sm);
}
.shop-calc__line:last-of-type { border-bottom: 0; }
.shop-calc__lab {
  color: var(--color-muted);
  flex: 0 0 220px;
  min-width: 0;
}
.shop-calc__val {
  color: var(--color-fg);
  font-variant-numeric: tabular-nums;
}
/* 结果行：做成"总计"的视觉量级，与上面三行拉开层级 */
.shop-calc__line--total { padding-top: var(--space-4); }
.shop-calc__line--total .shop-calc__lab {
  color: var(--color-fg);
  font-weight: 500;
}
.shop-calc__line--total .shop-calc__val {
  font-size: var(--font-size-xl);
  font-weight: 600;
  color: var(--color-accent-text);
}
/* 口径标签：小号柔底，跟在标签后面（不占一整行） */
.shop-calc__tag {
  display: inline-block;
  margin-left: var(--space-2);
  padding: 0 var(--space-2);
  border-radius: var(--radius-sm);
  font-size: var(--font-size-xs);
  font-weight: 400;
  line-height: 18px;
  white-space: nowrap;
  background: var(--color-primary-50);
  color: var(--color-primary-700);
}
.shop-calc__note {
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  border-top: 1px solid var(--color-border-soft);
  font-size: var(--font-size-xs);
  line-height: 1.7;
  color: var(--color-meta);
}

/* ══════════════════════════════════════════════════════════════════════════
   ③ 数字列
   ──────────────────────────────────────────────────────────────────────────
   订单号是 19 位、金额要上下对齐 ⇒ 一律等宽数字。⛔ 不加 `white-space: nowrap`
   到订单号上会让它在窄屏折行，看着像两条不同的订单。 */
.shop-num {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.shop-num--right { text-align: right; }

/* ══════════════════════════════════════════════════════════════════════════
   ④ 分页条
   ══════════════════════════════════════════════════════════════════════════ */
.shop-pager {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-4) 0 var(--space-2);
}
.shop-pager__info {
  font-size: var(--font-size-sm);
  color: var(--color-muted);
  font-variant-numeric: tabular-nums;
}

/* ══════════════════════════════════════════════════════════════════════════
   ⑤ 凭证表单
   ──────────────────────────────────────────────────────────────────────────
   ⚠️ 标签在上（DESIGN-SPEC §7.2 全站标准），不是标签在左。 */
.shop-form {
  padding: var(--space-2) var(--space-5) var(--space-5);
}
.shop-form__row {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-bottom: var(--space-4);
  max-width: 560px;
}
.shop-form__label {
  font-size: var(--font-size-sm);
  color: var(--color-fg);
  font-weight: 500;
}
.shop-form__hint {
  max-width: 560px;
  font-size: var(--font-size-xs);
  line-height: 1.7;
  color: var(--color-meta);
}


/* ==== /css/datetime-picker.css ==== */
/* ==========================================================================
   达播工作台 — datetime-picker.css
   三种模式：date(普通日期) | datetime(日期+时间) | range(同日时间范围)
   样式参考：飞书日历组件风格
   ========================================================================== */

.dtp {
  position: relative;
  display: block;
}

/* ---- 触发按钮 ---- */
.dtp-trigger {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  /* ⭐ 2026-09-29 乐哥全站审察：原硬编码 40px ⇒ 全站出现**第三档**，
     与同行 44px 的输入框/下拉不齐（实测：「地点」44 旁边「时间范围」只有 40）。
     ⇒ 归位到标准档令牌 44px；工具栏内由 `.toolbar .dtp-trigger` 兜到紧凑档 34px。 */
  height: var(--control-height);
  padding: 0 var(--space-4);
  border: 1px solid var(--input-border);
  border-radius: var(--radius-md);
  background: var(--input-bg);
  color: var(--color-fg);
  font-size: var(--font-size-base);
  cursor: pointer;
  transition: border-color var(--motion-fast) var(--motion-ease-standard),
    box-shadow var(--motion-fast) var(--motion-ease-standard);
}
.dtp-trigger:hover { border-color: var(--input-focus-border); }
.dtp-trigger:focus-visible { outline: none; border-color: var(--input-focus-border); box-shadow: var(--focus-ring); }
.dtp-trigger__icon { color: var(--color-meta); display: inline-flex; flex-shrink: 0; }
.dtp-trigger__text {
  flex: 1;
  text-align: left;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-feature-settings: "tnum", "lnum";
  font-variant-numeric: tabular-nums;
}
.dtp-trigger:not(.is-filled) .dtp-trigger__text { color: var(--color-meta); }
.dtp-trigger__clear { display: none; color: var(--color-meta); flex-shrink: 0; }
.dtp-trigger__clear:hover { color: var(--color-danger); }

/* ---- 弹出面板基础 ---- */
.dtp-panel {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  z-index: var(--z-dropdown);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: 0 4px 24px rgba(0, 0, 0, 0.12), 0 0 0 1px rgba(0, 0, 0, 0.04);
  overflow: hidden;
  animation: dtp-fade-in var(--motion-base) var(--motion-ease-sheet);
}
@keyframes dtp-fade-in {
  from { opacity: 0; transform: translateY(-4px); }
  to { opacity: 1; transform: translateY(0); }
}

/* hidden 必须优先于 .dtp-panel--datetime/range 的 display:flex，否则面板关不掉 */
.dtp-panel[hidden] { display: none !important; }

/* date 模式：窄面板 */
.dtp-panel--date {
  width: 296px;
}

/* month 模式：月份宫格窄面板 */
.dtp-panel--month {
  width: 296px;
}
.dtp-panel--month .dtp-panel__body {
  display: block;
}

/* datetime / range 模式：宽面板 flex 分栏 */
.dtp-panel--datetime,
.dtp-panel--range {
  display: flex;
  flex-direction: column;
}
.dtp-panel__body {
  display: flex;
}

/* ---- 日历区域 ---- */
.dtp-cal {
  flex-shrink: 0;
  padding: var(--space-3);
}
.dtp-panel--date .dtp-cal { width: 100%; }
.dtp-panel--datetime .dtp-cal,
.dtp-panel--range .dtp-cal { width: 296px; }

.dtp-cal__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--space-2);
}
.dtp-cal__title {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
  color: var(--color-fg);
  border: none;
  background: transparent;
  padding: 2px 6px;
  border-radius: var(--radius-sm);
  cursor: pointer;
  user-select: none;
  transition: background var(--motion-fast) var(--motion-ease-standard);
}
.dtp-cal__title:hover { background: var(--color-surface-2); }
.dtp-nav {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--color-fg-2);
  cursor: pointer;
  transition: background var(--motion-fast) var(--motion-ease-standard);
}
.dtp-nav:hover { background: var(--color-surface-2); color: var(--color-fg); }

.dtp-cal__week {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  margin-bottom: 4px;
}
.dtp-cal__week span {
  text-align: center;
  font-size: var(--font-size-xs);
  color: var(--color-muted);
  padding: 4px 0;
  user-select: none;
}

.dtp-cal__days {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 2px;
}
.dtp-day {
  height: 34px;
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--color-fg);
  font-size: var(--font-size-sm);
  cursor: pointer;
  font-feature-settings: "tnum", "lnum";
  font-variant-numeric: tabular-nums;
  transition: background var(--motion-fast) var(--motion-ease-standard),
    color var(--motion-fast) var(--motion-ease-standard);
}
.dtp-day:hover { background: var(--color-primary-50); }
.dtp-day.is-out { color: var(--color-meta); opacity: 0.4; }
.dtp-day.is-today { color: var(--color-accent-text); font-weight: var(--font-weight-semibold); }
.dtp-day.is-picked { background: var(--color-accent); color: var(--color-accent-on); font-weight: var(--font-weight-medium); }
.dtp-day.is-picked:hover { background: var(--color-accent-hover); }
.dtp-day.is-holiday:not(.is-picked) { color: var(--color-danger); }

/* ---- 月份快速跳转面板 ---- */
.dtp-month-panel {
  padding: var(--space-3);
  width: 100%;
}
.dtp-month-panel__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--space-2);
}
.dtp-month-panel__year {
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
  color: var(--color-fg);
  user-select: none;
}
.dtp-month-nav {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--color-fg-2);
  cursor: pointer;
  transition: background var(--motion-fast) var(--motion-ease-standard);
}
.dtp-month-nav:hover { background: var(--color-surface-2); color: var(--color-fg); }
.dtp-month-panel__grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--space-2);
}
.dtp-month-item {
  height: 40px;
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--color-fg);
  font-size: var(--font-size-sm);
  cursor: pointer;
  transition: background var(--motion-fast) var(--motion-ease-standard),
    color var(--motion-fast) var(--motion-ease-standard);
}
.dtp-month-item:hover { background: var(--color-primary-50); }
.dtp-month-item.is-active {
  background: var(--color-accent);
  color: var(--color-accent-on);
  font-weight: var(--font-weight-medium);
}
.dtp-month-item.is-active:hover { background: var(--color-accent-hover); }

/* range 模式日期高亮 */
.dtp-day.is-in-range { background: var(--color-primary-50); border-radius: 0; }
.dtp-day.is-range-start {
  background: var(--color-accent);
  color: var(--color-accent-on);
  border-radius: var(--radius-sm) 0 0 var(--radius-sm);
}
.dtp-day.is-range-end {
  background: var(--color-accent);
  color: var(--color-accent-on);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}
.dtp-day.is-range-start.is-range-end {
  border-radius: var(--radius-sm);
}

/* ---- 时间列表区域（datetime 模式右侧）---- */
.dtp-times {
  width: 112px;
  border-left: 1px solid var(--color-border-soft);
  display: flex;
  flex-direction: column;
}
/* 时间输入框（时间列表顶部，可直接输入具体时间） */
.dtp-time-input {
  display: block;
  width: 100%;
  height: 28px;
  padding: 0 var(--space-2);
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-sm);
  background: var(--input-bg);
  color: var(--color-fg);
  font-size: var(--font-size-sm);
  text-align: center;
  flex-shrink: 0;
  font-feature-settings: "tnum", "lnum";
  font-variant-numeric: tabular-nums;
  outline: none;
  transition: border-color var(--motion-fast) var(--motion-ease-standard);
}
.dtp-time-input:focus { border-color: var(--input-focus-border); box-shadow: var(--focus-ring); }
.dtp-time-input:disabled { opacity: 0.4; cursor: not-allowed; }
.dtp-time-input::placeholder { color: var(--color-meta); }
.dtp-times .dtp-time-input { margin: var(--space-2); width: auto; }
.dtp-range-col .dtp-time-input { margin-bottom: var(--space-1); }
.dtp-times__list {
  flex: 1;
  overflow-y: auto;
  padding: var(--space-1) 0;
  max-height: 300px;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: thin;
}
.dtp-times__list::-webkit-scrollbar { width: 4px; }
.dtp-times__list::-webkit-scrollbar-thumb { background: var(--color-gray-400); border-radius: 2px; }
.dtp-times__list::-webkit-scrollbar-track { background: transparent; }

.dtp-time-item {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 36px;
  padding: 0 var(--space-2);
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--color-fg);
  font-size: var(--font-size-sm);
  font-feature-settings: "tnum", "lnum";
  font-variant-numeric: tabular-nums;
  cursor: pointer;
  width: 100%;
  transition: background var(--motion-fast) var(--motion-ease-standard);
}
.dtp-time-item:hover { background: var(--color-surface-2); }
.dtp-time-item.is-active {
  background: var(--color-accent);
  color: var(--color-accent-on);
  font-weight: var(--font-weight-medium);
}
.dtp-time-item.is-active:hover { background: var(--color-accent-hover); }
.dtp-time-item.is-disabled {
  color: var(--color-meta);
  opacity: 0.35;
  cursor: not-allowed;
}
.dtp-time-item.is-disabled:hover { background: transparent; }

/* ---- 范围时间面板（range 模式右侧）---- */
.dtp-range-panel {
  width: 224px;
  border-left: 1px solid var(--color-border-soft);
  display: flex;
  flex-direction: column;
  padding: var(--space-3);
  gap: var(--space-2);
}
.dtp-range-tbd {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-1) 0;
}
.dtp-range-tbd__label {
  font-size: var(--font-size-sm);
  color: var(--color-fg);
  user-select: none;
}
.dtp-range-cols {
  display: flex;
  gap: var(--space-2);
  flex: 1;
  min-height: 0;
}
.dtp-range-col {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.dtp-range-col__label {
  font-size: var(--font-size-xs);
  color: var(--color-muted);
  margin-bottom: var(--space-1);
  text-align: center;
  user-select: none;
}
.dtp-range-col__list {
  flex: 1;
  overflow-y: auto;
  max-height: 240px;
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-md);
  -webkit-overflow-scrolling: touch;
  scrollbar-width: thin;
}
.dtp-range-col__list::-webkit-scrollbar { width: 4px; }
.dtp-range-col__list::-webkit-scrollbar-thumb { background: var(--color-gray-400); border-radius: 2px; }
.dtp-range-col__list::-webkit-scrollbar-track { background: transparent; }
.dtp-range-col__list .dtp-time-item { height: 30px; font-size: var(--font-size-xs); }

/* 时间待定：禁用态 */
.dtp-range-cols.is-disabled {
  opacity: 0.4;
  pointer-events: none;
}

/* ---- 底部操作栏 ---- */
.dtp-foot {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-2) var(--space-3);
  border-top: 1px solid var(--color-border-soft);
  background: var(--color-surface);
}
.dtp-foot__btn {
  height: 28px;
  padding: 0 var(--space-3);
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  font-size: var(--font-size-sm);
  cursor: pointer;
  transition: background var(--motion-fast) var(--motion-ease-standard);
  white-space: nowrap;
}
.dtp-foot__today { color: var(--color-accent-text); }
.dtp-foot__today:hover { background: var(--color-primary-50); }
.dtp-foot__clear { color: var(--color-danger); margin-left: auto; }
.dtp-foot__clear:hover { background: var(--color-danger-bg); }
.dtp-foot__save {
  background: var(--color-accent);
  color: var(--color-accent-on);
  font-weight: var(--font-weight-medium);
  padding: 0 var(--space-4);
}
.dtp-foot__save:hover { background: var(--color-accent-hover); }
.dtp-foot__dur {
  font-size: var(--font-size-xs);
  color: var(--color-muted);
  font-feature-settings: "tnum", "lnum";
  font-variant-numeric: tabular-nums;
  margin-left: var(--space-2);
}

/* ---- 响应式：小屏面板右对齐 / 全宽 ---- */
@media (max-width: 520px) {
  .dtp-panel--datetime,
  .dtp-panel--range {
    position: fixed;
    left: 8px;
    right: 8px;
    top: auto;
    bottom: 8px;
    width: auto;
    max-height: 80vh;
  }
  .dtp-panel--datetime .dtp-cal,
  .dtp-panel--range .dtp-cal { width: 100%; border-bottom: 1px solid var(--color-border-soft); }
  .dtp-panel__body { flex-direction: column; }
  .dtp-times { width: 100%; border-left: none; border-top: 1px solid var(--color-border-soft); }
  .dtp-range-panel { width: 100%; border-left: none; border-top: 1px solid var(--color-border-soft); }
  .dtp-times__list { max-height: 160px; }
  .dtp-range-col__list { max-height: 160px; }
}

/* 移动端：触发按钮与输入框统一收紧为 36px（需放这里，因本文件晚于 components.css 加载） */
@media (max-width: 768px) {
  .dtp-trigger { height: 36px; }
}


/* ==== /css/pricing-compare.css ==== */
/* ============================================================================
   机制比价 / 平台补贴 —— 页面专属样式
   ----------------------------------------------------------------------------
   ⚠️⚠️ 为什么单独一个文件、而不是塞进 `public/css/pages.css`（2026-09-23 定）：
     `pages.css` 是**多会话共用**文件。实测当天它被另一个会话**整体覆盖 5 次**，
     其中一次正好把我刚写好的样式冲掉（不报错、不冲突，`grep` 才发现内容没了）。
     页面专属样式留在共用文件里 ⇒ 每次改动都要承担"被覆盖"和"误推别人半成品"两种风险。
     ⇒ 本页样式**全部集中在这里**，与其他会话解耦：
        · 我只推自己的文件，不会把别人没做完的样式带上线；
        · 别人覆盖 pages.css 也冲不掉我的样式。
   ⚠️ 加载顺序：在 `index.html` 里放在**所有样式之后**（最后一条 link）——
      同特异性时后者胜，这样本页规则不会被 `pages.css` / `pages-schedule.css` 盖掉。
      ⚠️ 但 `.sf-card` 那条是**双类**（`.sf-card.pc-card`）：单类 `.pc-card` 特异性 0,1,0
         打不过 `.sf-card`（0,1,0 但更早）在别处的高特异性组合，双类最稳。
   ⚠️ 前缀 `.pc-` = pricing compare，与全站其他前缀不冲突（选前缀的原因见原注释）。
   ============================================================================ */

/* ── ⭐⭐ 警告一律用**红字 + 红色标记**，不用黄色、不用底色块（2026-09-23 乐哥定）──────
   乐哥原话：「我觉得这些有问题的提醒用红字吧，不要用黄字了，而且不要用这种黄框了，看着都不直观」
   ⚠️ 真因是**对比度**不是审美：warning 色 #F2A918 在白底上只有 ~2:1（可读线要 4.5:1），
      再叠一层浅黄底（#FEF5E7）就更糊 ⇒ "看不清"就是"不直观"。
      用 danger-strong #D93025（白底 ≈4.6:1）。
   ⚠️ 块的边界改用**左侧 3px 红竖条**表达，不再整块刷底色 ——
      同样醒目，但页面不会出现一片黄。
   ⛔ 别再引入第二套警告色；全页警告色只有下面这几处定义。 */
/* ==========================================================================
   机制比价（2026-09-23 乐哥需求）
   ========================================================================== */

/* ══════════════════════════════════════════════════════════════════════════
   对照矩阵（2026-09-23 第三版）—— **一行一个概念，左右逐行对齐**
   --------------------------------------------------------------------------
   乐哥：「你这个 UI 问题很大，每一行对应的位置都不一致。比如商品，我们就把商品放在
          同样的位置；到手价，我可能就放在同样的行里面，就不要来来回回的。」
   ⚠️⚠️ 为什么原来**必然**错位（不是样式没调好）：那时候是**两个各自独立的列**，
      列内自己往下堆字段 —— 我方 3 块、对方 6 块，行与行对不上。
      实测（1920 视口）「商品」表头 我方 y≈247 / 对方 y≈345，**差 98px**。
      靠调间距治不了本：再加一个字段就又错开。
   ⭐ 本矩阵用 **grid 三列**：概念名 | 我方 | 对方 ⇒ 对齐由**结构**保证。
   ⚠️ 格子顶部对齐（flex column）⇒ 同一行两边的输入框顶边相等。
   ⛔ 别退回"两个独立列各自堆"的写法：那是错位的结构性根因。
   ══════════════════════════════════════════════════════════════════════════ */
.pc-matrix {
  display: grid;
  /* 104px：容下最长的概念名「主推品件数」（5 字）+ 右侧的「?」按钮 + 两侧内边距，
     且 `white-space: nowrap` 保证**永不折行**（折行会让行高变化、与右侧控件错位）。 */
  grid-template-columns: 104px minmax(0, 1fr) minmax(0, 1fr);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  overflow: hidden;
}
.pc-matrix__corner,
.pc-matrix__head {
  background: var(--color-surface-2);
  padding: var(--space-3);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
  color: var(--color-fg);
  border-bottom: 1px solid var(--color-border);
}
/* ── 表头规格统一（2026-09-23 乐哥：「表头的字有些大有些小，颜色也有点奇奇怪怪的」）──
   ⚠️ 根因：同层级的东西用了不同规格 —— 列头 14px 深色、行标签 12px 灰色、明细表头 12px 灰色，
      三套字号两种颜色混在一起，看着就没规矩。
   ⭐ 定成**两档**，各自只用一套：
      · **列头**（我方机制 / 对方机制）：13px + semibold + 正常色 —— 它是列的身份，要立得住
      · **行标签**（概念名 / 明细表头）：12px + muted + 常规字重 —— 它是配角，不该抢内容
   ⭐ 两侧用**颜色区分**（乐哥要求）：我方=主色，对方=中性。
      做法克制：只在列头用色 + 各加一条 2px 底边色条，不动内容区底色
      （内容区染色会与「我方只读、对方可填」的控件底色打架，反而更乱）。 */
/* ⚠️ 左上角格子归到**行标签**档（不是列头档）—— 它竖着对应的是概念名那一列，
   跟着列头走会变得和我方/对方一样重，反而混淆层级。 */
.pc-matrix__corner {
  font-size: var(--font-size-xs);      /* 12px = 与概念名、明细表头同档 */
  font-weight: var(--font-weight-regular);
  color: var(--color-muted);
}
.pc-matrix__head {
  font-size: var(--font-size-sm);      /* 14px = 列头档（唯一比行标签大的地方） */
  font-weight: var(--font-weight-semibold);
  color: var(--color-fg);
  letter-spacing: .02em;
  text-align: center;
}
/* 左右两侧的色条：我方主色 / 对方中性 —— 让"哪边是哪边"一眼可辨 */
.pc-matrix__head { box-shadow: inset 0 -2px 0 var(--color-primary-500); }
.pc-matrix__head--theirs { box-shadow: inset 0 -2px 0 var(--color-border); }
/* 竖分隔线挂**显式类名**（我方 6 个格子、对方 6 个格子各自加类）。
   ⚠️ 不用 `:last-child` / `nth-child` 数学 —— 本项目实测过：漏一个收尾标签就会让这类
      选择器**静默不匹配**，分隔线整体消失且不报错（排查很久）。显式类名不受结构变化影响。 */
/* ⚠️⚠️ 分隔线用"**两边都有 1px 边框、我方透明**"的写法，而不是"只有对方有边框"：
   否则对方那格的内容宽度比我方窄 1px ⇒ 里面明细表宽 717 vs 718 ⇒
   「数量/标价」列的 x 差 1px（视觉几乎看不出，但"逐行对齐"的需求上不该留误差）。
   实测：改前 484/483，改后两列完全相等。 */
.pc-matrix__head,
.pc-matrix__cell { border-left: 1px solid transparent; }
.pc-matrix__head--theirs,
.pc-matrix__cell--theirs { border-left-color: var(--color-border); }

.pc-matrix__lbl {
  padding: var(--space-3) var(--space-2);
  background: var(--color-surface-2);
  border-bottom: 1px solid var(--color-border-soft);
  font-size: var(--font-size-xs);
  line-height: 1.45;
  color: var(--color-muted);
}
/* 单位跟概念名**同行**（小一号、弱色）—— 另起一行会让标签变两行、行高变高，
   与右侧输入框错位。 */
.pc-matrix__unit { font-size: 10px; color: var(--color-muted); margin-left: 2px; }

/* ── 「?」说明按钮 + 浮层（2026-09-23 乐哥：「这些注释显得好乱，能不能放在 ? 里，
      点击之后显示出来？这样界面会比较干净」）──────────────────────────────────
   ⚠️ 只收**静态说明**。动态提示（未维护补贴 / 假破价警告 / 还没勾主推品）**不在此列**，
      它们仍以 `.pc-field__hint--warn` 常驻显示 —— 藏起来会让人漏掉该处理的事。
   ⚠️ 有问号的格子加 `.pc-has-tip`，靠 `padding-top` 让出顶部一条给问号 ——
      不能用绝对定位压住内容：内容是输入框（数字右对齐会顶到右边）与表格，
      直接盖上去会遮住数字与「删除」按钮（实测过）。 */
/* ⚠️ 2026-09-23 乐哥：「能不能把 ? 放在表头（概念名）旁边，最左边好像加个?」
   ⇒ 问号从"格子右上角"移到**概念名后面**：一行只有一个（说明里用「我方：/ 对方：」分段），
      比原来一行两个更好认，也把格子内的顶部空间还给了控件。
   ⚠️ 概念名加 `nowrap`：折行会让这一格变高、与同行右侧控件错开（本项目对齐问题的老来源）。 */
.pc-matrix__lbl {
  white-space: nowrap;
  display: flex;
  align-items: center;
  gap: 3px;
  flex-wrap: nowrap;
}
/* 行标签统一规格：12px + muted（与明细表头同档） */
.pc-matrix__lbl { font-size: var(--font-size-xs); color: var(--color-muted); }
.pc-tip {
  position: static;
  flex: none;
  width: 16px;
  height: 16px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--color-border);
  border-radius: 50%;
  background: var(--color-surface);
  color: var(--color-muted);
  font-size: 10px;
  line-height: 1;
  cursor: pointer;
  font-family: inherit;
}
.pc-tip:hover, .pc-tip:focus-visible {
  border-color: var(--color-primary-500);
  color: var(--color-primary-500);
  outline: none;
}
.pc-tip.is-open {
  border-color: var(--color-primary-500);
  background: var(--color-primary-500);
  color: #fff;
}

/* 浮层：**必须是 fixed** —— `.pc-matrix` 有 `overflow: hidden`（为圆角），
   普通 absolute 浮层会被**裁掉**（本项目在别的浮层上踩过这个坑）。 */
.pc-tip-pop {
  position: fixed;
  z-index: 900;
  max-width: 320px;
  padding: var(--space-3);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg));
  font-size: var(--font-size-xs);
  line-height: 1.7;
  color: var(--color-fg-2);
  /* ⚠️ 必须 pre-line：说明里用 \n 分「我方：/ 对方：」两段，
     默认会折成一坨（实测过）。 */
  white-space: pre-line;
}
.pc-tip-pop[hidden] { display: none; }
/* 小三角：指向被点的那颗问号 */
.pc-tip-pop::before {
  content: '';
  position: absolute;
  top: -5px;
  width: 9px;
  height: 9px;
  background: var(--color-surface);
  border-left: 1px solid var(--color-border);
  border-top: 1px solid var(--color-border);
  transform: rotate(45deg);
}

.pc-matrix__cell {
  min-width: 0;
  padding: var(--space-3);
  border-bottom: 1px solid var(--color-border-soft);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  justify-content: flex-start;
}
/* 最后一行不画底线（否则与外框底边叠成双线） */
.pc-matrix > div:nth-last-child(-n+3) { border-bottom: 0; }

/* 矩阵里的明细表：**列宽写死一致** ⇒ 数量/标价/主推三列左右完全对齐。
   ⚠️ 必须 `table-layout: fixed` —— 否则列宽由内容决定（我方商品名长、对方是输入框）
      ⇒ 两个表的列 x 不同，看着还是"错位"（乐哥截图里除了行错位、列也错位）。 */
.pc-matrix__tbl { table-layout: fixed; }
/* ⚠️ 表头**不许换行** —— 实测踩到：对方表头多了一句说明文字导致折行，
   表头比我方高 20px ⇒ 整张表往下偏 20px，看着就是"商品没放在同一位置"
   （行高其实完全一样 69/69，偏差全在表头）。⇒ nowrap 兜住同类问题。 */
.pc-matrix__tbl thead th { white-space: nowrap; }
.pc-matrix__tbl .pc-col-qty { width: 62px; }
.pc-matrix__tbl .pc-col-price { width: 86px; }
.pc-matrix__tbl .pc-col-anchor { width: 52px; }
.pc-matrix__tbl .pc-col-del { width: 34px; }

/* 只读的静态值（「我方定价＝不含平台补贴价」这类）—— 虚线边框 + 浅底，
   一眼看出"这是结论、不用填"，与旁边真正可填的输入框区分开。 */
.pc-static {
  /* ⚠️⚠️ 高度必须**与框架 `.input` 逐像素一致**（44px）——
      乐哥：「需要输入内容的输入框高度，跟自动带出来不需要输的高度都不一样」（实测差 10px）。
      根因：我曾用 `var(--control-height, 34px)`，而**这个令牌并不存在** ⇒ 一直走兜底 34px。
      ⇒ 直接对齐 `components.css` 的 `.input`：height / padding / border / 圆角 / 字号**全部同值**。
      ⚠️ 别再引入自造令牌；框架值变了也要跟着改（这里写了来源，便于日后核对）。 */
  height: 44px;                        /* = components.css 的 .input */
  display: flex;
  align-items: center;
  padding: 0 var(--space-3);
  border: 1px dashed var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface-2);
  color: var(--color-fg);
  font-size: var(--font-size-base);    /* = .input 的 var(--font-size-base) */
}
.pc-static--empty { color: var(--color-muted); }

/* 二选一（对方报价是「不含平台补贴价」还是「含平台补贴价」）—— 与同行的输入框等高，视觉上同一行 */
.pc-radio-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  align-items: center;
  height: 44px;   /* 同 .pc-static：对齐框架 .input */
}
.pc-radio {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: var(--font-size-sm);
  color: var(--color-fg);
  cursor: pointer;
}
.pc-radio span { font-size: var(--font-size-xs); color: var(--color-muted); }

/* ⚠️⚠️ 桌面规则必须在**同处**写移动端覆盖 —— 本项目在媒体查询上踩过两次：
   带父选择器的桌面规则不会因媒体查询而失效，只写桌面规则会让手机端挤成一团。
   手机端塞不下「概念名 + 两列」⇒ 概念名改成**整行**标题，下面仍是左右两列（对齐不变）。 */
@media (max-width: 900px) {
  .pc-matrix__corner { display: none; }
  .pc-matrix__lbl {
    grid-column: 1 / -1;
    border-top: 1px solid var(--color-border);
  }
  .pc-matrix__cell { padding: var(--space-3) var(--space-2); }
}
/* 字段网格：两列布局。
   ⚠️ `min-height` 给 label 是**对齐的关键** —— 两列的 label 文字长短不一（有的还带说明），
      不给等高的话同一行两个输入框会错开几个像素（截图量出来过）。 */
.pc-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--space-3);
}
@media (max-width: 640px) {
  .pc-grid { grid-template-columns: minmax(0, 1fr); }
}
.pc-field {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
/* ⚠️ 2026-10-11：`.pc-field .sf-label` **目前没有使用方** ——
   本页唯一的 `.sf-label`（「新增/编辑平台补贴」弹窗那 7 处）容器是 `.form-grid`，
   一直命不中这条规则（标签文字与控件左缘完全对齐、也没有标签下间距）；
   已按 `DESIGN-SPEC §7.10` 归位成 `.field-label`（全站那套）。
   规则**暂留**（避免误删别处动态拼接的用法），下次清理时一并删。 */
.pc-field .sf-label {
  display: flex;
  align-items: center;
  min-height: 22px;
  margin-bottom: var(--space-1);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.pc-field__hint {
  margin-top: var(--space-1);
  font-size: var(--font-size-xs);
  color: var(--color-muted);
  line-height: 1.5;
}

/* ⭐ 数字输入框一律**右对齐 + 等宽数字** —— 上下几行的金额小数点才会对齐。
   （乐哥反馈"价格的数字没对齐"就是这里：左对齐时位数不同的数字参差不齐） */
.input.is-num,
.pc-table input.is-num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* ⚠️ 作为矩阵里的一格时与外框的输入框**顶部对齐**：原来带 `margin-top: var(--space-3)`
   （那是"两个独立列"时代的段间距）⇒ 实测与右侧输入框的顶边差 12px，看着就是没对齐。
   现在改成与输入框**等高、内容垂直居中**，顶边自然齐平。 */
.pc-subsidy {
  height: 44px;                        /* 同 .pc-static：对齐框架 .input */
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0 var(--space-1);
  font-size: var(--font-size-base);
  color: var(--color-fg-2);
  line-height: 1.3;
}
.pc-subsidy b { color: var(--color-primary-500); font-variant-numeric: tabular-nums; }
/* ⚠️ 补贴为 0 时不要用品牌蓝 —— 0 元不是"正面信息"，蓝色会让人以为"有补贴" */
.pc-subsidy.is-zero b { color: var(--color-muted); }
/* ⚠️ 用 danger-strong（#D93025）而不是 warning（#F2A918）：后者在白底上对比度只有 ~2:1，
   远低于可读线 —— 这正是乐哥说的"看着都不直观"。 */
/* ⚠️ `.pc-subsidy__miss`（未维护补贴的商品名）已于 2026-09-23 删除 ——
   合并「价格」行后，未维护提示移到格子下方（`.pc-field__hint--warn`，
   红字 + 常驻，符合"有问题的提醒用红字"的规则）。JS 里已零引用，死规则不留。 */

/* ⭐ 页面宽度：**唯一来源是 app.js 的 WIDTH_TIER 档位**（本页登记 `table`，
   现行内容区 **1440**；2026-09-28 放宽前为 1296）。
   --------------------------------------------------------------------------
   乐哥 2026-09-23 两轮（方向相反，以第二轮为准）：
     · 第一轮「这个页面的 UI 我们做宽一点吧，不要做这么窄了」⇒ 当时放成全宽（实测 1601px）
     · 第二轮「**不要拉这么宽了，拉这么宽确实很空**，就按照之前**编辑达人信息**的那个宽度吧」
       ⇒ 登记 `pricingCompare: 'narrow'`（与「编辑达人」同档），卡片回到 **1080px**（实测与编辑达人差 0px）
     · 第三轮（定稿）「**这个列宽稍微宽一点**吧，就不用一直按照**档案列表**那么窄」
       ⇒ 改登记 `pricingCompare: 'table'`（内容区 **1296**）。
          ⭐ 这其实才是本页按 layout.css 判据**该在的档**（「主体是不是表格」—— 本页是两个明细表并排）。
          ⚠️ 不再自造中间宽度：系统刻意只有两档（避免同一身份多种宽度）。
   ⚠️⚠️ **这一行的作用不是"不限宽"，而是"让卡片撑满外层容器的内容区"** ——
      我第一次改这版时把它当成"解除限宽"删掉了，结果卡片被 `.sf-card` 自带的
      `--form-max-width`(当时 1120) 压住 ⇒ 宽度变成**两个来源**（`.page--narrow` 的 1144 与 1120 取小），
      数值上**恰好**还是 1080（所以肉眼看不出来），但档位一改就不会跟着变了 —— 这种"巧合相等"
      正是本项目反复踩的坑（另见「我的报销」页两侧差 40px 那次）。⇒ 保留它，让档位说了算。
      ⭐ 2026-09-28 起：`--form-max-width` 已同步放宽到 1280（与 narrow 档内容一致），
         但**这一行仍必须保留** —— table 档内容 1440 > 1280，没有它卡片会被令牌压回 1280（正好差 160）。
   ⚠️ 必须写**两个类**（`.sf-card.pc-card`）：`pages-schedule.css` 在 `pages.css` 之后加载，
      同特异性时后者胜 ⇒ 单个 `.pc-card` 会被 `.sf-card` 静默盖掉（实测踩过）。 */
.sf-card.pc-card { max-width: none; }

/* ── 明细表：行距/列距放宽（乐哥：「数量和价格的 UI 优化一下，现在看着太挤太密了」）── */
.pc-table {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  margin-top: var(--space-3);
}
.pc-table th {
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-medium);
  color: var(--color-muted);
  text-align: left;
  padding: 0 var(--space-3) var(--space-2) 0;
  border-bottom: 1px solid var(--color-border-soft);
  white-space: nowrap;
}
.pc-table td {
  /* 行高从 space-2 放宽到 space-3 —— 输入框 44px 高，贴太紧就"密" */
  padding: var(--space-3) var(--space-3) var(--space-3) 0;
  border-bottom: 1px solid var(--color-border-soft);
  vertical-align: top;
}
/* ⭐ 2026-09-25：**隔行浅底（斑马纹）已按乐哥要求去掉**（全站统一，见 table.css 的汇总清单）。
   ▸ 原意：明细行多时（3~6 行）靠浅底区分，比只靠线更容易"读行"。
   ▸ 现在全站无斑马纹 ⇒ 本页靠**行分隔线**（上方 td 的 border-bottom）区分，与全站一致。
   ⛔ 原规则已删除（保留便于回溯）：
        .pc-table tbody tr:nth-child(even) td { background: var(--color-surface-2); }
        .pc-table tbody tr:nth-child(even) td:first-child { border-top-left-radius: var(--radius-sm); border-bottom-left-radius: var(--radius-sm); }
        .pc-table tbody tr:nth-child(even) td:last-child { border-top-right-radius: var(--radius-sm); border-bottom-right-radius: var(--radius-sm); }
      后两条是给"浅底"配的圆角（浅底没了，圆角也无意义）—— 一并删除，避免留下无主的圆角声明。 */
.pc-table tr:last-child td { border-bottom: 0; }
.pc-table th:last-child, .pc-table td:last-child { padding-right: 0; }
.pc-table input.input { width: 100%; }
.pc-table th.is-num, .pc-table td.is-num { text-align: right; }
.pc-table th.is-center, .pc-table td.is-center { text-align: center; }
.pc-table td.is-num, .pc-table--ro td.is-num { font-variant-numeric: tabular-nums; color: var(--color-fg-2); }
/* ⚠️⚠️ 列宽**只在 `.pc-matrix__tbl` 那一处定义**（见本文件上面的"对照矩阵"段）——
   原来这里还有一套 `.pc-table .pc-col-*`（96/128/64/44），与矩阵那套（62/86/52/34）
   特异性相同、**同一文件里两套值**，写在后面的这套胜出 ⇒ 实测量到的是 96/128/64/44，
   我声明的宽度根本没生效。⇒ 删除，保持**一处维护**（两处必然漂移，本项目铁律）。 */

/* 商品单元格：缩略图 + 名称上下排（乐哥：「把对应的商品图片也都带出来，这样更直观」） */
.pc-prod { display: flex; gap: var(--space-3); align-items: flex-start; min-width: 0; }
.pc-prod__img {
  flex: none;
  width: 44px; height: 44px;
  border-radius: var(--radius-sm);
  object-fit: cover;
  background: var(--color-surface-2);
  border: 1px solid var(--color-border-soft);
}
.pc-prod__img--empty {
  display: flex; align-items: center; justify-content: center;
  color: var(--color-muted);
}
.pc-prod__main { min-width: 0; flex: 1; }
.pc-prod__name { color: var(--color-fg); font-size: var(--font-size-sm); line-height: 1.5; }
.pc-prod__meta { font-size: var(--font-size-xs); color: var(--color-muted); margin-top: 2px; }

/* ── 机制搜索框 + 候选面板（2026-09-23 乐哥：「做成商品搜索框的形式」）──────────
   ⚠️ 面板要**绝对定位浮在下面**（不推开内容）—— 否则每次输入都让整页跳动。
   ⚠️ 必须有 max-height + 滚动：34 个机制全列出来会盖住半屏。 */
.pc-search { position: relative; }
.pc-search__panel {
  position: absolute;
  left: 0;
  right: 0;
  top: 100%;
  z-index: 30;
  margin-top: 2px;
  max-height: 264px;
  overflow-y: auto;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md));
  padding: var(--space-1) 0;
}
.pc-search__count {
  padding: var(--space-1) var(--space-3);
  font-size: var(--font-size-xs);
  color: var(--color-muted);
  border-bottom: 1px solid var(--color-border-soft);
  margin-bottom: 2px;
}
.pc-search__empty { padding: var(--space-3); font-size: var(--font-size-sm); color: var(--color-muted); }
.pc-search__item {
  display: block;
  width: 100%;
  text-align: left;
  padding: var(--space-2) var(--space-3);
  font-size: var(--font-size-sm);
  font-family: inherit;
  color: var(--color-fg);
  background: transparent;
  border: 0;
  cursor: pointer;
  line-height: 1.5;
}
.pc-search__item:hover { background: var(--color-surface-2); }
.pc-search__item.is-cur { color: var(--color-primary-500); font-weight: var(--font-weight-medium); }

/* 待核对的明细行（模糊匹配不确定 / 未匹配）—— 标出来让用户扫一眼就能看到。
   ⚠️⚠️ 2026-09-23 乐哥：「有问题的提醒用红字吧，不要黄字，也不要用这种黄框了，看着都不直观」
      ⇒ 原来**整行刷浅黄底**，改成 **① 行首左侧红竖条 + ② 该行商品输入框红边**。
      为什么更好：整行刷色是"这片区域有点黄"，指不出到底哪格要改；
      红边框是"**就是这个框**要你处理"，一眼落到具体控件上。
      ⚠️ 若某行同时是待核对 + 已确认锚点：锚点规则（`.pc-row--anchor td:first-child`，
        特异性更高）的蓝条会胜出 —— 但该行仍有**红边框 + 行内红字**，信息不丢。 */
.pc-table tr.pc-row--check td:first-child { box-shadow: inset 3px 0 0 var(--color-danger); }
.pc-table tr.pc-row--check .pc-ti[data-k="name"] { border-color: var(--color-danger); }
/* 被勾为主推品的那一行 —— 高亮，因为它是单件价的**分母**，必须一眼看到选了谁。
   ⚠️ 用主色淡底 + 左侧竖条：与"待核对"的黄色区分开（两种状态可能同时存在）。 */
.pc-table tr.pc-row--anchor td { background: var(--color-primary-50)); }
.pc-table tr.pc-row--anchor td:first-child { box-shadow: inset 3px 0 0 var(--color-primary-500); }
.pc-hint {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  align-items: center;
  margin-top: 4px;
}
.pc-hint__txt { font-size: var(--font-size-xs); color: var(--color-danger-strong); font-weight: var(--font-weight-medium); }
.pc-cand {
  font-size: var(--font-size-xs);
  padding: 0 6px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--color-border-soft);
  background: transparent;
  color: var(--color-fg-2);
  cursor: pointer;
  font-family: inherit;
}
.pc-cand:hover { border-color: var(--color-primary-500); color: var(--color-primary-500); }

/* ── 对方机制文案（粘贴 + 自动识别）── */
.pc-paste { margin-top: var(--space-3); }.pc-paste__row {
  display: flex;
  gap: var(--space-2);
  align-items: center;
}
.pc-paste__row .input { flex: 1; min-width: 0; }
.pc-paste__row .btn { flex: 0 0 auto; white-space: nowrap; }
@media (max-width: 640px) {
  .pc-paste__row { flex-wrap: wrap; }
  .pc-paste__row .btn { width: 100%; justify-content: center; }
}
.pc-parse-note {
  margin-top: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  font-size: var(--font-size-xs);
  line-height: 1.6;
  background: var(--color-surface-2);
  color: var(--color-fg-2);
}
/* 识别有问题的提示块：**去掉黄底**（乐哥："不要用这种黄框"），
   改成白底 + 左侧红竖条 + 红字 —— 块的边界靠那条红竖线表达，比整块刷黄干净得多。 */
.pc-parse-note--warn {
  background: transparent;
  box-shadow: inset 3px 0 0 var(--color-danger);
  color: var(--color-danger-strong);
}

/* ── 平台补贴维护：本页底部**可展开区块**（不再占顶部 Tab）──────────────────────
   ⚠️ 乐哥 2026-09-23：「还有一个『平台补贴』的 tab，有点多余。」
      处置 = 从 Tab 改成"默认收起的区块"，**不是删除** ——
      补贴是「含平台补贴价」口径的唯一数据来源，也是"平台补贴不算我方破价"的依据。
      做成收起态 ⇒ 页面主线清爽（满足"多余"的观感），能力不丢。 */
.pc-sub-panel { margin-top: var(--space-4); }
.pc-sub-panel__head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  cursor: pointer;
  user-select: none;
}
.pc-sub-panel__head:hover { background: var(--color-surface-2); }
.pc-sub-panel__title {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  color: var(--color-fg);
  flex: none;
}
.pc-sub-panel__desc {
  font-size: var(--font-size-xs);
  color: var(--color-muted);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.pc-sub-panel__caret { margin-left: auto; flex: none; font-size: var(--font-size-xs); color: var(--color-primary-500); }
.pc-sub-panel__inner { padding: 0 var(--space-4) var(--space-4); }
@media (max-width: 768px) {
  .pc-sub-panel__desc { display: none; }
}

/* 补贴来源说明（"取 X 平台那一档"）—— 弱化色，不与金额抢眼 */
/* ⚠️ `.pc-subsidy__src`（块内的"取 X 那一档"）已于 2026-09-23 删除 ——
   合并后子字段只有约 260px，这句放里面会折行 ⇒ 移到格子里、作为独立 hint。
   注意上面那条说明"为什么删"的注释（`.pc-subsidy__src` 也提了一嘴）保持一致。 */
/* ⚠️ 这里**不能**加左侧竖条 / 左内边距：本页是"一行一个概念"的对照矩阵，
   任何一边的内容左移都会让两格错位（这是本页反复踩过的对齐问题）。
   ⇒ 只用红字 + 加粗表达"这条要处理"。 */
.pc-field__hint--warn { color: var(--color-danger-strong); font-weight: var(--font-weight-medium); }

.pc-tag {
  display: inline-block;
  padding: 0 6px;
  border-radius: var(--radius-pill);
  font-size: var(--font-size-xs);
  background: var(--color-primary-50);
  color: var(--color-primary-500);
}
.pc-tag--gift { background: var(--color-surface-2); color: var(--color-muted); }
/* ── 对比口径：卡片式两列（2026-09-23 乐哥：「下面这块的展示感觉不直观」）──────
   ⚠️ 实测病根（改前）：两个选项各 623px 宽、**只有 21px 高**，字号 14px + 常规字重
      —— 与正文**完全同规格** ⇒ 整块就是两行长句子，没有层级、扫不出来；
      「含赠品按标价折算」还在两个选项里**各写了一遍**（纯重复噪音）；
      选中态只有一个 15px 圆点，两个选项看上去一模一样。
   ⇒ 现在：短标题（semibold）+「补贴前/后」徽标 + **独立一行**的说明；
      选中项 = 主色描边 + 淡底 + 标题变主色 ⇒ 一眼看出选了哪个。
   ⚠️ 旧的 `.pc-check` / `.pc-check--inline` 已删 —— 它们只服务这个单选，
      改版后全站零引用（死规则留着会让人以为还有第二套在用）。 */
.pc-caliber {
  margin-top: var(--space-4);
  padding-top: var(--space-3);
  border-top: 1px solid var(--color-border-soft);
}
.pc-caliber__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}
.pc-caliber__label {
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  color: var(--color-fg);
}
.pc-caliber__note { font-size: var(--font-size-xs); color: var(--color-muted); line-height: 1.5; }
.pc-caliber__opts {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-3);
}
/* ⚠️ 移动端必须落成一列 —— 本项目在媒体查询上踩过两次（桌面规则带父选择器时不随媒体查询失效） */
@media (max-width: 900px) {
  .pc-caliber__opts { grid-template-columns: minmax(0, 1fr); }
}
.pc-opt {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  padding: var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  cursor: pointer;
}
.pc-opt:hover { border-color: var(--color-primary-300)); }
.pc-opt.is-on {
  border-color: var(--color-primary-500);
  background: var(--color-primary-50));
  /* 描边加粗一格：只用颜色区分，色弱/低分屏看不出来 */
  box-shadow: inset 0 0 0 1px var(--color-primary-500);
}
.pc-opt > input[type="radio"] { flex: none; margin-top: 3px; }
.pc-opt__body { min-width: 0; }
.pc-opt__t {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
  color: var(--color-fg);
}
.pc-opt.is-on .pc-opt__t { color: var(--color-primary-600)); }
.pc-opt__badge {
  font-style: normal;
  font-size: 10px;
  font-weight: var(--font-weight-regular);
  padding: 1px 6px;
  border-radius: var(--radius-pill);
  background: var(--color-surface-2);
  color: var(--color-muted);
  white-space: nowrap;
}
.pc-opt.is-on .pc-opt__badge {
  background: var(--color-primary-100));
  color: var(--color-primary-600));
}
/* 说明**单独一行**（原来跟标题挤在同一行 ⇒ 623px 长句子，眼睛没法扫） */
.pc-opt__d {
  display: block;
  margin-top: 3px;
  font-size: var(--font-size-xs);
  color: var(--color-fg-2);
  line-height: 1.5;
}
/* ── 结果区：结论 + 大数字（hero）────────────────────────────────────────────
   ⚠️⚠️ 重做理由（2026-09-23 乐哥：「整个对比的方式看着一点都不清晰」）：
      实测量过，原来「结论 banner / 指标标签 / 说明文字」**全是 14px**，
      三个口径又是同构的平行块 ⇒ 整块是文字流，看不出哪条是结论。
      ⇒ 现在按重要性分四层：hero（28px 大数字）→ 对照表 → 提示 → 话术。 */
.pc-hero {
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-md);
  padding: var(--space-4);
  margin-bottom: var(--space-4);
  background: var(--color-surface-2);
}
/* 结论条：整块顶部一条色带，颜色跟着"谁更优"走 */
.pc-hero--ours { border-color: var(--color-success); background: var(--color-success-bg)); }
.pc-hero--theirs { border-color: var(--color-danger); background: var(--color-danger-bg)); }
.pc-hero__verdict {
  font-size: var(--font-size-lg);
  font-weight: var(--font-weight-semibold);
  text-align: center;
  margin-bottom: var(--space-3);
}
.pc-hero--ours .pc-hero__verdict { color: var(--color-success); }
.pc-hero--theirs .pc-hero__verdict { color: var(--color-danger); }
.pc-hero--same .pc-hero__verdict { color: var(--color-fg-2); }
.pc-hero__row {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  gap: var(--space-3);
  align-items: center;
}
.pc-hero__side {
  text-align: center;
  padding: var(--space-3);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  border: 1px solid var(--color-border-soft);
}
.pc-hero__who { font-size: var(--font-size-xs); color: var(--color-muted); }
/* ⭐ 大数字：这是"一眼看清"的关键，必须显著大于其它文字 */
.pc-hero__val {
  font-size: 28px;
  font-weight: var(--font-weight-semibold);
  line-height: 1.25;
  font-variant-numeric: tabular-nums;
  color: var(--color-fg);
}
.pc-hero__unit { font-size: var(--font-size-xs); color: var(--color-muted); }
/* 大数字下面标出"按哪个品折算"（2026-09-23 乐哥：「这个折后单价是哪个品的折后单价？」）
   ⚠️ 必须有这一行：光一个 ¥158.86，用户对不上 ¥298 那个标价 ⇒ 根本无从判断这个数是什么。 */
.pc-hero__anchor { font-size: 11px; color: var(--color-muted); margin-top: 2px; line-height: 1.4; }

/* ── 折算过程（2026-09-23 乐哥：「这里面的整体规则，我觉得不清晰、不直观」）────────
   ⚠️ 为什么要**常驻显示**而不是收进「?」：这一块解释的是**结论本身怎么来的**
      （158.86 与 298 / 1499 的关系），不是"怎么用这个功能"的使用说明 ——
      用户看不懂结论时不会去点问号，直接把每一步的数字摆出来才叫说清。
   ⚠️ 放在 hero 内、大数字正下方：困惑点就在那儿，解释要贴着它。
   ⚠️ 字号小、色弱、虚线分隔 —— 不与大数字抢视觉，但随时可读。 */
.pc-hero__calc {
  margin-top: var(--space-3);
  padding-top: var(--space-2);
  border-top: 1px dashed var(--color-border);
  font-size: var(--font-size-xs);
  line-height: 1.75;
  color: var(--color-fg-2);
  text-align: left;
}
.pc-hero__calc-t {
  font-weight: var(--font-weight-medium);
  color: var(--color-fg);
  margin-bottom: 2px;
}
.pc-hero__calc-note { color: var(--color-muted); margin-bottom: var(--space-1); }
.pc-hero__calc-row { display: flex; gap: var(--space-2); }
/* 等宽数字：两行的数字要能上下对齐（本项目在金额列上定过这条） */
.pc-hero__calc-fml { min-width: 0; font-variant-numeric: tabular-nums; }
.pc-hero__calc-who { flex: none; width: 28px; color: var(--color-muted); }
.pc-hero__calc b { color: var(--color-fg); font-weight: var(--font-weight-medium); }
/* 便宜绿 / 贵红（与"我方/对方"两栏各自对应） */
.pc-hero__side.is-good { border-color: var(--color-success); }
.pc-hero__side.is-good .pc-hero__val { color: var(--color-success); }
.pc-hero__side.is-bad { border-color: var(--color-danger); }
.pc-hero__side.is-bad .pc-hero__val { color: var(--color-danger); }
.pc-hero__mid { text-align: center; min-width: 96px; }
.pc-hero__diff {
  font-size: var(--font-size-lg);
  font-weight: var(--font-weight-semibold);
  color: var(--color-fg);
  font-variant-numeric: tabular-nums;
}
.pc-hero__rate { font-size: var(--font-size-xs); color: var(--color-muted); }
.pc-hero__caliber {
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  border-top: 1px solid var(--color-border-soft);
  font-size: var(--font-size-xs);
  color: var(--color-muted);
  text-align: center;
}
@media (max-width: 640px) {
  /* 窄屏：大数字缩一点，但保持"两侧对照"的结构（不要竖排成三行，否则又变文字流） */
  .pc-hero__val { font-size: 22px; }
  .pc-hero__row { gap: var(--space-2); }
  .pc-hero__mid { min-width: 64px; }
}

/* ── 逐项对照表（我方 / 对方 / 差）────────────────────────────────────────────
   ⭐ 把"两边对照"交给**表格结构**，而不是塞进"我方 X 对方 Y"的一句话里。
   ⚠️ 主口径行高亮（.pc-cmp__main）、参考项弱化（.pc-cmp__ref）——
      这样主次**看结构就分得出**，不必读说明文字。 */
.pc-cmp {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--font-size-sm);
}
.pc-cmp th {
  text-align: left;
  font-weight: var(--font-weight-medium);
  color: var(--color-muted);
  padding: var(--space-2) var(--space-3) var(--space-2) 0;
  border-bottom: 1px solid var(--color-border-soft);
  font-size: var(--font-size-xs);
}
.pc-cmp th.is-num { text-align: right; }
.pc-cmp td {
  padding: var(--space-3) var(--space-3) var(--space-3) 0;
  border-bottom: 1px solid var(--color-border-soft);
  color: var(--color-fg);
  vertical-align: top;
}
.pc-cmp tr:last-child td, .pc-cmp tr:last-child th { border-bottom: 0; }
.pc-cmp th:last-child, .pc-cmp td:last-child { padding-right: 0; }
.pc-cmp td.is-num {
  text-align: right;
  font-variant-numeric: tabular-nums;
  font-weight: var(--font-weight-medium);
  white-space: nowrap;
}
.pc-cmp__hint { font-size: 11px; color: var(--color-muted); margin-top: 2px; font-weight: var(--font-weight-regular); }
.pc-cmp__diff { font-size: var(--font-size-xs); color: var(--color-fg-2); white-space: nowrap; }
.pc-cmp__diff.is-dim { color: var(--color-muted); }
/* 主口径行：高亮 + 左侧主色竖条 ⇒ 一眼看出"定结论的是这行" */
.pc-cmp__main th, .pc-cmp__main td { background: var(--color-primary-50)); }
.pc-cmp__main th:first-child {
  border-left: 3px solid var(--color-primary-500);
  padding-left: var(--space-3);
}
.pc-cmp__star { color: var(--color-primary-500); }
/* 口径徽标（补贴前 / 补贴后）—— 让两行口径一眼可辨，不用去读长标题 */
.pc-cmp__badge {
  font-style: normal;
  font-size: 10px;
  font-weight: var(--font-weight-regular);
  margin-left: 5px;
  padding: 1px 5px;
  border-radius: var(--radius-pill);
  background: var(--color-surface-2);
  color: var(--color-muted);
  white-space: nowrap;
}
.pc-cmp__main .pc-cmp__badge { background: var(--color-primary-100)); color: var(--color-primary-600)); }
/* 参考项：弱化（但不隐藏 —— 信息要保留，只是不能当结论） */
.pc-cmp__ref th, .pc-cmp__ref td { opacity: .62; }
.pc-cmp__sep th, .pc-cmp__sep td { border-top: 1px solid var(--color-border); }
/* 数值语义色：与 hero 一致（便宜绿、贵红） */
.pc-cmp td.is-good { color: var(--color-success); }
.pc-cmp td.is-bad { color: var(--color-danger); }

/* ── 提示条（追平建议 / 口径警告）—— 辅助位，不抢结论的视觉重量 ── */
.pc-callout {
  display: flex;
  gap: var(--space-2);
  align-items: baseline;
  margin-top: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  font-size: var(--font-size-xs);
  line-height: 1.6;
  background: var(--color-surface-2);
  color: var(--color-fg-2);
}
.pc-callout__k { flex: none; font-weight: var(--font-weight-medium); color: var(--color-fg); }
.pc-callout--action { background: var(--color-primary-50)); }
.pc-callout--action .pc-callout__k { color: var(--color-primary-500); }
/* 结果区的口径提示：同样**去掉黄底**，改白底 + 左侧红竖条 + 红字。 */
.pc-callout--warn {
  background: transparent;
  box-shadow: inset 3px 0 0 var(--color-danger);
  color: var(--color-danger-strong);
}
.pc-callout--warn .pc-callout__k { color: var(--color-danger-strong); }

/* ── 「价格」行：到手价 + 平台补贴 **并排**（2026-09-23 乐哥：「到手价跟平台补贴
      是不是可以放在一起啊？」）────────────────────────────────────────────
   ⚠️ 为什么合并：两者都是"钱"、且直接相关（含平台补贴价 = 到手价 − 平台补贴）；
      原来是**上下两行**（实测到手价 69px + 平台补贴 99px = 168px），横向占满、纵向留空，
      既费高度又显得空。合并成一行内两个并排子字段 ⇒ 省约 90px。
   ⚠️ 子字段必须**各自带小标签**：合并后概念列只有一个「价格」，
      而两边输入框长得一样 —— 不标就分不清哪个是到手价、哪个是补贴。
   ⚠️ 我方补贴是只读块、对方是输入框：形态不同，靠标签 + 只读底色区分（不靠位置）。 */
.pc-duo {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--space-3);
}
@media (max-width: 900px) {
  .pc-duo { grid-template-columns: minmax(0, 1fr); }
}
.pc-duo__f { min-width: 0; }
.pc-duo__k {
  display: block;
  font-size: 11px;
  line-height: 1.4;
  color: var(--color-muted);
  margin-bottom: 3px;
  white-space: nowrap;
}
/* 子字段里的控件保持 44px（对齐框架 .input）—— 见各自定义处的说明 */
.pc-duo__f .pc-static,
.pc-duo__f .pc-subsidy,
.pc-duo__f .input { width: 100%; }

/* 自动带出的只读值（如「主推品件数」）—— 长得像输入框但不可编辑，
   让用户一眼看出「这是系统算的，不用填」。 */
.pc-auto {
  height: 44px;                        /* 同 .pc-static：对齐框架 .input（见其注释） */
  display: flex;
  align-items: center;
  padding: 0 var(--space-3);
  border: 1px dashed var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface-2);
  color: var(--color-fg);
  font-size: var(--font-size-base);
  font-variant-numeric: tabular-nums;
}
.pc-auto--empty { color: var(--color-muted); }

/* ⚠️ 旧结果区样式（.pc-verdict / .pc-metrics / .pc-metric* / .pc-note*）已于
   2026-09-23 第二版重做时**整块删除**（已实测全站无引用：
   `grep -rl "pc-metric\|pc-verdict\|pc-note" public/ server/` 只剩本文件自身）。
   留着会让人以为还有第二套结果区在用 —— 那正是最危险的状态。 */

.pc-talk { margin-top: var(--space-4); }
.pc-talk__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: var(--font-size-sm);
  color: var(--color-fg-2);
  margin-bottom: var(--space-2);
}
.pc-talk__body {
  margin: 0;
  padding: var(--space-3) var(--space-4);
  background: var(--color-surface-2);
  border-radius: var(--radius-md);
  font-size: var(--font-size-sm);
  line-height: 1.8;
  color: var(--color-fg);
  white-space: pre-wrap;
  word-break: break-word;
  font-family: inherit;
}

