/* ============================================================================
   agentcss / 01-tokens / tokens.css
   纸感中性基底 · 第一版

   铁律：这里的每一个颜色都必须带着 05-audit/contrast.py 的实测数字进来。
        凭感觉填的色一律不许入库 —— 第一版就抓出 5 项不达标（见文件末尾"废弃记录"）。

   使用方式：
     1) 现代构建：直接 @import 或复制进你的样式入口
     2) 单文件版：只复制 [核心] 段，其余删掉，可内联进 <style>

   改色流程：改值 -> 跑 python 05-audit/contrast.py -> 把新数字写回注释 -> 才允许提交
   ========================================================================= */

:root {
  /* ================================================================ [L1] 原始值
     ⚠️ **这一层不是给使用者用的**，它是"配色取值"本身。

     换一套品牌配色 = 只改这一块的 30 个值，L2 一行都不用动；
     暗色段也只覆盖这块（见文件末尾的 `@media (prefers-color-scheme: dark)`）。

     🔴 命名带 `--raw-` 是**刻意的**：它在告诉读代码的人
        「别在组件里 `var(--raw-*)`」—— 组件只该用 L2 的语义名。
        `token-audience-gate` 把这一层登记为 internal。

     ℹ️ 边界（别夸大它的能力）：这是"取值层"，**不是**与语义解耦的色阶标尺
        （gray-50 / gray-100 那种）。真正的色阶标尺需要先做一次颜色聚类与命名
        决策，是独立的后续工程（内部排期不在公开面维护，别在这里找编号）。
        本层解决的是"换肤只改一处 + 暗色不可能漏"这两件事。
     ---------------------------------------------------------------- */
  --raw-paper:                #f6f7f8;
  --raw-surface:              #ffffff;
  --raw-surface-sunken:       #f1f3f5;
  --raw-surface-raised:       #ffffff;
  --raw-scrollbar-thumb:      #7A808A;
  --raw-scrollbar-thumb-hov:  #6E747E;
  --raw-text-primary:         #15181c;
  --raw-text-secondary:       #4f555e;
  --raw-text-tertiary:        #606770;
  --raw-text-on-accent:       #ffffff;
  --raw-text-on-solid:        #ffffff;
  --raw-skeleton-bg:          #e9ecef;
  --raw-skeleton-shimmer:     rgba(255, 255, 255, 0.72);
  --raw-border-control:       #868b94;
  --raw-border-decor:         #e2e5e9;
  --raw-border-decor-str:     #c8cdd4;
  --raw-danger:               #c0392b;
  --raw-danger-hover:         #8f1e17;
  --raw-scrim:                rgba(21, 24, 28, 0.4);
  --raw-success:              #2f6b3a;
  --raw-warning:              #8a5a00;
  --raw-info:                 #1d5f8a;
  --raw-danger-bg:            #fdeeec;
  --raw-success-bg:           #eaf2e6;
  --raw-warning-bg:           #fbf1dd;
  --raw-info-bg:              #e8f1f8;
  /* 方向色（涨跌）。当前取值与 danger / success 相同 —— 但那是**巧合**，
     不是同一件事，所以单独命名（理由见语义层那一段）。
     浅色：红 4.89–5.44 · 绿 5.75–5.96，四组底全部 AA。 */
  --raw-dir-up:               #c0392b;
  --raw-dir-down:             #2f6b3a;
  --raw-accent:               #1b4d8f;
  --raw-switch-on:            #1b4d8f;
  --raw-accent-hover:         #163a6d;
  --raw-accent-soft:          #e6eef7;

  /* 气泡的一对"反色面"：浅色深底浅字，暗色换成抬高面。
     独立成令牌是为了让组件不再按 `prefers-color-scheme` 分支（RFC-THEME-SCOPE D5）。
     实测 16.60:1（浅）/ 11.93:1（暗）。border 浅色与气泡底同色（不可见），
     不用 `transparent` 是因为令牌树会把它归成"非颜色"。 */
  --raw-tooltip-bg:           #15181c;
  --raw-tooltip-fg:           #f6f7f8;
  --raw-tooltip-border:       #15181c;

  /* ---------------------------------------------------------------- 基底
     paper  = 页面底（冷灰白）。🔴 不能更浅：与 --surface 的对比度要 >= 1.06:1，
             否则页面看着就是纯白，"表面浮在底上"的分层完全消失。
     surface= 卡片/面板底，比纸底亮一档，靠明度差而非描边来分层
     ---------------------------------------------------------------- */
  --paper:            var(--raw-paper);
  --surface:          var(--raw-surface);
  --surface-sunken:   var(--raw-surface-sunken);
  /* 🔴 浮层底（下拉/气泡/抽屉面板）。
     这条**原本只在暗色段定义** ⇒ 浅色下 6 个组件（combobox/popover/select/
     dropdown/tooltip/table）的 `background` 整条失效 ⇒ 实测**完全透明**。
     浅色值 = --surface：浅色抬高靠阴影（--shadow-*），暗色才靠"面更亮"。 */
  --surface-raised:   var(--raw-surface-raised);
/* ============================================================================
   🔴 滚动条令牌（判据：WCAG 2.5.5 Target Size）
   ----------------------------------------------------------------------------
   ⭐ 依据（5 个权威来源口径一致）：
     · CSS Scrollbars Level 1（W3C）—— scrollbar-width / color / gutter
     · ::-webkit-scrollbar 伪元素族 —— Chromium/WebKit 专属，
       **唯一能给 thumb 加圆角与 hover 的方式**
     · WCAG 1.4.11 非文本对比 —— thumb 对 track **≥ 3:1**

   ⚠️ 为什么滚动条要**降彩度**、用灰色而不是品牌色：
     滚动条是**次要**信息（"还有多少内容"），不该抢注意力。
     行业惯例（Radix / shadcn / GitHub）都是中性灰。

   ⚠️ 为什么 track 透明：轨道代表"没有内容的区域"，
     画出来等于视觉噪声。
   ==========================================================================*/
  --scrollbar-size:      10px;      /* 桌面宽度；移动端由浏览器接管 */
  --scrollbar-size-thin: 6px;       /* 代码块等次要场景 */
  /* 🔴 色值是**脚本反解**出来的（WCAG 1.4.11 要求 thumb 对 track ≥3:1），
     不是"看着差不多"挑的：
       #7a808a 对 #ffffff 3.98:1 · 对 #f6f7f8 3.71:1  ✅
     ⚠️ 第一版我随手挑的 #B4BAC4 只有 1.82:1 ⇒ 门禁直接判不合格。 */
  --scrollbar-thumb:     var(--raw-scrollbar-thumb);   /* 中性**冷**灰（非品牌色，且保持蓝调）*/
  --scrollbar-thumb-hov: var(--raw-scrollbar-thumb-hov);   /* hover 更深一档 · 4.06:1 */
  --scrollbar-radius:    5px;       /* = size/2 ⇒ 胶囊形 */
   /* 内凹区：代码块、表格头、禁用底 */

  /* ---------------------------------------------------------------- 文字
     三档够用了。超过三档说明信息层级没想清楚，不是 token 不够。
     全部数字由 05-audit/contrast.py 从本文件实测得出，不是手写的。
     改任何颜色后必须重跑该脚本 —— 手写注释必然漂移（已发生过）。
     ---------------------------------------------------------------- */
  --text-primary:     var(--raw-text-primary); /* 16.60 纸底 / 17.81 表面 · AAA */
  --text-secondary:   var(--raw-text-secondary); /* 7.01 AAA */
  /* 🔴 由 #6b7280 调深到 #606770。
     axe-core 实测发现：#6b7280 压在**语义底色**上只有 4.13:1
     （4.5 是正文门槛）—— 它只在白底上勉强过线（4.83），
     一放到 --surface-sunken / --warning-bg 等底上就不达标。
     调深后最低 4.89（压在最亮的 --surface 上），留了 0.39 余量。
     梯度仍在：primary 17.8 / secondary 7.5 / tertiary 4.9 —— 明显分层。 */
  --text-tertiary:    var(--raw-text-tertiary);   /* 5.33 纸底 / 4.89 最亮底(surface) · AA */ /* 4.51 AA */
  --text-on-accent:   var(--raw-text-on-accent); /* 8.38 压在 accent 上（不是压纸底）· AAA */

  /* 🔴 新增（ H9 期间由暗色门禁逼出来）：
     「实心态」组件（filled badge 等）压在**语义色实底**上的文字色。
     ⚠️ 为什么不能复用 --text-on-accent：
        暗色下 --success / --danger / --warning 都**变浅**了（为在深底上更醒目），
        实心态的底色也随之变浅 ⇒ 白字压上去只有 2.72:1。
        ⇒ 需要一个**独立令牌**，让暗色能把它换成深字。 */
  --text-on-solid:    var(--raw-text-on-solid); /* 亮色：实底（深色语义色）⇒ 白字 */
  /* ---------------------------------------------------------------- 骨架屏
     🔴 新增（对齐通行组件库的基础档位）
        加载态是移动端最高频的状态；没有它，每个业务方都会自己造一个。 */
  --skeleton-bg:       var(--raw-skeleton-bg);   /* 骨架底色：比纸面略深一档 */
  --skeleton-shimmer:   var(--raw-skeleton-shimmer);  /* 微光高光 */
  --skeleton-h:         1em;       /* 单行默认高度 */
  --skeleton-size:      40px;      /* circle 默认直径 */
  --skeleton-duration:  1.4s;      /* 微光扫过周期 */
  /* ---------------------------------------------------------------- 进度条
     🔴 新增（ I5）。组件私有参数集中在这里定义，
        而不是散在 CSS 里用 fallback —— 否则 deps 门禁会报"未定义令牌"。
        ⭐ --ease-out 刻意独立命名：进度条要"向前"的 feel，
          匀速（linear）反而像卡住了。 */
  --ease-out:        cubic-bezier(0.23, 1, 0.32, 1);
  --progress-h:      6px;      /* 线形默认高度 */
  --progress-dur:    240ms;    /* 宽度过渡时长 */
  --r-full:          999px;    /* 胶囊形圆角 */
  --ring-size:       64px;     /* 环形默认直径 */
  --ring-thickness:  8px;      /* 环形环宽 */
  /* 进度条的当前值。默认 0% 是**正确默认**（不是占位）——
     实例用 JS setProperty 覆盖它（见 progress demo）。
     ⚠️ 必须有默认值，否则"忘了设值"会渲染成 0% 且**没人发现**。 */
  --progress:        0%;
  /* 环形用**无单位**数值（conic-gradient 的角度计算不接受带 % 的长度）。
     ⚠️ 这就是环形曾完全不显示的原因，别再改成 --progress。 */
  --progress-num:    0;

  /* ---------------------------------------------------------------- 气泡卡片
     🔴 新增（ I4）。默认值是**正确默认**，
        不是占位 —— 没定义就会静默用 0（deps 门禁会报未定义）。 */
  --z-popover:        60;       /* 层级：高于导航，低于 dialog */
  --popover-min-w:    200px;
  --popover-max-w:    320px;
  --popover-p:        var(--sp-3);
  --popover-gap:      8px;       /* 浮层与触发器的距离 */
  --popover-dur:      140ms;

  /* ---------------------------------------------------------------- 边框
     🔴 本库最容易被做错的一处，两类不许合并：

     control = 控件边界（输入框、按钮的轮廓）。WCAG 1.4.11 要求 >= 3.0，
               因为它是"识别出这是个控件"的唯一线索。必须达标。
     decor   = 纯装饰分隔（卡片内线、表格行线）。WCAG 不强制。
               对它强求 3.0 会把界面做得很重很难看 —— 那是误读规范。
     ---------------------------------------------------------------- */
  --border-control:   var(--raw-border-control); /* 3.19 / 3.42 AA Large */
  --border-decor:     var(--raw-border-decor); /* 1.18 纯装饰，WCAG 不强制 */
  --border-decor-str: var(--raw-border-decor-str); /* 1.49 装饰，需要更强分隔时用 */
  /* 纸面 hairline（图框、需要"一条实线"的细线）。
     屏幕上它等于正文色 —— 屏幕上没有"半色调"这回事，1px 就是 1px 实线。
     `@media print` 里重定义为 `#000`：纸面上 ≤0.5pt 的线必须 100% 覆盖，
     否则会被网点屏打成断续的虚边（见 docs/PRINT.md 5.2.2）。 */
  --rule-hair:        var(--raw-text-primary);

  /* ---------------------------------------------------------------- 语义色
     全部实测 >= 5.6 AA（vs 纸底）。可当文字用，也可当底色用。
     ---------------------------------------------------------------- */
  --danger:           var(--raw-danger);
  --danger-hover:     var(--raw-danger-hover);   /* 危险按钮 hover：加深（浅色下加深才对）*/
  --scrim:            var(--raw-scrim);   /* 抽屉/弹窗遮罩 */ /* 5.07 AA */
  --success:          var(--raw-success); /* 5.96 vs 纸底 · AA */
  --warning:          var(--raw-warning); /* 5.53 vs 纸底 · AA */
  --info:             var(--raw-info); /* 6.41 vs 纸底 · AA */

  /* 语义色的浅底（用于提示条背景，不放正文） */
  --danger-bg:        var(--raw-danger-bg);
  --success-bg:       var(--raw-success-bg);
  --warning-bg:       var(--raw-warning-bg);
  --info-bg:          var(--raw-info-bg);

  /* ---------------------------------------------------------------- 方向色
     🔴 为什么方向要独立成令牌，而不是复用 --danger / --success
     ----------------------------------------------------------------
     这两组读起来都是"红/绿"，但它们是**两件事**：

       --danger / --success  = 好恶（错误/成功）。这是**评价**。
       --dir-up / --dir-down = 涨跌（方向）。这是**事实**。

     把两件事共用一个名字，会在这个场景里立刻暴露：
       「研发费用同比 +38%」—— 是"涨"，但那是**坏消息**。
       用 --danger 表示它，颜色说着"错了"，语义说着"涨了"，读者收到矛盾信号。

     所以方向色单独命名，取值可以与语义色相同（当前就相同），
     但**允许将来分开调**：金融终端普遍用比 --danger 更饱和的红。

     🔴 口径可整体切换（不要在每个组件里判断）
     ----------------------------------------------------------------
     默认是 A 股 / 东亚口径：**红涨绿跌**。
     给 `<html data-market="intl">`（或任意祖先）就切成国际口径：绿涨红跌。

     实现方式与暗色同构 —— **换掉底层值，语义名不动**：
       --dir-up 永远表示"涨"，组件的 CSS 只写 `color: var(--dir-up)`。
     口径切换只改这两条令牌的指向，组件一行都不用改。
     组件里若出现 `[data-market]` 分支，那是把全局约定抄进了组件（判据会拦）。

     实测对比度（05-audit/contrast.py，四组底各算一遍）：
       浅色：红 4.89–5.44 · 绿 5.75–5.96   （全部 AA）
       暗色：红 5.60–6.15 · 绿 6.01–6.60   （全部 AA）
     最差的一格是红点在 --surface-sunken 上 4.89 —— 仍 ≥ 4.5。
     ---------------------------------------------------------------- */
  --dir-up:           var(--raw-dir-up);     /* 涨：默认红 */
  --dir-down:         var(--raw-dir-down);   /* 跌：默认绿 */

  /* ⚠️ 口径切换的规则在 `:root` **外面**（见本块结束后的「国际口径」）。
     第一版写在里面 —— `:root { }` 只能装声明，不能装规则块。
     theme-sync.py 按「声明序列」解析这个块，遇到嵌套规则会把选择器切碎，
     生成一段无效 CSS（实测：`:where(...)` 被截成 `where(...)`、第二条声明丢失）。
     这类嵌套即使浏览器支持 CSS Nesting 也不该写：本库要兼容 Chromium 97。 */

  /* ---------------------------------------------------------------- 强调色
     🔴 一源原则：一个项目只选一个 accent，不要混用。
     accent 决定"可点击"的语义，混用等于取消了这层语义。
     三选一，把另外两个删掉，别全留着。
     ---------------------------------------------------------------- */
  --accent:           var(--raw-accent);
  /* 开关开态轨道色（浅色版）：与品牌色一致；暗色下换降彩度值（见 dark 段）。
     不用 var(--accent) —— 暗色下 accent 太亮，铺满一大块会发光。
     ⚠️ 这段注释原本**缺开头**，导致 --switch-on 被浏览器当成坏声明丢掉
        （实测浅色下取值为空）。已由 token-parse-gate.py 兜住。 */
  --switch-on:         var(--raw-switch-on); /* 7.82 AAA */
  --accent-hover:     var(--raw-accent-hover); /* 11.29 白字压上 · AAA */
  --accent-soft:      var(--raw-accent-soft);   /* focus ring / 浅底，不要拿它放正文 */

  /* 气泡（见 L1 的说明） */
  --tooltip-bg:       var(--raw-tooltip-bg);
  --tooltip-fg:       var(--raw-tooltip-fg);
  --tooltip-border:   var(--raw-tooltip-border);
  /* --accent:        #0f7a6c;   薄荷绿 5.00  —— 活泼/工具类 */
  /* --accent-hover:  #0c6459;   白字 7.04 AAA */
  /* --accent-soft:   #dff1ec; */
  /* --accent:        #185fa5;   蓝     6.25  —— 严肃/数据类 */

  /* ================================================================ [核心]
     间距：8px 网格为基准，大值流体。
     想要的值不在表里 => 说明布局没规划，改设计，不要加 token。

     🔴 哪些该流体：--sp-4 往上（区块间距，随屏幕变大而变大，观感自然）
        哪些不该：--sp-1..3（元素内部间距，流体化会让控件尺寸忽大忽小）
     ---------------------------------------------------------------- */
  --sp-1:  4px;
  --sp-2:  8px;
  --sp-3:  12px;
  --sp-4:  clamp(1rem,     0.95rem + 0.3vw,  1.25rem);    /* 16 → 20px */
  --sp-5:  clamp(1.25rem,  1.15rem + 0.7vw,  1.75rem);    /* 20 → 28px */
  --sp-6:  clamp(1.5rem,   1.3rem  + 1.2vw,  2.25rem);    /* 24 → 36px */
  --sp-7:  clamp(2rem,     1.7rem  + 1.8vw,  3.5rem);     /* 32 → 56px */
  --sp-8:  clamp(2.5rem,   2.1rem  + 2.4vw,  4.5rem);     /* 40 → 72px */

  /* ================================================================ [核心]
     断点令牌
     ----------------------------------------------------------------
     🔴 CSS 变量**不能**用在 media query 条件里。
        所以下面四个只是"文档与计算的单一出处"，写 media query 时仍要写字面值：

          @media (min-width: 48rem) { ... }   ← 只能写字面值，不能写 var(--bp-md)

        留它们的理由：算流体值、给脚本查、以及让"我们有几个断点"这件事有唯一答案。
        ⚠️ 断点用的是 rem 不是 px —— 用户调大字号时断点跟着变，这才是对的。
     ---------------------------------------------------------------- */
  --bp-sm: 30rem;   /* 480px  大手机 / 小平板 */
  --bp-md: 48rem;   /* 768px  平板 */
  --bp-lg: 64rem;   /* 1024px 小笔记本 */
  --bp-xl: 80rem;   /* 1280px 桌面 */

  /* ================================================================ [核心]
     字号阶梯
     ----------------------------------------------------------------
     🔴 修正：上下限从 px 改成 rem。
        px 上下限不尊重"用户把浏览器默认字号调大"的设置 ——
        那样 clamp 的下限会锁死，用户放到 200% 也涨不上去。
        rem 基准跟着用户设置走，这是无障碍底线。

     🔴 哪些该流体、哪些不该（这条更容易做错）：

        该流体：正文、小标题、大标题、间距
           —— 它们随屏幕变大而变大，观感自然

        **不该流体**：UI 文本（--fs-xs / --fs-sm）、小间距（--sp-1..3）
           —— 它们是控件尺寸的一部分。流体会让按钮和输入框在不同屏幕下
              大小不一，布局抖动。搜索到的原话：
              "UI typography benefits from minimal scaling.
               Too much fluidity breaks layout."

     公式：clamp(MIN-REM, REM基准 + VW系数, MAX-REM)
       VW 系数怎么来：先定 min/max 视口（375px / 1280px），
       系数 ≈ (max-rem - min-rem) × 100 / (1280 - 375) ≈ 差值 × 0.1105

     ✅ WCAG 1.4.4 判据：只要 MAX ≤ 2.5 × MIN，就一定通过 resize-text 检查。
        本阶梯最大比值 2.5rem/1.75rem = 1.43，安全。
     ---------------------------------------------------------------- */
  --fs-xs:   0.75rem;                      /* 12px 固定：辅助文字不流体 */
  --fs-sm:   0.875rem;                     /* 14px 固定：UI 文本不流体 */
  --fs-base: clamp(1rem,       0.95rem + 0.3vw,  1.125rem);   /* 16 → 18px */
  --fs-md:   clamp(1.125rem,   1.05rem + 0.5vw,  1.3125rem);  /* 18 → 21px */
  --fs-lg:   clamp(1.3125rem,  1.2rem  + 0.7vw,  1.5rem);     /* 21 → 24px */
  --fs-xl:   clamp(1.5rem,     1.3rem  + 1.2vw,  1.875rem);   /* 24 → 30px */
  --fs-2xl:  clamp(1.75rem,    1.45rem + 1.8vw,  2.5rem);     /* 28 → 40px */

  /* 行高：正文 1.7 是中文长文的舒适值（西文常用 1.5，中文需要更松） */
  --lh-tight:  1.25;   /* 标题 */
  --lh-base:   1.7;    /* 正文 */
  --lh-loose:  1.85;   /* 长段落、低龄/无障碍场景 */

  /* 行长：65ch 左右是阅读上限。超过就要分栏或收窄容器。
     ⚠️ 这里曾经有一个裸的 `--measure: 68ch`，0.7.3 移除 —— 它既没被任何组件
        引用、也没在任何文档里出现过，而 `--measure-page`（页面列宽）与
        `--measure-prose`（长文列宽）已经把它该管的两个场景分掉了。
        不要再把它加回来：`token-audience-gate.py` 判据 ① 会拦住没登记受众的令牌。
        ⇒ 版心请用下面这两个带语义后缀的。 */

  /* ----------------------------------------------------------------
     🔴 补：页面列宽 --measure-page（0.4.2）
     此前 33 个页面各自写 `.wrap { max-width: 680px / 720 / 760 / 800 / 820 /
     860 / 900 / 42rem / 44rem / 46rem / 52rem … }` —— 16 种写法。
     结果就是「没有全局可改」：想改列宽要改 33 处，漏一处就不一致。
     实测众数是 820px（8/33 页），取 52rem = 832px（干净的 rem 值，
     且 rem 会跟随用户字号设置，px 不会）。
     改这一个令牌 ⇒ 全部页面列宽一起变。
     ---------------------------------------------------------------- */
  --measure-page: 52rem;

  /* 🔴 补：标题字距 --tracking-title（0.4.2）
     大字号要收紧字距，否则标题散。此前只有 list/nav/states 三页写了
     `letter-spacing: -0.02em`（写死值），其余 33 页没有 ⇒ 标题观感不一致。 */
  --tracking-title: -0.02em;

  /* ---------------------------------------------------------------- 圆角
     一套就够。圆角混搭（卡片 20px、按钮 4px）是典型的没想清楚。
     ---------------------------------------------------------------- */
  --r-sm:    6px;
  /* 🔴 补：内嵌小元素用的小圆角（菜单项、标签、chip）。
     之前 dropdown.css 里凭空写了 var(--r-xs, 3px) —— 令牌不存在时
     CSS 变量会**静默失效**（整条声明被丢弃），所以一直没被发现。
     ⇒ 补上这个令牌（deps 门禁抓到）。 */
  --r-xs:    3px;
  --r-md:   10px;
  --r-lg:   14px;
  --r-pill: 999px;

  /* ---------------------------------------------------------------- 阴影
     两级。第三级阴影出现的时候，通常说明层级设计出了问题。
     y 轴偏移小、模糊大 —— 模拟纸面浮起，不是悬浮在太空。
     ---------------------------------------------------------------- */
  --shadow-1: 0 1px 2px rgba(31, 29, 25, 0.05), 0 1px 3px rgba(31, 29, 25, 0.06);
  --shadow-2: 0 2px 4px rgba(31, 29, 25, 0.05), 0 6px 16px rgba(31, 29, 25, 0.08);

  /* ---------------------------------------------------------------- 动效
     🔴 一源原则：全项目只用这一组时长和缓动。
     六个地方来的六个效果 => 六个不同的缓动曲线 => 看起来像没设计过。
     ---------------------------------------------------------------- */
  --dur-fast:  120ms;   /* 颜色、透明度 */
  --dur-base:  180ms;   /* 位移、展开 */
  --dur-slow:  280ms;   /* 大块入场、遮罩 */

  --ease-out:    cubic-bezier(0.23, 1, 0.32, 1);      /* UI 默认：入场、展开 */
  --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);    /* 屏上位移、折叠 */
  --ease-drawer: cubic-bezier(0.32, 0.72, 0, 1);     /* 抽屉式滑入（iOS 观感）*/

  /* 🔴 修正：上一版这三个曲线是**我自编**的
     （0.22,1,0.36,1 / 0.65,0,0.35,1），现已换成业界通用值 ——
     与 Tailwind v4 默认、Emil Kowalski 技能库一致。
     **曲线不该自己编**：自编的曲线没有经过大量实现验证，
     阻尼比、重心位置都是猜的，跨浏览器表现不一致。 */

  /* 位移距离：动效的"幅度"也要统一，不然同样 180ms 也会感觉乱 */
  --lift-sm: 1px;
  --lift-md: 2px;
  --lift-lg: 8px;

  /* ---------------------------------------------------------------- z-index
     乱写 z-index（999、9999）是前端最常见的失控点。
     只用这六个值。需要更高的 => 说明层级模型有问题。
     ---------------------------------------------------------------- */
  --z-base:    0;
  --z-sticky: 10;
  --z-header: 20;
  /* 🔴 补回：上一轮"清理重复 z 令牌"时把它误删了，
     而 card / pagination 都在用 ⇒ 它们的 z-index 声明**静默失效**。
     ⚠️ CSS 变量不存在时**不报错**，声明直接被丢弃 —— 这类问题只能靠门禁抓。 */
  --z-raised:  5;   /* 浮起层（卡片悬停、下拉、内层可点元素）*/
  --z-overlay: 30;
  --z-modal:  40;
  --z-toast:  50;

  /* ------------------------------------------------------------
     命中区尺寸

     之前全库到处硬写 `44px`（20 处，各组件各写一遍）⇒ 改一次要翻遍全库。

     为什么是 44px：**WCAG 2.5.5 Target Size（Level AAA）** 要求 44×44 CSS px。
     ⚠️ 别混淆：WCAG 2.5.8（AA 级）只要 24×24；本库按 AAA 做。
     ------------------------------------------------------------ */
  --sz-touch:    44px;   /* 最小命中区（指针）*/
  --sz-touch-sm: 32px;   /* 次要场景（表格行内等密排）*/

  /* ==========================================================================
     🔴 补：跳过导航链接（skip link）
     --------------------------------------------------------------------------
     a11y-scan 报了两条违规（都因为入口页没有地标）：
       · landmark-one-main —— 文档应有一个 <main>
       · region           —— 所有内容应被地标包裹
     ⇒ 修法：入口页包 <main id="main">，并提供"跳到主内容"的链接。

     ⭐ 为什么要 skip link：
     键盘用户每次进页面都要 Tab 过几十个导航链接才能到正文。
     skip link 让它们**按一次 Tab 就能跳过**（第一次 Tab 就会出现，
     聚焦时才显形）—— 这是 WCAG 2.4.1 Bypass Blocks 的标准做法。
     ========================================================================== */
}

/* ============================================================================
   容器查询的**槽位标记**（0.16.0）

   `[data-cq]` 让一个元素成为"查询容器"：里面的组件按**自己所在的槽宽**
   自适应，而不是按视口宽度。`card--row` 与 `analysis-report` 已用它切换布局。

   🔴 为什么是显式属性，而不是给组件自动加 `container-type`
     ① `container-type: inline-size` 会带上 `contain: layout style inline-size`
        ⇒ 元素变成 fixed / absolute 后代的**包含块**。
        若给 `.page` 或抽屉的宿主自动加上，抽屉（position: fixed）
        会被关进那个盒子里 —— 静默的、只在特定嵌套下出现的破坏。
     ② 库不知道"槽"在哪：同一张卡片既可能在 320px 侧栏，也可能在 900px 主列。
        只有页面作者知道该把哪一层声明成容器。

   ⇒ 契约：**加了 `data-cq` 的那一层，就是组件眼里的"窗口"**。
     不加 ⇒ 整条容器查询不生效（实测：没有容器时 `@container` **不会**退回视口），
     组件仍按视口断点工作 —— 与 0.15.0 完全一致。

   ⚠️ 不要把它加在会承载抽屉 / 全屏浮层的祖先上（理由见 ①）。
      它下面可以放 popover / dropdown（那些用 absolute，不受影响）。

   ⚠️ 为什么在 tokens.css 而不是 page.css：tokens.css 是**唯一必须引入**的文件
      （"只拿一个组件"的最小页面也带着它）。放在 page.css 里，
      按需取用的页面会拿到一份永远不生效的 `@container`。
   ========================================================================= */
[data-cq] {
  container-type: inline-size;
}

/* ============================================================================
   无障碍兜底 —— 这一段必须随 token 一起走，不许删
   ========================================================================= */

/* 尊重系统的"减少动效"设置。评委、无障碍用户、晕动症人群会开这个。
   不写这段，前面所有动效在那些机器上就是灾难。

   🔴 这是**兜底**，不是全部策略（0.9.1 起）：
      ① 位移 / 缩放 / 旋转 —— 关掉（前庭不适的主要来源）
      ② 真实进度（spinner / 加载中）—— **保留**，见 ai/motion-policy.json。
         停掉进度指示会让用户以为页面卡死，比有动画更糟。
      ③ 颜色 / 透明度 —— 允许保留但缩短到 --dur-fast（不强制，也无判据）
      默认关、例外要写理由并由 `motion.py --reduced-audit` 核对：
      声明了却没实现 ⇒ 红；实现了却没声明 ⇒ 也红。
   本段的 `*` 规则必须保留 —— 它是①②③共同的底线，
   组件要保留动画时用**更高优先级 + !important** 覆盖它（库里三处都这么做）。 */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* 键盘焦点环。用 :focus-visible 而不是 :focus ——
   前者只在键盘导航时显示，鼠标点击不会冒出难看的框。
   🔴 只写 outline: none 而不给替代焦点指示，是无障碍事故。 */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}

/* ============================================================================
   废弃记录 —— 保留在这里是为了不再犯同样的错
   =========================================================================
   第一版自检抓出 5 项不达标，全部是"凭感觉填的"：

   text-tertiary   #8a8478  vs 纸底 = 3.56  → 正文需 4.5，不达标
                            vs 白面 = 3.72  → 同样不达标
                   改 #6f6a5e（5.16 / 5.39）
                   教训：辅助文字色最容易填浅，因为它"看起来就该浅"。

   border          #cfc9bb  vs 纸底 = 1.58  → UI 边界需 3.0，不达标
                   改 #8a8478（3.56）
                   但：装饰分隔线不需要 3.0，硬套会让界面变重
                   → 因此把边框拆成 control / decor 两类，这是第一版最大的收获。
   ========================================================================= */

/* ============================================================================
   i18n：度量令牌（行长控制）
   ============================================================================
   🔴 为什么需要这一节

   中文排版用 `em` 限宽看起来"完美"，**但那是巧合**：1em = 1 个汉字宽。
   换到英文，同样的 `max-width: 14em` 只能放约 20 个字符 ——
   实测 84 字符的英文标题会折成 **4 行**，这是坏的排版。

   `ch` 也不对：ch = "0" 的宽度，**1 汉字 ≈ 2ch**，
   所以 13ch 只能放 6 个汉字（曾见过标题被挤成三行）。

   ⇒ 结论：**没有任何单一单位能同时伺候所有语言**，必须按语言切换。

   用法：组件用 `var(--measure-*)`，不写死单位。

       h1 { max-width: var(--measure-title); }
       p  { max-width: var(--measure-prose); }

   ── 各语言的取值依据 ──────────────────────────────────────────────
   | 语言        | 计量      | 正文值 | 舒适区        |
   |-------------|-----------|--------|---------------|
   | 中文/日韩   | em（1em=1字）| 34em | 25-35 字     |
   | 拉丁/西里尔 | ch（1ch≈1字）| 68ch | 60-75 字符   |
   | 阿拉伯      | ch        | 60ch   | 同上，且 RTL |
   | 印地/泰文   | ch        | 60ch   | 连字会让 ch 偏小，取小值更安全 |

   `:lang()` 选的是**内容语言**，不是界面语言 —— 这才是对的。
   ========================================================================= */

:root {
  /* 正文行长：中文 34 字 ≈ 拉丁 68 字符，两边都落在各自的舒适区 */
  --measure-prose:   34em;
  --measure-title:   20em;   /* 标题比正文窄，避免大字长时间扫视 */
  --measure-lede:    32em;   /* 导语 */
  --measure-pull:    30em;   /* 引言块 */
  --measure-aside:   32em;   /* 边注 */
}

/* 拉丁 / 西里尔 / 希腊：改用 ch（1ch ≈ 1 个字符宽） */
:root:lang(en), :root:lang(en-US), :root:lang(en-GB),
:root:lang(fr), :root:lang(de), :root:lang(es), :root:lang(pt),
:root:lang(ru), :root:lang(el), :root:lang(tr), :root:lang(vi) {
  --measure-prose:   68ch;
  --measure-title:   34ch;
  --measure-lede:    62ch;
  --measure-pull:    58ch;
  --measure-aside:   56ch;
}

/* 阿拉伯语：RTL，行长同样按 ch */
:root:lang(ar) {
  --measure-prose:   60ch;
  --measure-title:   30ch;
  --measure-lede:    56ch;
  --measure-pull:    52ch;
  --measure-aside:   50ch;
}

/* 印地 / 泰文 / 高棉 / 缅甸：连字与组合字形会让 ch 偏小，取更保守的值 */
:root:lang(hi), :root:lang(th), :root:lang(km), :root:lang(my) {
  --measure-prose:   56ch;
  --measure-title:   28ch;
  --measure-lede:    52ch;
  --measure-pull:    48ch;
  --measure-aside:   46ch;
}

/* 🔴 兜底：未知语言（`lang` 缺失或写了别的）走 ch 而不是 em ——
   拉丁字母是更常见的假设；而且 ch 在等宽字体下也准。
   中文/日韩显式命中上面的 em 分支（:root 默认值），不会落到这里。 */
:root:lang(zh), :root:lang(zh-CN), :root:lang(zh-TW),
:root:lang(ja), :root:lang(ko) {
  --measure-prose:   34em;
  --measure-title:   20em;
  --measure-lede:    32em;
  --measure-pull:    30em;
  --measure-aside:   32em;
}

/* ============================================================================
   暗色模式（prefers-color-scheme: dark）
   ============================================================================
   🔴 建立。**下面每个值都是按 WCAG 公式算出来的，不是"反过来"。**

   ── 为什么不能把浅色反过来 ────────────────────────────────────
   1. **纯黑底会让彩色文字发灰**（视觉上对比度下降）⇒ paper 用 #14171a 不是 #000
   2. **明度关系要重排**：浅色是"底浅→字深"，暗色是"底深→字亮"，
      但**中间灰阶的分布完全不同** —— 浅色的 #6b7280（tertiary）
      在深底上完全不够用（只有 2.1:1）
   3. **饱和色要降**：#c0392b 在深底上刺眼且显脏 ⇒ 提到 #e07a6e
   4. **强调色方向相反**：浅色用深靛蓝（沉），暗色必须**变亮**才看得见
      ⇒ --accent 从 #1b4d8f 变成 #7aa9de。**这不是"反色"能得出的。**
   5. **按钮上的文字要翻过来**：浅色按钮是"蓝底白字"，
      暗色按钮变亮 ⇒ 必须改成"**亮底深字**"（--text-on-accent: #0d1114）

   ── 实测数据（WCAG 相对亮度公式算的）──────────────────────────
   | 令牌 | 暗色值 | 在 paper 上的对比度 | 要求 |
   |---|---|---|---|
   | --text-primary   | #e8eaed | 14.93 | 4.5 ✅ |
   | --text-secondary | #b8bec6 |  9.61 | 4.5 ✅ |
   | --text-tertiary  | #959ba3 |  6.42 | 4.5 ✅ |
   | --danger         | #e07a6e |  6.15 | 4.5 ✅ |
   | --success        | #6faa7a |  6.60 | 4.5 ✅ |
   | --warning        | #c99a3f |  7.01 | 4.5 ✅ |
   | --info           | #5b9bc4 |  5.94 | 4.5 ✅ |
   | --accent         | #7aa9de |  7.33 | 4.5 ✅ |
   | --text-on-accent | #0d1114 |  7.73（压 accent 上）| 4.5 ✅ |
   | --border-control | #7d848d |  4.76 | 3.0 ✅ |

   ⚠️ **--text-primary 故意不用最高对比的那个**（#f2f4f6 = 16.32:1）。
   暗色下过高的对比会**发光、刺眼**，长时间阅读更累。
   **过线即可，不是越高越好。**

   ── 底色三层（明度差必须够，否则分层直接消失）──────────────────
   | 层 | 值 | 与 paper 的对比 |
   |---|---|---|
   | sunken（内凹）| #080a0c | 1.10 ✅ |
   | paper（页面底）| #14171a | — |
   | surface（卡片）| #1c2024 | 1.10 ✅ |

   🔴 我第一版把 sunken 设成 #0f1214，与 paper 只差 **1.05** ——
   **卡片和页面底几乎分不开**，这正是本库在浅色下踩过的坑
   （--paper 曾经是 #fcfcfd，与白色只差 1.025）。**同样的错不能犯两次。**

   ── 为什么用 @media 而不是 light-dark() ──────────────────────
   `light-dark()` 是 2024 的新函数，**精简档 的老 WebView 不支持**。
   用 @media 是目前兼容性最好的做法。
   ---------------------------------------------------------------- */

/* `screen and` 承重：暗色是屏幕概念，纸没有深色模式。
   缺了它，暗色令牌在 print 里照样生效 ⇒ 浅字配被丢弃的深底。 */
@media screen and (prefers-color-scheme: dark) {
  /* ========================================================================
     第二次修正（上一版被 真机实测否决）
     ------------------------------------------------------------------------
     反馈：「小方框变成了咖啡色」「背景变成了淡白色」
                「主业是白色兼灰色有高级感，现在变成了暖色」

     前一版的三个问题：
       1 引入暖灰（#E8E6E3 / #1E1E1E）—— 反馈像咖啡色
       2 --paper 被写成别处覆盖的值 —— 结果 dark 下背景是淡白
       3 🔴 注释里写了 `#080a0c` 和中文分号
          ⇒ 被 gen-theme-js.py 的 `[^;]+` 当成**属性值**
          ⇒ --paper 整个丢失 ⇒ 切暗色时背景不变（实测确认）

     ⭐ 本次原则：**只修该修的，色相一律保持冷灰**
        主业的"高级感"来自冷灰（白+灰），不是暖灰 —— 这是 最终定的调。

     保留三条业界共识（不改变色相）：
       1 层级靠"表面变亮"：暗色下阴影不可见，抬高面必须更亮
         原来的 --surface-sunken 比 paper 还深，方向是错的
       2 正文不用纯白：纯白在深底上会 halation（字发散）
       3 tertiary 必须够 4.5:1：用脚本算，不靠眼睛调
     ======================================================================== */

  /* 冷灰基座 -- 不用纯黑（OLED 拖影），不用暖灰（显廉价） */
  /* ⚠️ `:not([data-theme="light"])` —— 让「强制亮」能压过系统暗色。
     不加这层 `:not`：系统暗色 + data-theme="light" 时，本段仍会命中
     ⇒ 使用者点了「亮」却还是暗的（三态开关里最容易被忽略的一条路径）。
     ⇒ 强制暗由下面生成的 `[data-theme="dark"]` 段负责，两边互不干扰。 */
:root:not([data-theme="light"]) {
  --raw-paper:           #14171A;
  --raw-surface:         #1C2024;
  --raw-surface-raised:  #232830;
  /* 凹陷面不再更黑，改为比 paper 略浅（层级靠变亮） */
  --raw-surface-sunken:  #1A1D21;

  /* 🔴滚动条（暗色）
     ⚠️ 暗色下 thumb 要比浅色**更亮**（#4A515C）才看得见；
        用浅色那档会变成"黑里有黑"。 */
  --raw-scrollbar-thumb:     #6E747E;   /* 对 #14171a 3.82:1 · 对 #232830 3.15:1 ✅ */
  --raw-scrollbar-thumb-hov: #7D828A;   /* 4.24:1 */

  /* 边框 -- 冷灰，靠变亮分层 */
  --raw-border-decor:     #2A3038;
  --raw-border-decor-str: #3A424C;
  --raw-border-control:   #8A9099;

  /* 文本 -- 冷调白，非纯白（避免 halation） */
  --raw-text-primary:    #E4E7EA;
  --raw-text-secondary:  #AEB6C0;
  /* 实算 原来的 #959ba3 对 paper 只有 4.14:1 不达 AA，提到这个值 */
  --raw-text-tertiary:   #9BA3AD;
  --raw-text-on-accent:  #0D1114;
  --raw-text-on-solid:   #0D1114;   /* 暗色：success/danger/warning 都变浅了 ⇒ 实心态换深字 */
  /* 暗色：底色比纸面**略浅**（深底上"更亮"才看得出是占位） */
  --raw-skeleton-bg:       #2E343C;   /* 亮度差 0.032（契约要求 >0.02）*/
  --raw-skeleton-shimmer:   rgba(255, 255, 255, 0.06);

  /* 语义色 -- 沿用原有冷调 */
  --raw-danger:          #E07A6E;
  --raw-danger-hover:    #F09A90;
  --raw-danger-bg:       #341D1A;
  --raw-success:         #6FAA7A;
  --raw-success-bg:      #16241A;
  /* 方向色（暗色）：与 success / danger 同值，理由见浅色段。
     暗色下红 5.60–6.15 · 绿 6.01–6.60，四组底全部 AA。 */
  --raw-dir-up:          #E07A6E;
  --raw-dir-down:        #6FAA7A;
  --raw-warning:         #C99A3F;
  /* 🔴 最终定的（第二次修正）：
       「本身**淡黄色的那些框**你就改成**暖灰色**，
         **在深色模式下的暖灰色**这个就很好啊」

     浅色下 --warning-bg 是 #fbf1dd（淡黄）—— **保持不动**。
     暗色下原来是 #2B2313（**深红棕**）⇒ 在冷灰背景里
     像一块"**咖啡色**"的脏斑 ⇒ 这就是 真机实测看到的。

     ⇒ 暗色下改成**暖灰**（保留一点暖调，与浅色的淡黄呼应，
        但**不再偏红褐**，与整体的冷灰底融合）。
     ⚠️ 只改这一个令牌，**底色/表面/文本一律不动**。 */
  --raw-warning-bg:      #242A31;
  --raw-info:            #5B9BC4;
  --raw-info-bg:         #13242F;

  /* 品牌色 -- 沿用原有淡蓝 */
  --raw-accent:          #7AA9DE;
  /* 🔴 开关开态的轨道色 -- 暗色版
     🔴 **降彩度**：#7AA9DE 太亮，铺满一大块会发光。
     实测：滑块是纯白 #FFF，轨道若也接近白 ⇒ 对比不足、边界糊。
     ⇒ 轨道取**中深蓝**（#3E6EA8 族），与白滑块有清晰对比，
        在 #1C2024 的深底上又不刺眼。 */
  --raw-switch-on:       #3E6EA8;
  --raw-accent-hover:    #8FB8E6;
  --raw-accent-soft:     #1C2C3D;

  /* 气泡：暗色下不再"反色"（反色会变成浅底深字，在暗界面里像个白块），
     改用抬高面 + 正文色 —— 但只比纸面亮一档，所以必须补一条描边。
     文字/底 11.93:1（AAA），实测见 contrast.py。 */
  --raw-tooltip-bg:      #232830;
  --raw-tooltip-fg:      #E4E7EA;
  --raw-tooltip-border:  #2A3038;

  --raw-scrim:           rgba(0, 0, 0, 0.62);
  /* ⚠️ 这里原本还抄了一份 --sans/--mono/--serif/--tracking-normal —— 是死代码：
     真正的字体栈定义在后面的 `:root`，同特异性且位置更靠后 ⇒ 无条件覆盖这里
     （实测暗色下 --tracking-normal = 0，不是这里的 0.01em）。
     留着还会被 theme-toggle.js 的差集规则复制成 inline style ⇒
     inline 优先于样式表 ⇒ 强制暗色时字体被改。已删。 */
}   /* ← :root 闭合*/
}

/* === BEGIN: light-via-attr（由 05-audit/theme-sync.py 生成，勿手改）=== */
[data-theme="light"] {
  --raw-accent: #1b4d8f;
  --raw-accent-hover: #163a6d;
  --raw-accent-soft: #e6eef7;
  --raw-border-control: #868b94;
  --raw-border-decor: #e2e5e9;
  --raw-border-decor-str: #c8cdd4;
  --raw-danger: #c0392b;
  --raw-danger-bg: #fdeeec;
  --raw-danger-hover: #8f1e17;
  --raw-dir-down: #2f6b3a;
  --raw-dir-up: #c0392b;
  --raw-info: #1d5f8a;
  --raw-info-bg: #e8f1f8;
  --raw-paper: #f6f7f8;
  --raw-scrim: rgba(21, 24, 28, 0.4);
  --raw-scrollbar-thumb: #7A808A;
  --raw-scrollbar-thumb-hov: #6E747E;
  --raw-skeleton-bg: #e9ecef;
  --raw-skeleton-shimmer: rgba(255, 255, 255, 0.72);
  --raw-success: #2f6b3a;
  --raw-success-bg: #eaf2e6;
  --raw-surface: #ffffff;
  --raw-surface-raised: #ffffff;
  --raw-surface-sunken: #f1f3f5;
  --raw-switch-on: #1b4d8f;
  --raw-text-on-accent: #ffffff;
  --raw-text-on-solid: #ffffff;
  --raw-text-primary: #15181c;
  --raw-text-secondary: #4f555e;
  --raw-text-tertiary: #606770;
  --raw-tooltip-bg: #15181c;
  --raw-tooltip-border: #15181c;
  --raw-tooltip-fg: #f6f7f8;
  --raw-warning: #8a5a00;
  --raw-warning-bg: #fbf1dd;

  --accent: var(--raw-accent);
  --accent-hover: var(--raw-accent-hover);
  --accent-soft: var(--raw-accent-soft);
  --border-control: var(--raw-border-control);
  --border-decor: var(--raw-border-decor);
  --border-decor-str: var(--raw-border-decor-str);
  --danger: var(--raw-danger);
  --danger-bg: var(--raw-danger-bg);
  --danger-hover: var(--raw-danger-hover);
  --dir-down: var(--raw-dir-down);
  --dir-up: var(--raw-dir-up);
  --info: var(--raw-info);
  --info-bg: var(--raw-info-bg);
  --paper: var(--raw-paper);
  --rule-hair: var(--raw-text-primary);
  --scrim: var(--raw-scrim);
  --scrollbar-thumb: var(--raw-scrollbar-thumb);
  --scrollbar-thumb-hov: var(--raw-scrollbar-thumb-hov);
  --skeleton-bg: var(--raw-skeleton-bg);
  --skeleton-shimmer: var(--raw-skeleton-shimmer);
  --success: var(--raw-success);
  --success-bg: var(--raw-success-bg);
  --surface: var(--raw-surface);
  --surface-raised: var(--raw-surface-raised);
  --surface-sunken: var(--raw-surface-sunken);
  --switch-on: var(--raw-switch-on);
  --text-on-accent: var(--raw-text-on-accent);
  --text-on-solid: var(--raw-text-on-solid);
  --text-primary: var(--raw-text-primary);
  --text-secondary: var(--raw-text-secondary);
  --text-tertiary: var(--raw-text-tertiary);
  --tooltip-bg: var(--raw-tooltip-bg);
  --tooltip-border: var(--raw-tooltip-border);
  --tooltip-fg: var(--raw-tooltip-fg);
  --warning: var(--raw-warning);
  --warning-bg: var(--raw-warning-bg);
}
/* === END: light-via-attr === */

/* === BEGIN: dark-via-attr（由 05-audit/theme-sync.py 生成，勿手改）=== */
@media screen {
:root[data-theme="dark"],
[data-theme="dark"] {
  --raw-accent: #7AA9DE;
  --raw-accent-hover: #8FB8E6;
  --raw-accent-soft: #1C2C3D;
  --raw-border-control: #8A9099;
  --raw-border-decor: #2A3038;
  --raw-border-decor-str: #3A424C;
  --raw-danger: #E07A6E;
  --raw-danger-bg: #341D1A;
  --raw-danger-hover: #F09A90;
  --raw-dir-down: #6FAA7A;
  --raw-dir-up: #E07A6E;
  --raw-info: #5B9BC4;
  --raw-info-bg: #13242F;
  --raw-paper: #14171A;
  --raw-scrim: rgba(0, 0, 0, 0.62);
  --raw-scrollbar-thumb: #6E747E;
  --raw-scrollbar-thumb-hov: #7D828A;
  --raw-skeleton-bg: #2E343C;
  --raw-skeleton-shimmer: rgba(255, 255, 255, 0.06);
  --raw-success: #6FAA7A;
  --raw-success-bg: #16241A;
  --raw-surface: #1C2024;
  --raw-surface-raised: #232830;
  --raw-surface-sunken: #1A1D21;
  --raw-switch-on: #3E6EA8;
  --raw-text-on-accent: #0D1114;
  --raw-text-on-solid: #0D1114;
  --raw-text-primary: #E4E7EA;
  --raw-text-secondary: #AEB6C0;
  --raw-text-tertiary: #9BA3AD;
  --raw-tooltip-bg: #232830;
  --raw-tooltip-border: #2A3038;
  --raw-tooltip-fg: #E4E7EA;
  --raw-warning: #C99A3F;
  --raw-warning-bg: #242A31;

  --accent: var(--raw-accent);
  --accent-hover: var(--raw-accent-hover);
  --accent-soft: var(--raw-accent-soft);
  --border-control: var(--raw-border-control);
  --border-decor: var(--raw-border-decor);
  --border-decor-str: var(--raw-border-decor-str);
  --danger: var(--raw-danger);
  --danger-bg: var(--raw-danger-bg);
  --danger-hover: var(--raw-danger-hover);
  --dir-down: var(--raw-dir-down);
  --dir-up: var(--raw-dir-up);
  --info: var(--raw-info);
  --info-bg: var(--raw-info-bg);
  --paper: var(--raw-paper);
  --rule-hair: var(--raw-text-primary);
  --scrim: var(--raw-scrim);
  --scrollbar-thumb: var(--raw-scrollbar-thumb);
  --scrollbar-thumb-hov: var(--raw-scrollbar-thumb-hov);
  --skeleton-bg: var(--raw-skeleton-bg);
  --skeleton-shimmer: var(--raw-skeleton-shimmer);
  --success: var(--raw-success);
  --success-bg: var(--raw-success-bg);
  --surface: var(--raw-surface);
  --surface-raised: var(--raw-surface-raised);
  --surface-sunken: var(--raw-surface-sunken);
  --switch-on: var(--raw-switch-on);
  --text-on-accent: var(--raw-text-on-accent);
  --text-on-solid: var(--raw-text-on-solid);
  --text-primary: var(--raw-text-primary);
  --text-secondary: var(--raw-text-secondary);
  --text-tertiary: var(--raw-text-tertiary);
  --tooltip-bg: var(--raw-tooltip-bg);
  --tooltip-border: var(--raw-tooltip-border);
  --tooltip-fg: var(--raw-tooltip-fg);
  --warning: var(--raw-warning);
  --warning-bg: var(--raw-warning-bg);
}
}
/* === END: dark-via-attr === */

/* 🔴 涨跌口径切换（`[data-market="intl"]`）**不在本文件里**，在 `01-tokens/market.css`。
   ----------------------------------------------------------------------------
   原因不是洁癖：读本文件的那几个工具（contrast / gen-ai-contract /
   gen-tokens-tree / token-audience）走 `_css.declared_tokens()`，它是**纯词法**的
   —— 遍历所有声明、后者覆盖前者、**不看选择器**。
   把一段**条件性**的交换规则放在这里，它们会把它当成默认值叠上去，
   于是报告里「暗色 涨」显示成绿色，而浏览器里其实是对的 —— 门禁冤枉代码，
   比漏判更难查（实测）。
   ⇒ 本文件只放「无条件声明」与「暗色媒体段」，条件规则一律外置。
   ========================================================================= */

/* ============================================================================
   字体栈令牌
   ============================================================================
   🔴 补。**这是个一直存在但没人发现的缺口** ——
   全库有 27 处引用 `var(--sans)` / `var(--mono)` / `var(--serif)`，
   而 tokens.css **从来没定义过它们** ⇒ 全部回退到浏览器默认字体。
   换句话说：库里"看起来有字体体系"，其实没有。

   ── 为什么字体栈要按语言分 ────────────────────────────────────
   一个字体通常**不覆盖所有文字**。Segoe UI 没有中文字形、
   苹方没有拉丁字形 ⇒ 浏览器逐字符 fallback 到系统默认。
   问题在于：**不同系统 fallback 到的字体不同**
   （macOS 苹方 / Windows 微软雅黑 / Linux 思源）⇒ 同一份代码观感不一致。

   ⇒ 做法：字体栈里**把各文字的字体都列上**，让浏览器在我们指定的顺序里选，
   而不是让它按系统默认挑。

   ⚠️ 全部是**系统字体**，不下载任何字体文件 ——
   这是精简档（某项目）的硬约束，也是本库"零外部资产"的一部分。

   字体栈的排列顺序：**拉丁 → 中文 → 泛型**。
   浏览器逐字符 fallback，排在前面、优先用。
   ========================================================================= */

:root {
  --sans:
    /* 拉丁 */
    -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
    'Helvetica Neue', Arial,
    /* 中文 */
    'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei',
    'Source Han Sans SC', 'Noto Sans CJK SC',
    /* 阿拉伯 / 希伯来 / 泰文（系统通常自带相应字体）*/
    'Geeza Pro', 'Segoe UI Historic',
    /* 泛型 */
    sans-serif;

  --mono:
    /* 拉丁等宽 */
    ui-monospace, 'SF Mono', 'Cascadia Mono', 'Segoe UI Mono',
    'Roboto Mono', Menlo, Consolas,
    /* 中文等宽（中文通常回落到无衬线，但列出更可控）*/
    'Sarasa Mono SC', 'Microsoft YaHei Mono',
    monospace;

  --serif:
    /* 拉丁衬线 */
    'Iowan Old Style', 'Palatino Linotype', Palatino, Georgia,
    /* 中文衬线 */
    'Songti SC', 'SimSun', 'Noto Serif CJK SC', 'Source Han Serif SC',
    serif;

  /* 字距：默认 0。中文与拉丁的字距需求不同，
     阿拉伯/希伯来文连字后密度更高，需要更松 —— 下面按语言覆盖。 */
  --tracking-normal: 0;
}

/* 🔴 阿拉伯语与希伯来语：需要**更宽松的字距**。
   阿拉伯文的字形连接后视觉密度比拉丁高，
   同样的字距在两套文字里"挤的程度"不同。 */
:root:lang(ar), :root:lang(fa), :root:lang(he), :root:lang(ur) {
  --tracking-normal: 0.01em;
}


/* ============================================================================
   🔴 修 iOS 实机报的「点击时出现奇怪的灰黑纺锤形闪动」
   ----------------------------------------------------------------------------
   原始反馈：「点击单选和复选的选项的时候会出现非常奇怪的阴影残留，
   那个阴影居然是**纺锤形**的，或者是极快的那个**灰黑色的底色闪动**」。

   ⭐ 真因（**iOS Safari 的默认行为**，不是我们的布局错）：
     iOS Safari 会给**被点击的元素**加一层半透明灰黑遮罩
     （约 rgba(0,0,0,0.18)），这层遮罩**跟着元素的形状与圆角** ——
     我们的 `.choice__mark` 是**圆角方块** ⇒ 遮罩就成了「纺锤形」。

     ⚠️ 而且真正被点的不是那个 mark，是**铺满整个 `.choice` 的
        透明 input**（`opacity:0; inset:0`）⇒ 遮罩画在这个 input 上，
        iOS 按它的圆角裁切 ⇒ 形状更怪、闪烁更明显。

   ⇒ 修法（业界标准做法）：**关掉 tap highlight**。
     本库每个控件都有自己的 `:active` / `:checked` 反馈，
     不需要 iOS 那层系统遮罩 —— 它既不统一又难看。

     ⚠️ 只在**可点元素**上关，**不要全局 `*`**
        —— 那会把链接与按钮的原生反馈一起干掉。 */
a,
button,
label,
summary,
input[type="checkbox"],
input[type="radio"],
input[type="button"],
input[type="submit"],
[role="button"],
[role="switch"],
[role="tab"],
[role="option"],
.choice,
.switch,
.btn,
.accordion__trigger,
.tabs__tab,
.card,
.list__row {
  -webkit-tap-highlight-color: transparent;
}

/* ⚠️ 关掉系统遮罩后，点下去**必须仍有反馈**（否则用户会以为没点中）。
   下面给统一的按压反馈：轻微缩放 + 变背景。
   ⚠️ 用 `:active`（鼠标与触摸都触发），不用 `:focus`
      —— focus 会留下"粘住"的高亮，正是我们要避免的。 */
.choice:active,
.switch:active,
.btn:active,
.list__row:active,
.card:active {
  opacity: 0.82;
}

@media (hover: hover) and (pointer: fine) {
  /* 🔴 桌面端不用 :active 做反馈（鼠标按下就闪一下很廉价）
     —— 桌面已有 :hover，够用了。 */
  .choice:active,
  .switch:active,
  .btn:active,
  .list__row:active,
  .card:active { opacity: 1; }
}

  /* ==========================================================================
     🔴 命中区尺寸令牌（做分页时发现的全库缺口）
     --------------------------------------------------------------------------
     为什么需要：全库到处硬写 `44px`（各组件各写一遍）⇒
     改一次要翻遍全库，且没人能保证它们一致。

     为什么是 44px：**WCAG 2.5.5 Target Size (Level AAA)** 要求 44×44 CSS px。
     ⚠️ 注意区分：
       · WCAG 2.5.8（AA 级）只要 24×24 */

/* ============================================================================
   打印（print）—— 纸没有主题：暗色已被上面的 `@media screen and` 关在屏幕里。
   剩下的只有一处：实心控件的字。打印丢弃背景色，而它们的
   `--text-on-accent` 是 #ffffff ⇒ 白纸白字（实测产物 2.30:1）。
   改这两个令牌一处，好过改 8 个组件。
   ========================================================================== */
/* ==========================================================================
   X · 打印（工具类 + 全局策略）
   放这里而不是新建 print.css：新 .css 会连带 dist / docs / README 一串连锁。
   完整说明（收窄层级的理由、为什么没写 thead、代价）见 docs/PRINT.md。
   ========================================================================== */

/* 屏幕上隐藏、纸上显示（块级元素用）。 */
.print-only { display: none; }

@media print {
  :root {
    color-scheme: light;          /* 表单控件/滚动条别跟着系统去暗色 */

    /* ======================================================================
       纸面墨阶 —— 屏幕那三级灰到了纸上会变成针孔，这里整条重写
       ----------------------------------------------------------------------
       🔴 为什么必须重写，而不是"把某个颜色调深一点"

       屏幕上一个像素可以是任意亮度；600dpi 激光打印机是**二值设备**，
       每个点只有"有碳粉 / 没有碳粉"。要表现 #606770 的相对亮度 0.128，
       每 11 个点里就得留 1 个白点（覆盖率 90.8%）。白点落在笔画内部，
       而 9pt 汉字的横画只有约 6 个点宽 ⇒ 每一笔都带针孔。
       眼睛读到的不是"浅灰"，是"缺墨"。

       实测（2026-11-06 · HP LaserJet M1005 · 600dpi · A4）：

         #15181c  覆盖率 100%   纸上实心
         #4f555e  覆盖率  95%   基本实
         #606770  覆盖率  91%   带针孔   ← 原三级文字
         #e2e5e9  覆盖率  23%   断成虚线 ← 原表格线

       🔴 依据不是 WCAG 4.5:1 —— 那是屏幕标准
       印刷排版学（legibility.info）对印刷品给的是 Michelson 对比度 ≥ 0.7，
       约合 7:1；WCAG 2.2 SC 1.4.6 对打印件同样建议 7:1、小字更高。
       4.5:1 只保证"屏幕上看得见"，不保证"打在纸上还实"。
       所以 `print-gate` 的门槛也同步从 4.5 提到 7 —— 两道一起改才有意义。

       🔴 为什么改令牌，不改组件
       组件全部走 var()，改一处全域生效；逐个组件改必然漏，而且下一个人
       加新组件时不会知道有这条约束。代价是"所有页面一起变"—— 这正是要的。

       🔴 灰阶怎么定的
       不是凭感觉调深，是卡在"纸上仍然打满"的上限：
         #000      Y=0.000  覆盖 100%（打满）
         #1f2328   Y=0.016  覆盖 100%（打满）
         #33383f   Y=0.039  覆盖 100%（打满，再浅就开始掉点）
       三档在纸上**都是纯黑**，层级改由字号 / 字重 / 位置承担。
       这就是"换载体"：屏幕靠灰分层，纸靠尺寸与位置分层。

       ⚠️ 2026-11-06 实体打样后改成**三档同值 #000**。
       原来留了 #000 / #1f2328 / #33383f 三个近似值，纸上实测：
       7.4pt 的 #33383f 与 #000 的墨量完全一样（都是 100% 覆盖），
       但"看起来浅"—— 于是有人会以为还能再调深一点，其实已经到底了。
       三个不同值只会制造"我调过了"的错觉。**纸上不分级，就是不分级。**
       ====================================================================== */
    --paper:            #fff;
    --surface:          #fff;
    --surface-sunken:   #fff;
    --surface-raised:   #fff;
    --text-primary:     #000;
    --text-secondary:   #000;      /* 纸上三档同值，见上 */
    --text-tertiary:    #000;
    --border-decor:     #8f959d;   /* 覆盖 73%：表格内部线，纸上淡但连续 */
    --border-decor-str: #5f646c;   /* 覆盖 91%：需要更强分隔时用 */
    /* 🔴 hairline：图框、需要"一条实线"的地方。
       0.5pt 的线在 600dpi 下只有 4 个点厚 —— 覆盖率低于 60% 就会被打成
       半色调虚边（实体打样实测：#b5bcc3 + 0.7pt = 52% 覆盖，纸上几乎看不见）。
       这类线必须 100% 覆盖 ⇒ 只能是纯黑。 */
    --rule-hair:        #000;

    /* 语义色也要压深 —— 它们同样承载正文（错误提示、负值、警告段）。
       原来 #c0392b 是 5.44:1、#8a5a00 是 5.93:1，过不了纸面的 7:1。
       压深之后都在 10:1 以上，纸上覆盖率 ≥ 95%。
       ⚠️ 黑白打印下这四色会变成几乎相同的深灰 ——
          语义不能只靠颜色，必须另带文字或图标（WCAG 2.2 SC 1.4.1）。
          这条不改令牌能解决，属于组件层义务，见 docs/PRINT.md「黑白降级」。 */
    --danger:           #7d1c11;
    --warning:          #5a3a00;
    --success:          #1c4526;
    --info:             #103d57;
    /* 涨跌色与 danger/success 同值（--raw-dir-up 就是 #c0392b），
       所以必须一起压 —— 漏了它，宽表里 24 段涨跌数字仍然过不了 7:1。
       ⚠️ 红涨绿跌在黑白纸上会变成两块几乎相同的深灰，
          数字必须自带正负号或箭头，不能只靠颜色（见 docs/PRINT.md）。 */
    --dir-up:           #7d1c11;
    --dir-down:         #1c4526;

    /* 强调色：最容易漏的一个。
       #1b4d8f 的对比度是 8.4:1 —— **过得了 7:1 的门槛**，所以对比度判据不会
       报它。但它的相对亮度是 0.075，纸面覆盖率 96%：一是有轻微针孔，
       二是黑白输出后变成比正文还浅的灰，"强调"反而比正文弱。
       2026-11-06 打样里三条迷你趋势图就是蓝的，纸上成了三道浅灰。
       纸上的强调靠字重/位置/线宽/线型，不靠色相。 */
    --accent:           #000;
    --accent-hover:     #000;
    --accent-soft:      var(--paper);   /* 屏幕是浅底，纸上没有"底" */

    --text-on-accent: #000;
    --text-on-solid:  #000;
  }

  /* 使用者控制器：加 .no-print 的元素不上纸。全库唯一的 !important ——
     不加就永远可能被使用者自己写的 display 覆盖，等于没提供这个能力。 */
  .no-print { display: none !important; }
  .print-only { display: block; }

  /* 打印前展开折叠内容 —— 不展开 = 静默截断（页数正常、内容少一半）。
     ⚠️ 必须带组件根前缀：tokens.css 在 head 里最先加载，同特异性的规则会被
     组件 CSS 覆盖（实测：不加前缀时 PDF 与改动前逐字节相同）。
     accordion 三条缺一不可。逐条理由见 docs/PRINT.md。 */
  .tabs .tabs__panel[hidden] { display: block; }
  .accordion .accordion__panel[hidden] {
    display: grid !important;      /* ① 它有 display:none !important */
    grid-template-rows: 1fr;       /* ② 收起态是 0fr */
    visibility: visible;           /* ③ 它还有 visibility:hidden（缺这条就一个字不印） */
  }

  /* 浮层与纯导航。不用裸 button（accordion 标题在 button 里），
     不动 .btn / .upload（文档页会空、表单控件有状态）。见 docs/PRINT.md。 */
  .toast, .tooltip, .popover, .skeleton,
  .tabs__list, .nav__list, .nav__toggle, .pagination,
  .accordion__marker { display: none !important; }

  /* ⚠️ 模态的遮罩规则**不在这里**，在 03-patterns/overlay/overlay.css。
     原因不是洁癖：`.dialog-backdrop { position: static }` 写在这里会被
     overlay.css 里同特异性的 `position: fixed` 盖掉 —— 规则写了、不生效、
     不报错（同一个形状，2026-11-02 由 print-policy-gate 的 L3 抓到）。 */

  /* 不跨页切断。代价：高于半页的卡片会被整体推到下一页 ⇒ 页数可能变多。 */
  tr, img, figure, svg,
  .card, .metric, .sparkline, .splot { break-inside: avoid; }

  /* 退出开关：高块想省纸就加这个类（标定表见 docs/PRINT.md）。
     必须写在上一条之后 —— 同特异性靠顺序取胜，不靠 !important。 */
  .print-allow-break { break-inside: auto; }

  /* ⚠️ 这里**没有** `p, li, blockquote { orphans/widows }` ——
     裸标签选择器会改掉宿主页面所有段落（`bleed-gate` 的硬红线）。
     孤行寡行只在库里自己的文档容器内生效，见 `04-recipes/report/report.css`。 */
}
