/* ============================================================================
 * SGS — 角色层。页面声明「这是什么」, 格式自动跟上。
 *
 * 这是全站唯一允许出现 font-size / font-family / line-height / letter-spacing
 * 字面量的地方（另一处是 tokens.css, 它定义阶梯本身）。
 * test/design-roles.test.mjs 执行这条 —— 页面里再写一处字面量就会红。
 *
 * 为什么要有这一层： tokens.css 早就存在且写得很认真， 全站照样漂出 170 处脱阶梯字号、
 * 19 种行高、20 种字间距。原因不是没人知道标准， 是没有东西阻止页面另起一套。
 * 阶梯只是原材料， 角色才是让格式统一的机制。
 *
 * ⛔ 需要一个这里没有的角色时： 先在这里加， 并说清为什么现有的不够用。
 *    不要在页面里开特例 —— 这次要修的 170 处漂移全是这么来的。
 * ========================================================================== */

/* ---- 表单控件的字族复位 -------------------------------------------------------
 * 🔴 这不是一个角色， 是把浏览器让开。
 *
 * `<button> <input> <select> <textarea>` **不继承 font-family** —— 浏览器的 UA 样式表
 * 给表单控件另发一套字体。Chrome 上那套是 `Arial`（现造一个空 button 实测确认）。
 * 于是任何没有显式声明字族的控件都是 Arial， 而**源码里搜「Arial」是零结果**：
 * 它不写在任何文件里， 读源码的守卫永远看不见， 只有把页面渲染出来才认得出。
 *
 * 2026-08-24 生产实测：/prism 上 `.et-play`（那个 ▶ 播放键）、`.chat-fab`、
 * 以及聊天面板的 `.cp-close` 全是 Arial；/prism/mf 的播放键同样。而同页的 `.sgs-btn`
 * 是对的 —— 只因为 prism-ds.css 里那条规则**碰巧写了** font-family。
 * 「写了的对， 没写的全是 Arial」不是一个可以靠自律维持的状态。
 *
 * 放在角色层的最前面： 它只是把起点从 Arial 拨回体系， 后面任何角色类都能照常覆盖它。
 * ⚠️ 一个已知的残留： 字族对了不等于**字形**对了。`▶` `❚❚` `×` 这类符号如果目标字体里
 *    没有对应字形， 浏览器会逐字符回退到别的字体去取， 而 getComputedStyle 报的是**声明的**
 *    字族， 报不出这件事。渲染普查在这一点上是看不见的 —— 符号类按钮要另外肉眼确认。
 * --------------------------------------------------------------------------- */
button, input, select, textarea {
  font-family: inherit;
}


/* ---- 排版角色 ---------------------------------------------------------------
 * 行高与字间距长在角色里， 不单设 token。理由： 它们是字号的伴生属性， 拆开就会重演
 * 「有 token 也没人用」—— 实测行高 86 处声明里只有 2 处走了 token。
 * -------------------------------------------------------------------------- */
.r-hero    { font-family: var(--font-display); font-size: var(--fs-hero);    font-weight: 400; line-height: 1.15; letter-spacing: 0; }
.r-display { font-family: var(--font-display); font-size: var(--fs-display); font-weight: 400; line-height: 1.2;  letter-spacing: 0; }
.r-h1      { font-family: var(--font-display); font-size: var(--fs-h1);      font-weight: 400; line-height: 1.2;  letter-spacing: 0; }
.r-brand   { font-family: var(--font-display); font-size: var(--fs-h1);      font-weight: 500; line-height: 1.2;  letter-spacing: 0; }
.r-h2      { font-family: var(--font-display); font-size: var(--fs-h2);      font-weight: 400; line-height: 1.25; letter-spacing: 0; }
.r-metric  { font-family: var(--font-mono);    font-size: var(--fs-h2);      font-weight: 500; line-height: 1.2;  letter-spacing: 0; font-variant-numeric: tabular-nums; }
.r-lede    { font-family: var(--font-body);    font-size: var(--fs-h2);      font-weight: 400; line-height: 1.5;  letter-spacing: 0; }
.r-body    { font-family: var(--font-body);    font-size: var(--fs-body);    font-weight: 400; line-height: 1.65; letter-spacing: 0; }
.r-panel   { font-family: var(--font-body);    font-size: var(--fs-small);   font-weight: 400; line-height: 1.55; letter-spacing: 0; }
.r-value   { font-family: var(--font-mono);    font-size: var(--fs-small);   font-weight: 500; line-height: 1.55; letter-spacing: 0; font-variant-numeric: tabular-nums; }
.r-caption { font-family: var(--font-body);    font-size: var(--fs-caption); font-weight: 400; line-height: 1.5;  letter-spacing: 0; }
.r-label   { font-family: var(--font-mono);    font-size: var(--fs-caption); font-weight: 400; line-height: 1.4;
             letter-spacing: var(--track-label); text-transform: uppercase; }
/* Fix round 2 (Task 8): r-label defaults to 400 because 500 is the minority weight for
   tracked uppercase mono labels under every accounting anyone has been able to construct --
   not because of a specific count. Round 1's comment here cited one (29 against 5) and it did
   not survive a recount: it missed prism.html/prism-sfr.html's body-level font-weight:300
   (a third weight class, not 400) and never looked at styles/mf-market.css /
   styles/resi-market.css at all, both of which carry more explicit 500 mono/uppercase rules.
   No number here on purpose -- whichever task actually converts those files owns re-deriving
   the real one. .r-label--strong opts into 500 for the callers that want it; that set is also
   larger than first thought and needs its own recount there, not assumed from this comment. */
.r-label--strong { font-weight: 500; }
/* Task 23 fix round 4 (Eddie's ruling, 2026-08-20): the site has two label contexts, not one.
   The Prism data surfaces (panels, cards, chart chrome) are instrumentation -- .r-label's mono
   family reads as data there, which is the point. The marketing homepage runs an editorial
   voice (serif headings + Inter body); a mono kicker on that page would change its character,
   not just its label styling. That is the justification for a second label role instead of
   forcing every tracked-uppercase kicker through .r-label regardless of context.
   Found via Task 23's own verification, not invented ahead of a need: .tag and .credential-
   label (index.html) were assigned .a-caption -- sized for body captions, not tracked
   uppercase kickers -- and then hand-patched the letter-spacing/text-transform .a-caption
   doesn't supply. bb1229d already carried this exact letter-spacing:2px pairing on both
   selectors, predating this project entirely; .r-label--body reproduces it exactly (11px,
   Inter, 2px tracking, uppercase) so adopting it is a zero-visible-change role reassignment,
   not a new design decision -- see the verification report's §1.1b/§1.1c. The 2px tracking is
   a literal here, not var(--track-label) (1px, .r-label's own value): the two roles are
   deliberately allowed to disagree on tracking width because they're visually distinct
   contexts, and forcing one token across both would be the same "one role, ill-fitting
   everywhere" problem this role exists to fix.
   line-height is 1.6, not r-label's 1.4, for the same zero-visible-change reason: bb1229d never
   declared a line-height for .tag/.credential-label at all, so both inherited body's 1.6
   (measured: 17.59px / 11px = 1.6 exactly). Fix round 4's first attempt copied r-label's 1.4
   by analogy instead of measuring, which recaptured 15.4px against a bb1229d value of 17.6px --
   a small but real, unintended new visible change the "pixel-identical" promise this role exists
   to keep does not allow. Caught by recapturing and diffing against bb1229d, not by inspection.
   Task 25 (2026-08-20): there was a THIRD call site with the same history and fix round 4 missed it
   -- index.html's .logo-sub. Task 6's report lists all three (.logo-sub / .insight-card .tag /
   .credential-label) as taking .a-caption for the same reason, and measured .logo-sub at 11px at
   the time, correctly: .a-caption was --fs-caption then. Task 13 fix round 1 (300e8c2) re-pointed
   .a-caption to --fs-small for chart sub-captions and carried all three to 13px with it. Round 4
   found two of them because it was looking at the elements it had diffs for; .logo-sub produced no
   diff to look at, because it was not in the capture's selector list at all (Task 25 adds it). It
   now carries .r-label--body and its own color:var(--muted-strong), exactly like the other two. */
.r-label--body { font-family: var(--font-body); font-size: var(--fs-caption); font-weight: 400; line-height: 1.6;
                 letter-spacing: 2px; text-transform: uppercase; }
/* Task 25 round 3. The 24th role, and the only one whose line-height is `normal` on purpose.
   A single-line UI control's label should sit on the font's own metrics: a fixed ratio adds phantom
   leading above and below a word that has nothing above or below it, and the box is sized by
   padding + border, not by leading.
   Found the way .r-label--body was -- by measuring an element that had been given a role for the
   right reason and the wrong property. Task 6 gave index.html's .lang-toggle `.r-panel`, and its
   own report says why: "family/weight already matched (body/400)". True, and it says nothing about
   line-height. The same report, two paragraphs earlier, is explicit about the hazard: chrome
   elements were kept property-only "because the roles bake in letter-spacing:0 + a fixed
   line-height that would either fight the page's own override or silently shift it". It shifted.
   .r-panel is panel BODY TEXT at 1.55, so a <button> that had always rendered at the UA's `normal`
   grew from 28px tall to 30.14px (26 -> 30.14 in Chinese). Nobody saw it for the same reason nobody
   saw .logo-sub: .lang-toggle was not in the capture's selector list until Task 25 round 2.
   Measured against bb1229d (h=28, 13px, Inter, line-height normal, 1px tracking):
     .r-panel (what shipped) ....... h=30.14, line-height 20.15px   -- +2.14px, unapproved
     .a-btn ........................ h=23,    line-height 13px      -- -5px, a bigger change
     no role at all ................ h=29,    13.3333px Arial       -- the UA button font takes over
     .r-control (this) ............. h=28,    line-height normal    -- identical to production
   .a-btn is the more coherent design answer -- it is line-height:1 and every other button on the
   site takes it -- but it is not what this control has ever rendered as, so adopting it is a
   visible change needing its own ruling, not a defect fix. Recorded as a proposal in the Task 25
   report rather than shipped. If it is ever ruled in, this role goes with it.
   ⚠️ letter-spacing:0 here loses to the page's own declaration by source order (roles.css is a
   <link> before the page's <style>), which is how .lang-toggle keeps its 1px tracking -- the same
   mechanism Task 6 documented for the r-panel links, and the reason the specificity of any page
   rule that overrides a role must stay at or above (0,1,0). */
.r-control { font-family: var(--font-body); font-size: var(--fs-small); font-weight: 400;
             line-height: normal; letter-spacing: 0; }

/* ---- 语义角色 ---------------------------------------------------------------
 * 依据是「读者要回答哪个问题」, 不是「长什么样」。16 个旧类名收敛到这 6 个。
 * ⛔ 不留旧名的别名。留一个逃生口就会有人用 —— 与 11px 地板同一条理由。
 * -------------------------------------------------------------------------- */
/* Fix round 1 (Task 13): the addendum's role table routed a-caption through r-caption
   (11px), but spec Section 7 (chart roles) says chart sub-captions are 13/400/Inter/
   --muted-strong -- the 11px step in § 7 belongs to three other roles (legend items, axis
   ticks, notes/attribution), not sub-captions. This is the second time the addendum's table
   has disagreed with § 7 on a chart role (the first was a-legend, Fix round 1/Task 8 below);
   Section 7 governs chart roles, so a-caption follows it: --fs-small not --fs-caption.
   Pre-project sizes of the classes that (per the original brief's table) collapsed into this
   role were 11.5/12/13 -- the two that mattered most, entry-sub and g-sub, were already 13,
   confirming 11 was the wrong step, not that the 13-to-11 drop during collapse was an
   acceptable side effect (g-sub itself was later moved to .a-legend by Task 13's own content
   read -- see resi-market.css -- but its pre-project size is still the evidence that matters
   here: the a-caption/a-legend split didn't happen because either role was meant to be 11px). */
.a-caption    { font-family: var(--font-body); font-size: var(--fs-small);   font-weight: 400; line-height: 1.5; color: var(--muted-strong); }
.a-scope      { font-family: var(--font-body); font-size: var(--fs-caption); font-weight: 400; line-height: 1.5; color: var(--muted-strong); }
.a-source     { font-family: var(--font-mono); font-size: var(--fs-caption); font-weight: 400; line-height: 1.5; color: var(--muted-strong); }
.a-flag       { font-family: var(--font-body); font-size: var(--fs-caption); font-weight: 400; line-height: 1.5; color: var(--warn); }
.a-legend     { font-family: var(--font-body); font-size: var(--fs-caption); font-weight: 400; line-height: 1.4;
                letter-spacing: 0; color: var(--muted-strong); }
/* Fix round 1 (Task 8): the addendum's role table routed a-legend through r-label (mono, 500),
   but spec Section 7 (chart roles) says legend items are 11/400/Inter -- Section 7 governs
   chart roles, so a-legend follows it: body family, 400, no tracking, no uppercase. */
.a-disclosure { font-family: var(--font-body); font-size: var(--fs-small);   font-weight: 400; line-height: 1.7; color: var(--body); }

/* 出处固定在区块末尾， 与免责声明分开 —— 同一句话放法律条款里读作撇清责任， 放图下读作严谨。 */
.a-source { display: block; margin-top: var(--sp-2); }

/* Task 13: tokens.css 原来的 .disclosure(page-level legal block: filled/ruled/body-sized)那份
   "重"视觉搬进来 —— 否则 .a-disclosure 只有字体四件套， 撑不起被它替换掉的那个视觉， retire
   tokens.css 里的旧规则就会丢样式。属性逐一照抄， 不是重新设计。 */
.a-disclosure {
  padding: 16px 20px; background: var(--surface); border-left: 2px solid var(--accent);
}

/* ---- 按钮 -------------------------------------------------------------------
 * E4 的「同一概念只有一个类名」原本只写了标注， 但实测按钮有 15 种自造类名、
 * 共享的 .sgs-btn 零处使用 —— 按钮是页面上最显眼的可点元素。
 * ⚠️ 触摸目标尺寸（≥44×44)属于 U1, 不在本次。落地后 U1 只需改这里一处。
 * -------------------------------------------------------------------------- */
.a-btn {
  cursor: pointer; font-family: var(--font-body); font-size: var(--fs-small); font-weight: 400;
  line-height: 1; letter-spacing: 0; padding: var(--sp-2) var(--sp-3) ;
  border-radius: var(--radius-sm); background: none;
  border: 1px solid var(--border); color: var(--body);
  text-decoration: none;
  transition: border-color var(--dur-fast), color var(--dur-fast), background var(--dur-fast);
}
.a-btn:hover          { border-color: var(--accent-line); color: var(--ink); }
.a-btn--accent        { background: var(--accent); border-color: var(--accent); color: var(--on-accent); font-weight: 500; }
.a-btn--accent:hover  { background: var(--accent-strong); border-color: var(--accent-strong); color: var(--on-accent); }
/* 🔴 Task 8 实测发现： 没有这行 color 时， .a-btn:hover{color:var(--ink)} 与本规则同优先级
   (两条都是 .class:hover, 单类 + 伪类）, 靠源码顺序险胜 —— 但它只赢了 background/border-color,
   从没设过 color, 于是 hover 态的文字颜色只有 .a-btn:hover 那一条能匹配， 真落地成 --ink。
   --ink on --accent-strong 实测 2.33:1, 而这一段注释（见 test/contrast.test.mjs)断言的是
   "白字 on --accent-strong = 5.39" —— 断言过没有算过。index.html:154 的 hero CTA 已经在用
   .a-btn--accent, 同一个洞在那里同样成立， 不是这个页面独有的。 */
.a-btn--quiet         { border-color: transparent; color: var(--muted-strong); }
.a-btn--quiet:hover   { border-color: var(--border); color: var(--ink); }
/* 禁用态的低不透明度是 WCAG 明确豁免的（1.4.3 不适用于失效控件）。
   ⚠️ 正文绝不许用 opacity 压低 —— 实测 --muted-strong 加 opacity:.7 后只剩 2.83:1。
   见 test/contrast.test.mjs 的 opacity 断言。 */
.a-btn:disabled { opacity: .5; cursor: default; }

/* ---- 图表 figure ------------------------------------------------------------
 * E12: 标题 / 副说明 / 绘图区 / 口径注 / 来源， 固定顺序、同属一个单元。
 * E5: 图标题左对齐（推翻 2026-08-10 的居中决定）。
 * 图表只用 13px 与 11px 两级， 层级靠字族、字重、颜色区分， 不新增字号。
 * -------------------------------------------------------------------------- */
figure.a-figure { margin: 0 0 var(--sp-6); }
figure.a-figure > figcaption { text-align: left; }
figure.a-figure > figcaption .a-figure-title {
  font-family: var(--font-mono); font-size: var(--fs-small); font-weight: 500;
  line-height: 1.4; letter-spacing: 0; color: var(--ink); display: block;
}
figure.a-figure > figcaption .a-caption { display: block; margin-top: var(--sp-1); }
figure.a-figure > .a-plot { margin-top: var(--sp-3); }
figure.a-figure > .a-scope,
figure.a-figure > .a-source { display: block; margin-top: var(--sp-2); }
/* 图内标注压在填充上时垫一块衬板 —— 否则读起来是"两层叠在一起"而不是"一层注解一层数据"。 */
.a-plot-note {
  font-family: var(--font-body); font-size: var(--fs-small); font-weight: 400;
  line-height: 1.5; letter-spacing: 0; color: var(--body);
  background: color-mix(in srgb, var(--surface) 88%, transparent);
  padding: var(--sp-1) var(--sp-2); border-radius: var(--radius-sm);
}

/* ---- 选中文字 ---------------------------------------------------------------
 * 全站原本 0 处 ::selection, 选中底色是系统蓝。这一行让它用品牌绿。
 * -------------------------------------------------------------------------- */
::selection { background: var(--accent-soft); color: var(--ink); }

/* ---- 长文阅读语境 -------------------------------------------------------------
 * Task 26. `.r-read` 是第 25 个角色， 也是唯一一个**只带行高**、并且**与排版角色叠加使用**的。
 * 它取代 styles/article.css 的 `body { line-height: 1.8 }` —— 那一行的注释自己写着
 * 「文章阅读基准， 早于角色体系」, 也就是说它是一条**没有被角色层表达过的排版决定**,
 * 而不是漂移。决定层查证结果： 主 spec E1–E16 从未决定过阅读行高； 补遗 §7.2 唯一的裁定是
 * 「行高不单独设 token, 长进排版角色」—— 它规定的是行高的归属（角色层）, 正是这里做的事，
 * 没有一个字说 1.8 要收掉。
 *
 * 🔴 为什么它必须是叠加类， 而不是「一个 1.65→1.8 的正文角色」:
 * `body{line-height:1.8}` 是**继承**下去的， 五个报告页上 h2 / 站标 / 数字块 / 微标签 / 页脚
 * 全部按 1.8 排版 —— 实测 `50.4/28`(h2 与 .stat-value)、`19.8/11`(.tag/.logo-sub)、
 * `23.4/13`(.date/.stat-label/nav a.back/footer)、`28.8/16`(p/.highlight)。
 * 只补一个 16px/1.8 的正文角色， 只能抵消 `<p>` 那一档（la-market 实测 748.7px 里的 −62.3px),
 * 其余各档照样按各自角色的 1.2/1.55/1.6 重排， 五页仍会矮 5–7.4%(Task 25 报告实测
 * −235 ~ −307px)。要让转换真的是零可见变化， 1.8 必须跟着**每一个**被改派的元素走。
 *
 * ⚠️ 与 Eddie 2026-08-20 补 `.r-label--body` 的裁定同型： 站点有两种语境， 不是一种。
 * 长文阅读（五个报告页）的行距本来就比界面正文松， 这是这批页面从建站起的排版性格，
 * 不是某个页面私自加的特例。按铁律「需要标准里没有的东西时先改标准」, 补角色， 不在页面开特例。
 *
 * ⚠️ 它靠**源码顺序**赢下与其他角色的 (0,1,0) 平局 —— 所以它必须写在本文件最后。
 * test/design-roles.test.mjs 有一条断言钉住这个位置； 把它挪到别的角色前面， 行高就会静默失效。
 *
 * ============================================================================
 * ⛔⛔ 标准条款： 跨角色修饰类（cross-cutting modifier）—— Eddie 2026-08-21 裁定
 * ============================================================================
 * `.r-read` 是本层第一个「只带一个属性、并且与排版角色叠加使用」的类。这类东西**获准存在**，
 * 但必须同时满足两个条件， 二者都由 test/design-roles.test.mjs 强制：
 *
 *   ① **必须写在本文件的最后。** 它与排版角色同为 (0,1,0)， 打平只能靠源码顺序；
 *      写在任何一个角色前面， 它就静默失效， 而所有「角色类还在不在」的断言照样全绿。
 *
 *   ② **同一个元素不许带两个。** 这一条才是让这个模式安全的那一条。两个修饰类叠在一个
 *      元素上时， 谁赢取决于谁写在文件后面 —— 而写标记的人根本不会去读那个顺序。
 *      裁定是把它**禁掉**， 不是指望没人撞上。
 *
 * ⛔ 再加一个跨角色修饰类时： 定义写在本文件最末尾（排在已有修饰类旁边）， 并且必须登记进
 *    test/design-roles.test.mjs 的 CROSS_CUTTING_MODIFIERS —— 不登记， 两条断言都会放它过去。
 * ⛔ 这不是「可以随便加修饰类」的许可。默认解法仍然是**补一个自带四件套的完整角色**；
 *    只有当那样做会把阶梯复制一份时（`.r-read` 的情况： 否则要补七个 --read 变体）， 才用修饰类。
 * -------------------------------------------------------------------------- */
.r-read { line-height: 1.8; }
