/* ============================================================
   tokens.css — Dreamanual --dt-* token 体系（统一站合并版）
   my.dreamanual.com · 2026-09-11 由 book / words 两侧合并
   ------------------------------------------------------------
   来源与裁定（依据 MY-ARCHITECTURE.md §6.2）：
     · 品牌 / 交互 / 预警色  → 采用 book 的墨黑系（蓝退让给「收入」语义）
     · --dt-income / -expense / --lh-* / --gap-list  → book 独有，保留
     · --fw-bold  → words 独有，并入
     · 字号  → 采用 book 的 5 档，words 旧名走下方【兼容别名层】
   本文件是唯一需要维护的 token 源；两侧不再各持一份。
   ============================================================ */

/* ---- 全局 hidden 守卫（2026-09-14）----
   `hidden` 属性在 UA 样式表里只是 `[hidden] { display: none }`；任何作者样式里的
   `display: flex/block/...` 都会把它覆盖（作者样式胜 UA，与选择器优先级无关）。
   本库有多个组件类自带 display，漏写守卫的元素会「本该隐藏却永远显示」——
   打卡点地图就曾被自己的降级层整块盖住，表现为「白屏且无任何提示」。
   统一在此收口，组件侧不必再逐个写 `[hidden]`。 */
[hidden] { display: none !important; }

/* ---- 全局横向溢出守卫（2026-09-15）----
   任何元素比视口宽都会把整个页面拖出横向滚动（真机上「页面能左右晃」的根源）。
   只在根上收口：根的 overflow 会传播给视口、不产生新的滚动容器；
   容器内部的横向滚动（.chip-row 单行横滑等）也不受影响。
   hidden → clip 级联：clip 连滚动容器都不产生，对 sticky / 弹性滚动零副作用；
   老浏览器（Safari<16）回退 hidden（Chrome 实测 sticky 不受影响）。
   隐藏溢出 ≠ 修复溢出：各滚动容器自身的 overflow-x 约束见各模块 CSS。 */
html { overflow-x: hidden; overflow-x: clip; }

:root {
    /* ---- 基础表面 ---- */
    --dt-bg: #f8fafc;
    --dt-raised: #FFFFFF;
    --dt-overlay: #FFFFFF;
    --dt-inset: #f1f5f9;
    --dt-fill2: #f1f5f9;

    /* ---- 品牌与交互：黑白化（2026-09-10）----
       蓝色全面退让给「收入」语义，交互色 = 墨黑；
       hover 染色用黑 alpha，焦点环用深灰，蓝不再承载交互含义 */
    --dt-brand: #0f172a;
    --dt-brand-lt: #1e293b;
    --dt-brand-bg: #0f172a;
    --dt-brand-sec: #334155;
    --dt-brand-acc: #e2e8f0;
    --dt-brand-surf: #f1f5f9;
    --dt-interact: #0f172a;
    --dt-interact-lt: #334155;
    --dt-interact-bg: #1e293b;      /* btn-primary / fab hover */
    --dt-interact-sec: #475569;
    --dt-interact-acc: #94a3b8;
    --dt-interact-surf: #f1f5f9;    /* 激活 chip 底 = inset 灰 */
    --dt-focus: #64748b;
    --dt-focus-light: rgba(100,116,139,.10);
    --dt-interact-a04: rgba(15,23,42,.04);
    --dt-interact-a06: rgba(15,23,42,.06);
    --dt-interact-a12: rgba(15,23,42,.12);
    --dt-interact-a20: rgba(15,23,42,.20);

    /* ---- 文本与边框 ---- */
    --dt-text1: #0f172a;
    --dt-text2: #475569;
    --dt-text3: #64748b;
    --dt-text4: #94a3b8;
    --dt-text5: #cbd5e1;
    --dt-text-inv: #FFFFFF;
    --dt-border: #e2e8f0;
    --dt-border-str: #cbd5e1;
    --dt-card-border: #e2e8f0;

    /* ---- 数据语义色（收支对：蓝橙为色盲安全对） ---- */
    --dt-income: #2563eb;       /* blue-600，蓝只表达收入 */
    --dt-income-bg: #dbeafe;    /* blue-100 */
    --dt-expense: #f97316;      /* orange-500，橙只表达支出 */
    --dt-expense-bg: #ffedd5;   /* orange-100 */
    --dt-error: #ef4444;        /* 红 = 涨（行情模块 §15.5 局部类）/ 异常 / 预警 */
    --dt-warning: #ef4444;      /* 预警并入红（原橙退役，避免与支出橙撞车） */
    --dt-amber: #f59e0b;        /* 保留：评分中间档 */
    --dt-amber-bg: #fffbeb;
    --dt-amber-text: #d97706;
    --dt-success: #16a34a;      /* 绿 = 跌（行情模块 §15.5 局部类）/ 收入好 */
    --dt-info: #0284c7;         /* 已退役：原转账天蓝，转账改灰；保留 token 备用 */
    --dt-error-bg: #fee2e2;
    --dt-warning-bg: #fee2e2;   /* 预警底随红 */
    --dt-success-bg: #dcfce7;
    --dt-info-bg: #e0f2fe;

    /* ---- 语义色边框（语义色浅阶） ---- */
    --dt-success-border: #bbf7d0;
    --dt-warning-border: #fecaca;
    --dt-error-border: #fca5a5;
    --dt-tooltip-bg: #1e293b;

    /* ⚠️ 原 book 在下方「此处」还有一组 oklch 蓝色重复定义
       （--dt-focus / --dt-focus-light / --dt-interact-a04/06/12/20），
       覆盖了上面的墨黑值，导致「黑白化」实际未生效（R3）。
       合并时已整组删除，只保留墨黑一组。
       验收：--dt-focus 计算值为 #64748b，非 oklch 蓝。 */

    /* ---- 字体 ---- */
    --font-base: 'Inter', 'Noto Sans SC', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'PingFang SC', 'Microsoft YaHei', sans-serif;
    --font-mono: 'JetBrains Mono', 'SF Mono', Monaco, monospace;
    --font-en: 'Inter', -apple-system, sans-serif;

    /* ---- 字号阶梯（5 档，对齐 Ant Design / Arco：12/14/16/20/24） ---- */
    --fs-caption: 12px;  /* 标签/badge/图表角标/时间戳（最小档） */
    --fs-body:   14px;   /* 正文/输入框/按钮（基准，body 默认字号） */
    --fs-title:  16px;   /* 卡片标题/次级标题 */
    --fs-heading: 20px;  /* 卡片大数/次级页标题/弹窗标题 */
    --fs-page:   24px;   /* 页面标题/概览大数 */

    /* ---- 兼容别名层（words 沿用旧名，值即上表，不产生第二套字阶）----
       合并策略：不改 words 的 139 处引用，而在源头做别名。
       下列 5 个 token 在各页面的计算值与 book 同名档位完全一致，视觉不变。 */
    --fs-sm:  var(--fs-caption);   /* 12px ← words 57 处 */
    --fs-md:  var(--fs-body);      /* 14px ← words 31 处 */
    --fs-lg:  var(--fs-title);     /* 16px ← words 13 处 */
    --fs-xl:  var(--fs-heading);   /* 20px ← words  6 处 */
    --fs-2xl: var(--fs-page);      /* 24px ← words  2 处 */

    /* ---- words 的扩展档（book 5 档里无对应值，独立保留） ---- */
    --fs-xs:  10px;   /* 日历/趋势角标（最小可用）       ← words  8 处 */
    --fs-3xl: 28px;   /* 展示级：专注模式的单词大字号    ← words  4 处 */
    --fs-4xl: 36px;   /* 展示级：专注模式 / 总览大数     ← words  2 处 */

    /* ---- 输入框档（2026-09-17）· 全站输入控件的**唯一**字号开关 ----
       iOS Safari 对「聚焦时字号 < 16px」的 input / select / textarea 会自动放大页面，
       且**不保证缩回**（只要用户捏合过、或在放大态里滚过，blur 后就可能停在放大态）。
       所以正确解法是**不触发**，而不是想办法缩回 —— 触屏下把输入控件抬到 ≥16px。
       ⚠️ 值必须写 var(--fs-body)/var(--fs-caption) 而不是字面 14px/12px：
          一是桌面观感与改动前逐像素一致（零回归），二是将来调字阶时输入框自动跟随。
       ⚠️ 任何作用在输入控件上的 font-size 都必须走这两个 token —— 写死字面值会被
          check:input 门禁咬住（它是跨模块的，见 tools/check-input-zoom.cjs）。
          `::placeholder` 也走同一 token：它不参与缩放判定（缩放只看控件自身字号），
          但让它与控件同档，触屏下不会出现「占位字比输入字小一号」的错位。 */
    --fs-input:    var(--fs-body);      /* 输入框基准档：桌面 14px */
    --fs-input-sm: var(--fs-caption);   /* 输入框小档：桌面 12px（紧凑行内输入） */

    /* ---- 行高（三档，与字号角色配套；book 独有） ---- */
    --lh-tight: 1.35;    /* 标题 / 单行数据 */
    --lh-body:  1.45;    /* 正文 / 列表行（body 基准） */
    --lh-prose: 1.55;    /* 段落 / 说明文字 */

    /* ---- 字重（四档：regular + medium + semibold + bold） ---- */
    --fw-normal: 400;
    --fw-medium: 500;
    --fw-semibold: 600;
    --fw-bold: 700;      /* words 独有，16 处引用 */

    /* ---- 圆角（book 的紧凑账本密度层会在 pages/book.css 的 :root 里覆盖为 6/4/2/999）---- */
    --radius-sm: 4px;
    --radius-md: 8px;
    --radius-lg: 12px;
    --radius-full: 9999px;   /* 胶囊/圆形：角标 / 开关 / 状态点 / 拖拽把手 */

    /* ---- 阴影 ---- */
    --shadow-sm: 0 1px 2px rgba(0,0,0,0.04);
    --shadow-md: 0 2px 8px rgba(0,0,0,0.08);
    --shadow-lg: 0 8px 24px rgba(0,0,0,0.12);

    /* ---- 间距原子层（2026-09-17）· 全站间距的**唯一**可调层级 ----
       🔴 语义 token（--gap-* / --pad-*）一律引用本层；模块侧**禁止**写间距字面值 ——
          字面值不参与触屏补偿，会在「字号整条上移」时显得更紧（全站变成
          「有的松了有的还紧」，比不调更难看）。
       为什么需要原子层：字号在触屏下整条上移（见文件末触屏块）后，**绝对间距
          相对变小**、观感变紧。要让间距跟着字号走，就得先让它在源头可调。
          实测（2026-09-17）：全站间距声明 595 处，其中字面值 572 处、
          token 覆盖仅 10.8% —— 所以本轮先建「可调层级」，再分两批收编字面值。
       命名为 --sp-<桌面值>：名字里的数字是**桌面**像素值，触屏值见文件末触屏块
          （与 --fs-caption 在两态下取不同值同理）。
       🔴 奇数档（5 / 7 / 9 / 11 / 13 / 17）是**为保存历史字面值而设的镜像档**，
          它们不构成设计阶梯。之所以保留：补偿只应发生在触屏 —— 桌面必须逐像素
          不动（实机反馈只提到手机），所以不能把 9px 直接吸成 10px。
          新增间距请优先用偶数档，否则触屏阶梯会碎成「差 1px 的半档」。 */
    --sp-4:  4px;
    --sp-5:  5px;
    --sp-6:  6px;
    --sp-7:  7px;
    --sp-8:  8px;
    --sp-9:  9px;
    --sp-10: 10px;
    --sp-11: 11px;
    --sp-12: 12px;
    --sp-13: 13px;
    --sp-14: 14px;
    --sp-16: 16px;
    --sp-17: 17px;
    --sp-18: 18px;
    --sp-20: 20px;
    --sp-22: 22px;   /* 仅作为触屏档的落点存在（桌面 20 的下一档） */
    --sp-24: 24px;   /* 区块级：触屏**刻意不动**（页高涨幅的总闸门） */

    /* ---- 容器间距（区块 / Tab 之间的 gap）—— 值一律走原子层 ---- */
    --gap-section: var(--sp-24);   /* L1 主内容区 / Tab / 统计页区块间距 */
    --gap-block:   var(--sp-20);   /* 首页网格 / 中等密度区块间距 */
    --gap-card:    var(--sp-12);   /* L2 设置页 / 词库卡片 / 默认卡片间距 */
    --gap-head:    var(--sp-12);   /* 卡内标题（section-head / card-head）→ 内容间距（与 gap-card 语义分离，勿混用） */
    --gap-tight:   var(--sp-12);   /* 统计网格 / 紧凑区块间距 */
    --gap-list:    var(--sp-8);    /* L3 卡内列表 / 色块子卡间距（各密度统一） */

    /* ---- 卡片内边距（2026-09-12 全站统一紧凑密度，对齐原 book 覆盖层）
           —— 值一律走原子层（理由见上方「间距原子层」） ---- */
    --pad-card-lg: var(--sp-16);   /* 大卡片 / 弹窗 / 登录卡垂直 */
    --pad-card-md: var(--sp-12);   /* 标准卡片垂直（最常用） */
    --pad-card-sm: var(--sp-8);    /* 紧凑卡片 / 迷你卡垂直 */
    --pad-card-xs: var(--sp-8);    /* 列表项 / 词条卡垂直 */
    --pad-x-lg:    var(--sp-20);   /* 大卡片水平 */
    --pad-x-md:    var(--sp-16);   /* 标准卡片水平 */
    --pad-x-sm:    var(--sp-12);   /* 紧凑卡片水平 */
    --pad-x-xs:    var(--sp-12);   /* 列表项水平 */
}

/* ============================================================
   触屏输入框字号（2026-09-17）· 防 iOS Safari 聚焦自动缩放
   ------------------------------------------------------------
   判据：iOS 对 font-size < 16px 的 input / select / textarea 聚焦时会放大页面，
        14px 与 12px 两档都在阈值之下 ⇒ 全站输入框都会触发（实机：iPhone 17）。
   🔴 为什么整条阶梯一起上移，而不是只抬输入控件（第 31 批定案）：
      只把输入框抬到 16px 等于**把落差留在原地** —— 实机量测触屏 390px 下
      ≤14px 占文本元素 82.9%（其中 12px 一档就占 58.6%），输入框于是成了页面上
      唯一被拉到正常尺寸的元素，比正文大 2px，观感突兀（用户实机反馈）。
      真身是「整条阶梯按桌面密排定的，搬到手机上偏小」，不是「输入框太大」。
      故 body 14→16 与输入框同档 ⇒ 落差归零；caption 12→13、title 16→18 配套。
      ⚠️ 该约束由 check:input 门禁锁住：--fs-input 必须 **等于** --fs-body。
         将来若要让两者分档，必须同时改门禁与本注释（否则门禁会红）。
   为什么用指针能力查询而不是宽度断点：宽度断点会误伤窄窗桌面 ——
        Mac 上把窗口拖窄不该改变字号。Mac 无触摸屏 ⇒ 媒体查询不匹配，
        各档回到 12 / 14 / 16px，**桌面视觉零变化**。
   ⚠️ 窄屏布局覆盖仍走宽度断点（如 overview.css 的 max-width: 768px 改网格列数）——
      **布局密度与字号是两个正交维度**：窗口窄了要少列，但字号取决于「手指是否在点」。
      所以改字号必须回到本块，别写进宽度断点里（overview 那个块里的 20px 已收编到
      var(--fs-heading)，正是为此）。
   🔴 为什么是 any-pointer 而不是 pointer：`pointer` 取的是**主指针**，iPad 一旦
      接上妙控键盘/触控板，主指针就变成 fine —— 而那台设备照样会有人用手指去点
      输入框（照样缩放）。`any-pointer: coarse` 问的是「这台设备是否具备粗指针」，
      与「手指可能点它」这个真实前提对齐。Mac 无触摸屏，两者皆为 false，无差别。
      代价：带触摸屏的笔记本会一并拿到 16px 输入框 —— 那些机器本来也会缩放，
      属于顺带修好，可接受。
   🔴 为什么在 tokens.css 换值，而不是在 shell.css 写
      `@media (any-pointer: coarse) { .form-input { font-size: 16px } }`：
      CSS 加载顺序是 tokens → shell → 模块，**同特异性规则后加载者胜**，
      tokens.css 里的选择器会被 shell.css 的同名规则反杀（静默失效，且只有真机看得见）。
      自定义属性在 :root 上换值，发生在层叠**之后**的求值阶段，不受文件顺序与
      特异性的影响 —— 既不必上 !important，也不用堆优先级 hack。
   ⚠️ 这一层 jsdom 测不到（不求值媒体查询），真机几何由 check:input:browser 锁
      （puppeteer 触屏模拟 → 逐控件量 computed font-size）。
   ============================================================ */
@media (any-pointer: coarse) {
    :root {
        /* 移动端阶梯上移（第 31 批）—— 理由见上方长注释第 2 段 */
        --fs-caption:  13px;   /* 12 → 13：全站最高频档（129 处），小字可读性 */
        --fs-body:     16px;   /* 14 → 16：本轮支点 —— 与输入框同档，落差归零 */
        --fs-title:    18px;   /* 16 → 18：正文上移后维持层级间距（正文 16 / 标题 18） */
        --fs-input:    16px;   /* = 正文档：必须 ≥16（iOS 阈值）且 = --fs-body（门禁锁） */
        --fs-input-sm: 16px;   /* 紧凑档（页码框 / 行内日期）同样抬到阈值，否则照样缩放 */
        /* --fs-heading(20) / --fs-page(24) / --fs-xs(10) 刻意不动：
           前两个大字号本就够用；--fs-xs 是「最小可用」的角标档，
           它所在的紧凑布局（badge 挨着时间戳）抬大会挤压，收益不抵风险。 */

        /* ---- 间距原子层上移（2026-09-17 第 32 批）----
           动机：字号整条上移后，绝对间距**相对变小**（比值被稀释），
                 用户实机反馈「卡片内边距、元素间距显得略微有点小」。
           规则：**并入 2px 网格 + 每档 +2**，小档补得多、大档补得少 ——
                 8→10 是 1.25 倍、12→14 是 1.17 倍、20→22 是 1.10 倍。
                 「每档 +2」而非「等比 ×1.15」，是为了让值全部落在 2px 网格上
                 （等比会产生 9.2 / 13.8 这类非网格值，与相邻元素对不齐）。
           闸门：--sp-24 区块级间距**按住不动** —— 页面变长的主要来源就是区块间距，
                 放它会直接把页高推高 8%+。故本块最大只到 --sp-22。
           ⚠️ 补偿发生在**求值阶段**（:root 上的自定义属性换值），不受文件加载
              顺序与特异性影响；这与字号那层同源，理由详见上方长注释第 4 段。
           ⚠️ 因此模块侧任何间距字面值都不会跟随 —— 收编进度见 check:spacing。 */
        --sp-4:  6px;
        --sp-5:  6px;
        --sp-6:  8px;
        --sp-7:  8px;
        --sp-8:  10px;
        --sp-9:  10px;
        --sp-10: 12px;
        --sp-11: 12px;
        --sp-12: 14px;
        --sp-13: 14px;
        --sp-14: 16px;
        --sp-16: 18px;
        --sp-17: 18px;
        --sp-18: 20px;
        --sp-20: 22px;
        /* --sp-22 / --sp-24 未覆盖：前者只是 --sp-20 的触屏落点（本身不作桌面
           基准，写了也是同值）；后者是区块级，见上方「闸门」。 */
    }
}

/* ============================================================
   颜色书写契约（全站）：颜色一律走 --dt-* 变量，禁止硬编码 hex；
   JS 里同理 —— 返回 token 名，不返回色值。
   ============================================================ */
