/* ============================================================================
   agentcss / 01-tokens / page.css
   页面骨架（跨页面共享的"全局"层）

   依赖：tokens.css（只用变量，不写死值）
   版本：A ✅ / B ✅ / C ✅

   🔴🔴 为什么要有这个文件（0.4.2 · 实测事故驱动）
   ----------------------------------------------------------------------------
   事故：用本库做十几页的成套页面，改一处"全局"的东西，整批立刻崩。

   复盘结论不是"模型笨"，而是**这个库根本没有"全局"**：
   「页面骨架」被 33 个页面**各抄了一份**在内联 <style> 里。实测（脚本量过）：

     选择器      出现在   写法种数
     h1          36 页     8
     h2          35 页    11
     .wrap       33 页    16   ← 列宽从 680px 到 900px 有 13 个不同数字
     body        32 页     9
     code        24 页     9
     .lede       21 页     7   ← typography.css 里**已经有了**，页面还在重抄
     .note       21 页    10   ← 同上
     .box        13 页     2

   于是"改全局"这件事在物理上不可能成立：
   没有一处可改，只有 33 处要改 —— 漏一处就是不一致，改错一处就是崩。

   本文件把上面这些收编成**一处定义**。页面不再声明它们，
   改这里（或改对应令牌）⇒ 全部页面一起变。
   ============================================================================

   🔴 为什么全部挂在 `.page` 下面（作用域，不是偷懒）
   ----------------------------------------------------------------------------
   裸标签选择器（`h1 {}`）的作用域是**整个文档**。
   本库若留下这种规则，宿主页面后面怎么改都会被它影响
   （05-audit/bleed-gate.py 就是拦这个的，它禁止组件 CSS 里的裸标签）。

   骨架也必须先遵守这条 ⇒ 全部写成 `.page h1`、`.page .wrap`。
   用法：`<body class="page">` 才生效 —— **不引就不生效**，
   宿主页面不会莫名其妙被改。

   代价：每个页面要写一次 `class="page"`。
   收益：库对宿主页面**零副作用**，且骨架仍然只有一处定义。
   这两件事没有第三个选项（要么污染全局，要么显式挂载），这里选后者。
   ============================================================================

   ⚠️ 加载顺序：本文件要放在 tokens.css / typography.css **之后**、
      组件 CSS 与页面内联 <style> **之前**。
      这样组件若要覆盖骨架，靠"后来者同特异性胜出"就能做到，不需要 !important。
   ========================================================================= */

/* ============================================================================
   1. 页面框（body）
   ----------------------------------------------------------------------------
   众数写法是 `background: var(--paper); margin: 0; padding: var(--sp-7) var(--sp-5)`
   （10/32 页）。这里在众数基础上补两件众数里漏掉、但真机上必需的事：

     ① safe-area：iOS 有刘海/底部横条的机型上，
        不写 env() 的内容会被刘海和横条压住（实测 10-review/ios 就是这个原因）。
        env() 在无安全区的设备上求值为 0 ⇒ 对普通机型**无副作用**。

     ② 字体：众数写法**没有** font-family / font-size，
        靠浏览器默认（Times 一类的衬线默认字体，16px）。
        页面因此在不同系统上观感不一致 ⇒ 这里显式落到令牌上。
   ========================================================================= */
.page {
  margin: 0;
  /* 🔴 两行是有顺序的，别合并、别调换：
     先 100vh（所有浏览器都认），再 100dvh（动态视口，新浏览器覆盖上一行）。
     只用 100vh 时，iOS Safari 收起地址栏后 100vh 不跟着变 ⇒ 底部留白
     （10-review/ios 就是为这个才单独写了一份 body —— 现在回到骨架里）。 */
  min-height: 100vh;
  min-height: 100dvh;
  background: var(--paper);
  color: var(--text-primary);
  font-family: var(--sans);
  font-size: var(--fs-base);
  line-height: var(--lh-base);

  /* iOS 横屏会自动放大字号，导致布局跟竖屏不一致 —— 关掉这个行为。 */
  -webkit-text-size-adjust: 100%;

  padding-block: calc(env(safe-area-inset-top, 0px) + var(--sp-6))
                 calc(env(safe-area-inset-bottom, 0px) + var(--sp-6));
  padding-inline: max(env(safe-area-inset-right, 0px), var(--sp-4))
                  max(env(safe-area-inset-left, 0px), var(--sp-4));
}

/* 纸面视口高度不等于一屏，屏幕安全区与末尾留白也不属于正文。
   实测长金融报告：仅去掉 padding-block-end，8 页（末页全空白）→ 7 页，
   3689 个非空白字符保持不变；只去掉首端内边距或 min-height 都不能修它。
   保留首端留白与左右内边距，避免顺手改变整份文档的分页位置。 */
@media print {
  .page {
    min-height: 0;
    padding-block-end: 0;
  }
}

/* ============================================================================
   1b. 盒模型（`border-box`）—— 只在 .page 范围内生效
   ----------------------------------------------------------------------------
   🔴 0.4.2 补。实测证据（02-primitives/date-range/demo.html @393px）：
     `.drange__input { width: 100%; padding: 0 var(--sp-3); border: 1px }`
     算出来内容是 162.08px，**实际盒宽 188px**（162 + 12×2 + 1×2）
     ⇒ 比它的容器宽 26px ⇒ 两个日期框并排时把整页撑出横向滚动。

   根因不是这一个组件写错了，而是**全库没有统一的盒模型**：
   翻遍 01/02/03/04/09，`box-sizing` 只出现 5 次，都是某一处临时补的
   （input.css / switch.css / dropdown.css / overlay.css / model-viewer.css）。
   ⇒ 谁记得写谁没事，谁忘了谁静默溢出 —— 又是一次"漏一处就崩"。

   ⚠️ 为什么不放在 tokens.css 写成 `* { box-sizing: border-box }`：
      那是**全局 reset**，会改掉宿主页面自己的所有盒子。
      组件库不该改宿主的盒模型（compare 05-audit/bleed-gate.py 的立论）。
      ⇒ 放进骨架、挂 `.page`：**选配**。宿主不用骨架就不受影响。

   为什么是 `.page *` 而不是逐个组件补：
      逐个补 = 永远会漏下一个新组件。这一条把"新组件忘了写"变成不可能。
   ========================================================================= */
.page *,
.page *::before,
.page *::after {
  box-sizing: border-box;
}

/* ============================================================================
   2. 内容列 `.wrap`
   ----------------------------------------------------------------------------
   🔴 这是本次事故最典型的一处：33 个页面写了 16 种 `.wrap`，
      max-width 出现过的数字有 680 / 720 / 760 / 800 / 820 / 860 / 900 px
      和 42 / 44 / 46 / 52 rem，还有 1 页写 72ch。
      它们之间**没有语义差别**，纯粹是每次手敲时随手写的。

      现在只有一处：`min(var(--measure-page), 100%)`。
      · --measure-page（52rem）来自实测众数 820px，见 tokens.css 里的注释
      · min(…, 100%)：列宽永远不超过视口 ⇒ 窄屏上不会横向溢出
   ========================================================================= */
.page .wrap {
  margin-inline: auto;
  max-width: min(var(--measure-page), 100%);
}

/* 窄列：长文。68ch 是阅读上限，宽了眼睛回扫会跳行。
   只有长文页需要，其余页面一律用上面的默认列宽。 */
.page .wrap--prose {
  max-width: min(var(--measure-prose), 100%);
}

/* ============================================================================
   3. 标题阶梯
   ----------------------------------------------------------------------------
   h1：实测 8 种写法。这里取**最讲究的那一种**（list / nav / states 三页在用：
       font-weight 500 + 收紧字距 + --measure-title 限宽），
       而不是取数量最多的那一种（19 页，只有 font-size/line-height/margin）。
       理由：19 页那种是"没写完"，不是"另一种设计" ——
       它缺少的正是标题该有的三件事（字重、字距、限宽）。
       收敛成"讲究版"之后，19 页的标题会变整齐，这是改进不是回归。

   h2：实测 11 种写法，但它们其实是**两种东西**混在一起：
       · 光秃秃的小节标题（16 页）
       · 带分隔线的小节标题（13 页用上边线、6 页用下边线）
       ⇒ 骨架只定义"光秃秃"的基础款；分隔线做成两个修饰类，
         要线的页面加 class，不要自己再写一遍 border。
   ========================================================================= */
.page h1 {
  font-family: var(--sans);
  font-size: var(--fs-2xl);
  font-weight: 500;
  letter-spacing: var(--tracking-title);
  line-height: var(--lh-tight);
  margin: 0 0 var(--sp-3);
  /* 标题比正文窄：大字长时间扫视会累（--measure-title 见 tokens.css） */
  max-width: var(--measure-title);
}

.page h2 {
  font-size: var(--fs-lg);
  margin: var(--sp-7) 0 var(--sp-3);
}

/* 小节分隔线：加在 h2（或任何块）上。两种方向，二选一，不要同时用。 */
.page .rule-top {
  border-top: 1px solid var(--border-decor);
  padding-top: var(--sp-4);
}
.page .rule-bottom {
  border-bottom: 1px solid var(--border-decor);
  padding-bottom: var(--sp-2);
  margin-bottom: var(--sp-4);
}

/* ============================================================================
   4. 行内代码 `code`
   ----------------------------------------------------------------------------
   typography.css 已经定义了 `code { font-family: var(--mono) }`。
   这里补齐"行内代码片段"剩下的部分（底色 / 内边距 / 圆角 / 相对字号），
   取自实测众数（badge / separator / switch / accordion / tabs / sparkline 六页）。

   ⚠️ 字号用 em 不用 rem：行内代码要跟着**它所在的那段文字**缩放。
      在 .small 里就该更小，在 h2 里就该更大。写成固定值会在小字区里显得突兀。
   ========================================================================= */
.page code {
  font-family: var(--mono);
  font-size: 0.9em;
  background: var(--surface-sunken);
  padding: 1px 4px;
  border-radius: var(--r-xs);
}

/* ============================================================================
   4b. 代码块的兜底：任何 <pre> 自己横向滚，不许把页面撑宽
   ----------------------------------------------------------------------------
   🔴 为什么需要（0.24.1 实测，不是推演）
     在 `.page` 环境里放一条 220 字符的长行：

       裸 <pre>                    doc=2048 / vw=375   ❌ 撑破
       <pre class="code">          doc=2211 / vw=375   ❌ 撑破   （没有 guide.css 时）
       <div class="prose"><pre>    doc=2048 / vw=375   ❌ 撑破   （没有 content.css 时）

     三种都是 `overflow-x: visible`、`white-space: pre`
     ⇒ 长行直接顶到页面上，整页横向滚。

     现有 84 个页面之所以没红，是因为每个 pre 恰好都另有来源：
     `.prose > pre`（content.css）、`pre.code`（guide.css）。**但那是巧合，不是契约**
     —— 使用者照 page.css 写一页文档、贴一段长代码，就会踩到。

   🔴 为什么用 `:where(.page)` 而不是 `.page`
     特异性压到 0,0,1（只算 `pre`）⇒ 任何带类的规则都能盖掉它：
       `.prose > pre`  (0,1,1) 赢
       `pre.code`      (0,1,1) 赢
     这条的定位是**兜底**，不是"给所有 pre 定样式"。写成 `.page pre`（0,1,1）
     会与上面两条**同权重**，胜负取决于加载顺序 —— 那就成了一个随时会翻的脸。

   ⚠️ 只写 `overflow-x`，不写 padding / background / font：
      那些是"代码块长什么样"，属于 content.css / guide.css 的决定。
      这里只管"它不许撑破页面"。加了别的属性就会改动已有页面的观感。
   ========================================================================= */
:where(.page) pre {
  overflow-x: auto;
}

/* ============================================================================
   5. 演示块 `.box`
   ----------------------------------------------------------------------------
   13 页里 12 页写法完全一致（`background: var(--surface)` + 1px 边框 +
   --r-md 圆角 + --sp-5 内边距），只有 1 页多了个 margin。
   ⇒ 直接收编，多出来的那页用 --box--tight 之外的方式解决（它其实不需要）。
   ========================================================================= */
.page .box {
  background: var(--surface);
  border: 1px solid var(--border-decor);
  border-radius: var(--r-md);
  padding: var(--sp-5);
}

/* 反例块：同一块，底色沉一档，用来放"错误示范" */
.page .box.bad {
  background: var(--surface-sunken);
}

/* ============================================================================
   6. 对错对照 `.cmp`（并排两栏：错写法 / 本库写法）
   ----------------------------------------------------------------------------
   为什么要收编（量过才写的，不是凭印象）：
     3 个页面（button / card / input）各自写了一遍，**语义完全相同**，
     但参数各是各的 —— `grid-template-columns` 都是 1.55fr 1fr（这一条一致，
     说明它确实是个固定意图），而 gap 是 sp-4 还是 sp-5、断点是 640 还是 700，
     三页三种。这不是"各页局部排布"，是同一个东西的无意分叉。
     判据与 `.row` 的区别就在这里：
       `.row`  15 页 14 种写法 —— **语义**都不一样（有的是工具条、有的是按钮排），不收；
       `.cmp`   3 页 3 种写法 —— **语义**一样，差的只是手抖，收。
     另外 21 个页面缺"能看见的反例"，补上之后若不收编，就是 24 份副本。

   为什么列宽是 1fr 1fr（而不是原来那三页的 1.55fr 1fr）：
     那三页的左栏是"一大段解释 + 示例"，右栏只是一句话，所以给左栏 1.55。
     现在每一栏都是"一个小示例 + 一句理由"，两栏等宽才不会让人以为
     某一栏更重要。真需要不等宽时用 `.cmp--wide-first`，别在页面里重写栅格。

   两栏各写一条能被机器量的断言（`data-bad` / `data-good`）：
     `<div class="cmp__pane cmp__bad"  data-bad="li.dd-bug|h|<=24">`
     `<div class="cmp__pane cmp__good" data-good="li.dd__item|h|>=44">`
     格式是 `选择器|指标|断言`，指标见 `05-audit/demo-claim-gate.js` 头部。
     两栏的断言必须**互斥**且各自成立 —— 能同时成立的两条断言，
     什么也证明不了。这两个属性不影响渲染，但缺了它们，
     这块对照就退化成"看着像错的"，`demo-claim` 门禁会报红。

   反例栏里还得有**标本**：真渲染出来的控件或内容。只有一段
   `<p class="small">` 描述"别这么做"，不算反例 —— 读者看不见那个"错"。
   摆不出来的（运行时行为 / 读屏输出 / 这页没有图表实例）写在块上：
     `<div class="cmp__pane cmp__bad" data-prose="为什么摆不出来">`
   单栏的 `box bad` 块没有成对结构，**在块上**写清为什么：
     `<div class="box bad" data-claim-skip="为什么写不出互斥断言">`
   合法的两种典型：两栏在视觉上必然相同（差别只在无障碍树里）、
   差别是对比度（那件事由 `contrast.py` 单独测）。理由会被逐条打印。
   ========================================================================= */
.page .cmp {
  display: grid;
  /* ⚠️ 必须是 `minmax(0, 1fr)`，不能写 `1fr`：`1fr` 的下限是 `auto`（= 内容最小宽度），
     一栏里只要有不换行的长串（`.ls-clip` 的 nowrap、`.ar-long` 的股票代码、宽表格），
     两栏就一起被顶宽 ⇒ **整页**横向溢出。
     实测代价：本库 4 个页面在 320px / 834px / RTL 阿拉伯语膨胀后溢出 81–155px，
     `zoom-reflow` `rtl-render` `responsive` 三道门禁同时红。
     ——这条正是 `04-recipes/analysis-report` 那一页在教别人的东西，
       我写这个收编的时候照着旧三页抄了 `1fr`，把要讲的坑又踩了一遍。 */
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--sp-4);
  /* 不写 align-items: start —— 两栏等高（stretch）边框才对得齐；
     start 会让短的那栏提前结束，看起来像"没写完"。 */
}
.page .cmp--wide-first {
  grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr);
}
/* 断点 640 而不是 720：文档页里的活标本 iframe 实测只有 734px 宽
   （guide/xxx.html 里那个框），720 的断点意味着"再窄一点就叠成单栏"——
   而叠成单栏，"并排"这件事就不存在了。留出 90px 余量。 */
@media (max-width: 640px) {
  .page .cmp,
  .page .cmp--wide-first,
  .page .cmp--clip { grid-template-columns: minmax(0, 1fr); }
}
.page .cmp__pane {
  padding: var(--sp-4);
  border: 1px solid var(--border-decor);
  border-radius: var(--r-md);
  background: var(--surface-sunken);
  /* 栅格项默认 min-width:auto ⇒ 内容最小宽度照样能把栏顶开。 */
  min-width: 0;
}
/* 内容本身就是"宽、且故意不收缩"的页（超长不换行串 / 宽表格）用这个修饰符。
   ⚠️ 为什么不写进 `.cmp__pane` 全局：`popover` / `tooltip` / `dropdown` 的对照块里
      气泡是绝对定位伸出栏外的，`overflow: hidden` 会把气泡直接裁掉 ——
      那几页的演示就没了。裁的是"演示内容越出栏框"，不是"栏内的布局算错"：
      栏内列的宽度仍按内容算，读者看到的还是被撑宽的那一栏。 */
.page .cmp--clip .cmp__pane { overflow: hidden; }
.page .cmp__pane > h3 {
  margin: 0 0 var(--sp-3);
  font-size: var(--fs-sm);
  font-weight: 500;
}
.page .cmp__bad > h3 { color: var(--danger); }
.page .cmp__good > h3 { color: var(--success); }
.page .cmp__why {
  margin: var(--sp-3) 0 0;
  font-size: var(--fs-xs);
  line-height: 1.6;
  color: var(--text-secondary);
}

/* ============================================================================
   反模式（本文件自己的边界）
   ----------------------------------------------------------------------------
   1. 在页面内联 <style> 里再写一遍上面任何一个选择器
      写了也不会生效吗？**会生效一部分，而且是静默的**：
      `.page h1`（0,1,1）会盖掉页面的 `h1`（0,0,1），
      页面那句变成死代码 —— 不报错、看不出来，只在你以为改了的时候没改。
      ⇒ 05-audit/shell-gate.py 拦这个。

   2. 在这里加组件样式
      这是骨架层，只放"改一处必须全库跟随"的东西。
      组件的样式属于组件自己的 CSS。

   3. 为了一个页面改这里的公共值
      一页的特殊需求请用页面自己的 class（不要挂到骨架类上），
      或者在 tokens.css 里加令牌。**不要**为了迁就一页而把骨架改窄。

   ⚠️ 有意没收编的（不是遗漏，是判断后不收）：
      · `.row`  15 页 14 种写法 —— 它是各页的局部排布工具，不是骨架；
                强行压成 1 种会破坏 9 个页面的实际布局。
      · `.log`  10 页 7 种写法，且其中 2 页的 `.log` 是"一行淡字"、
                另 8 页是"日志面板" —— 一个类名两种语义，收编前要先改名。
      · `.pad`  6 页 4 种写法，都是各页自己的占位高度。
      这三项的重复**不会**造成"改全局崩"（它们不是全局），
      所以留在页面里；shell-gate 会把它们作为"已知重复"报出来，但不 fail。
   ========================================================================= */
