/* ==== /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-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，理由是标题行要与内容卡对齐。） */
  --form-max-width:        1330px;
  /* ⭐⭐ 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-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;

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

  /* ── 侧栏一级菜单图标 · 业务域色（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-09-14 收进令牌，原先散在 ui.js 里硬编码） */
  --color-tag-fallback:    #A6ADBC;   /* 标签未指定颜色时的兜底灰 */
  --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);

  /* 开关：激活态轨道色。模块需换色时**只覆盖这个变量**，不要去重写 .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 更深、财务更浅"——那会让三个板块彼此之间又出现新的不一致。
   ⚠️ 位移与曲线**完全复用**上面的 `page-fade-in` + `--motion-rise` + `--motion-ease-pop`，
      不新造 keyframes；调参仍是一处生效。
   ⚠️ 延迟只有第 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 灰字会让菜单入口比它下面的内容项更弱，读成"注释"，用户找不到入口。
   ⭐ 竖向基线 **62**（容器 padding-left 12 + 项 padding-left 22 + 图标 16 + gap 12）
     = 品牌名 / 分组标题 / 二级子项文字的**同一竖线** ✓
     ⚠️ 改内边距 / 图标尺寸时**整串一起算**，否则四级文字错位。
   ⚠️ 图标保留 **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);
}

.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**（乐哥：「一级菜单图标按飞书那种风格做」）
 * ────────────────────────────────────────────────────────────────────────────────
 * ⛔⛔ **先说清"原注释的理由为什么过期了"**（这是本条最容易被回改的地方）：
 *   上面那段 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：会出现"一级图标比二级文字还弱"的层级倒挂（同注释原话，仍然有效）。 */
.sidebar .nav-group-icon svg { width: 18px; height: 18px; }
/* 收起箭头 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);
  transition: transform var(--motion-fast) var(--motion-ease-standard);
}

/* 统一用 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：展开是**高度形变**，
      不是"元素飞进来"，两端缓才不会有"顶上去"的冲劲。 */
  transition: grid-template-rows var(--motion-base) var(--motion-ease-in-out);
  /* ⭐ 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;
}
/* ⚠️ 内层容器（JS 注入）：承载 overflow:hidden 的那个"被压缩的格子"。
   必须在它上面裁切 —— 裁在 `.nav-subitems` 上会把引导线 ::before 一起裁掉
   （它是绝对定位、相对 .nav-subitems 的，纵向超出会被切）。 */
.nav-subitems__inner {
  overflow: hidden;
  min-height: 0;   /* grid 项默认 min-height:auto 会拒绝被压到 0（关键，漏了就等于没动画） */
}
/* ⛔⛔⛔ 2026-10-07（乐哥：「还有那个引导线（我觉得可以不要）」）—— **二级引导线已移除**
   ────────────────────────────────────────────────────────────────────────
   ▸ 为什么不留：它原本承担"这一组属于上面那个一级项"的从属关系，
     但 **2026-10-07 一级改成 15px/w600/深墨后，从属关系已经由「字号 + 字重 + 颜色」
     三个维度说清楚了** ⇒ 线成了**冗余的第二套语言**，且它挂在"一级图标中轴"上，
     一级变粗后那条线显得更细、更像"划痕"。
   ▸ 为什么这次能直接去掉（上次不能）：上一轮（09-25）一级被做成 12.5px 灰"章节名"，
     **比二级还弱** ⇒ 从属关系只能靠线撑；现在一级是最重的，从属靠体量就成立。
   ⛔ 别再把它加回来。若将来一级又被压成小灰字，那才是需要这条线的场景。

   ▸ 保留整段历史（都是真踩过的坑，将来若要恢复"线"可以少走弯路）：
     · 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()` 加深那条。 */
.nav-subitems::before {
  content: none;             /* 等价 display:none，但保留选择器与注释作为历史锚点 */
  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);
}

/* 子项：单列、无图标、缩进对齐一级文字起点 */
.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;
  padding: 0 22px 0 50px;
  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;
}

/* ---- 全局文字选中：主色高亮 ---- */
::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 让它按按钮高度（20px）撑起一行。
   ⚠️ 按钮自带 `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;
}
/* 顶栏里的图标按钮：**回归全站基准 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;
}
.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-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` 同理，
        一并在下面归到紧凑档。
     ⛔ 只限本作用域（页头筛选行）：表格卡内的日期选择器另有其档，别动。 */
  .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-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);
}

/* 页面标题旁的「数据说明」问号按钮 */
.page-help-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  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;
}

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


/* ==== /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);
}

.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 不同源，隔着几页就能看出两种蓝）。 */
input[type="checkbox"],
input[type="radio"] {
  accent-color: var(--color-accent);
  transition: transform var(--motion-base) var(--motion-ease-sheet);
}
input[type="checkbox"]:active:not(:disabled),
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;
}

/* 可清空下拉（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;
  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);
}

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

/* ---- 卡片（白底 + 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 先行实践统一。
     字重保持 medium(500)：18:12 定的层级「卡片标题 16/500 · 正文 14/400 · 注释 12/400」不变。 */
  font-size: var(--font-size-lg);
  font-weight: var(--font-weight-medium);
  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;
}

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

.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);
  padding: 4px;
  gap: var(--space-1);
  width: fit-content;
  flex: 0 0 auto;
}
/* ⚠️⚠️ 托盘里的按钮必须**重设回"胶囊形态"** —— 否则会吃到 `.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);
}
.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-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;
}
.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;
}
.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-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 ============ */
.mp { position: relative; display: inline-block; }
.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 {
  background: var(--color-accent-bg);
  color: var(--color-accent);
  font-weight: var(--font-weight-semibold);
}
.mp__all:hover { border-color: var(--color-accent); }

/* 筛选栏「重置」按钮：高度必须对齐同一行里的输入框 / 下拉 / 月份选择器（都是 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);
}


/* ==== /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-09-15：「其他所有的位置都不变」）。
     即：正文数值靠右、表头居中 —— 这是"只动数字、别的一律不动"的忠实执行。
     若要改成表头也靠右（整列一条竖轴），把这里改成 right 即可，只此一处。 */
  text-align: center;
}

/* 表头排序（库存列表等）：所有可排列表头常驻箭头指示，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;
}

.table tbody tr {
  transition: background-color var(--motion-fast) var(--motion-ease-standard);
}

.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 张子表。）

   ⚠️ 表头**保持居中不动** —— 乐哥明确「其他所有的位置都不变」。
      若日后想改成"表头也跟着靠右"（整列同一竖轴、更整齐），
      只需给下面 `thead th.is-num` 那块也加 `right`，一处即可切换。 */
.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); }

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

/* ⚠️ 兜底：`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 .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-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 {
  background: var(--color-surface-2);
  font-weight: var(--font-weight-semibold);
  color: var(--color-fg);
  border-top: 1px solid var(--color-border);
}
/* 合计行里的金额用等宽感更强的排版，方便与明细逐位核对；不换行避免金额被折行 */
.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; }


/* ==== /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;
}
/* 退场：只滑 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;
  /* 最小高度 360 → 420（2026-09-13）：内容少的弹窗也不至于「扁扁一条」。
     ⚠️ 确认框与内容自适应的弹窗会显式传 bodyMinHeight:0 覆盖本值（见 ui.js openModal），不受影响。 */
  min-height: 420px;
}

.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 {
  max-width: 380px;
}
.form-grid .span-2 .input,
.form-grid .span-2 .select,
.form-grid .span-2 .dtp,
.form-grid .span-2 .fp__box {
  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;
}

/* 基本信息卡片：图片居左 + 右侧字段网格（货品/套餐新增编辑页） */
.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);
  background: var(--color-surface);
  /* ⭐⭐ 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);
}

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

/* 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-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); }

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

/* 净实收卡按渠道（微信小店/抖音）拆分 */
.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);
  background: var(--color-surface-2);
  /* ⭐ 2026-09-25 UI 美化第二刀：由 `transparent` 改为可见的浅描边。
     为什么：页面底压到近白后，`.quick-link` 的 surface-2 底与页面底几乎同色，
     静态下来是一块"没有边界、软塌塌"的灰块（乐哥要调的「那些框」）。
     给一条极浅描边后入口卡有了明确轮廓，hover 再加深一档（见下）。 */
  border: 1px solid var(--color-border-soft);
  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);
}

.quick-link:hover {
  border-color: var(--color-border);
  background: var(--color-surface);
  box-shadow: var(--shadow-sm);
}

/* 高亮快捷入口（排期日历）：主色浅底 + 主色图标，克制不抢眼 */
.quick-link--highlight {
  background: var(--color-primary-50);
}
.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'); }
  .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));
}

/* ---- 套餐商品选择器 ---- */
.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);
}
/* 已结束的排期（列表）：整行淡化，悬停恢复，仍可查看/编辑 */
.table tr.is-past {
  opacity: 0.55;
  transition: opacity var(--motion-fast) var(--motion-ease-standard);
}
.table tr.is-past:hover {
  opacity: 1;
}
/* 订单业绩天维度展开子表：去掉内缩，让子表列与主表对齐 */
.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;
}
.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 万+ 像素的色块。
   为什么要改：与 `.st-cover` 同理（见该处注释）—— 大色块与表格内状态色抢注意力，
       且满屏黄条会导致「警示疲劳」。实测微信小店全图彩色像素只占 0.72%。
   为什么**这条保留左边框**（而 `.st-cover` 是下细线）：
       内容性质不同 —— `.st-cover` 是**单行 + 需要行动**（有按钮），去掉边界仍读得通；
       本 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; }
/* 类目色点（方案 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); }
/* 标题行的筛选态胶囊（含取消按钮）：滚动到表格时卡片已出视野，必须在这儿再写一次"已筛选" */
.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); }
.st-drill-head__t { font-size: var(--font-size-sm); 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);
}
/* 私域行：与直播场次行区分（浅黄底，与「当月私域」标签同源） */
.st-drill .table--sub tbody tr.st-drill-row--priv td { background: var(--color-warning-bg); }
.st-drill .table--sub tbody tr.st-drill-row--priv:hover td { background: var(--color-warning-bg); }

/* ==========================================================================
   达人结算：业绩归属「取数规则」备注（乐哥 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-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-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;                  /* 保证在卡片内容之上可点（卡片内可能有滚动/覆盖层） */
}
/* 该列不参与应付（如线上佣金由平台直付）→ 金额用次要色，从视觉上"退出主读数" */
.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 {
  border-top: 2px solid var(--color-border);
  background: var(--color-surface-2);
  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); }

/* 展开区「非福利款 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，做正文色不合格。
   ⚠️ 不要改回"有底色"：那种做法已被验证会与表内状态色打架（见上）。 */
/* 提示条 · 统一形态 + 保留操作按钮（2026-09-14 乐哥定稿）
   ---------------------------------------------------------------
   乐哥：「统一色底，保留按钮」
   本类特殊：**带「一键生成」操作按钮**（不是纯说明）→ 统一底色/圆角/图标，
   但按钮照旧右对齐（用 margin-left:auto 推过去）。
   改前是「透明底 + 底部细线」——与提成总览那条又不一样。 */
.st-cover {
  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-4);
  border: 1px solid var(--notice-warn-border); border-radius: var(--notice-radius);
  background: var(--notice-warn-bg);
  font-size: var(--font-size-sm);
  color: var(--color-fg-2);
}
/* 右侧按钮推到最右（原来是靠 justify/内容撑，统一后固定住） */
.st-cover > .btn, .st-cover > button:last-child { margin-left: auto; }
.st-cover:empty { display: none; }
.st-cover__ic { flex: none; display: inline-flex; align-items: center; color: var(--notice-warn-ico); }
.st-cover__txt { flex: 1; min-width: 260px; line-height: 1.7; }
/* 关键数字/关键词用橙字（对齐微信小店「待发货」的做法）——它是本条里唯一的强调手段 */
.st-cover__txt b { color: var(--color-warning-text); font-weight: var(--font-weight-semibold); }
.st-cover .btn { flex: none; }

/* ============================================================================
   品牌佣金 · 通用佣金条 + 待维护（乐哥 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);
}
/* 「本期 | 预估」的分界线：两列同色同粗，靠这条竖线区分主次（本期 = 现在付，预估 = 含未满）。
   与展开区表头 `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);
  background: var(--color-primary-50);
}
.st-drill .table--sub thead th.st-col-fcasthead {
  background: var(--color-primary-50);
  border-left: 1px solid var(--color-border);
}
.st-drill .table--sub tbody tr.st-drill-sumrow td.st-col-fcast {
  background: var(--color-primary-50);
  font-size: var(--font-size-base);
}

/* ============================================================================
   华大营养对账（应收 / 开票台账）— 2026-09-13
   与「达人结算」同构：分达人主表 + 点行展开商品明细；多一块开票/回款台账。
   ============================================================================ */
/* 达人分账附表 · 工具栏（2026-09-27 乐哥：「这个页面的UI不对，包括日期、时间还有这些」）
   ──────────────────────────────────────────────────────────────────────────
   改前三个毛病：
     ① 标签误用 `.hs-static`（那是**弹窗只读信息块**：灰底 + 边框 + 38px 高），
        放进工具栏像个灰方块，与全站"纯文字标签"不是一个语言。
     ② 日期用浏览器原生 `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; }

.hs-ledger { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-4); }
@media (max-width: 900px) { .hs-ledger { grid-template-columns: 1fr; } }
.hs-ledger__col {
  border: 1px solid var(--color-border); border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-4); background: var(--color-surface);
}
.hs-ledger__t {
  font-size: var(--font-size-sm); font-weight: var(--font-weight-semibold); color: var(--color-fg);
  margin-bottom: var(--space-2); padding-bottom: var(--space-2); border-bottom: 1px solid var(--color-border);
}
.hs-ledger__row {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-3);
  padding: 6px 0; font-size: var(--font-size-sm);
}
.hs-ledger__k { color: var(--color-muted); flex: none; }
.hs-ledger__v { color: var(--color-fg); text-align: right; font-variant-numeric: tabular-nums; }

/* 对账金额变了（又有订单满 35 天）：提示「票面要不要跟着改」，用警示色但不做成按钮态 */
.hs-warn {
  display: flex; align-items: flex-start; gap: 6px; margin-top: var(--space-3);
  padding: var(--space-2) var(--space-3); border-radius: var(--radius-sm);
  background: var(--color-warning-bg); color: var(--color-warning-text);
  font-size: var(--font-size-sm); line-height: 1.7;
}
/* 图标垂直对齐（首行行高 1.7em = 23.8px @14px → 下移 (1.7em − 16px) / 2 = 3.9px，原手写 3px 偏上 0.9px） */
.hs-warn svg { flex: none; align-self: flex-start; margin-top: calc((1.7em - 16px) / 2); }

.hs-row:hover { background: var(--color-surface-2); }
.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-led-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-4); }
@media (max-width: 1200px) { .hs-led-grid { grid-template-columns: 1fr; } }
.hs-led-card {
  border: 1px solid var(--color-border); border-radius: var(--radius-lg);
  padding: var(--space-3); background: var(--color-bg);
}
.hs-led-card__hd {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-3);
  padding-bottom: var(--space-2); margin-bottom: var(--space-2); border-bottom: 1px solid var(--color-border);
}
.hs-led-card__amt {
  font-size: var(--font-size-sm); font-weight: var(--font-weight-semibold);
  color: var(--color-fg); text-align: right; font-variant-numeric: tabular-nums;
}
.hs-led-card__amt .cell-sub { display: block; font-weight: var(--font-weight-regular); }
.hs-led-card__actions {
  display: flex; justify-content: flex-end; gap: var(--space-2);
  margin-top: var(--space-3); padding-top: var(--space-3); border-top: 1px solid var(--color-border);
}
/* 卡片内的台账两列在窄卡里要能塌成单列（卡片本身只有半屏宽） */
.hs-led-card .hs-ledger { grid-template-columns: 1fr; gap: var(--space-2); }
@media (min-width: 1500px) { .hs-led-card .hs-ledger { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.hs-led-card .hs-ledger__col { border: none; padding: 0; background: transparent; }

/* 展开区标题：店铺标签 + 达人说明 + 右侧合计（合计靠右、要显眼 —— 财务对的就是这个数） */
.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 {
  background: var(--color-surface-2); 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);
}

/* 弹窗里的只读信息块（店铺 + 该店铺应收），与输入框区分开 */
.hs-static {
  display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap;
  padding: 8px var(--space-3); border-radius: var(--radius-sm);
  background: var(--color-surface-2); border: 1px solid var(--color-border);
  min-height: 38px;
}

/* 发票文件选择器（支持 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); }
.st-comp__t { font-size: var(--font-size-sm); 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; }

/* 项目名：这一列是"主角"，给主文字色 + 中等字重 */
.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); }
/* 金额：有值时用主色（与「截止目前应结」同色系，一眼认出"这是我方要付的"） */
.st-comp__amt { font-weight: var(--font-weight-semibold); }
.st-comp__amt.is-on { color: var(--color-accent-text); }
/* 状态说明：三种语义各有颜色 —— 未配置=灰（无需行动）、已计入=绿（正常）、
   未计=警示（**这是最需要看的一类**：配了费率却没算出钱，必须显眼） */
.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-success-text); }
.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-09-28 页头包盒（乐哥拍板「方案A」）：白底圆角卡 —— 参考图（PMS）页头是独立白框，
   包盒后内容区顶部有了视觉锚点，与左侧侧栏白岛呼应，解决「内容区上面太空/侧栏显高」。
   ⚠️ align-items 由 flex-start 改 **center**（乐哥：返回、编号文字不居中）——
      页头三件套（返回 34 / 标题文字 / 操作按钮 44）高度参差，顶对齐时上下留白失衡。 */
.detail-head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-5);
  background: var(--color-surface);
  border: 1px solid var(--color-frame-border);
  border-radius: var(--radius-lg);
  padding: var(--space-3) 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); }

/* 键值网格（成本明细）：两列自适应，长值横跨整行避免被截断 */
.dt-kv { display: grid; grid-template-columns: repeat(2, minmax(0, 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: 88px; 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: var(--color-surface-2);
  font-weight: var(--font-weight-semibold);
  color: var(--color-fg-2);
  border-bottom: 1px solid var(--color-border);
}
/* 细项表头：比分组头弱一档（字号小、常规字重），层级才立得住 */
.pl-table thead tr:last-child th {
  font-weight: var(--font-weight-regular);
  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-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 的水平内边距对齐，
      否则这个块会比卡片里的表格"缩进不齐"。
   ========================================================================== */
.st-foot {
  display: flex;
  align-items: flex-start;
  gap: var(--notice-gap);
  margin: 0 var(--space-5) var(--space-5);
  padding: var(--notice-pad-y) var(--notice-pad-x);
  border: 1px solid var(--notice-border);
  border-radius: var(--notice-radius);
  background: var(--notice-bg);
  color: var(--notice-fg);
  font-size: var(--font-size-sm);
  line-height: 1.6;
}
/* 图标垂直对齐：沿用全站提示条的推导法（16px 图标在 1.6em 行盒内居中） */
.st-foot svg { flex: none; align-self: flex-start; margin-top: calc((1.6em - 16px) / 2); color: var(--notice-ico); }
.st-foot b { color: var(--color-fg); font-weight: var(--font-weight-semibold); }

/* ==========================================================================
   角色权限页 · 权限目录（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); }
.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;
}
.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; }
.rf-sec__head { display: flex; gap: var(--space-3); align-items: flex-start; }
.rf-sec__no {
  flex: 0 0 auto; width: 22px; height: 22px; border-radius: 50%;
  background: var(--color-accent); color: var(--color-accent-on);
  font-size: 12px; line-height: 22px; text-align: center; margin-top: 1px;
}
.rf-sec__sub { font-size: var(--font-size-sm); color: var(--color-muted); margin-top: 3px; line-height: 1.5; }

/* 分区内字段：单列、纵向间距均匀 */
.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);
  background: var(--color-accent-bg);
}
.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 {
  --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)`：
      后者靠"页面内容恰好带某个类"推断，隔了一层，后来者不易发现。 */
.app-shell.is-standalone.is-finance .sidebar {
  display: flex !important;
}
.app-shell.is-standalone.is-finance .app-main {
  margin-left: var(--sidebar-width);
  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;
}
.app-shell.is-standalone.is-admin .app-main {
  margin-left: var(--sidebar-width);
  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;
}
.app-shell.is-standalone.is-portal .app-main {
  margin-left: var(--sidebar-width);
  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); }

/* 非图片附件（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); }

/* 附件预览弹窗里的内容（走全站 `UI.openModal`，与「我的报销」的凭证预览同一套）。
   ⚠️ 高度给 `70vh` 而不是固定 px：屏幕有大有小，固定值在笔记本上会把弹窗顶出屏幕。
   ⚠️ 图片用 `margin: 0 auto` 居中 —— 弹窗宽 880、发票往往更窄，不居中会靠左留一大片白。 */
.tt-pv__img { display: block; max-width: 100%; max-height: 70vh; margin: 0 auto; }
.tt-pv__frame { display: block; width: 100%; height: 70vh; border: 0; }
.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); }

/* —— 明细（2026-10-08 起住在**小弹窗**里，乐哥：「查看做成弹小窗口的样子，不要展开了」）——
   改前是"就地展开一行"（`.tt-detail > td`），现在由操作列的「查看」按钮弹 `UI.openModal`。
   ⚠️ 底色/单元格内边距那两条**随展开行一起删了**：弹窗自带背景与内边距，
      留着会变成"弹窗里再套一层灰底"（看着像内容没对齐）。
   ⚠️ 网格改成**两列**：弹窗宽 620，四列每格只有 140px（标签+值挤成一坨）。
   ⚠️ `.tt-detail__box` 的 padding 归零 —— 内边距由 `.modal__body` 负责，
      两个都加会叠成双倍留白。 */
.tt-detail__box { padding: 0; }
.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;
}
.tt-detail__att {
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px solid var(--color-border-soft);
}
/* `.tt-detail__attbox` 已删除（2026-10-08）：明细里的图片改成**直接显示 + 点击弹窗**，
   不再需要那个 `<a>` 包裹层（它原来包着图片去 `target="_blank"` 跳新页）。 */
/* 发票是白纸黑字：容器给白底（--color-surface），透明底会让它看着像"缺了一块" */
.tt-detail__img {
  display: block;
  max-width: 340px;
  max-height: 240px;
  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);
  border: 1.5px dashed var(--color-primary-200);
  border-radius: var(--radius-md);
  background: var(--color-accent-bg);
  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); }
.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 会连带关掉底层弹窗"的坑）。 */

/* —— 手机端（乐哥：「得适配 pc 和手机页面」）—— */
@media (max-width: 768px) {
  /* 明细小窗：两列在窄屏也放不下 ⇒ 单列（标签在上、值在下，与 .tt-detail__item 同构） */
  .tt-detail__grid { grid-template-columns: 1fr; }
  .tt-detail__img { max-width: 100%; }
  .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; }

/* 报销人条（默认本人、不让人手输）。代报时切成警示色 —— 见 .eb-who--proxy */
.eb-who {
  display: flex; align-items: center; gap: var(--space-2);
  padding: var(--space-2) var(--space-3); margin-bottom: var(--space-4);
  border: 1px solid var(--color-border-soft); border-radius: var(--radius-md);
  background: var(--color-surface-2);
}
/* 「帮他人报销」展开后：这一条变成"你在替别人报"的提醒。
   用 --color-warning-border / --color-warning-bg-soft（设计规范里给"待结算卡片外框"的那对令牌），
   语义正好：这是一个"要注意"的状态，不是错误（所以不用红）。 */
.eb-who--proxy {
  border-color: var(--color-warning-border);
  background: var(--color-warning-bg-soft);
}
.eb-who__k { font-size: var(--font-size-sm); color: var(--color-fg-2); }
.eb-who__me { font-size: var(--font-size-base); font-weight: var(--font-weight-medium); }
.eb-who-other { margin-bottom: var(--space-4); }

/* 抬头字段网格：两列，长字段（排期）占满整行 */
.eb-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-4); }
.eb-fld { display: block; }
/* 「票面要素」那一行（2026-09-20）。
   ⚠️ 它**曾经是 3 个手填输入框**（号码/代码/开票日期），现已改成只读汇总 ——
      但仍用 auto-fit 而不是写死列数：窄屏时自动堆成一列，
      否则「票面金额 + 对不上 + 票面要素」三格挤在 375px 里会看不全
      （票号有 20 位）。 */
.eb-grid--inv { grid-template-columns: repeat(auto-fit, minmax(150px, 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: 44px; 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); }
.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 {
  border: none; background: transparent; padding: 0; cursor: pointer;
  color: var(--color-accent-text); font-size: var(--font-size-sm);
}
.eb-ro__lnk:hover { text-decoration: underline; }

/* 票面要素清单（默认 hidden，点「点开查看」才展开）——
   三栏从界面上拿掉了，但要素必须**有地方能看**（财务核对要用）。
   ⚠️ 默认收起而不是展开：它是"给财务核对用"的明细，
      填单的人不需要天天看到 20 位的票号（那是纯噪音）。 */
.eb-invpanel {
  margin-top: var(--space-2); padding: var(--space-3);
  border: 1px solid var(--color-border-soft); border-radius: var(--radius-md);
  background: var(--color-surface-2);
}
.eb-invpanel__row {
  display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap;
  padding: 5px 0; border-bottom: 1px solid var(--color-border-soft);
  font-size: var(--font-size-xs);
}
.eb-invpanel__row:last-of-type { border-bottom: none; }
.eb-invpanel__n {
  flex: 0 0 auto; width: 18px; height: 18px; line-height: 18px; text-align: center;
  border-radius: 50%; background: var(--color-surface); color: var(--color-meta);
  font-size: 11px;
}
.eb-invpanel__src { flex: 0 0 auto; max-width: 180px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--color-meta); }
.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; font-weight: var(--font-weight-medium); color: var(--color-fg); }
.eb-invpanel__tip { margin-top: 8px; font-size: var(--font-size-xs); color: var(--color-meta); line-height: 1.6; }

.eb-fld .input, .eb-fld .select { width: 100%; }
.eb-fld--full { grid-column: 1 / -1; }

/* 关联排期块：整块套一层主色浅底框。
   为什么单独上色：它是**只有「达人溯源直播」才出现**的必填项，也是这张单
   "钱算到哪场"的唯一依据 —— 漏选会把达人成本错记成项目成本（不报错、只错账）。
   给它一圈主色，是为了让它在一屏字段里一眼被看见。 */
.eb-schedbox {
  border: 1.5px solid var(--color-primary-200);
  border-radius: var(--radius-md);
  background: var(--color-accent-bg);
  padding: var(--space-3) var(--space-4);
}

/* ⚠️ 已删除（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`）
   删而不留的理由：本项目踩过"用了不存在的类名"（卡片头挤成一坨），
   反向的"定义了没人用的类名"同样是负债 —— 下次改版会以为它还有用、照着改。 */

/* 报销人条上的「本人 / 代报」小标签 —— **仍在用**，所以保留 */
.eb-chip--tag { height: 22px; font-size: var(--font-size-xs); color: var(--color-muted); background: var(--color-surface); }

/* ==========================================================================
   底部固定操作条（合计 + 取消/提交）（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），
      否则日历弹窗会被这条压住。 */
/* ⚠️ sticky / 白底 / 上边线放在**外层**（整宽），限宽放在内层 ——
   两者不能合成一层：合了之后白条只有 1120 宽，两侧会透出页面灰底、
   "条"就不贯穿了（达人表单的 .form-actions 正是这个两层结构）。
   ⚠️ 覆盖达人表单那条公共规则（`.sf-shell > .form-actions` 自带 border-top + 白底），
      用 `--total` 变体精确作用于本页，**不动达人表单**。 */
.sf-shell > .form-actions--total {
  display: block;
  padding: 0;
  position: sticky;
  bottom: 0;
  z-index: 5;
  background: var(--color-surface);
  border-top: 1px solid var(--color-border);
  /* 顶部一点投影，让它"浮"在明细之上，暗示下面还有内容 */
  box-shadow: 0 -2px 10px color-mix(in srgb, var(--color-gray-900) 6%, transparent);
}
/* 内层限宽（跟随 --form-max-width）居中 —— 合计靠左、按钮靠右，两端都与内容列对齐
   ⭐ 2026-09-28 硬编码 1120 → var(--form-max-width)（「对齐链」第 ④ 环）：
      它是多明细提单页的底部合计条，与 `.sf-body__content` 同宽才对得上；
      不改会变成"内容 1280 / 合计条 1120"，两端各错 80px。 */
.sf-shell > .form-actions--total .eb-total__inner {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  max-width: var(--form-max-width);
  margin: 0 auto;
  padding: var(--space-3) var(--space-6);
}
.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: 44px; }

/* 明细区：明细卡直接坐在分区卡里，不再需要独立的分区标题行
   （标题已由 .module-card__head 承担） */
.eb-dt .dt__bd { padding: var(--space-4); }
.eb-dt .eb-grid { gap: var(--space-4) var(--space-4); }

/* 「再加一笔」——放在明细分区卡的底部，整宽虚线按钮 */
.eb-add {
  width: 100%; margin-top: var(--space-3); height: 44px;
  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），"传没传"一眼可辨。 */
.eb-grid--up { margin-top: var(--space-4); }
.eb-up {
  display: flex; align-items: center; gap: var(--space-2);
  min-height: 44px; padding: var(--space-2) var(--space-3);
  border: 1px dashed var(--color-primary-200); border-radius: var(--radius-md);
  background: var(--color-accent-bg); color: var(--color-accent-text);
  font-size: var(--font-size-sm); cursor: pointer;
}
.eb-up:hover { border-color: var(--color-accent); }
/* 走替票时发票那格是"不用传"的说明 —— 退回中性灰，别让它看着还能点 */
.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-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: 8px; flex-wrap: wrap;
  /* 68px（2026-09-20 调高，乐哥「框框要不要高一点，现在太矮了」）——
     原先 44px 时方块 44px 上下只剩 1px，顶着边框显局促。 */
  min-height: 68px; padding: 0 var(--space-3);
  border: 1px dashed var(--color-primary-200); border-radius: var(--radius-md);
  background: var(--color-accent-bg);
  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); }
.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); }
/* 上传中：降低不透明度，让"正在传"有反馈（不转圈 —— 这一格太矮，圈会挤掉方块） */
.eb-files.is-uploading { opacity: .6; pointer-events: 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); }

/* 「＋」追加格：与文件方块同尺寸，虚线 → 一眼看出"这里还能加" */
.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; }
}

/* 排期选择弹窗 */
.eb-ps-list { max-height: 320px; overflow-y: auto; margin-top: var(--space-3); }
.eb-ps-row {
  display: flex; align-items: center; gap: var(--space-3);
  padding: var(--space-2) var(--space-3); border-radius: var(--radius-md); cursor: pointer;
}
.eb-ps-row:hover { background: var(--color-surface-2); }
.eb-ps-row.is-on { background: var(--color-accent-bg); }
.eb-ps-inf { flex: 1; font-size: var(--font-size-sm); }
.eb-ps-d { font-size: var(--font-size-xs); color: var(--color-muted); font-variant-numeric: tabular-nums; }
.eb-ps-empty { padding: var(--space-6); text-align: center; color: var(--color-muted); font-size: var(--font-size-sm); }

/* 手机端：单列 + 汇总不换行截断 */
@media (max-width: 768px) {
  .eb-grid { grid-template-columns: 1fr; }
  .eb-sum { flex-direction: column; align-items: flex-start; gap: var(--space-1); }
  .eb-chip { height: 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);
}
.eb-dt .dt__no {
  flex: 0 0 auto; width: 22px; height: 22px; border-radius: 50%;
  background: var(--color-accent); color: var(--color-accent-on);
  font-size: 12px; line-height: 22px; text-align: center;
}
.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);
}
.eb-dt { border: 1px solid var(--color-border-soft); border-radius: var(--radius-lg); background: var(--color-surface); }
.eb-dt + .eb-dt { margin-top: var(--space-3); }

/* ==========================================================================
   「我的报销」列表按单折叠（2026-09-19，配合多明细报销单）
   ─────────────────────────────────────────────────────────────────────────────
   一张报销单含多笔明细 → 列表折叠成一行摘要（几笔 · 合计 · 事由 · 日期范围 · 状态），
   点击展开看每笔。**单笔的单不折叠**（直接是普通行）—— 库里 260 条多为历史单笔，
   一律折叠等于给所有人凭空多一次点击。

   ⚠️ 展开/收起只切 class（`tr.me-item--child.is-open`），不重新渲染：
      重渲染会丢滚动位置，还会把"正在看第 5 笔"变成"回到列表顶部"。
   ========================================================================== */
.me-sheet > td {
  padding: 0;                             /* 内边距交给内部 .me-sheet__in，避免 td 直接做 flex */
  background: var(--color-surface-2);
  cursor: pointer;
}
.me-sheet:hover > td { background: var(--table-row-hover); }   /* 与全站表格 hover 同一色，别自造 */
.me-sheet__in {
  display: flex; align-items: center; gap: var(--space-3);
  padding: var(--space-2) var(--space-4);
  flex-wrap: wrap;
}
.me-sheet__caret { color: var(--color-muted); font-size: var(--font-size-sm); width: 12px; }
.me-sheet__n { font-weight: var(--font-weight-medium); }
.me-sheet__amt { font-weight: var(--font-weight-medium); font-variant-numeric: tabular-nums; }
.me-sheet__reason { color: var(--color-fg); }
.me-sheet__range { color: var(--color-muted); font-size: var(--font-size-sm); }
.me-sheet__st { margin-left: auto; }

/* 折叠的子明细行：默认收起；展开只在"有 .is-open"时出现 */
tr.me-item--child { display: none; }
tr.me-item--child.is-open { display: table-row; }
/* 缩进一小格 + 左侧竖线，让人一眼看出"这几行属于上面那张单" */
tr.me-item--child > td:first-child {
  padding-left: var(--space-6);
  box-shadow: inset 2px 0 0 var(--color-border-soft);
}

/* 凭证/发票缩略图（约 2KB 的本地小图，原图在飞书） */
.me-thumb {
  display: block; float: left; width: 28px; height: 28px; margin: 0 var(--space-2) 0 0;
  object-fit: cover; border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-sm); cursor: zoom-in;
}
/* 「+N」（2026-09-19 深夜，多文件附件）：跟着缩略图一起 float: left，
   尺寸与缩略图一致（28px）—— 让它读起来是"这一排里的一个"，而不是另一段文字。 */
.me-thumb-more {
  display: flex; float: left; align-items: center; justify-content: center;
  width: 28px; height: 28px; margin: 0 var(--space-2) 0 0;
  border: 1px solid var(--color-border-soft); border-radius: var(--radius-sm);
  background: var(--color-surface-2); color: var(--color-muted);
  font-size: var(--font-size-xs); cursor: default;
}

/* 明细卡片「说明（备注）」尾行（2026-09-19 乐哥指定字段顺序）
   顺序：费用类型 → 类型相关（交通方式/出发到达/日期）→ 金额 → 发票类型
        → 支付凭证 → 发票 → **说明**（这里）
   ⚠️ 独立成一段而不是塞进上面的两列网格：备注可能写得较长（"汉庭王府井店 3 晚"），
      挤在半列里看不全；也与"钱的字段"分开，扫一眼不会混。 */
.eb-grid--tail { margin-top: var(--space-3); }
.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; }
.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); }

/* 四道进度条 —— 让"卡在哪一道"一眼可见（乐哥要求"审批流程要看得见"） */
.ap-steps { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; margin: var(--space-3) 0; }
.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; }
.ap-step--done { color: var(--color-muted); }
.ap-step--done i { background: var(--color-success); color: #fff; }
.ap-step--current { border-color: var(--color-accent); color: var(--color-fg); font-weight: 600; }
.ap-step--current i { background: var(--color-accent); color: #fff; }
.ap-step__line { width: 14px; height: 1px; background: var(--color-border); }

.ap-section { margin-top: var(--space-3); }
.ap-section__title { font-size: var(--font-size-sm); font-weight: 600; margin-bottom: 6px; }
.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; }
.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); }
.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); }
.ap-logs { margin: 0; padding-left: 18px; font-size: var(--font-size-sm); color: var(--color-muted); }
.ap-logs li { margin-bottom: 4px; }

/* 改动记录（谁改的、改了什么、为什么）—— 后续审批人靠它看清"这单被改过" */
.ap-change { border-left: 3px solid var(--color-warning); padding: 8px 12px; margin-bottom: var(--space-2);
  background: var(--color-warning-bg); border-radius: 0 var(--radius-seg) var(--radius-seg) 0; }
.ap-change__head { font-size: var(--font-size-sm); font-weight: 600; }
.ap-change__reason { font-size: var(--font-size-sm); color: var(--color-muted); margin: 2px 0 4px; }
.ap-change ul { margin: 0; padding-left: 18px; font-size: var(--font-size-sm); }
.ap-change s { color: var(--color-meta); }

/* 「修改并同意」弹窗里的逐笔字段网格 */
/* ── 「修改并同意」弹窗的字段区（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 人工验票区（乐哥：「把发票号码打印出来，让财务一笔一笔复制」）────
   财务的工作方式：复制号码 → 税局网站查询 → 回来标「通过 / 有问题」。
   设计遵循三件事：
     ① 号码等宽 + `user-select: all`（点一下全选）+ word-break —— 复制是最高频动作，
        绝不能被截断或选不中；
     ② 操作按钮走紧凑档（34px）—— 它住在弹窗里、不是表单主控件；
     ③ 已验状态用底色区分（通过=浅绿 / 有问题=浅红），一眼扫过去知道哪些验过。 */
.ap-vfy { 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__row { display: flex; align-items: center; gap: 10px; }
.ap-vfy__no { flex: 1; min-width: 0; font-family: var(--font-family-mono); font-size: var(--font-size-sm);
  letter-spacing: .4px; word-break: break-all; user-select: all; -webkit-user-select: all; }
.ap-vfy__ops { display: flex; gap: 6px; flex-shrink: 0; }
/* ⚠️ 高度用**令牌**（两档制纪律：控件高度一律 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 { margin-top: 8px; font-size: var(--font-size-xs); color: var(--color-muted); }
.ap-vfy__reason { width: 100%; margin-top: 8px; }
/* 财务道「未验完不能通过」的提示（跟在 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-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）─────────────────────────────
   贴在 tab 文字右侧的小字。`--warn` 用于「未填部门」那个 tab（同一条警示逻辑）。 */
.u-dept-tab__n { margin-left: 6px; font-size: var(--font-size-xs); color: var(--color-fg-3); }
.u-dept-tab__n--warn { color: var(--color-warning-text); font-weight: var(--font-weight-semibold); }
/* 选中态的 tab 文字已经是白字（蓝底），人数跟着继承即可，不要再抢一层色。
   ⭐⭐ 2026-10-08 选择器去掉 .table-tabs 前缀（乐哥：「tab 里文字看不清」）——
   旧写法要求祖先带 .table-tabs 类，用户管理的 seg 当时裸在 toolbar 里（没有该类）
   ⇒ 规则不命中 ⇒ 数字保持 --color-fg-3 灰字贴蓝底。结构升级后已带该类（双保险）。 */
.seg__btn.is-active .u-dept-tab__n { color: inherit; }

/* ── 用户管理 · 表格（2026-09-23）─────────────────────────────────────────
   ⭐⭐ 为什么需要这两条：乐哥报障「用户管理里面，这个不对」——
      截图红框圈在被切掉的「删除」按钮上（窄窗口下最右侧那颗被裁掉）。

   ⛔ 根因（实测，不是推测）：「用户名」列装的是飞书 36 字符 open_id
      （`ou_a9581258e7b2ff3eec0df1f9331e534f`），**一串没有空格** ⇒ 它的
      `min-content` 就是整串宽 **319px**，而 `table-layout:auto` 下浏览器
      **压不动任何一列低于它的 min-content** ⇒ 整表最小宽被钉在 **1133px**：
        · 视口 1440（容器 1134）→ 刚好塞下
        · 视口 1300（容器 ~1000）→ 溢出 133px ⇒ 最右侧「删除」被切
        · 视口 1280（容器 974）→ 溢出 159px ⇒ 切得更狠（实测数据）

   ⇒ 修法两件事一起做（**只做一件不够**，实测过）：
     ① `.u-username` 限宽 + 省略号 ⇒ 把这一列的 min-content 解放出来
        （只收窄操作列、不处理这一列：实测只省 29px，仍溢出 18px —— 因为省下的
          空间立刻被这一列膨胀吃掉）
     ② 其余列锁单行（`nowrap`）⇒ 它们的 min-content == max-content、浏览器压不动，
        富余宽度只能让给那唯一可换行的列。
        依据：项目既有标准（技能 `tables-and-lines.md`「列宽溢出的正解：
        把宽度让给唯一可换行的列」）。
   ⚠️ 用 `#u-table` 作用域，**新开范围**不动全站 `.table` / `.is-num`（几十张表在用）。
   ⚠️ 用户名列**不能删**：外部账号与飞书账号只能靠它区分（姓名会重名，实测 3 个「张乐」）。
      截断了也要给 `title` 让鼠标悬停能看到全文（模板里已加）。 */
#u-table .u-username {
  display: inline-block;
  /* ⚠️ 限宽值取自"列宽分配合适"的实测（未截断时是 319px，锁单行后能压到 ~240px） */
  max-width: 215px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  /* inline-block 默认按基线对齐，会比同行其它文字矮一截 —— 必须改掉，否则整行错位 */
  vertical-align: bottom;
}
/* 「外部账号」徽章要跟用户名同行、不折行（它是 inline 元素，会被 nowrap 规则管到） */
#u-table .badge { white-space: nowrap; }
#u-table table tbody td:not(:nth-child(2)) { white-space: nowrap; }

/* 「本地账号」标签（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; }
}


/* ==== /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-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);
}
.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);
}
/* 字段行：标签在左、控件在右（标签定宽对齐），不再各占一行 */
.pg-field {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-width: 0;
}
.pg-field__label {
  flex: 0 0 76px;
  font-size: var(--font-size-xs);
  color: var(--color-fg-2);
  text-align: right;
}
.pg-field__ctl { flex: 1; min-width: 0; }
/* 行内右侧的字段区：固定两列网格 —— 这样第一行「平台账号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 { flex: 0 0 64px; }
}

/* 二级胶囊筛选已收敛为全局通用组件 .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-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;
}
/* 混播/私域开关关闭时，整行置灰禁用 */
.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; }

.ia-group__title {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-4);
  font-size: var(--font-size-lg);
  font-weight: var(--font-weight-semibold);
  color: var(--color-fg);
}
/* 分区编号方块（⭐ 2026-09-27：乐哥给 PMS「新建项目」参考图 ——
   「整个 UI 的二级页面应该按照项目管理系统那个模型去做」。该模型第一眼线索就是这个编号：
   `① 基础信息` `② 业务与计费` —— 给出"一共几部分、现在在哪一部分"的顺序感。
   原实现是一条 3px 竖条，信息量只有"这里是一组的开头"，没有顺序。 */
.ia-group__no {
  width: 18px;
  height: 18px;
  border-radius: 5px;
  background: var(--color-primary-50);
  color: var(--color-primary-500);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-medium);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  font-variant-numeric: tabular-nums;
}
.ia-group__hint {
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-regular);
  color: var(--color-muted);
}

/* ── ② 组内卡片收紧 + 去卡化 + 首卡标题接管（⭐ 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 靠右），
      整块藏会把控件一起弄丢。 */
.ia-page .ia-group > .ia-group__title + .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 > .ia-group__title + .module-card > .module-card__head:not(:has(> :not(.module-card__title))) {
  display: none;
}

/* ── ③ 次模块卡：降一档（附属信息）───────────────────────────────────────
   用途：本身是"上面那张主卡的细节/附属"的模块 ——
     详情页「平台账号 / 机构」＝「合作信息」的明细；
     编辑页「带货小号」＝「平台相关」的附属。
   用全站既有的"次级底"令牌 --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); }
}


/* ==== /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;
}

.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-09-29 乐哥同意全站统一到「同一行」语言，
   与 .form-row（pages-influencer.css）同一形态；此处标签列 80px —— 本卡标签最长 4 字，
   3 列布局下 112px 会把控件挤太窄。窄屏退回上下排。 */
.sf-card-row .sf-field-col { flex-direction: row; align-items: center; gap: var(--space-3); }
.sf-card-row .sf-field-col > .sf-label { flex: none; width: 80px; text-align: right; margin-bottom: 0; }
.sf-card-row .sf-field-col > .sf-ctrl { flex: 1; min-width: 0; }
/* 定宽列随"标签在左"加宽（原 170/220 是按"标签在上"定的，标签要占 80+12）—— 见上方 2026-09-29 说明 */
/* 直播平台：说明小字在勾选框下方（标签顶对齐） */
.sf-comm-plat .sf-field-col { align-items: flex-start; }
.sf-comm-plat .sf-field-col > .sf-label { padding-top: 10px; }
@media (max-width: 900px) {
  .sf-card-row .sf-field-col { flex-direction: column; align-items: stretch; }
  .sf-card-row .sf-field-col > .sf-label { width: auto; text-align: left; margin-bottom: var(--space-2); }
}
.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);
  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） */
.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);
}
/* 标题栏内状态文字：底色已由整条标题栏承载，这里不再挂药丸底 */
.sf-comm-state {
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-medium);
  color: var(--color-fg-2);
}
/* 状态提示：无底色，直接用状态色文字（橙=单独维护 / 绿=跟随达人） */
.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-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); }
.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); }
.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;
}
.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;
  }
  /* 场地 + 直播类型：一行两个，各占一半 */
  .calendar-filters .input--search {
    flex: 1 1 calc(50% - 6px) !important;
    max-width: calc(50% - 6px);
  }
  .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;
  }
  /* 视图切换（月/周/日程）+ 只看与我相关：一行 */
  #cal-viewswitch {
    margin-left: 0;
    flex: 0 0 auto;
  }
  #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-09-29 节点内字段间距收敛（乐哥：「每个环节要填的内容向右移动了很奇怪」）：
   时间轴/住宿节点里的标签最长 4 字（地点/接待人/助播/商务助理/时间范围），
   沿用全站 112px 列宽（按 8 字标签定的）会让输入框整体右移一大截、和节点标题脱节。
   ⇒ 节点内标签列收窄到 64px（4 字 + 必填星号），"空档"从 124px 缩到 76px；
     备注等不在 .form-grid 里的输入框撑满剩余宽（原先是 150px 小框孤零零挂着）。
   ⚠️ 只限 #sf-timeline / #sf-acc-list（住宿），不影响其他 tab 的 112px 全站列宽。 */
#sf-timeline .form-row > .field-label,
#sf-acc-list .form-row > .field-label { width: 64px; }
#sf-timeline .form-row__ctrl > .input,
#sf-acc-list .form-row__ctrl > .input { width: 100%; }

/* ============================ 视图切换 / 节假日 / 周视图 / 日程视图 ============================ */

/* 视图切换（月/周/日程）：与全局 .seg 同规格——白容器 + 选中项内缩蓝色圆角块 */
.view-switch {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 4px;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  margin-left: auto;
}
.view-switch__btn {
  height: 28px;
  padding: 0 var(--space-4);
  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);
  cursor: pointer;
  transition: background var(--motion-fast) var(--motion-ease-standard),
    color var(--motion-fast) var(--motion-ease-standard);
}
.view-switch__btn:hover {
  color: var(--color-accent-text);
}
.view-switch__btn.is-active {
  background: var(--color-accent);          /* 纯色作**回落**，与 .btn--primary 同款 */
  /* ⭐ 2026-10-08 补：选中块填充 = **品牌渐变**（与 `.btn--primary` / tab 选中同源）。
     本组件是 `.seg` 的**自造副本**（见上方"与全局 .seg 同规格"注释），当时漏接了渐变
     —— 乐哥当天统一口径「选中的样子要做的和右边新增达人一样，是蓝色渐变」时一并纳入。
     ⚠️ 托盘保持白底/28px 不动：那是本组件的既有形态（工具栏内紧凑场景），本次只动填充。 */
  background-image: var(--gradient-brand);
  color: var(--color-accent-on);
  /* ⭐ 2026-10-08 选中态浮起（乐哥动效第②条）—— 与全站选中胶囊同一令牌 */
  box-shadow: var(--shadow-pill-active);
}

/* 节假日 / 周末 / 调休标注 */
.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; }
.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; }

/* 所属商务列：多个商务并排（chip 列表）——同理不折
   内容 136px，列宽 176 − padding 24 = 152 ⇒ 余 16px */
.sch-bd { white-space: nowrap; }

/* ⭐⭐ 2026-10-06 佣金区四列：多平台行的**顶端对齐**
   ──────────────────────────────────────────────────────────────────────────
   背景（乐哥：「视频号和抖音的佣金，在佣金开头就显示一个渠道信息就行了，
   没必要每个佣金前面显示」）⇒ 渠道标签只在最左佣金列显示，其余三列纯数值。
   ⇒ 于是四列的**行必须严格对齐**，标签才认得出标的是哪一行。

   ⛔ 不修会错位（实测）：全表惯例是 `vertical-align: middle`（table.css 的
      `.table td`），而多平台四列的内容高度**各不相同** ——
        · 标签列：「视频号」「抖音」2 行
        · 线上列：2 行数值 + 1 行「已单独设立」胶囊（尾行只在这一列有）
        · 其余列：常常全空（0 值留白后为 0 高）
      各自垂直居中 ⇒ 标签与对应数值**错开**。实测「第0行 -15px、第1行 -12px」，
      即上面的「视频号」比它该标的 40.0% 低了 15px —— 标签失去意义。

   ✅ 修法两条一起上（缺一不可）：
      ① 本类：`vertical-align: top` ⇒ 四列都从单元格顶部起排；
      ② 行内容器的 `min-height: 1.5em`（写在 JS 内联样式里）⇒
         抹平「平台标签 `.tag--sm` 18px」与「数值文字行 21px」的 3px 差。
      实测：① 后 0px/3px，② 补上后 **0px/0px**。

   ⚠️ **只作用于多平台单元格**（JS 里只在 `plats.length > 1` 那一支挂此类）：
      单平台是一行，居中本来就对；改它会动到本页既有观感（乐哥没要求）。
   ⚠️ 不用 `td:nth-child()` 选列 —— 多选模式首列插 checkbox 会错位（本项目反复踩）。
   ⚠️ 不改成「给其他列补空占位行」的写法：实测只能做到 -4.5/-1.5/+4.5，且给
      空列平白加节点（分页重渲染时更脆）。 */
/* ⛔⛔ **必须写成 `.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 td.sch-comm {
  vertical-align: top;
}


/* ==== /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;
  /* 门户自己的底色（独立外壳下没有侧栏，整屏都是它）。
     ⚠️ 用页面底 --color-bg 而非白：白色留给卡片，层次才出得来。 */
  background: var(--color-bg);
}

/* ⚠️ 门户要**铺满整屏**，所以必须清掉 `.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;
  background: var(--color-bg);
}

/* ⚠️ 清掉 `.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;
  /* -8px 抵消 `.page-header` 的 gap:16px ⇒ 与标题的间距收成 8px（紧贴但不贴死） */
  margin-right: auto;
  margin-left: calc(-1 * var(--space-2));
}
.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。 */
.fin-chipbar { margin-bottom: var(--space-4); }
.fin-chips {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--space-3);
}
.fin-chip {
  /* ⭐⭐⭐ 2026-09-21 **第 5 次改：横向 → 纵向两行**（乐哥：「上面那个卡片的数字，
     现在放大的数字位置很奇怪，看下整体怎么调整下」）。
     ⚠️ 先说清"奇怪"到底是什么 —— **量出来的**，不是感觉：
        88px 高、301px 宽的卡片里，标签「未结清」宽 42px、数字组「10单 ¥2.04万」宽 114px，
        而两者之间空出 **103px**（片2 是 121px、片4 是 89px）。
        也就是说**卡片 1/3 的宽度是一块毫无内容的死白**，数字被 `margin-left:auto`
        推到最右边、孤零零地飘着；高度从 44 加到 88 之后，这块空白在纵向上更显眼。
     ⇒ 根因不是对齐、是**布局形态**：单行横向排布 + 两端对齐，在"标签短、数值长"的
        情形下必然留大洞（这正是它从 44px 起就有的问题，只是矮的时候不明显）。
     ⇒ 改成**纵向两行**（这是 KPI 卡片的标准形态，也正是 88px 高度该有的用法）：
           行1：标签（14px，次级色）
           行2：数字(30px) + 单位 + 说明
        两行**左对齐** ⇒ 空白自然消失，且高度被真正用起来（不再"高而空"）。
     ⚠️ 别改回"标签左 / 数值右"的横向两端对齐 —— 它会重新制造那块死白。 */
  /* ⭐⭐⭐ 2026-09-27 **B 方案落地：改 grid + 彩色图标块**（乐哥拍板，见
     docs/UI评审修复任务-启动文档-20260926.md §三：全站 KPI 统一「彩色图标块 + 白底 + 黑粗体数字」）。
     形态：行1 = [彩色图标块 30px] [标签]，行2 = 数字组（跨两列）。
     ⚠️ 为什么从 flex-column 改 grid：图标块要与标签**同行**（这是看板 KPI 卡已确立的
        语言：图标块在左、标签紧跟其右、大数字在下一行）。flex-column 做不到"图标与标签
        同行 + 数字独占下一行"这个两行结构，grid 两列自然表达。
     ⚠️ 定位策略（**为兼容无图标调用方**）：图标块与标签**不写死 grid-column**，
        靠自然流填充（ico→(1,1)、l→(1,2)）；数字显式 `grid-column: 1 / -1` 跨两列。
        ⇒ 没传 `icon` 时第一列为空、标签自动落到 (1,1)，布局回退成旧的两行形态，零副作用。
        ⛔ 别给 `.fin-chip__l` 写死 `grid-column: 2` —— 那会让"无图标"场景的标签无故缩进。 */
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  align-content: center;
  column-gap: 10px;
  row-gap: 4px;
  /* 纵向居中：两行（图标行 + 数字行）作为一个整体在 88px 内垂直居中 */
  height: 88px;
  padding: 0 var(--space-4);
  background: var(--color-surface);
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-lg);
  white-space: nowrap;
  min-width: 0;
}
/* 彩色图标块（B 方案核心要素）——30px 圆角方块 + 白图标，配 88px 小片。
   ⚠️ 尺寸有意比看板（44px 块 + 22px 图标）小一号：小片矮（88px vs 看板 ~130px），
      同比例缩放才协调（本项目规格：图标块与容器高度成比例，别硬套 44px）。 */
.fin-chip__ico {
  width: 30px;
  height: 30px;
  border-radius: var(--radius-md);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  flex: none;
}
/* 5 色与 pages.css 的 `.kpi-card__icon--*` 一一对应（同一组 `--kpi-icon-*` 令牌，不新造色板） */
.fin-chip__ico--blue   { background: var(--kpi-icon-blue); }
.fin-chip__ico--green  { background: var(--kpi-icon-green); }
.fin-chip__ico--orange { background: var(--kpi-icon-orange); }
.fin-chip__ico--purple { background: var(--kpi-icon-purple); }
.fin-chip__ico--pink   { background: var(--kpi-icon-pink); }
/* 警示态（warn）：**米黄底**语义变体（全站认可的"要盯的项"处理，售后分析同款）——
   白卡一排里米黄底一眼跳出来；柔底不再描边（描边会让柔底显脏，同 `.kpi-card--tone-*` 的做法）。 */
.fin-chip--warn {
  background: var(--color-warning-bg);
  border-color: transparent;
}
/* 标签：次级信息，放**上面一行** */
.fin-chip__l {
  /* ⚠️ 不写 grid-column：靠自然流落位（有图标→(1,2) / 无图标→(1,1)），见 `.fin-chip` 注释 */
  align-self: center;      /* 图标块 30px 高、标签行盒 21px ⇒ 垂直居中而非顶对齐 */
  font-size: var(--font-size-sm);
  color: var(--color-meta);
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}
.fin-chip__v {
  /* ⭐ 2026-09-27：数字组**跨两列**（从最左起排，与看板 KPI 的"大数字占满行"同一语言） */
  grid-column: 1 / -1;
  /* ⚠️ 这里原来有 `margin-left: auto`（把数值推到右端）—— 纵向布局下**必须删掉**，
     否则数值仍会被推到最右、与标签错开，死白换个形式回来。 */
  flex-shrink: 0;
  /* 数字与单位**各自居中**（2026-09-21 第 4 次改的成果，保留）：
     原来的写法是「30px 数字 + 14px 单位」同在一条基线上 ⇒ 单位跟着数字的**基线**走，
     小字贴在大字基线上、字形中心偏低 ⇒ 看起来"没上下居中"。
     ⇒ 拆成两个 flex item + `align-items: center`，各自居中。 */
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);                 /* 数字与单位贴近但不粘连 */
  font-size: var(--font-size-3xl);     /* 30px：金额分支（.af-money）继承它 */
  font-weight: var(--font-weight-semibold);
  color: var(--color-fg);
  font-variant-numeric: tabular-nums;
  min-width: 0;
  /* ⚠️⚠️ **行高必须收紧到贴近字号**（2026-09-21 实测，这是"两行版"能居中的关键）：
     默认行高是 1.5 ⇒ 30px 数字的行盒有 **45px**，而数字字形只有 22.7px
     ⇒ 行盒里上下各留 11px 空气，与上面那行（14px 标签、行盒 21px）的留白不对等
     ⇒ 整块文字（标签 + 数值）的**墨迹**并集偏上 **2.85px**（实测上下留白 12.3 / 18）。
     ⇒ 收紧行高后，数值行盒 = 33px，墨迹并集中心落到 43.6（片中心 44）⇒ 偏差 0.4px。
     ⚠️ 别用 `line-height: 1`：中文字形可能被裁（数值行里含「单」「张」等单位）。
     ⚠️ 别删这一行去"图省事"—— 删掉后 B4（上下留白对称）会立刻报红。 */
  line-height: 1.1;
}
/* 数字 / 单位 / 金额：都是 flex item，**不许被压缩**（否则窄屏会把「10单」挤变形） */
.fin-chip__v > * { flex-shrink: 0; }
.fin-chip__num { font-size: var(--font-size-3xl); font-weight: var(--font-weight-semibold); }
/* 单位比数字小一档、颜色更轻 —— 但**中心对齐**（不再贴数字基线） */
.fin-chip__unit {
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-regular);
  color: var(--color-muted);
}
.fin-chip__s {
  flex-shrink: 0;
  font-size: var(--font-size-sm);
  color: var(--color-meta);
  font-variant-numeric: tabular-nums;
}
/* 有需要注意的量（未结清 / 待付款 > 0）→ 数字用警示色 */
.fin-chip--warn .fin-chip__v { color: var(--color-warning-text); }
/* 窄屏：4 等分会把每个格子挤到放不下（≥305px → ≤90px）⇒ 退成 2 列；
   高度与字号同步降档（88px×2 行在手机上占 176px，太占屏） */
@media (max-width: 768px) {
  .fin-chips { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .fin-chip { height: 64px; padding: 0 var(--space-4); }
  .fin-chip__v, .fin-chip__num { font-size: var(--font-size-2xl); }
}

/* ---- 报销单号列靠左（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; }
/* 固定布局下内容超宽会被裁 —— 给单号列与报销人列一个"至少要放得下"的兜底：
   允许它们换行而不是溢出（财务最怕看到被切掉的单号）。 */
.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; }
.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; }
.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); }
/* 金额：元信息行里**唯一**加重的项（财务扫这一屏第一个要找的数）。 */
.fin-doc__amt {
  font-size: var(--font-size-sm);
  color: var(--color-fg-2);
}
.fin-doc__amt b {
  color: var(--color-fg);
  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。 */
.af-step-card {
  background: var(--color-surface);
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-lg);
  padding: var(--space-6) var(--space-5);
}

/* ══ 详情页「全流程」= 审批 + 付款 **并排在同一条节点行、分两段**（2026-09-24 乐哥）══
   ──────────────────────────────────────────────────────────────────────────
   乐哥原话：「红框审批流程，我觉得是不是可以放在同一行，但是两个阶段，
             分别是提报销和报销付款，两个阶段可以不同颜色区分，
             这样的话下面明细就有更多地方展示内容了」

   ⛔ 不是"合成一条 7 步的流程"（2026-09-21 已论证：审批有否决权、付款没有，
      混成一条会让财务以为付款也要等人批）⇒ 仍是**两段**，只是横向并排。
   「两段」靠三处区分，**缺一处就会看成一条 7 步流程**：
     ① 卡头两个段标签（配色不同）  ② 节点形状（圆点 vs 方点）
     ③ 节点配色（审批绿系 vs 付款主色蓝系）

   ⚠️ 删掉了原来的 `.fin-chain__sep`（居中那条"审批通过 · 已入账"分隔行）：
      它是"上下两行"的产物，并成一行后再插一条横线会把这一块撑回两段式的高度（白改）。
      衔接关系改由 ④⑤ 之间的**桥接线** `.af-step__bar--bridge` 表达（见下）。 */
.fin-chain { display: block; }

.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 { margin-left: auto; }        /* 「刷新审批进度」靠右（它属于审批那一段） */
/* 两个段标签之间的竖分隔 —— 让"这是两段"在卡头就成立（光靠间距容易被读成"审批 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; }

/* ── 提报单详情：头部右侧的状态标签 + 操作条 ── */
.ebd-head__r { display: flex; align-items: center; gap: var(--space-3); }
.ebd-actions {
  display: flex;
  flex-wrap: wrap;
  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; }
.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; }
.table .ea-thumb { width: 32px; height: 32px; }
.table .ea-set__lab { font-size: 11px; }
/* ⭐ 2026-09-24：表格这一格里**两类附件挨得太挤** —— 组内（标签↔图、图↔图）与
   组间（发票组↔凭证组）都还是详情页那套 4px/6px，而这里是**行内小图**、
   尺寸只有详情页的 0.7 倍 ⇒ 同样的间距在小尺寸下看着"糊成一坨"（实测截图），
   财务扫一行时要花时间分辨"哪几张是票、哪几张是证"。
   ⚠️ 只加大 `.ea-cell` 里的间距，**不动全局 `.ea-*`** —— 详情页那些大卡里的
      44px 图用 6px 间距是合适的，全局改会把那边也改挤。
   ⚠️ 组间(12px)必须明显大于组内(6px)：2 倍差距才看得出"这是两组"，
      1.5 倍在这种小尺寸下仍然会被读成一排。 */
.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-09-24）
   渲染实现见 `public/js/expense-attach.js` 的 `openViewer()`（**全站唯一实现**）
   ──────────────────────────────────────────────────────────────────────────
   乐哥：「报销详情查快附件，同一个报销明细的，能不能查看的时候左右键快速切换，
          这样就不用再一个个点开关闭看了」

   ⚠️ 为什么要"两侧悬浮箭头"而不是"底部一排按钮"：
      看大图时视线在图上，箭头贴着图左右两侧最省眼动（这也是图片查看器的通用做法）。
      同时键盘 ←→ 是主路径，箭头是给鼠标用户的等价入口。
   ⚠️ 尺寸只在这里定（JS 只给语义）—— 与 `.ea-thumb` 同一条纪律。
   ⚠️ 图片**必须** `max-height` 限制（否则竖长的发票会把弹层顶到屏幕外、
      底部条和关闭按钮都看不到）。用 vh 而不是 px，屏幕高矮都合适。
   ══════════════════════════════════════════════════════════════════════════ */
.ea-view { display: flex; flex-direction: column; gap: var(--space-3); }

/* 舞台：图 + 两侧箭头。`position: relative` 是箭头定位的锚点。 */
.ea-view__stage {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 220px;
  padding: var(--space-2);
  background: var(--color-surface-2);
  border-radius: var(--radius-md);
}
.ea-view__pic { display: flex; flex-direction: column; align-items: center; gap: var(--space-2); }
.ea-view__img {
  display: block;
  max-width: 100%;
  max-height: 62vh;          /* ⚠️ 不限制会把底部信息条顶出视口 */
  object-fit: contain;
  border-radius: var(--radius-sm);
}
/* 「只有缩略图（原件未留存）」的注明 —— 放在图下方，不遮图 */
.ea-view__note {
  font-size: var(--font-size-xs);
  color: var(--color-muted);
}

/* 「原件在飞书」的说明块：渲染不了图（文件码不是图片地址），但必须说清去哪儿看 */
.ea-view__none {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-8) var(--space-4);
  font-size: var(--font-size-sm);
  color: var(--color-fg-2);
  text-align: center;
}
.ea-view__none-sub { font-size: var(--font-size-xs); color: var(--color-muted); }

/* 两侧导航箭头。`top: 50%` + translateY 垂直居中（图高会变，不能写死 top）。 */
.ea-view__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;
}
.ea-view__nav--prev { left: var(--space-2); }
.ea-view__nav--next { right: var(--space-2); }
.ea-view__nav:hover:not(:disabled) {
  border-color: var(--color-accent);
  color: var(--color-accent-text);
}
.ea-view__nav:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 1px; }
/* 到边界：**禁用**而不是隐藏 —— 灰色箭头表达"到头了"，
   比"按钮消失"更让人确信"这一笔的附件都看过了"（财务核对怕漏看）。 */
.ea-view__nav:disabled { opacity: .35; cursor: default; box-shadow: none; }

/* 底部信息条：类型胶囊 + 文件名 + 快捷键提示 */
.ea-view__bar {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--font-size-xs);
  color: var(--color-muted);
}
.ea-view__pill {
  flex-shrink: 0;
  padding: 1px 8px;
  border-radius: var(--radius-sm);
  background: var(--color-accent-bg);
  color: var(--color-accent-text);
  font-weight: var(--font-weight-medium);
}
/* 凭证用中性色（发票才是财务第一眼要看的，与缩略图组的语言一致） */
.ea-view__pill--proof { background: var(--color-surface-2); color: var(--color-muted); }
.ea-view__name {
  color: var(--color-fg-2);
  /* 文件名可能很长（"微信支付截图-20260917-北京.jpg"）⇒ 截断而不是换行挤掉提示 */
  max-width: 46%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ea-view__hint { margin-left: auto; flex-shrink: 0; }
/* `<kbd>`：让人一眼看出"这是键盘按键"而不是正文里的箭头符号 */
.ea-view__hint kbd {
  display: inline-block;
  min-width: 18px;
  padding: 0 4px;
  border: 1px solid var(--color-border);
  border-bottom-width: 2px;
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  color: var(--color-fg-2);
  font-family: inherit;
  font-size: var(--font-size-xs);
  text-align: center;
}
/* 这一笔的说明（"第 1 笔 · 餐饮 · ¥2,680.00"）—— 切换时始终知道在看哪一笔 */
.ea-view__scope {
  font-size: var(--font-size-xs);
  color: var(--color-muted);
  padding-top: var(--space-1);
  border-top: 1px solid var(--color-border-soft);
}

/* 「在新标签打开」在"没有本地原件"时是禁用态（点了会打开空白页 ⇒ 那是假链接）。
   ⚠️ 用 `pointer-events: none` 而不只是视觉置灰 —— 假链接点了有反应比没反应更糟。 */
.btn.is-disabled { opacity: .5; pointer-events: none; }

/* 窄屏：两侧箭头会压到图上（图本身已经占满宽度）⇒ 改成静态排在图下方一行。 */
@media (max-width: 768px) {
  .ea-view__stage { flex-direction: column; gap: var(--space-2); }
  .ea-view__nav { position: static; transform: none; }
  .ea-view__nav--prev { order: 2; }
  .ea-view__nav--next { order: 3; }
  .ea-view__bar { flex-wrap: wrap; }
  .ea-view__hint { margin-left: 0; }
  .ea-view__name { max-width: 100%; }
}


/* ==== /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 真踩过）。 */
.app-shell.is-standalone.is-hr .app-main {
  margin-left: var(--sidebar-width);
  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); }

/* ── 照片（预览 / 已上传标记）── */
.kq-photo-row { display: flex; gap: var(--space-4); align-items: flex-start; }
.kq-photo-preview {
  width: 88px; height: 88px; flex: 0 0 88px;
  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;
}
.kq-photo-preview--img { border-style: solid; }
.kq-photo-preview img { width: 100%; height: 100%; object-fit: cover; }
.kq-photo-hint { margin-top: var(--space-2); }

/* ── 人员排除页：弹窗里的"排除对象"与"后果提示" ──────────────────────────
   ⚠️ 两个都必须**留在弹窗里**（不是收进「?」）：
      · 排除对象 = 操作对象，点错人就是删错人的考勤（必须眼看确认）
      · 后果说明 = 这一步**会删数据**，动手前必须看到"会删卡、门禁不受影响"
      按小字注释分级（技能 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-hint`（塞在筛选栏里的一行小字）—— 2026-09-29 已删，原因两件：
     ① **它把筛选栏挤折行**：那一行总宽 ≈1330px > 容器 1090px（日期区间 346 + 本句 332
        + 搜索 220 + 三个下拉 292 + 共N条 100 + 间距）⇒ 折成两行、第二行只剩两个下拉。
        ⚠️⚠️ 本地空库时下拉选项少、勉强不折 ⇒ **本地绿、生产红**（真机才暴露）。
     ② 这类"先说清口径"的条全站已有公共件 `.st-rule`（华大对账在用）⇒ 不该自造。
     现在口径条走 `.st-rule`，位置在**筛选栏之外**（见 kq.js 的 stRule()）。 */


/* ==== /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` 是本板块自造的（下一条说明为什么）
     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 宽
      （本项目在财务板块上真踩过，且**不报错、也不出横向滚动条**，
      只能靠窄视口量宽度才发现）。
   ══════════════════════════════════════════════════════════════════════════ */
.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: var(--sidebar-width);
  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;
}

/* ── ① 页面竖向节奏 ──────────────────────────────────────────────────────────
   ⚠️ 与 kq / finance 板块保持同一套节奏（16px 起的元素间距、卡片间距 20px）。
      这里只给"本板块特有的元素"设间距，公共区块（.kpi-row / .toolbar / .card）
      的默认间距由公共件提供，**不重复声明** —— 否则改公共间距时这里不跟随（双源）。 */
.pms-page .pms-body > * + * {
  margin-top: var(--space-5);
}

/* 页面说明条：一行浅底提示，带信息图标。
   ⚠️ 为什么不用页头「?」面板（`.fin-help`）：那套是**点开才看**的收纳；
      而本板块这几句是"读页面之前就该知道的前提"（如"合同只存链接不存原件"），
      收起来反而让人做出错误预期 —— 与 DESIGN-SPEC 的注释分级一致：
      **必要前提留在页面上、算式与长解释才收进「?」**。
   ⚠️ 配色走 `--color-surface-2` + `--color-muted`（浅底浅字），不新造色。 */
.pms-help {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  background: var(--color-surface-2);
  border-radius: var(--radius-md);
  color: var(--color-muted);
  font-size: var(--font-size-sm);
  line-height: 1.5;
}
.pms-help svg { flex-shrink: 0; margin-top: 2px; }

/* ── ② 表格里的双行单元格 ────────────────────────────────────────────────────
   主文本（品牌名 / 合同编号）+ 次文本（备注 / 合同名）。
   ⚠️ 次文本用 `--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 统一成"软蓝实心"，
      所以行内操作**不能用文字按钮**（会整列蓝块），这也是这里选图标按钮的原因之一。 */
.pms-col-op { white-space: nowrap; }
.pms-rowbtn {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  justify-content: flex-end;
}

/* 表格卡：卡片内表格贴边（与全站列表页一致 —— 表头那行的 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）——
   否则横向滚到右边会不知道这一行是哪个品牌（本项目在宽表上踩过同类问题）。
   ⚠️ 每个单元格固定宽度，不按内容自适应：勾选框宽窄不一会让"哪一列是哪个渠道"
      读起来错位（表头与列必须严格对齐）。 */
.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);
}
.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);
  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; }
.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 保底（见下方）。 */
.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); }
.pms-group__title {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-4);
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-semibold);
  color: var(--color-fg);
}
.pms-group__no {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  border-radius: var(--radius-sm);
  background: var(--color-primary-50);
  color: var(--color-accent-text);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-semibold);
}
/* 跨两列（备注这类整行字段） */
.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 { 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）分工：空态用在"整块内容没有"，这里用在
   "卡片里的一小段说明"——后者画个大空态会把卡片撑得很高（看着像页面坏了）。 */
.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` 本来就够）。 */
.pms-dl {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: var(--space-3) var(--space-6);
  margin: 0;
}
.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); }


/* ==== /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;
}
.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;
}

