/* ============================================================================
 * site.css —— 公开面 · 组件层（**生产**，`/static/site.css`）
 *
 * 配套：`app/static/tokens.css`（**令牌层，先加载**）
 *      本文件**一个令牌都不定义** —— 只写组件。
 *      ⚠️ 令牌在这一层重新定义一次 = 两份会各自漂移的真理，而「哪份是对的」
 *         没人知道。`check_tokens.py` 判的是令牌层，看不见这里。
 *
 * ⚠️ 本文件是**唯一一份**：公开面静态样板（`_design/public/`）直接链它
 *    （`../../app/static/site.css`，与 tokens.css 同款做法），不另拷一份。
 *    另拷一份只有两个下场：改版式要改两处、迟早漂移，而**没有任何判据会发现两份不一样**。
 *    ⇒ 样板要做实验，加**覆盖层**（那正是 `brands.css` 的形态），别复制本文件。
 *
 * 规矩（来自《网站设计安排 v1》§1.1 / §3）：
 *   1. 零外链 —— 字体/图标/CDN 都会把平台品牌漏到代理站点上（C2）
 *   2. `--brand` 只作用于**三处**：主按钮底 / 品牌强调块 / 强调描边。
 *      其余一律走 `--accent` 与中性色 —— 品牌色撒得越开，对比度风险面越大
 *   ★ 由此推出一条**本层自定的硬规矩**：`--brand` **只做底色与描边，不做文字色**。
 *     理由：色板里有浅色品牌（`--preset-sky` / `--preset-amber`）配**深字**，
 *     这时 `--brand-700` 当文字压在浅底上不够 4.5 ——
 *     浅底上的品牌色文字只能拿 `--ink`（实测 amber 系 `#1F2933` 压 `#FFF8EC` 极高）。
 *     品牌感由**底色的品牌调**提供，不由字色提供。写文字色一律 `--ink` / `--ink-soft` / `--muted`。
 *     （同族的历史事故：金色 `#D4AF37` 白底当文字只有 2.10:1，是量 computed style 才发现的。）
 *   3. 正文基准 13.5px 是**量出来的**，不是"看着小"就调大
 *   4. 层级靠描边不靠阴影（商务场景的阴影一重就显廉价）
 *
 * 类名：本层定义 97 个，六页实际用到 **89** 个 —— 比《设计安排》§2.1 的 60–80 预算多 9 个。
 *   ⚠️ 这 9 个恰好就是 `.mock-*` 那一族（mock / mock-bar / mock-body / mock-btn /
 *      mock-code / mock-dot / mock-drop / mock-row / mock-tr）。它是用 CSS 画的
 *      「界面截图」占位 ⇒ 净 80 个，正好顶着预算上沿。
 *      留着这条记录，是因为"预算超了"和"预算超在那一族上"是两回事，
 *      而单看那个 89 是看不出来的。
 *   ★★ 2026-10-22 复核：本层定义 **118** 个类名（登录页换版 +20、`.form-card` −1）。
 *      上面那个 97/89 是**当时的读数**，留作对照，⛔ 不是今天的数。
 *
 *   ★ 这一族**不是**脚手架，进生产后**刻意留着**（P1 搬模板时定的事，见下）：
 *     它们出现在 index / bid / inspect 三页的营销段落里，是"产品长什么样"的示意。
 *     换成真 `<img>` 的代价是**每张图都要为每个品牌重出**（截图上带平台配色 = C2 直接破防），
 *     而 CSS 画的图**跟着 `--brand` 自动变色**。⇒ 白标场景下，CSS 画的示意图
 *     比位图更省事、也更难漏品牌。真接 API 截图时再回来还这笔债。
 *     ⚠️ 因此**不要**因为"这是 mock"就把这一族删掉 —— 生产模板里有 30 处在用它们
 *        （`grep -c 'mock-' app/web/templates/*.html`），删了页面会塌成一片空白，
 *        而**没有任何判据**会报红（几何判据量的是"有没有越界"，空白不越界）。
 * ==========================================================================*/


/* ---------------------------------------------------------------- 基础 */
*,*::before,*::after{ box-sizing:border-box; }
html{ -webkit-text-size-adjust:100%; }
/* ⚠️ 竖排撑满 + main 吃余量 = **页脚永远贴在底边**。
   不加这两行的后果在 `auth.html` 上肉眼可见：内容只有 700px 高，
   页脚就停在 700px 处，底下空出来的一整块页底比页脚还显眼——
   自动化判据一条都不会响（没有越界、没有溢出、对比度全绿）。 */
body.site{
  margin:0; background:var(--bg); color:var(--ink); min-height:100vh;
  display:flex; flex-direction:column;
  font:var(--fs-base)/1.65 -apple-system,BlinkMacSystemFont,"Segoe UI",
       "Microsoft YaHei","PingFang SC","Hiragino Sans GB",sans-serif;
  /* ★ T6-04：**任何一串没有空格的文字都不许撑破视口**。
     中文靠字间断行本来不会溢出，但页面上有大量**外部数据**：
     客服邮箱 / 订单号 / 品牌名 / 阶段码 —— 它们可以是 60~80 个字符的英文或数字，
     浏览器默认 `overflow-wrap:normal` 不会在词中间断，于是一串 464px 的邮箱
     在 390px 屏上直接把页面撑到 480px。
     `anywhere` 而非 `break-word`：前者同时参与 min-content 计算，
     弹性/网格子项才**真的挤得动**（后者只在排版时断，子项的最小宽度仍按整串算）。 */
  overflow-wrap:anywhere;
}
body.site > main{ flex:1 0 auto; }
h1,h2,h3,p,ul,ol,figure{ margin:0; }
a{ color:var(--accent); text-decoration:none; }
a:hover{ text-decoration:underline; }
img{ max-width:100%; display:block; }
.sr-only{
  position:absolute; width:1px; height:1px; overflow:hidden;
  clip:rect(0 0 0 0); clip-path:inset(50%); white-space:nowrap;
}
:focus-visible{ outline:2px solid var(--accent); outline-offset:2px; border-radius:var(--r-xs); }


/* ---------------------------------------------------------------- 外壳 */
/* 内容列：1120 是设计定的读数，别"顺手"改成 1200 */
.wrap{ max-width:var(--w-wide); margin:0 auto; padding:0 var(--sp-6); }
.wrap.read{ max-width:var(--w-read); }
/* 阅读列：**左沿与页面基线对齐**，只把右边收窄。
   ⚠️ 它和 `.wrap.read` **不是一回事** —— 后者是 `margin:0 auto`（整块居中）。
      同一页里混用两者，那一个 section 会**整块平移**（1280 下差 100px），
      而几何判据一条都不响（不越界、不重叠、不溢出）。
      本次是看图才发现的：`inspect@1000` 的「常被问到的」比上面几个标题右移了 37px。
      `check_design.py` 的 L1 就是拿这条钉住的 —— 量的是 `.wrap` 的**盒左沿**，
      不是标题文字的左沿（文字左沿会被 `text-align:center` 带跑）。 */
.read-col{ max-width:var(--w-read); }

.head{
  position:sticky; top:0; z-index:20; background:var(--surface);
  border-bottom:1px solid var(--line);
}
.head-in{
  max-width:var(--w-wide); margin:0 auto; padding:0 var(--sp-6);
  height:var(--topbar-h); display:flex; align-items:center; gap:var(--sp-6);
  /* ⚠️⚠️ **不换行**是这里的硬规矩，不是"默认值恰好如此"：顶栏是 height 写死的
     单行条，内容一旦折行，子块会比顶栏还高、从它里面戳出来 —— 而 `.head-in`
     自己量不出来（它恒等于 58），所以这件事**只能量子块**（判据 H1）。 */
  flex-wrap:nowrap;
}
/* ⚠️ 品牌名长短是**代理数据**（代理完全可以叫"深圳市中建智远国际工程技术咨询
   有限责任公司"），所以弹性必须落在它身上：flex-shrink:1 + min-width:0 让它
   **可以被挤**，挤到装不下就在 `.brand-name` 上出省略号。
   这里原来写的是 `flex:0 0 auto`（不许挤），后果实测过（601px 档、10 字品牌名）：
   被挤的是**导航与按钮** —— 导航项折成"一行一个汉字"、高 77px，从 58px 的顶栏里
   戳出来，而**横向溢出还是 0** ⇒ 所有现成判据全绿，只有看图才看得见。
   `min-width:0` 不能省：名字上有 white-space:nowrap，它的自动最小尺寸就是
   整串文字宽，不显式归零的话这个块根本挤不动。 */
.brand{ display:flex; align-items:center; gap:var(--sp-2); flex:0 1 auto; min-width:0; }
.brand:hover{ text-decoration:none; }
/* 品牌标：**底色 + 配对 ink**，不做线条描出来的彩色图标。
   理由同文件头第 2 条 —— 浅色品牌画成线条压在白色页头上不够 3:1（非文本元素门限）。
   做成「底块」之后，`.btn-primary` 那一套配对规则原样适用，代理换色也换不出事故。
   真站点上这里换成代理上传的 logo；**没有就给文字品牌名**（§3.3 回落规则）。
   ⚠️ 真 logo 那一路（`<img>`）必须 `object-fit:contain`：盒子是**写死的 26×26**，
      不声明的话默认 `fill` 会把代理传的任意比例图**拉伸变形** —— 而这件事
      在尺寸上完全量不出来（盒子永远 26×26），只有 `object-fit` 看得见（判据 H3）。
      取 contain 不取 cover：宁可两侧留白，也不裁掉代理 logo 的内容。 */
.brand-logo{
  width:26px; height:26px; flex:0 0 auto; border-radius:var(--r-sm);
  background:var(--brand); color:var(--brand-ink);
  font-size:var(--fs-sm); font-weight:700; line-height:1;
  display:flex; align-items:center; justify-content:center;
}
img.brand-logo{ object-fit:contain; }
/* 名字过长 → 省略号：**不折行、不撑破顶栏**。⚠️ 这几条要成套写 ——
   少了 nowrap 会折行（顶栏就那么高），少了 overflow 看不见省略号，
   少了 min-width:0 挤不动（理由见 `.brand` 那段）。
   被省略的是**顶栏这一处**；完整品牌名仍在 <title> 与页脚（页脚是 wrap 布局，
   长名字多占一行，不影响别的）。 */
.brand-name{
  font-size:var(--fs-lg); font-weight:650; color:var(--ink); letter-spacing:.3px;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis; min-width:0;
}
/* ⚠️ 不许被挤：导航项被压缩会折成"一行一个汉字"，而顶栏高是写死的（见 `.brand`）。 */
.nav{ display:flex; align-items:center; gap:var(--sp-6); margin-left:var(--sp-4); flex:0 0 auto; }
.nav a{ color:var(--ink-soft); font-size:var(--fs-base); padding:4px 0; border-bottom:2px solid transparent; }
.nav a:hover{ color:var(--ink); }
/* ⚠️ 「当前页」**不用品牌色写文字**，用品牌色画下划线 —— 见文件头第 2 条规矩。
   当前态**只留一种视觉信号**：下划线。颜色 + 字重 + 下划线三件套一起上会让
   「当前页」比主按钮还重；`aria-current` 仍然保留给读屏。 */
.nav a[aria-current="page"]{ border-bottom-color:var(--brand); }
/* ⚠️ 同上：两个按钮（`white-space:nowrap`）被挤会折成两行，而顶栏高是写死的。 */
/* ⚠️ T6-04：登录后的**显示名是用户数据**，可以是一整串没有空格的邮箱。
   原来这里是 `flex:0 0 auto`（不许挤）⇒ 390px 档上它与品牌名互不相让，
   整条顶栏被顶出视口。改成可挤之后，名字走省略号、按钮保持原尺寸：
   "退到看不见"的只能是名字，不能是「退出」这个动作。 */
.head-cta{ margin-left:auto; display:flex; align-items:center; gap:var(--sp-3); flex:0 1 auto; min-width:0; }
.head-cta .link-quiet{
  color:var(--ink-soft); min-width:0;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.head-cta form{ flex:0 0 auto; }

.foot{
  margin-top:var(--sp-8); border-top:1px solid var(--line);
  background:var(--surface);
}
.foot-in{
  max-width:var(--w-wide); margin:0 auto; padding:var(--sp-6);
  display:flex; flex-wrap:wrap; gap:var(--sp-5) var(--sp-8);
  color:var(--muted); font-size:var(--fs-sm);
}
.foot-in a{ color:var(--muted); }
.foot-in a:hover{ color:var(--ink); }
/* ---- 页脚分组：列标题 + 链接；390 下按组堆叠，标题保留 ----
   ⚠️ 没有分组标题时，窄屏页脚会折成一长串无标题链接（导航在 ≤600 已收起，
   页脚是产品页唯一的兜底入口）⇒ 这里按「产品 / 方案 / 资源」三组排。 */
.foot-col{ display:flex; flex-direction:column; gap:var(--sp-1); min-width:0; }
.foot-h{
  margin:0 0 var(--sp-1); font-size:var(--fs-xs); font-weight:600;
  color:var(--ink-soft); letter-spacing:.04em;
}
.foot-meta{
  display:flex; flex-direction:column; gap:var(--sp-2);
  margin-left:auto; min-width:0; max-width:100%;
}
.foot-meta .foot-note{ margin-left:0; }
/* ⚠️ T6-04：客服串是**代理数据**，可以是一整串没有词间空格的邮箱。
   `margin-left:auto` 让它贴右；不显式给 `min-width:0` 时，flex 子项的最小宽度
   是整串的 max-content（实测 463.7px）⇒ 390px 档把整页撑到 480px。
   断行规则由 body 的 `overflow-wrap:anywhere` 继承，这里只负责"允许被挤"。 */
.foot-note{ margin-left:auto; min-width:0; max-width:100%; }
@media (max-width:820px){
  .foot-meta{ margin-left:0; }
}
@media (max-width:480px){
  .foot-in{ flex-direction:column; gap:var(--sp-5); }
}


/* ---------------------------------------------------------------- 按钮 */
.btn{
  display:inline-flex; align-items:center; justify-content:center; gap:var(--sp-2);
  height:38px; padding:0 var(--sp-5); border-radius:var(--r-md);
  border:1px solid transparent; font-size:var(--fs-md); font-weight:600;
  line-height:1; cursor:pointer; white-space:nowrap;
}
.btn:hover{ text-decoration:none; }
.btn-primary{ background:var(--brand); color:var(--brand-ink); }
.btn-primary:hover{ background:var(--brand-700); }
.btn-ghost{ background:var(--surface); color:var(--ink); border-color:var(--line-strong); }
.btn-ghost:hover{ background:var(--accent-50); border-color:var(--accent); color:var(--accent-on-soft); }
.btn-sm{ height:31px; padding:0 var(--sp-4); font-size:var(--fs-base); }
.btn-lg{ height:46px; padding:0 var(--sp-7); font-size:var(--fs-lg); }
.btn-row{ display:flex; gap:var(--sp-3); flex-wrap:wrap; align-items:center; }


/* ---------------------------------------------------------------- 首屏 */
.hero{ padding:var(--sp-8) 0 var(--sp-7); }
/* ⚠️ 这里原本有一行 `.hero .wrap{ max-width:var(--w-wide); }` —— 已删。
   它在五个页面上**是空转的**（那些页的 hero 里就是 `.wrap`，本来就 1120），
   唯一真正起作用的地方是 `help.html`：那一页整页是 `.wrap read`（920 的阅读列），
   而 `.hero .wrap` 与 `.wrap.read` **特异性相同、源序更后** ⇒ 静默把 hero 顶回 1120。
   症状是 help 页的 hero 比正文宽出 200px、左沿差 100px，而
   越界 / 重叠 / 溢出 / 对比度**一条都不响**（`check_design.py` 的 L1 抓的就是它）。
   一条"只在别处生效、且生效方式是破例"的规则，删掉比留着好。 */
.hero-eyebrow{
  display:inline-block; margin-bottom:var(--sp-4); padding:3px var(--sp-3);
  background:var(--brand-soft); color:var(--ink);
  border-radius:var(--r-full); font-size:var(--fs-sm); font-weight:600;
}
/* 38px 是 --fs-4xl，设计定的。中文标题别再往上堆 */
.hero-title{ font-size:var(--fs-4xl); line-height:1.25; font-weight:700; letter-spacing:-.2px; }
/* 首屏主标题在 390 下会把「中标」孤到第二行（实测一行 13 字）。这支换行只在
   放不下整句的窄屏生效，断在「让每一份标书都 / 更合规、更中标」；480 起整句一行放得下
   （实测 fs30、容器 448 恰好不换行）⇒ 上限取 479，别跟着 767 那个断点走。 */
.br-narrow{ display:none; }
@media (max-width:479px){ .br-narrow{ display:inline; } }
.hero-sub{ margin-top:var(--sp-5); font-size:var(--fs-lg); color:var(--ink-soft); max-width:var(--ch); }
.hero-actions{ margin-top:var(--sp-6); }
.hero-note{ margin-top:var(--sp-4); font-size:var(--fs-sm); color:var(--muted); }
.hero.centered{ text-align:center; }
.hero.centered .hero-sub{ margin-left:auto; margin-right:auto; }
.hero.centered .btn-row{ justify-content:center; }


/* ---------------------------------------------------------------- 区块 */
.section{ padding:var(--sp-7) 0; }
.section.alt{ background:var(--surface); border-top:1px solid var(--line); border-bottom:1px solid var(--line); }
.section-title{ font-size:var(--fs-2xl); font-weight:680; letter-spacing:-.1px; }
.section-sub{ margin-top:var(--sp-3); color:var(--ink-soft); max-width:var(--ch); }
.section-head{ margin-bottom:var(--sp-6); }

.grid{ display:grid; gap:var(--sp-5); }
.grid.c2{ grid-template-columns:repeat(2,minmax(0,1fr)); }
.grid.c3{ grid-template-columns:repeat(3,minmax(0,1fr)); }
/* ⚠️ 这里**没有** `.grid.c3` 的中间档 —— 这是量出来的，不是省事。
   原先写着 `@media (max-width:1000px){ .grid.c3{ 2 列 } }`，理由是一句想当然的
   「三列在窄屏挤，塌两列好读」。1000px 下逐页看图：`.grid.c3` 里**都是 3 张卡**，
   塌成 2 列 ⇒ 第 3 张独占一行、右边空出小半屏（定价页约 470×300 的空洞）。
   而**没有一条判据会响**：不越界、不重叠、不溢出，落差聚类因为第 3 张独自成组被跳过。
   接缝宽度的"什么都没有"是这一族最典型的形态（历史事故：右栏 268px 的洞）。
   ⇒ 3 列一路留到 820 再塌成一列。821px 时每张卡约 239px，实测放得下。 */
@media (max-width:820px){
  .grid.c2,.grid.c3{ grid-template-columns:minmax(0,1fr); }
  .wrap,.head-in,.foot-in{ padding-left:var(--sp-4); padding-right:var(--sp-4); }
  .hero-title{ font-size:var(--fs-3xl); }
  /* ⚠️ T6-04 复验（`_selfcheck_render` 的 601 档 + 21 字品牌名变体）：601–820
     这一段导航**是可见的**，而 `.nav` 与 `.head-cta` 都不许被挤（折行会戳破
     写死高度的顶栏）⇒ 能伸缩的只剩品牌名，它挤到 0 之后还差 20px。
     修法是把两处间距各收一档（`30 → 16`），⛔ 不是让导航折行、也不是
     `overflow-x:hidden`（那是藏，不是修）。601 那一档正是 `.nav` 刚从
     `display:none` 回来、又还没到 820 的接缝，只有它撑不住。
     ⚠️ 2026-10-23（导航从 4 项加到 5 项「分销合作」）601 档又差 18px：
        再收一档 `16 → 12`，并把 `.nav` 的左外边距 `16 → 8`（合计让出 32px）。
        加导航项**必须**回来跑 `_selfcheck_render` —— 那条 601 档的阳性对照
        只在「5 项 + 21 字品牌名」这个组合下才看得见。 */
  .head-in{ gap:var(--sp-2); }
  .nav{ gap:var(--sp-3); margin-left:0; }
  /* ⚠️⚠️ 真正吃掉最后 4px 的是**按钮不可压缩**：收缩被分到 `.head-cta` 头上时，
     里面的「免费注册」按钮（`white-space:nowrap`）压不下去 ⇒ 它右沿戳出视口 4px，
     而 `.head-cta` 自己的盒子还在里面 —— 量 `.head-cta` 永远是"装得下"。
     ⇒ 601–820 这一段让它**整体不参与收缩**（见下面那条 601–820 的规则）；
      ⛔ 不能在 ≤600 也这么写：那里登录态的用户名要靠 `.head-cta` 可缩才出省略号，
        `check_overflow390` 的 `stress-header-long-user@390` 当场回到 575px。 */
  /* 5 项导航在 601 档还差 6px：字号收半档（13.5 → 12.5）让出约 17px。
     ⛔ 不走"把 601 档的导航藏起来"——那会让 601–820 这一段整段没有导航；
     ⛔ 也不走 `overflow:hidden`。 */
  .nav a{ font-size:var(--fs-sm); }
  .head-cta{ gap:var(--sp-2); }
}
/* 601–820 这一档：导航可见、按钮又压不下去 ⇒ 让 `.head-cta` 保持自然宽度，
   收缩**全部**落到品牌名上（它就是为此设计的：名字走省略号，按钮保持原尺寸）。
   ⚠️ 上下界都要写：上限 820 与上一条媒体查询同界，下限 600 是 `.nav` 消失的那一档 ——
      再窄就只剩"名字 vs 按钮"，那时反而必须让 `.head-cta` 可缩。 */
@media (min-width:601px) and (max-width:820px){
  .head-cta{ flex:0 0 auto; }
}
@media (max-width:600px){
  .head-in{ gap:var(--sp-3); }
  .nav{ display:none; }   /* 窄屏收起导航，入口在页脚与首屏按钮里都有 */
}
/* 390 档的区块内边距：`.section` / `.hero` 在 820 档仍是 42px 上下的留白，
   在 844 高的视口里那是一大截首屏。收的是**留白**，不是内容。
   （登录页自己那一套内边距在 `--- 登录 / 注册` 段的 ≤480 / 矮视口两条 media 里。） */
@media (max-width:480px){
  .section{ padding:var(--sp-6) 0; }
  .hero{ padding:var(--sp-7) 0 var(--sp-6); }
}


/* ---------------------------------------------------------------- 卡片 */
.card{
  background:var(--surface); border:1px solid var(--line);
  border-radius:var(--r-lg); padding:var(--sp-6); box-shadow:var(--sh-1);
}
.card.tight{ padding:var(--sp-5); }
.card-h{ font-size:var(--fs-xl); font-weight:660; }
.card-sub{ margin-top:var(--sp-2); color:var(--ink-soft); }
.card-b{ margin-top:var(--sp-4); }
.card-edge{ border-left:3px solid var(--brand); }


/* ---------------------------------------------------------------- 三步 */
.steps{ display:grid; gap:var(--sp-5); grid-template-columns:repeat(3,minmax(0,1fr)); }
@media (max-width:820px){ .steps{ grid-template-columns:minmax(0,1fr); } }
.step{ display:flex; gap:var(--sp-4); }
.step-n{
  flex:0 0 auto; width:26px; height:26px; border-radius:var(--r-full);
  background:var(--brand-soft); color:var(--ink);
  font-size:var(--fs-base); font-weight:700;
  display:flex; align-items:center; justify-content:center;
}
.step-t{ font-size:var(--fs-md); font-weight:640; }
.step-d{ margin-top:var(--sp-1); color:var(--ink-soft); font-size:var(--fs-base); }


/* ---------------------------------------------------------------- 清单 */
.list{ list-style:none; padding:0; display:grid; gap:var(--sp-3); }
.list li{ position:relative; padding-left:var(--sp-6); color:var(--ink-soft); }
.list li::before{
  content:""; position:absolute; left:2px; top:.55em;
  width:9px; height:5px; border-left:2px solid var(--brand);
  border-bottom:2px solid var(--brand); transform:rotate(-45deg);
}
.list li b{ color:var(--ink); font-weight:640; }


/* ---------------------------------------------------------------- 截图占位 */
/* 样板上用 CSS 画的"界面截图"。真页面换成 <img> —— 这一块只是为了让
   版式关系看得见，别把它当成最终视觉。 */
.mock{
  background:var(--surface); border:1px solid var(--line-strong);
  border-radius:var(--r-md); overflow:hidden; box-shadow:var(--sh-2);
}
.mock-bar{
  height:26px; background:var(--surface-2); border-bottom:1px solid var(--line);
  display:flex; align-items:center; gap:6px; padding:0 var(--sp-3);
}
.mock-dot{ width:7px; height:7px; border-radius:var(--r-full); background:var(--line-strong); }
.mock-body{ padding:var(--sp-5); display:grid; gap:var(--sp-3); }
.mock-drop{
  border:1px dashed var(--line-strong); border-radius:var(--r-sm);
  padding:var(--sp-5); text-align:center; color:var(--muted); font-size:var(--fs-sm);
  background:var(--surface-2);
}
/* 假清单行：左边一个规则码，右边两行灰条。
   第一版这里只有五根灰条，1280px 下摊成一大片空盒子，**什么也没说**——
   「版式占位」和「看着像坏了」之间只差一个看得出结构的形状。 */
.mock-tr{ display:flex; gap:var(--sp-3); align-items:flex-start; }
.mock-tr > div{ flex:1 1 auto; display:grid; gap:6px; }
.mock-code{
  flex:0 0 auto; padding:2px 6px; border-radius:var(--r-xs);
  border:1px solid var(--line); background:var(--surface-3); color:var(--muted);
  font:700 var(--fs-xs)/1.5 ui-monospace,Consolas,"Courier New",monospace;
}
.mock-row{
  height:11px; border-radius:var(--r-xs); background:var(--surface-3);
}
.mock-row.w70{ width:70%; } .mock-row.w45{ width:45%; } .mock-row.w85{ width:85%; }
.mock-row.brand{ background:var(--brand-200); }
.mock-btn{
  justify-self:start; height:26px; padding:0 var(--sp-4); border-radius:var(--r-sm);
  background:var(--brand); color:var(--brand-ink);
  font-size:var(--fs-sm); font-weight:600; display:inline-flex; align-items:center;
}


/* ---------------------------------------------------------------- 定价 */
.price{ display:flex; flex-direction:column; }
.price.rec{ border-color:var(--brand); border-width:2px; position:relative; }
.badge{
  position:absolute; top:-11px; left:var(--sp-6);
  background:var(--brand); color:var(--brand-ink);
  font-size:var(--fs-xs); font-weight:700; letter-spacing:.5px;
  padding:3px var(--sp-3); border-radius:var(--r-full);
}
.price-amt{ font-size:var(--fs-3xl); font-weight:700; letter-spacing:-.5px; }
.price-unit{ font-size:var(--fs-base); font-weight:500; color:var(--muted); margin-left:var(--sp-1); }
.price-name{ font-size:var(--fs-md); font-weight:640; }
.price-desc{ margin-top:var(--sp-1); color:var(--muted); font-size:var(--fs-sm); }
.price-list{ margin-top:var(--sp-5); }
.price-act{ margin-top:auto; padding-top:var(--sp-5); }
.price.rec .price-act .btn{ width:100%; }


/* ---------------------------------------------------------------- 表单 */
/* ⚠️ `.form-card`（单列 420 宽居中的白卡）2026-10-22 已删除：它只有 `/auth` 一页在用，
   而那一页换成了 `.auth-modal` / `.auth-modal--solo`（同样的尺寸语义）。 */
.field{ margin-bottom:var(--sp-5); }
.field label{ display:block; font-size:var(--fs-base); font-weight:600; margin-bottom:var(--sp-2); }
.input{
  width:100%; height:40px; padding:0 var(--sp-4);
  border:1px solid var(--line-strong); border-radius:var(--r-md);
  background:var(--surface); color:var(--ink); font-size:var(--fs-md);
  font-family:inherit;
}
.input::placeholder{ color:var(--muted-2); }
.input:focus{ outline:none; border-color:var(--accent); box-shadow:0 0 0 3px var(--accent-50); }
/* 验证码：数字之间拉开，方便一位一位念。
   ⚠️ 字距会**连 placeholder 一起拉**，于是「6 位数字」被摊成「6  位  数  字」，
   看着像坏了。占位符要单独把字距还回去（这条只有看图才发现）。 */
.input-code{ letter-spacing:6px; font-variant-numeric:tabular-nums; }
.input-code::placeholder{ letter-spacing:normal; }
.field-row{ display:flex; gap:var(--sp-3); }
/* 输入框的默认最小宽度是 `size` 个字符，flex 子项默认 `min-width:auto`
   ⇒ 窄屏（390）上"验证码 + 获取验证码"那一行会被输入框的固有宽度顶出去。
   `min-width:0` 才让它真的能缩，宽度仍由 `flex:1 1 auto` 分。 */
.field-row .input{ flex:1 1 auto; min-width:0; }
.hint{ margin-top:var(--sp-2); font-size:var(--fs-sm); color:var(--muted); }
.form-act{ margin-top:var(--sp-6); }
.form-act .btn{ width:100%; }
.form-foot{ margin-top:var(--sp-5); text-align:center; font-size:var(--fs-sm); color:var(--muted); }


/* ---------------------------------------------------------------- 提示条 */
/* 三态里的「错误态」：文案必须含「发生了什么 + 你该点哪」（C4 判据） */
.alert{
  display:flex; gap:var(--sp-3); padding:var(--sp-3) var(--sp-4);
  border-radius:var(--r-md); border:1px solid; font-size:var(--fs-base);
  /* 错误文案里会带**用户输入/外部数据的原文**（文件名、订单号），
     它们可以是一整串没有空格的字符。见 body.site 的 overflow-wrap 说明。 */
  min-width:0;
}
.alert > div{ min-width:0; }
.alert .alert-t{ font-weight:640; }
.alert .alert-d{ color:var(--ink-soft); margin-top:2px; }
.alert.err{  background:var(--err-bg);  border-color:var(--err-line);  }
.alert.err .alert-t{ color:var(--err); }
.alert.ok{   background:var(--ok-bg);   border-color:var(--ok-line);   }
.alert.ok .alert-t{ color:var(--ok); }
.alert.info{ background:var(--info-bg); border-color:var(--info-line); }
.alert.info .alert-t{ color:var(--info); }


/* ---------------------------------------------------------------- 登录 / 注册 */
/* 2026-10-22 **二次换版**：**浮层弹窗**（遮罩 + 居中弹窗 + 右上角关闭），
   取代同日早些时候那张"页面里的居中卡"（`.auth-card`）。用户要的是
   「弹出窗口 + 商务风」。仍是独立路由 `/auth`，改的是壳不是模型。
   文件头那两条硬规矩在这里的落点是：
     · 深色块 = `--brand` 底 + `--brand-ink` 字。这一对由令牌层**成对注入**
       （深色系 12 个配白字、浅色系 2 个配深字）⇒ 代理换色自动跟着走，
       对比度不由本层负责，所以这里**不要**出现第二套品牌色（`--auth-*` 之类）。
     · 活动 Tab 的文字用 `--ink`、下划线用 `--brand` —— 「`--brand` 只做底色与
       描边、不做文字色」对这里同样成立（B1 判据扫的是 `color:` 上的 `--brand*`）。
     · 四个卖点前面的圆点是 `background:var(--brand-ink)`，不是字色。
   ★ 切换器是**纯 CSS**（`sr-only` 单选 + label + `:checked`），零脚本 ——
     公开面不许有 `<script>`。 */
/* 遮罩：整块视口压暗，弹窗浮在上面。
   ⚠️ 页头页脚仍在 DOM 里（删了要动 base.html，而且免责声明 / 备案号是按 HTML 文本
      查的），由 `.site.is-auth .head/.foot{visibility:hidden}` 藏起来 ——
      `visibility` 连 Tab 顺序一起移出，弹窗才配得上 `aria-modal="true"`。 */
.auth-overlay{
  position:fixed; inset:0; z-index:60;
  display:flex; justify-content:center; align-items:flex-start; overflow:auto;
  padding:var(--sp-6);
  background:rgba(10,37,64,.55);   /* 深蓝底上的半透明压暗（不是黑：黑压上去发灰） */
}
/* `.wrap` 在这里不承担宽度，只当**居中器**（`check_render` 的 L2 又要求每页都有它）。
   `margin:auto 0` 让它在遮罩里垂直居中；装不下时自动边距归零 ⇒ 从顶部开始滚，
   ⛔ 不用 `align-items:center`（居中会把上边裁掉，Tab 到第一个输入框时看不见它）。 */
.auth-overlay .wrap{
  flex:1 1 auto; max-width:none; padding:0; margin:auto 0;
  display:flex; justify-content:center;
}
.auth-modal{
  position:relative; width:100%; max-width:920px; margin:0;
  display:grid; grid-template-columns:minmax(0,1fr) minmax(0,1fr);
  background:var(--surface); border:1px solid var(--line);
  border-radius:var(--r-xl); overflow:hidden; box-shadow:var(--sh-3);
}
/* 已登录态：这一页退化成"我是谁 + 退出"的小面板。 */
.auth-modal--solo{ display:block; max-width:420px; padding:var(--sp-7); }
/* 关闭：右上角一枚 32×32 的链接（不是按钮 —— 它只回首页，不提交任何东西）。
   ⚠️ 次要动作，颜色停在 `--muted`、hover 才到 `--ink`，不跟白卡里的主按钮抢注意力。 */
.auth-close{
  position:absolute; top:var(--sp-3); right:var(--sp-3); z-index:1;
  width:32px; height:32px; display:grid; place-items:center;
  border-radius:var(--r-md); color:var(--muted); text-decoration:none;
  font-size:20px; line-height:1;
}
.auth-close:hover{ color:var(--ink); background:var(--surface-2); }
.auth-close:focus-visible{ outline:2px solid var(--accent); outline-offset:2px; }
/* 壳藏起来（登录页专用）。只藏"看不见的壳"，不动 `main`。 */
.site.is-auth .head,
.site.is-auth .foot{ visibility:hidden; }
.auth-page-title{ font-size:var(--fs-xl); font-weight:660; }
.auth-page-sub{ margin-top:var(--sp-3); color:var(--ink-soft); }

/* ---- 左：品牌块 ---------------------------------------------------------- */
.auth-brand{
  background:var(--brand); color:var(--brand-ink);
  padding:var(--sp-7); display:flex; flex-direction:column; gap:var(--sp-6);
}
.auth-logo{ display:flex; align-items:center; gap:var(--sp-3); }
/* 描边字母标。
   ⚠️ 不用「`--brand-ink` 底 + 品牌色字」那种反白块：浅色系品牌（sky / amber）
      配的是**深字** `--brand-ink`，反白块会变成深底深字；而把字色写成
      `var(--brand)` 又撞上「`--brand` 不做文字色」。描边 + 继承色对两种品牌
      都成立，也不用再维护第三种颜色。 */
.auth-logo-mark{
  width:30px; height:30px; border-radius:var(--r-md);
  border:1.5px solid var(--brand-ink); display:grid; place-items:center;
  font-size:var(--fs-md); font-weight:700; line-height:1;
}
.auth-logo-name{ font-size:var(--fs-md); font-weight:640; }
.auth-brand-copy{ margin:auto 0; }
.auth-brand-title{
  font-size:var(--fs-2xl); line-height:1.35; font-weight:660; color:var(--brand-ink);
}
/* ⚠️ 不叠 opacity：半透明的前景色压在**代理自定义**的品牌底上，对比度就不再由
   令牌层保证了 —— 而 C2 那条判据量到的正是这个配对。 */
.auth-brand-lede{ margin-top:var(--sp-3); font-size:var(--fs-md); line-height:1.6; }
.auth-points{ list-style:none; margin:0; padding:0; display:grid; gap:var(--sp-3); }
.auth-points li{
  position:relative; padding-left:var(--sp-5);
  font-size:var(--fs-base); line-height:1.6; min-width:0;
}
.auth-points li::before{
  content:""; position:absolute; left:0; top:7px;
  width:6px; height:6px; border-radius:var(--r-full); background:var(--brand-ink);
}

/* ---- 右：表单区 ---------------------------------------------------------- */
.auth-form-side{ padding:var(--sp-7); display:flex; flex-direction:column; }
/* 两个单选按钮带 `.sr-only`：键盘到得了、读屏念得出，但不占版面。 */
.auth-tabs{ display:flex; gap:var(--sp-6); border-bottom:1px solid var(--line); }
.auth-tab{
  padding-bottom:var(--sp-3); margin-bottom:-1px;
  font-size:var(--fs-md); color:var(--muted); cursor:pointer; white-space:nowrap;
  border-bottom:2px solid transparent;
}
.auth-tab:hover{ color:var(--ink); }
#mode-login:checked ~ .auth-tabs [for="mode-login"],
#mode-register:checked ~ .auth-tabs [for="mode-register"]{
  color:var(--ink); font-weight:640; border-bottom-color:var(--brand);
}
/* 焦点环画在 label 上：单选按钮本身是 1×1 的 `.sr-only`，画在它身上看不见。
   ⚠️ 这不是装饰：去掉之后键盘用户在这两个切换器上**没有**任何位置提示，
     而自动判据量不到这件事（`getBoundingClientRect` 看不见"看不见"）。 */
#mode-login:focus-visible ~ .auth-tabs [for="mode-login"],
#mode-register:focus-visible ~ .auth-tabs [for="mode-register"]{
  outline:2px solid var(--accent); outline-offset:2px; border-radius:var(--r-xs);
}
/* ⚠️ 面板显隐**只靠这两条 `:checked`**。⛔ 不要给面板加 `hidden` 属性：
   属性优先于样式，加了以后点 Tab 也切不出来（页面会像"按钮坏了"）。 */
.auth-panel{ display:none; padding-top:var(--sp-6); }
#mode-login:checked ~ #panel-login,
#mode-register:checked ~ #panel-register{ display:block; }
.auth-alert{ margin-top:var(--sp-5); }
/* 验证码回退：收在 `<details>` 里（用密码的人不该被它干扰）。
   展开后它是登录表单的**同一段提交**，所以只补间距，不另起一块。 */
.auth-alt{ margin-top:var(--sp-5); }
.auth-alt summary{ cursor:pointer; }
.auth-alt .field{ margin-top:var(--sp-4); margin-bottom:0; }
/* 页脚两句（帮助中心 / 条款）：原来各是一个 `.form-foot`，两条之间 22px 太空 */
.auth-foot{
  margin-top:var(--sp-5); display:grid; gap:var(--sp-2);
  text-align:center; font-size:var(--fs-sm); color:var(--muted);
}

@media (max-width:820px){
  /* 单栏：品牌块变成卡片顶部的一条深色带。
     ⚠️ 不能只把 `grid-template-columns` 改成一列了事 —— `.auth-brand` 的
        `margin:auto 0` 在单栏里会把标题推到整块的正中、卖点压到卡片底部。 */
  .auth-modal{ grid-template-columns:minmax(0,1fr); max-width:520px; }
  .auth-brand{ padding:var(--sp-5) var(--sp-6); gap:var(--sp-4); }
  .auth-brand-copy{ margin:0; }
  /* 卖点改横排：四条竖排会把卡片顶高，矮视口就装不下主按钮了 */
  .auth-points{ grid-template-columns:1fr 1fr; gap:var(--sp-2) var(--sp-4); }
  .auth-form-side{ padding:var(--sp-6); }
}
/* 639 而不是 480：实测 483px 宽（Windows 上"390"截图的真实视口）时半栏横排会把
   「评分点不漏项」拆成「评分点不漏 / 项」—— 中文词被拆断，而几何判据看不出来
   （它不越界、也不溢出，只是难看）。真 390 当然也要竖排，所以门限定在能容下
   最长那条卖点的宽度上。 */
@media (max-width:639px){
  /* 长 lede 让位，四条卖点保留（竖排一行一条） */
  .auth-brand-lede{ display:none; }
  .auth-points{ grid-template-columns:minmax(0,1fr); gap:var(--sp-1); }
  .auth-points li{ font-size:var(--fs-sm); line-height:1.5; }
}
@media (max-width:480px){
  .auth-overlay{ padding:var(--sp-3); }
  .auth-brand{ padding:var(--sp-4) var(--sp-5); }
  .auth-form-side{ padding:var(--sp-5) var(--sp-4); }
  .auth-tab{ font-size:var(--fs-base); }
}
/* 矮视口（768 高的笔记本、200% 缩放档）：把纵向留白收一档，
   目标是**不用滚动**就看得见主按钮与页脚第一句。⛔ 收的是留白，不是内容：
   卖点与 lede 一条都不删（删了就是"窄屏看到的是另一个产品"）。 */
@media (max-height:780px){
  .auth-overlay{ padding:var(--sp-4); }
  .auth-brand{ padding:var(--sp-4) var(--sp-6); gap:var(--sp-3); }
  .auth-form-side{ padding:var(--sp-4) var(--sp-6); }
  .auth-points{ gap:var(--sp-2); }
  .auth-panel{ padding-top:var(--sp-5); }
  .auth-alert{ margin-top:var(--sp-4); }
}

.pill{
  display:inline-block; padding:2px var(--sp-3); border-radius:var(--r-full);
  font-size:var(--fs-xs); font-weight:600; background:var(--surface-3); color:var(--muted);
}
.pill.ok{ background:var(--ok-bg); color:var(--ok); }
.pill.brand{ background:var(--brand-soft); color:var(--ink); }
.pill.err{ background:var(--err-bg); color:var(--err); }
.pill.warn{ background:var(--warn-bg); color:var(--warn); }


/* ---------------------------------------------------------------- 问答 */
.faq{ display:grid; gap:var(--sp-4); }
.qa{ background:var(--surface); border:1px solid var(--line); border-radius:var(--r-lg); padding:var(--sp-5); }
.qa-q{ font-size:var(--fs-md); font-weight:640; }
.qa-a{ margin-top:var(--sp-3); color:var(--ink-soft); }
.qa-a + .qa-a{ margin-top:var(--sp-2); }
.term{
  border-left:3px solid var(--brand); padding:var(--sp-2) 0 var(--sp-2) var(--sp-4);
  background:var(--surface-2); border-radius:0 var(--r-sm) var(--r-sm) 0;
}
.term dt{ font-weight:660; }
.term dd{ margin:var(--sp-1) 0 0; color:var(--ink-soft); }


/* ---------------------------------------------------------------- 其它 */
.dim{ color:var(--muted); font-size:var(--fs-sm); }
.nowrap{ white-space:nowrap; }
.mt-4{ margin-top:var(--sp-4); }
.mt-5{ margin-top:var(--sp-5); }
.mt-6{ margin-top:var(--sp-6); }
.center{ text-align:center; }
.split{ display:grid; grid-template-columns:minmax(0,1fr) minmax(0,1fr); gap:var(--sp-7); align-items:center; }
@media (max-width:820px){ .split{ grid-template-columns:minmax(0,1fr); gap:var(--sp-6); } }


/* ---------------------------------------------------------------- 兜底页 */
/* `holding.html`（T1-07）：整屏垂直居中。
   ⚠️ 为什么需要单独一条：`.hero` 的 padding 是给**内容页**定的（那些页
      上下都有别的区块），单独停在兜底页上就是"三行字贴在顶边、底下空一大片" ——
      而**几何 / 对比度 / 溢出判据一条都不会响**（它们量的是越不越界，
      不是"平不平衡"）。这一条是**看图**才发现的（`_shots` 那三张）。
   ⚠️ 类名只挂在兜底页上，六页内容页不受影响（`base.html` 的 `<main>` 没有类）。 */
.holding{ min-height:100vh; display:grid; place-items:center; }
.holding .hero{ padding:var(--sp-8) 0; }


/* ============================================================================
 * v2 组件层（2026-10-22，按 `_design/web-20261022/` 的样板部署）
 *
 * 这一层只**新增**类名，不改上面任何既有类的语义 —— 上面那批组件有十几条判据
 * 在量（G1 主按钮、G5 底边落差、C1/C2 对比度、B1 品牌色用法），改语义 = 一次性
 * 动一批读数，而"改了哪个数"在报告上看不出来。
 *
 * 三条从样板带过来的硬规矩：
 *   1. `--brand` 只做底色与描边（B1 判据扫 `color:` 上的 `--brand*`）。
 *      浅底上的品牌感文字走 `--accent-on-soft`（#0E4FD8 压 #EAF1FF 5.92:1）。
 *   2. 语义色两档：`--ok/--warn/--err` 做文字，`--*-mark` 只做色块/圆点/图表段。
 *   3. 深色面只用 `--navy`，它的文字用 `--on-navy*`；⛔ `--on-navy*` 不上白面板。
 * ==========================================================================*/

/* ---------------------------------------------------------------- 面板与表格 */
/* `.panel` / `.table-wrap` / `.table` 这三个类名**早就在控制台模板里用了**
   （`grep -c 'class="table' app/web/templates` = 13 处），但组件层一直没定义 ——
   也就是说那些页的表格到今天为止是**浏览器默认样式**。本批补上定义，
   模板一个字都不用改就能变好看；反过来，改模板前先看这里有没有对应类。 */
.panel{ background:var(--surface); border:1px solid var(--line); border-radius:var(--r-lg); }
.panel-head{
  display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between;
  gap:var(--sp-3); padding:var(--sp-4) var(--sp-5); border-bottom:1px solid var(--line);
}
.panel-head h2{ font-size:var(--fs-lg); font-weight:660; }
.panel-body{ padding:var(--sp-5); }
.panel + .panel{ margin-top:var(--sp-5); }
.filters{ display:flex; flex-wrap:wrap; gap:var(--sp-1); }
.chip{
  display:inline-flex; align-items:center; min-height:28px;
  padding:0 var(--sp-3); border-radius:var(--r-full);
  border:1px solid var(--line); background:var(--surface);
  color:var(--muted); font-size:var(--fs-sm); font-family:inherit; cursor:pointer;
}
.chip:hover{ border-color:var(--line-strong); color:var(--ink); }
.chip[aria-pressed="true"]{ background:var(--brand-soft); border-color:var(--brand-200); color:var(--accent-on-soft); font-weight:600; }

/* 宽表横滑。⚠️ `overflow-x:auto` 必须落在**包一层的 div** 上：
   直接写在 table 上不生效（table 的显示类型会吃掉 overflow），
   而"表格把页面撑出横向滚动"这件事在 390 档是 G3 判据会抓的。 */
.table-wrap{ overflow-x:auto; border:1px solid var(--line); border-radius:var(--r-md); background:var(--surface); }
.table{ width:100%; border-collapse:collapse; font-size:var(--fs-md); }
.table th,.table td{ padding:var(--sp-3) var(--sp-4); text-align:left; vertical-align:top; }
.table th{
  background:var(--surface-2); border-bottom:1px solid var(--line);
  color:var(--muted); font-size:var(--fs-sm); font-weight:600; white-space:nowrap;
}
.table td{ border-bottom:1px solid var(--line-soft); }
.table tr:last-child td{ border-bottom:0; }
.table tbody tr:hover{ background:var(--surface-2); }
.table .num{ font-variant-numeric:tabular-nums; white-space:nowrap; }
.table-wide{ min-width:460px; }
.table-hint{ display:none; margin-top:var(--sp-2); color:var(--muted); font-size:var(--fs-xs); }
@media (max-width:639px){
  .table th,.table td{ padding:var(--sp-2) var(--sp-3); }
  .table-hint{ display:block; }
}

/* 规则码 / 等宽串：体检报告里每条问题右侧那串 REQ-* 就是它 */
.rule-code{
  display:block; margin-top:var(--sp-1);
  font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
  font-size:var(--fs-xs); letter-spacing:.03em; color:var(--muted);
}
.mono{ font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; font-size:var(--fs-sm); letter-spacing:.02em; }

/* ---------------------------------------------------------------- 分段控件 */
/* 月/年切换这类**互斥选项**用它，不用两个按钮（两个按钮会被 G1 当成两个主按钮） */
.seg{ display:inline-flex; padding:var(--sp-1); border:1px solid var(--line); border-radius:var(--r-full); background:var(--surface); }
.seg button{
  min-height:30px; padding:0 var(--sp-4); border:0; border-radius:var(--r-full);
  background:none; color:var(--muted); font-family:inherit; font-size:var(--fs-md); cursor:pointer;
}
.seg button:hover{ color:var(--ink); }
.seg button[aria-pressed="true"]{ background:var(--brand); color:var(--brand-ink); font-weight:600; }

/* ---------------------------------------------------------------- 占比图 */
/* 横向占比条：三段都是**标记档**（--*-mark），它们只做色块 */
.bar{ height:8px; border-radius:var(--r-full); background:var(--surface-3); overflow:hidden; display:flex; }
.bar i{ display:block; height:100%; }
.bar .ok{ background:var(--ok-mark); }
.bar .warn{ background:var(--warn-mark); }
.bar .err{ background:var(--err-mark); }

/* 环形统计：纯 CSS conic-gradient，中孔用 ::before 盖出来。
   占比从行内 `--p-ok` / `--p-warn` 两个自定义属性进（默认值在 tokens.css），
   ⛔ 颜色不从参数过 —— 三段写死只认标记档，理由见 tokens.css 的语义色分工。 */
.ring{
  position:relative; width:132px; height:132px; flex:none; border-radius:var(--r-full);
  background:conic-gradient(var(--ok-mark) 0 var(--p-ok),
                            var(--warn-mark) var(--p-ok) var(--p-warn),
                            var(--err-mark) var(--p-warn) 100%);
}
.ring::before{ content:""; position:absolute; inset:14px; border-radius:var(--r-full); background:var(--surface); }
.ring b{
  position:relative; height:100%; display:grid; place-items:center; align-content:center;
  color:var(--ink); font-size:var(--fs-3xl); font-weight:700; font-variant-numeric:tabular-nums;
}
.ring b i{ display:block; margin-top:var(--sp-1); font-style:normal; color:var(--muted); font-size:var(--fs-xs); font-weight:400; }
.legend{ display:grid; gap:var(--sp-2); align-content:center; }
.legend div{ display:flex; align-items:center; gap:var(--sp-2); color:var(--ink-soft); font-size:var(--fs-md); }
.legend i{ width:8px; height:8px; border-radius:var(--r-full); flex:none; }
.legend .n{ margin-left:auto; color:var(--ink); font-weight:600; font-variant-numeric:tabular-nums; }
.legend .ok i{ background:var(--ok-mark); }
.legend .warn i{ background:var(--warn-mark); }
.legend .err i{ background:var(--err-mark); }

/* ---------------------------------------------------------------- 指标 / 清单 */
.kpi-row{ display:grid; grid-template-columns:repeat(4,minmax(0,1fr)); gap:var(--sp-4); }
.kpi{ background:var(--surface); border:1px solid var(--line); border-radius:var(--r-lg); padding:var(--sp-4) var(--sp-5); box-shadow:var(--sh-1); }
.kpi .k{ color:var(--muted); font-size:var(--fs-sm); }
.kpi .v{ margin-top:var(--sp-1); color:var(--ink); font-size:var(--fs-2xl); font-weight:700; line-height:1.2; font-variant-numeric:tabular-nums; }
.kpi .v .unit{ margin-left:var(--sp-1); color:var(--muted); font-size:var(--fs-sm); font-weight:400; }
.kpi .d{ margin-top:var(--sp-1); color:var(--muted); font-size:var(--fs-sm); }
.kpi .d .up{ color:var(--ok); }
.kpi .d .down{ color:var(--err); }

.timeline{ list-style:none; margin:0; padding:0; display:grid; gap:var(--sp-4); }
.timeline li{ position:relative; padding-left:var(--sp-6); }
.timeline li::before{ content:""; position:absolute; left:0; top:.45em; width:8px; height:8px; border-radius:var(--r-full); background:var(--brand-200); }
.timeline li.done::before{ background:var(--ok-mark); }
.timeline strong{ display:block; font-size:var(--fs-md); color:var(--ink); }
.timeline span{ color:var(--muted); font-size:var(--fs-sm); }

.kv{ display:grid; gap:var(--sp-3); margin:0; }
.kv div{ display:flex; justify-content:space-between; gap:var(--sp-4); font-size:var(--fs-md); }
.kv dt{ color:var(--muted); }
.kv dd{ margin:0; color:var(--ink); font-weight:500; }
.kv-stack div{ flex-direction:column; align-items:flex-start; gap:var(--sp-1); }
.kv-stack dd{ font-weight:400; color:var(--ink-soft); line-height:1.65; }

.tiles{ display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:var(--sp-4); }
.tile{ background:var(--surface); border:1px solid var(--line); border-radius:var(--r-lg); padding:var(--sp-5); }
.tile h2{ font-size:var(--fs-lg); font-weight:660; }
.tile .sub{ margin-top:var(--sp-1); color:var(--muted); font-size:var(--fs-sm); }
.tile .body{ margin-top:var(--sp-4); }
.progress{ height:6px; border-radius:var(--r-full); background:var(--surface-3); overflow:hidden; }
.progress > i{ display:block; height:100%; background:var(--brand); }

.empty{ padding:var(--sp-8) var(--sp-5); text-align:center; }
.empty h3{ font-size:var(--fs-lg); }
.empty p{ max-width:44ch; margin:var(--sp-2) auto var(--sp-5); color:var(--muted); font-size:var(--fs-md); }

/* ---------------------------------------------------------------- 控制台外壳 */
/* 左侧固定栏：用户后台 / 代理后台 / 管理员后台共用。
   深色栏 = `--navy`（与登录弹窗左栏同一个面），**不是** `--brand` ——
   主色 #165DFF 当整栏底色时白字 5.19 只算刚过，次级文字还得另配一套，
   而"一个深色面"只能有一个来源。
   ⚠️ `.side` 是 `position:sticky` + `height:100vh`，不是 `fixed`：fixed 会脱离栅格，
      主区宽度要另算一遍（多一处会漂的真值）。 */
.shell{ display:grid; grid-template-columns:var(--side-w) minmax(0,1fr); min-height:100vh; background:var(--bg); }
.side{ position:sticky; top:0; height:100vh; display:flex; flex-direction:column; background:var(--navy); color:var(--on-navy); }
.side-brand{
  display:flex; align-items:center; gap:var(--sp-3); flex:none;
  height:var(--topbar-h); padding:0 var(--sp-5);
  border-bottom:1px solid var(--on-navy-line);
  color:var(--brand-ink); font-size:var(--fs-lg); font-weight:650;
  white-space:nowrap; overflow:hidden;
}
.side-brand .brand-logo{ width:26px; height:26px; flex:none; border-radius:var(--r-sm); background:var(--brand); color:var(--brand-ink); display:grid; place-items:center; font-size:var(--fs-sm); font-weight:700; }
.side-nav{ display:grid; gap:var(--sp-1); align-content:start; overflow-y:auto; padding:var(--sp-3) var(--sp-3) var(--sp-4); }
.side-nav a{
  display:flex; align-items:center; gap:var(--sp-3); min-height:36px; padding:0 var(--sp-3);
  border-radius:var(--r-sm); color:var(--on-navy); font-size:var(--fs-md);
}
.side-nav a:hover{ background:var(--on-navy-fill); color:var(--brand-ink); text-decoration:none; }
.side-nav a[aria-current="page"]{ background:var(--brand); color:var(--brand-ink); font-weight:500; }
.side-nav .ico{ width:18px; height:18px; flex:none; display:grid; place-items:center; color:currentColor; }
.side-group{ margin-top:var(--sp-4); padding:0 var(--sp-3) var(--sp-1); color:var(--on-navy-dim); font-size:var(--fs-xs); letter-spacing:.08em; }
.side-foot{ margin-top:auto; padding:var(--sp-4); border-top:1px solid var(--on-navy-line); }
.side-user{ display:flex; align-items:center; gap:var(--sp-3); color:var(--on-navy); font-size:var(--fs-sm); min-width:0; }
.side-avatar{ width:30px; height:30px; flex:none; border-radius:var(--r-full); background:var(--on-navy-fill-2); color:var(--brand-ink); display:grid; place-items:center; font-size:var(--fs-sm); font-weight:600; }
.side-user b{ display:block; color:var(--brand-ink); font-weight:500; }
.side-user span{ display:block; color:var(--on-navy-dim); font-size:var(--fs-xs); }
.side-foot .btn-link{
  display:inline-flex; align-items:center; min-height:24px; margin-top:var(--sp-3);
  border:0; background:none; padding:0; color:var(--on-navy-dim);
  font-family:inherit; font-size:var(--fs-sm); cursor:pointer;
}
.side-foot .btn-link:hover{ color:var(--brand-ink); }

.main{ min-width:0; padding:var(--sp-6) var(--sp-7) var(--sp-9); }
.main-head{ display:flex; flex-wrap:wrap; align-items:flex-end; justify-content:space-between; gap:var(--sp-4); margin-bottom:var(--sp-5); }
.main-head h1{ font-size:var(--fs-2xl); font-weight:680; letter-spacing:-.1px; }
.main-head .sub{ margin-top:var(--sp-1); color:var(--muted); font-size:var(--fs-md); }
.main-actions{ display:flex; flex-wrap:wrap; align-items:center; gap:var(--sp-3); }

@media (max-width:1023px){
  .shell{ grid-template-columns:minmax(0,1fr); }
  .side{ position:static; height:auto; flex-direction:row; flex-wrap:wrap; align-items:center; gap:var(--sp-2); padding:var(--sp-2) var(--sp-4); }
  .side-brand{ height:auto; border:0; padding:0; min-width:0; }
  .side-nav{ display:flex; gap:var(--sp-1); overflow-x:auto; padding:0; margin-left:var(--sp-4); }
  .side-nav a{ white-space:nowrap; min-height:32px; }
  .side-group{ display:none; }
  .side-foot{ margin:0 0 0 auto; padding:0; border:0; }
  .main{ padding:var(--sp-5) var(--sp-4) var(--sp-8); }
  .kpi-row{ grid-template-columns:repeat(2,minmax(0,1fr)); }
  .tiles{ grid-template-columns:minmax(0,1fr); }
}
@media (max-width:639px){
  .kpi-row{ grid-template-columns:minmax(0,1fr); }
  .main-head h1{ font-size:var(--fs-xl); }
  /* 窄屏侧栏折成顶部条之后，用户块只留头像 —— 别让它占一整行 */
  .side-user > span:not(.side-avatar){ display:none; }
  .side-foot{ display:flex; align-items:center; gap:var(--sp-2); }
  .side-foot .btn-link{ margin-top:0; }
}

/* 2026-10-27：`首屏悬浮预览` 那一组 `.hv*`（21 条规则、21 个类）整块删掉 ——
   全仓 HTML / 模板 / 脚本对它的引用是 **0 次**（`grep -r "hv-" --include=*.html` 空）。
   它画的是"文档 + 浮起指标卡"，首屏改成摄影底 + SVG HUD 之后就没有载体了。
   ⚠️ 想恢复请从 git 历史取（提交 2026-10-27 之前的那一版），不要在别处另写一套。 */

/* ---------------------------------------------------------------- 引语卡 / 深色带 */
.quote{
  display:flex; flex-direction:column; gap:var(--sp-3);
  background:var(--surface); border:1px solid var(--line); border-radius:var(--r-lg); padding:var(--sp-5);
}
.quote p{ color:var(--ink-soft); font-size:var(--fs-md); line-height:1.65; }
.quote .who{ display:flex; align-items:center; gap:var(--sp-3); margin-top:auto; padding-top:var(--sp-3); border-top:1px solid var(--line-soft); }
.quote .avatar{
  width:32px; height:32px; flex:none; border-radius:var(--r-full);
  background:var(--brand-soft); color:var(--accent-on-soft);
  display:grid; place-items:center; font-size:var(--fs-sm); font-weight:600;
}
.quote .who b{ display:block; color:var(--ink); font-size:var(--fs-md); font-weight:500; }
.quote .who span{ display:block; color:var(--muted); font-size:var(--fs-xs); }

/* 深色行动带：`--navy` 底 + 反白主按钮。
   ⚠️ 反白按钮的字色写 `--accent-on-soft` 而不是 `--brand`（B1：品牌色不做文字）。 */
.coop-band{
  display:grid; grid-template-columns:minmax(0,1fr) auto; gap:var(--sp-6); align-items:center;
  background:var(--navy); border-radius:var(--r-xl); padding:var(--sp-7);
}
.coop-band h2{ color:var(--brand-ink); font-size:var(--fs-2xl); font-weight:660; }
.coop-band p{ max-width:56ch; margin-top:var(--sp-2); color:var(--on-navy); font-size:var(--fs-md); }
.coop-band .cta-actions{ display:flex; flex-wrap:wrap; gap:var(--sp-3); }
.coop-band .btn-primary{ background:var(--surface); color:var(--accent-on-soft); border-color:var(--surface); }
.coop-band .btn-primary:hover{ background:var(--brand-soft); border-color:var(--brand-soft); }
.coop-band .btn-ghost{ background:transparent; color:var(--brand-ink); border-color:var(--on-navy-dim); }
.coop-band .btn-ghost:hover{ background:var(--on-navy-fill); color:var(--brand-ink); border-color:var(--on-navy); }
.coop-list{ display:flex; flex-wrap:wrap; gap:var(--sp-2) var(--sp-5); margin-top:var(--sp-3); color:var(--on-navy); font-size:var(--fs-sm); }
.coop-list span{ display:inline-flex; align-items:center; gap:var(--sp-2); }
.coop-list span::before{ content:""; width:5px; height:5px; border-radius:var(--r-full); background:var(--brand-200); }
@media (max-width:1023px){ .coop-band{ grid-template-columns:minmax(0,1fr); padding:var(--sp-6); } }

/* ---------------------------------------------------------------- 定价卡补充 */
/* 生产已有 `.price*`（amt/unit/name/desc/list/act）—— 这里只补样板新增的两件：
   勾选清单与「推荐」标记。⛔ 不新建一套 `.plan*`，那会变成同一个组件的两份定义。 */
.price-feat{ display:grid; gap:var(--sp-2); margin:var(--sp-4) 0 var(--sp-5); }
.price-feat div{ display:flex; align-items:flex-start; gap:var(--sp-2); color:var(--ink-soft); font-size:var(--fs-md); }
.price-feat i{ flex:none; width:14px; font-style:normal; }
.price-feat .yes{ color:var(--ok); }
.price-feat .no{ color:var(--muted-2); }
.avail{ color:var(--ok); font-weight:500; }
.na{ color:var(--muted-2); }

/* ---------------------------------------------------------------- 模板库 */
.toolbar{
  display:flex; flex-wrap:wrap; align-items:center; gap:var(--sp-3);
  padding:var(--sp-4); margin-bottom:var(--sp-5);
  background:var(--surface); border:1px solid var(--line); border-radius:var(--r-lg);
}
.toolbar .grow{ margin-left:auto; }
.toolbar .search{ display:flex; align-items:center; gap:var(--sp-2); }
.toolbar .search .input{ width:220px; min-height:34px; }
.tpl-grid{ display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:var(--sp-4); }
.tpl{ display:flex; flex-direction:column; overflow:hidden; background:var(--surface); border:1px solid var(--line); border-radius:var(--r-lg); }
.tpl:hover{ border-color:var(--brand-200); box-shadow:var(--sh-2); }
.tpl-cover{ position:relative; display:flex; flex-direction:column; gap:var(--sp-2); height:168px; padding:var(--sp-4); background:var(--surface-3); border-bottom:1px solid var(--line); }
.tpl-cover i{ display:block; height:8px; border-radius:var(--r-full); background:var(--line-strong); }
.tpl-cover i.t{ width:52%; height:12px; background:var(--brand-200); }
.tpl-cover i.s{ width:76%; }
.tpl-body{ display:flex; flex-direction:column; flex:1; gap:var(--sp-2); padding:var(--sp-4); }
.tpl-body h3{ font-size:var(--fs-md); font-weight:640; }
.tpl-meta{ display:flex; flex-wrap:wrap; gap:var(--sp-2); color:var(--muted); font-size:var(--fs-sm); }
.tpl-acts{ display:flex; gap:var(--sp-2); margin-top:auto; padding-top:var(--sp-3); }
.tpl-acts .btn{ flex:1; }
.tpl-preview{ margin-top:var(--sp-3); }
.tpl-preview summary{ display:inline-flex; align-items:center; min-height:24px; color:var(--accent); font-size:var(--fs-sm); cursor:pointer; list-style:none; }
.tpl-preview summary::-webkit-details-marker{ display:none; }
.tpl-preview summary:hover{ color:var(--accent-700); }
.tpl-preview .pv{ margin-top:var(--sp-3); padding:var(--sp-3); border-radius:var(--r-md); background:var(--surface-3); color:var(--muted); font-size:var(--fs-sm); line-height:1.6; }
@media (max-width:1023px){ .tpl-grid{ grid-template-columns:repeat(2,minmax(0,1fr)); } }
@media (max-width:639px){ .tpl-grid{ grid-template-columns:minmax(0,1fr); } .toolbar .search,.toolbar .search .input{ width:100%; } }

/* ---------------------------------------------------------------- 表单卡 */
/* ⚠️ `.form-card` 在 2026-10-22 早先那版被删过一次（当时只有 /auth 在用，
   换成了 .auth-modal）—— 这里**重新加回来**，用途不同：商务合作申请与后台配置
   是"页面里的表单块"，不是"整页的登录弹窗"。 */
.form-card{ background:var(--surface); border:1px solid var(--line); border-radius:var(--r-lg); padding:var(--sp-6); }
.form-grid{ display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:var(--sp-4); }
.form-grid .full{ grid-column:1/-1; }
textarea.input{ min-height:96px; padding:var(--sp-3) var(--sp-4); resize:vertical; }
select.input{ min-height:40px; padding:0 var(--sp-3); }
.req{ color:var(--err); }
.field-error{ margin-top:var(--sp-1); color:var(--err); font-size:var(--fs-sm); }
.form-note{ color:var(--muted); font-size:var(--fs-sm); line-height:1.65; }
.inline-form{ display:flex; align-items:center; gap:var(--sp-2); }
.inline-form .input{ width:180px; min-height:32px; }
.row-acts{ display:flex; flex-wrap:wrap; gap:var(--sp-2); }
.code-box{
  display:flex; align-items:center; gap:var(--sp-2); padding:var(--sp-2) var(--sp-2) var(--sp-2) var(--sp-4);
  background:var(--surface-2); border:1px solid var(--line); border-radius:var(--r-md);
  font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; font-size:var(--fs-sm); color:var(--ink-soft);
}
.code-box .u{ flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.mini-note{ color:var(--muted); font-size:var(--fs-xs); line-height:1.6; }
.sec-head{ display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between; gap:var(--sp-3); margin:var(--sp-6) 0 var(--sp-3); }
.sec-head h2{ font-size:var(--fs-lg); font-weight:660; }
@media (max-width:639px){ .form-grid{ grid-template-columns:minmax(0,1fr); } }

/* ---------------------------------------------------------------- 体检报告 */
.report-top{ display:grid; grid-template-columns:minmax(0,380px) minmax(0,1fr); gap:var(--sp-4); margin-bottom:var(--sp-5); }
.ring-card{ display:flex; align-items:center; gap:var(--sp-5); padding:var(--sp-5); }
.ring-card .cap{ display:grid; gap:var(--sp-1); }
.ring-card .cap b{ color:var(--ink); font-size:var(--fs-md); font-weight:500; }
.ring-card .cap span{ color:var(--muted); font-size:var(--fs-sm); line-height:1.6; }
.risk-body{ display:grid; gap:var(--sp-4); padding:var(--sp-5); }
.risk-rows{ display:grid; gap:var(--sp-2); }
.risk-rows div{ display:flex; align-items:center; gap:var(--sp-2); color:var(--ink-soft); font-size:var(--fs-md); }
.risk-rows .n{ margin-left:auto; color:var(--ink); font-weight:500; font-variant-numeric:tabular-nums; }
.risk-rows i{ width:8px; height:8px; flex:none; border-radius:var(--r-full); }
.risk-rows .ok i{ background:var(--ok-mark); }
.risk-rows .warn i{ background:var(--warn-mark); }
.risk-rows .err i{ background:var(--err-mark); }
@media (max-width:1279px){ .report-top{ grid-template-columns:minmax(0,1fr); } }

/* ---------------------------------------------------------------- 个人中心 */
/* ⚠️ 白面板上的账号头**不要复用** `.side-user`：它的文字色是 `--on-navy-dim`，
   放到白底只有约 2.5:1（tokens.md 里写着这条）。 */
.acct-head{ display:flex; align-items:center; gap:var(--sp-3); margin-bottom:var(--sp-4); }
.acct-head .av{
  width:48px; height:48px; flex:none; border-radius:var(--r-full);
  background:var(--brand-soft); color:var(--accent-on-soft);
  display:grid; place-items:center; font-size:var(--fs-lg); font-weight:600;
}
.acct-head b{ display:block; color:var(--ink); font-size:var(--fs-lg); font-weight:600; }
.acct-head div span{ display:block; margin-top:var(--sp-1); color:var(--muted); font-size:var(--fs-sm); }

/* ---------------------------------------------------------------- 小工具 */
.dim{ color:var(--muted); }
.small{ font-size:var(--fs-sm); }
.foot a{ display:inline-flex; align-items:center; min-height:24px; }
@media (max-width:639px){
  .foot a,.check{ min-height:44px; }
  .btn-sm{ min-height:44px; }
}
.check{ display:flex; align-items:center; gap:var(--sp-2); color:var(--muted); font-size:var(--fs-sm); cursor:pointer; }
.check input{ width:16px; height:16px; flex:none; margin:0; accent-color:var(--brand); }
@media (max-width:639px){ .check input{ width:24px; height:24px; } }
.form-row{ display:flex; align-items:center; justify-content:space-between; gap:var(--sp-3); }
[hidden]{ display:none !important; }

/* 首屏两栏（左文案 / 右示意）。⚠️ 用**独立类名**而不是复用 `.split`：
   `.split` 在 G5（并排栏底边落差）的取样范围里，而首屏两栏的高度天然不等，
   差多少取决于文案长度 —— 让它进判据等于给每次改文案加一条新的红。
   这里 `align-items:center` 自己保证视觉平衡。 */
.hero-grid{ display:grid; grid-template-columns:minmax(0,1.05fr) minmax(0,.95fr); gap:var(--sp-8); align-items:center; }
@media (max-width:1023px){ .hero-grid{ grid-template-columns:minmax(0,1fr); gap:var(--sp-7); } }
/* 四步流程：`.steps` 默认三列（首页那三步用），四步版单独给类名 */
.steps.four{ grid-template-columns:repeat(4,minmax(0,1fr)); }
@media (max-width:1023px){ .steps.four{ grid-template-columns:repeat(2,minmax(0,1fr)); } }
@media (max-width:820px){ .steps.four{ grid-template-columns:minmax(0,1fr); } }


/* ============================================================================
 * v3 视觉层（2026-10-23 第八十六批，源 `_design/web-20261022/site/assets/site.css`）
 *
 * 这一层只做四件事，都是 12 段出图需求里反复出现的那一行：
 *   「深色深蓝顶部导航，白色内容区域，发光卡片边框，简约渐变蓝色背景」
 *   ⇒ ① 顶栏改深（`.head`）② 页底渐变（`body.page-grad`）
 *      ③ 卡片发光描边（`.glow`）④ 深蓝首屏（`.hero-navy`）
 *
 * 三条口径：
 *   - **几何一个字不动**：`.head-in` / `.nav` / `.head-cta` 的间距、字号、flex
 *     全部保持原样，这里只换颜色 —— 601 档顶栏溢出是上一批刚收干净的，
 *     给导航加内边距会当场把它顶回去（`check_overflow390` / check_render G3）。
 *   - 深色面上的文字一律走 `--on-navy*` / `--brand-ink` 这一对，
 *     ⛔ 不要在白底上直接写白色、也不要在深底上用 `--muted`。
 *   - `.hp-*` 只属于首页（hp = home page），别的页面不许拿去用；
 *     通用件（网格/步骤/定价卡/胶囊）一律复用既有类名，不另起一套。
 * ==========================================================================*/

/* ---- ① 页底渐变 ---------------------------------------------------------
   特异性必须是 `body.page-grad`：`body.site` 是 (0,1,1)，裸 `.page-grad`
   只有 (0,1,0)，写在后面也**盖不过**它 —— 症状是"类名加了但背景没变"，
   而且判据看不出来（渐变背景本来就不进对比度取样）。 */
body.page-grad{ background-color:var(--bg); background-image:var(--grad-page); }

/* ---- ② 深色顶栏 --------------------------------------------------------- */
.head{ background:var(--navy); border-bottom:1px solid var(--on-navy-line); }
.head .brand-name{ color:var(--on-navy-strong); }
.head .nav a{ color:var(--on-navy); }
.head .nav a:hover{ color:var(--on-navy-strong); }
.head .nav a[aria-current="page"]{ border-bottom-color:var(--brand-200); }
.head .head-cta .link-quiet{ color:var(--on-navy); }
.head .head-cta .link-quiet:hover{ color:var(--on-navy-strong); }
/* 顶栏的「免费注册 / 退出」保持白底深字（`.btn-ghost` 原样）：
   深底上再画一个透明描边按钮，和导航项的视觉重量分不开。 */

/* ---- ③ 发光卡片 --------------------------------------------------------- */
.glow{ border-color:var(--brand-200); box-shadow:var(--glow-sh); }
a.glow:hover, .card.glow:hover, .kpi.glow:hover, .tile.glow:hover{
  box-shadow:var(--glow-sh-hover); border-color:var(--brand);
}

/* ---- ④ 深蓝首屏 --------------------------------------------------------- */
.hero-navy{ background-color:var(--navy); background-image:var(--grad-hero); color:var(--on-navy-strong); }
.hero-navy .hero-title{ color:var(--on-navy-strong); }
.hero-navy .hero-sub{ color:var(--on-navy); }
.hero-navy .hero-note{ color:var(--on-navy-dim); }
.hero-navy .hero-eyebrow{
  background:transparent; color:var(--on-navy-strong);
  border:1px solid var(--on-navy-fill-2);
}
/* 深底上的主按钮改白底：纯品牌色按钮压在渐变右端时边界只有 ~1.8:1
   （样板实测），按钮本身快看不见了。文字用 `--ink` 而不是 `--brand`：
   浅色品牌（`--preset-sky`）压白底只有 2.6:1，那是**必然**的对比度事故。 */
.hero-navy .btn-primary{ background:var(--surface); color:var(--ink); border-color:var(--surface); }
.hero-navy .btn-primary:hover{ background:var(--brand-soft); color:var(--ink); border-color:var(--brand-soft); }
.hero-navy .btn-ghost{ background:transparent; color:var(--on-navy-strong); border-color:var(--on-navy-fill-2); }
.hero-navy .btn-ghost:hover{ background:var(--on-navy-fill); color:var(--on-navy-strong); border-color:var(--brand-200); }

/* 深蓝首屏右侧的三格分佣口径（合作页）。⚠️ 三格里印的 15–25% / 8–12%
   是**用户 2026-10-23 拍板的商务承诺**，不是从代码里算出来的 —— 生产的分佣比例
   实际是租户级可配置项（`acct.pricing.effective_rate`）。改文案前先确认承诺变了。
   ⚠️ 底色必须给**实色**：`rgba()` 只当遮罩层叠在 `background-image` 上 ——
      对比度判据读的是 computed `background-color`，纯 rgba 会让它退回到上层
      （同一个坑让首屏在 §7.89 红过 54 处）。 */
.hero-stats{ display:grid; gap:var(--sp-3); }
.stat-tile{
  display:flex; justify-content:space-between; align-items:baseline; gap:var(--sp-4);
  padding:var(--sp-3) var(--sp-4); border-radius:var(--r-md);
  border:1px solid var(--on-navy-line);
  background-color:var(--navy);
  background-image:linear-gradient(180deg, rgba(255,255,255,.10), rgba(255,255,255,.02));
}
.stat-tile .k{ color:var(--on-navy); font-size:var(--fs-sm); }
.stat-tile .v{ color:var(--on-navy-strong); font-size:var(--fs-lg); font-weight:var(--fw-semibold); }

/* ---- 通用件（v3 样板有、生产缺的那几个）-------------------------------- */
/* 小标签：用 `--accent` 不跟品牌走 —— 理由同 tokens.css 里那段
   「链接色若随品牌变，每个品牌色都要重算一次链接对比度」。 */
.kicker{ color:var(--accent); font-size:var(--fs-sm); font-weight:var(--fw-semibold); letter-spacing:.08em; }
.card-ico{
  width:34px; height:34px; border-radius:var(--r-md);
  display:grid; place-items:center;
  background:var(--brand); color:var(--brand-ink);
  font-weight:var(--fw-bold); font-size:var(--fs-md);
  margin-bottom:var(--sp-4);
}

/* ---- 首页专用（`.hp-*`）------------------------------------------------- */
.hp-grid-4{ display:grid; grid-template-columns:repeat(4,minmax(0,1fr)); gap:var(--sp-5); }
.hp-card{ display:flex; flex-direction:column; }
.hp-card h3{ font-size:var(--fs-xl); font-weight:660; }
.hp-go{ margin-top:auto; padding-top:var(--sp-4); font-size:var(--fs-md); font-weight:var(--fw-semibold); }

/* 首屏右栏：工作台预览（纯 CSS，零图片） */
.hp-ws{ display:grid; grid-template-columns:78px minmax(0,1fr); gap:var(--sp-3); margin-top:var(--sp-3); }
.hp-ws-side{ display:grid; gap:var(--sp-1); align-content:start; }
.hp-ws-side span{
  display:flex; align-items:center; height:24px; padding:0 var(--sp-2);
  border-radius:var(--r-xs); background:var(--surface-2);
  color:var(--muted); font-size:var(--fs-xs);
}
.hp-ws-side .on{ background:var(--brand-soft); color:var(--ink); font-weight:var(--fw-medium); }
.hp-ws-main{ display:grid; gap:var(--sp-2); align-content:start; }
.hp-ws-row{ display:flex; align-items:center; gap:var(--sp-2); }
.hp-ws-row i{ width:6px; height:6px; flex:none; border-radius:var(--r-full); background:var(--ok-mark); }
.hp-ws-row i.warn{ background:var(--warn-mark); }
.hp-ws-t{ color:var(--ink-soft); font-size:var(--fs-sm); }

/* 浮动图表卡顶部：标题 + 百分比 */
.hp-float-head{ display:flex; align-items:baseline; justify-content:space-between; gap:var(--sp-2); }
.hp-float-head span{ color:var(--muted); font-size:var(--fs-sm); }
/* 迷你柱状：七根柱子的高度写死在选择器里（占位示意，不是数据） */
.hp-spark{ display:flex; align-items:flex-end; gap:var(--sp-1); height:42px; margin:var(--sp-3) 0; }
.hp-spark i{ flex:1; border-radius:var(--r-xs); background:var(--brand-100); }
.hp-spark i:nth-child(1){ height:34%; }
.hp-spark i:nth-child(2){ height:52%; }
.hp-spark i:nth-child(3){ height:44%; }
.hp-spark i:nth-child(4){ height:68%; }
.hp-spark i:nth-child(5){ height:60%; }
.hp-spark i:nth-child(6){ height:84%; }
.hp-spark i:nth-child(7){ height:96%; background:var(--brand); }

/* 风险类型（静态三列：⛔ 生产公开面零脚本，样板里那套「点选切换」不搬） */
.hp-risk-grid{ display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:var(--sp-5) var(--sp-6); }
.hp-risk-item{ border-top:2px solid var(--brand-100); padding-top:var(--sp-3); }
.hp-risk-item b{ display:block; color:var(--ink); font-size:var(--fs-md); font-weight:var(--fw-semibold); }
.hp-risk-item span{ display:block; margin-top:var(--sp-1); color:var(--muted); font-size:var(--fs-base); }

/* 案例卡 */
.hp-case{ display:flex; flex-direction:column; gap:var(--sp-3); }
.hp-case-num{
  color:var(--accent); font-size:var(--fs-3xl); font-weight:var(--fw-bold);
  line-height:var(--lh-tight); font-variant-numeric:tabular-nums;
}
.hp-case-num span{ display:block; margin-top:var(--sp-1); color:var(--muted); font-size:var(--fs-sm); font-weight:var(--fw-normal); }

/* ⚠️  是 inline-block，但它一旦进了**纵向 flex 卡**（.price / .hp-case），
   就成了 flex 子项 ⇒ align-self 默认 stretch ⇒ 整条胶囊被拉成一根横杠，
   看着像分隔线不像图章。样板那边同一个坑写在 .badge 上（"徽章在 flex 列里"）。 */
.hp-case > .pill, .price > .pill{ align-self:flex-start; }

@media (max-width:1023px){
  .hp-grid-4{ grid-template-columns:repeat(2,minmax(0,1fr)); }
  .hp-risk-grid{ grid-template-columns:repeat(2,minmax(0,1fr)); }
}
@media (max-width:820px){
  .hp-grid-4,.hp-risk-grid{ grid-template-columns:minmax(0,1fr); }
  .hp-ws{ grid-template-columns:minmax(0,1fr); }
  .hp-ws-side{ grid-auto-flow:column; }
}

/* ---- 代理概览：转化漏斗 + 业绩排行（第一百批）-------------------------------
   样图 `_design/web-20261022/site/agent.html` 的 `.fn*` / `.rank*` / `.mid`。
   ⚠️ 与第九十九批不同，这一次生产 CSS 里**确实没有**对应组件（先 `grep` 过
      `fn-` / `rank` / `.rb` / `.mid`，全部 0 命中），所以这 12 个类是真新增。
   ⚠️ 样图用 `--grad-brand` 做漏斗底色，生产**没有这个令牌**（只有 `--grad-page`
      与 `--grad-hero`）⇒ 改用纯 `var(--brand)`：
      ① 不给渐变**同时写死 background-color**，对比度判据会把底色问成白
         （§7.89 栽过那次，C1/C2 一次红 54 处）；
      ② 深色条上印字用 `--brand-ink`，它是**成对**的令牌（`check_tokens` 的 D 组）。
   ⚠️ 尺寸一律走 `--sp-*`：22px 圆标 = `--sp-5`，40px 条 = `--sp-7`(42)。 */
.mid{
  display:grid; grid-template-columns:minmax(0,1.32fr) minmax(0,.68fr);
  gap:var(--sp-4); align-items:stretch;
}
@media (max-width:1023px){ .mid{ grid-template-columns:minmax(0,1fr); } }

.fn{ display:grid; gap:var(--sp-3); }
.fn-row{
  /* ⚠️ 右列用 `auto` 而不是定宽 `--sp-8`(64px)：`¥128,600` 在 12.5px 下约 60px，
     定宽轨道配合 `white-space:nowrap` 会**溢出轨道**（390 判据抓不到轨道内溢出，
     只抓文档级 scrollWidth）。`auto` 让数字自己撑开，中列 `minmax(0,1fr)` 让出空间。 */
  display:grid; grid-template-columns:var(--sp-6) minmax(0,1fr) auto;
  align-items:center; gap:var(--sp-3);
}
.fn-k{ color:var(--ink-soft); font-size:var(--fs-sm); }
.fn-track{ display:flex; justify-content:center; min-width:0; }
.fn-bar{
  display:flex; align-items:center; justify-content:center;
  height:var(--sp-7); max-width:100%; padding:0 var(--sp-2);
  border-radius:var(--r-sm);
  background:var(--brand); color:var(--brand-ink);
  font-size:var(--fs-sm); font-weight:var(--fw-medium);
  font-variant-numeric:tabular-nums; white-space:nowrap;
}
.fn-r{ color:var(--ink); font-size:var(--fs-sm); text-align:right;
       font-variant-numeric:tabular-nums; }
@media (max-width:639px){
  /* 窄屏：标签列与数值列都收到最小，条留在中间；⚠️ 条里的字数不变，
     靠 `max-width:100%` 与 `padding` 收口 —— 溢出判据 390/320 两档都盯着这里。 */
  .fn-row{ grid-template-columns:minmax(0,var(--sp-6)) minmax(0,1fr) auto; gap:var(--sp-2); }
  .fn-bar{ font-size:var(--fs-xs); }
}

.rank{ display:grid; gap:var(--sp-3); }
.rank li{
  display:grid; grid-template-columns:var(--sp-5) minmax(0,1fr) auto;
  gap:var(--sp-1) var(--sp-2); align-items:center;
}
.rank .no{
  width:var(--sp-5); height:var(--sp-5); border-radius:var(--r-full);
  display:grid; place-items:center;
  background:var(--surface-3); color:var(--muted);
  font-size:var(--fs-xs); font-weight:var(--fw-semibold);
}
.rank li:first-child .no{ background:var(--brand); color:var(--brand-ink); }
.rank .nm{
  color:var(--ink); font-size:var(--fs-sm);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.rank .amt{
  color:var(--ink); font-size:var(--fs-sm); font-weight:var(--fw-medium);
  font-variant-numeric:tabular-nums; white-space:nowrap;
}
.rank .rb{
  grid-column:2 / -1; display:block; height:var(--sp-1);
  border-radius:var(--r-full); background:var(--surface-3); overflow:hidden;
}
.rank .rb i{ display:block; height:100%; border-radius:var(--r-full); background:var(--brand); }

/* 表格下方的合计口径说明：必须**贴着面板左内边距**对齐 ——
   放在 `.table-wrap` 外面、面板里面，纯 `.mini-note` 会左贴 0px（样图那一版就带这条）。
   样图把 padding 写在本类里，这里照搬；只做外距，不改字色字号（.mini-note 已定）。 */
.tfoot-note{ padding:var(--sp-3) var(--sp-5) var(--sp-4); }

/* ---- 大纲页：三栏工作区（第一百零一批）-------------------------------------
   样图 `_design/web-20261022/site/outline.html` 的 `.work` / `.work-col` /
   `.tree` / `.tnode` / `.outline`。生产 `grep` 过：这些类**全部 0 命中**。

   ⚠️⚠️ 样图用 `.h` / `.b` 两个**极通用的裸类名**做列头与列身。落生产必须写成
      `.work-col > .h` / `.work-col > .b`（作用域到直接子元素），⛔ **不许**写成
      全局 `.h{}` / `.b{}` —— 那等于给以后的每一页埋一个会静默改版式的雷。
      这也是这条规则**没法靠「照抄样图」得到**的一处。
   ⚠️ 树用原生 `<details>/<summary>`，**展开收起零脚本**（样图也是这么做的）——
      它是本页唯一真能点的东西，别顺手换成要 JS 的组件。
   ⚠️ `.tnode-v` 与 `<summary>` 里的 `.pill` 都要 `flex:none`：`summary` 是 flex，
      默认 `min-width:auto` 会让长标题把状态 pill 压成一条竖线（390 档才看得见）。 */
.work{
  display:grid; grid-template-columns:250px minmax(0,1fr) 320px;
  gap:var(--sp-4); align-items:start;
}
@media (max-width:1279px) and (min-width:1024px){
  .work{ grid-template-columns:200px minmax(0,1fr) 268px; }
}
@media (max-width:1023px){
  .work{ grid-template-columns:minmax(0,1fr); }
}

.work-col{
  background:var(--surface); border:1px solid var(--line);
  border-radius:var(--r-lg); min-width:0;
}
.work-col > .h{
  display:flex; align-items:center; justify-content:space-between;
  gap:var(--sp-2); padding:var(--sp-3) var(--sp-4);
  border-bottom:1px solid var(--line-soft);
  color:var(--ink); font-size:var(--fs-md); font-weight:var(--fw-medium);
}
.work-col > .b{ padding:var(--sp-3) var(--sp-4); }
/* ⚠️ 列头是 `h2`：样图把字号/字重写在 `.work-col > .h` 上，**但 UA 的 h2 自带
   1.5em 与上下外边距**，不写这一行的话列头会比正文大一圈、把整列顶高 40px 左右。
   样图是靠页面内的 `<style>` 补的，我们不在模板里写样式，所以必须收进组件层。 */
.work-col > .h h2{ margin:0; font-size:inherit; font-weight:inherit; line-height:1.4; }

.tree{ display:grid; gap:var(--sp-1); }
.tnode > summary{
  display:flex; align-items:center; gap:var(--sp-2); min-height:32px;
  padding:0 var(--sp-2); border-radius:var(--r-sm);
  color:var(--ink-soft); font-size:var(--fs-md);
  cursor:pointer; list-style:none;
}
.tnode > summary::-webkit-details-marker{ display:none; }
.tnode > summary::before{
  content:"▸"; flex:none; width:var(--sp-3); color:var(--muted); font-size:var(--fs-xs);
}
.tnode[open] > summary::before{ content:"▾"; }
.tnode > summary:hover{ background:var(--surface-3); color:var(--ink); }
.tnode > summary .pill{ flex:none; }
.tnode-t{ flex:1; min-width:0; }
.tnode-v{ flex:none; color:var(--muted); font-size:var(--fs-xs); white-space:nowrap; }
.tnode ul{ margin:0; padding:var(--sp-1) 0 var(--sp-2) var(--sp-6); }
.tnode li{ padding:var(--sp-1) 0; color:var(--muted); font-size:var(--fs-sm); line-height:1.6; }

.outline{ display:grid; gap:var(--sp-1); margin:0; padding:0; list-style:none; }
.outline > li{
  display:flex; align-items:flex-start; gap:var(--sp-2);
  min-height:32px; padding:var(--sp-1) var(--sp-2); border-radius:var(--r-sm);
}
.outline .n{ flex:none; color:var(--muted); font-size:var(--fs-xs); font-variant-numeric:tabular-nums; }
.og-body{ flex:1; min-width:0; display:grid; gap:var(--sp-1); }
.og-body b{ color:var(--ink); font-size:var(--fs-md); font-weight:var(--fw-medium); }
.og-sum{ color:var(--muted); font-size:var(--fs-sm); line-height:1.6; }
.outline .tail{ flex:none; margin-left:auto; white-space:nowrap; }

/* 右栏的主动作：`<a class="btn">` 是 inline-flex，不给 width 就只剩一块小矩条，
   而左两栏的内容都是满宽的 ⇒ 看图才发现不平衡。 */
.set-cta{ margin-top:var(--sp-4); width:100%; }

/* ---- 智能撰写工作台：正文 / 建议 / 素材库 / 页脚（第一百零二批）-------------
   样图 `_design/web-20261022/site/write.html` 的 `.doc*` / `.ai-*` / `.mat-*` /
   `.work-foot`。动笔前 `grep` 过：这 11 个类在生产**全部 0 命中**。

   ⚠️ 三栏的 `.work` / `.work-col` / `.h` / `.b` / `.tree` / `.tnode*` 是第一百零一批
      的，这里**一个都不重定义** —— 同一仓里两套树，下次改样式只会改到一半。
   ⚠️⚠️ 样图的 `.sp{margin-left:auto}` 在 `.doc-bar` 与 `.work-foot` 各有一份，本批只用
      在 `.work-foot`，且写成 `.work-foot > .sp`（作用域到直接子元素）。
      ⛔ 不许落成裸 `.sp{}` —— 那是给以后每一页埋一个「写个 .sp 就被顶开」的雷。
   ⚠️ `.badge` 全页**不用**：生产那份是定价卡角标（`position:absolute`），
      照抄会把状态标签甩出文档流。状态一律走 `.pill`。
   ⚠️ 正文用 `line-height:1.75` 而不是令牌：`--lh-base` 在生产**不存在**
      （样图有；照抄会得到一个无效声明，浏览器静默忽略，读数全绿但行距是默认值）。 */
.doc{ padding:var(--sp-5) var(--sp-6); }
.doc h3{
  margin:0 0 var(--sp-3); font-size:var(--fs-lg);
  font-weight:660; color:var(--ink); line-height:1.4;
}
.doc h3:not(:first-child){ margin-top:var(--sp-5); }
.doc p{ color:var(--ink-soft); line-height:1.75; }
.doc p + p{ margin-top:var(--sp-3); }

.ai-suggest{
  margin:var(--sp-4) 0 var(--sp-2); padding:var(--sp-3) var(--sp-4);
  background:var(--brand-soft); border:1px solid var(--line);
  border-left:3px solid var(--brand); border-radius:var(--r-md);
}
.as-h{ display:flex; flex-wrap:wrap; align-items:center; gap:var(--sp-2); }
.as-h b{ color:var(--ink); font-size:var(--fs-sm); font-weight:660; }
.ai-suggest p{ margin:var(--sp-2) 0 0; color:var(--ink-soft); line-height:1.75; }

.doc-bar{
  display:flex; flex-wrap:wrap; align-items:center; gap:var(--sp-2);
  padding:var(--sp-3) var(--sp-4); border-top:1px solid var(--line-soft);
  background:var(--surface-2); border-radius:0 0 var(--r-lg) var(--r-lg);
}
.doc-status{ color:var(--muted); font-size:var(--fs-sm); }

.ai-out{
  padding:var(--sp-3); border-radius:var(--r-md);
  background:var(--surface-3); color:var(--ink-soft);
  font-size:var(--fs-sm); line-height:1.7;
}
.mat-h{
  margin:var(--sp-4) 0 var(--sp-2); color:var(--muted);
  font-size:var(--fs-sm); font-weight:660; letter-spacing:.06em;
}
.mat-list{ display:grid; gap:var(--sp-2); margin:0; padding:0; list-style:none; }
.mat-card{
  display:flex; align-items:baseline; justify-content:space-between; gap:var(--sp-2);
  min-height:44px; padding:var(--sp-2) var(--sp-3);
  background:var(--surface); border:1px solid var(--line); border-radius:var(--r-md);
}
.mat-card b{ color:var(--ink); font-size:var(--fs-md); font-weight:var(--fw-medium); }
.mat-card span{ color:var(--muted); font-size:var(--fs-xs); white-space:nowrap; }

.work-foot{
  display:flex; flex-wrap:wrap; align-items:center; gap:var(--sp-4);
  margin-top:var(--sp-4); padding:var(--sp-3) var(--sp-4);
  background:var(--surface); border:1px solid var(--line); border-radius:var(--r-lg);
  color:var(--muted); font-size:var(--fs-sm);
}
.work-foot b{ color:var(--ink); font-weight:660; }
.work-foot > .sp{ margin-left:auto; }

@media (max-width:639px){
  .doc{ padding:var(--sp-4); }
  /* 390 下页脚一行放不下，`margin-left:auto` 会把按钮推到右侧留一块空 —— 收掉。 */
  .work-foot > .sp{ margin-left:0; }
}

/* ---- 定价页：FAQ 从 `div.qa` 改成原生 details（第一百零三批）-------------
   样图 `web-20261022/site/pricing.html` 的 `.faq .qa[open]` 是 details 形态；
   生产原来是 `div.qa` + `.qa-q`。改成 details 后**零脚本**就有展开收起，
   但 `.qa` 原有的 `padding:var(--sp-5)` 会与 summary 的 padding 叠一层 ⇒
   details 那一档把 padding 收掉，改由 summary / 正文各自带。

   ⚠️ `.qa` 的底色 / 描边 / 圆角**不动**（div 形态的用法还在别处）。
   ⚠️ 三角用 `::after` 而不用 `::marker`：Chromium 的 marker 不能换内容，
      `▾ / ▴` 只能靠伪元素；`::-webkit-details-marker` 一并收掉。 */
details.qa{ padding:0; }
.qa > summary{
  display:flex; align-items:center; justify-content:space-between; gap:var(--sp-3);
  min-height:52px; padding:var(--sp-4) var(--sp-5);
  color:var(--ink); font-size:var(--fs-md); font-weight:640; cursor:pointer;
  list-style:none;
}
.qa > summary::-webkit-details-marker{ display:none; }
.qa > summary::after{ content:"\25BE"; flex:none; color:var(--muted); font-size:var(--fs-sm); line-height:1; }
details.qa[open] > summary::after{ content:"\25B4"; }
.qa > .qa-a{ margin:0; padding:0 var(--sp-5) var(--sp-5); }


/* ============================================================================
   ⑤ 首屏摄影底 + 矢量科技层（第 105 批，2026-10-26）
   ----------------------------------------------------------------------------
   ⚠️ 三条硬约束（用户原话的口径，改动前先回读）：
      ① 矢量不失真 —— 位图只有一张（hero-office.jpg），线条 / 面板 / 光晕全是
         SVG 描边与 CSS 渐变；`vector-effect:non-scaling-stroke` 让描边在任何
         缩放比下都保持 1~2px，4K/8K 上不会变粗、也没有位图颗粒。
      ② 焦点防挤压 —— 容器 `overflow:hidden` + `background-position:80% center`，
         超宽屏上人物不会被挤到画外；背景只 `cover` 等比铺，绝不拉伸变形。
      ③ 底部过渡 —— `.hero-fade` 把大图渐变到 `--bg`，与下方内容衔接。
   ⚠️ 背景图写成**相对 url**（`hero-office.jpg`，相对本文件所在的 app/static/）：
      生产从 `/static/site.css` 取、样板从 `../../app/static/site.css` 取，
      两种上下文都解析到同一张图；写成 `/static/…` 会让 file:// 下的样板 404。
   ⚠️ 遮罩只在 `::before` 那层渐变里做。**不要**在 `.hero-photo` 上写 rgba 的
      background-color —— 对比度判据拿 background-color 当文字底色（§7.89 那 54 处红）。
   ⚠️ 窄屏把 `.hero-hud` 整块藏掉：字是压在整张图上的，HUD 会抢可读性。
   ============================================================================ */
.hero-photo{
  position:relative;
  isolation:isolate;              /* 层叠只在首屏内部比较，不跟页头抢 z-index */
  display:flex;                   /* 文字块垂直居中；其它层是 absolute，不参与排版 */
  align-items:center;
  overflow:hidden;                /* 焦点防挤压：多出来的图直接裁掉，绝不拉伸 */
  min-height:clamp(560px, 96vh, 1040px);
  min-height:clamp(560px, 96svh, 1040px);
  background-image:none;          /* 盖掉 .hero-navy 的渐变（摄影底自带氛围） */
  background-color:var(--navy);   /* 兜底底色；也是对比度判据读到的那个底 */
}
.hero-photo > .wrap{ position:relative; z-index:3; width:100%; }
.hero-photo .hero-grid{ grid-template-columns:minmax(0,1fr); }
.hero-copy{ max-width:min(560px, 52%); }

/* 摄影底：只等比 cover，焦点在右侧人物与屏幕 */
.hero-media{
  position:absolute; inset:0; z-index:0;
  background-image:url("hero-office.jpg");
  background-repeat:no-repeat;
  background-size:cover;
  background-position:80% center;
  /* ⚠️ 这里**不要**加 transform:scale() 做"防一像素缝"：元素矩形会越出视口，
     而 `check_overflow390` 的容差只有 1px（第一版 390 档越界 4.7px、320 档 3.8px）。
     `cover` 已经把盒子铺满，盒子内部没有缝可露。 */
}

/* 文字底下的遮罩：左侧压深保证可读，右侧基本不动，人物保持原样 */
.hero-photo::before{
  content:""; position:absolute; inset:0; z-index:1; pointer-events:none;
  background:
    linear-gradient(90deg,
      rgba(7,20,38,.92) 0%, rgba(7,20,38,.85) 30%, rgba(7,20,38,.56) 48%,
      rgba(7,20,38,.20) 66%, rgba(7,20,38,0) 86%),
    linear-gradient(180deg,
      rgba(7,20,38,.62) 0%, rgba(7,20,38,.10) 22%,
      rgba(7,20,38,.08) 64%, rgba(7,20,38,.55) 100%);
}

/* 底部过渡：大图 → 页面底色（首尾两个 rgba 的数值与 --bg 对齐，改 --bg 时同批改这里） */
.hero-fade{
  position:absolute; left:0; right:0; bottom:0; z-index:4; pointer-events:none;
  height:clamp(80px, 15vh, 200px);
  background:linear-gradient(180deg,
    rgba(247,249,252,0) 0%, rgba(247,249,252,.82) 62%, var(--bg) 100%);
}

/* ---- 矢量科技层（全部 decorative；aria-hidden 在模板里） ------------------- */
.hero-hud{ position:absolute; inset:0; z-index:2; pointer-events:none; }
.hud-svg{ display:block; width:100%; height:100%; }
.hud-svg *{ vector-effect:non-scaling-stroke; }

.hud-frame{ fill:rgba(7,20,38,.34); stroke:url(#hgLine); stroke-width:1.4;
  opacity:.62; transition:opacity .45s ease; }
.hud-tick{ fill:none; stroke:#C6FAFF; stroke-width:1.8; opacity:.8; }
.hud-grid{ fill:none; stroke:#9BEFFF; stroke-width:1; opacity:.32; }
/* 左右两块面板压在**不同的底**上：A 在亮天空上（需要玻璃底），
   B 在暗桌面上（玻璃底太重会变成一个大黑方块）⇒ B 单独压轻 */
.hud-b .hud-frame{ fill:rgba(7,20,38,.20); }
.hud-link{ fill:none; stroke:#9BEFFF; stroke-width:1.2; opacity:.35;
  stroke-dasharray:5 7; animation:hudFlow 3s linear infinite; }

.hud-bars rect{ fill:#9BEFFF; opacity:.5; transform-box:fill-box;
  transform-origin:50% 100%; animation:hudBar 3.6s ease-in-out infinite; }
.hud-bars rect:nth-child(2){ animation-delay:.28s; }
.hud-bars rect:nth-child(3){ animation-delay:.56s; }
.hud-bars rect:nth-child(4){ animation-delay:.14s; }
.hud-bars rect:nth-child(5){ animation-delay:.42s; }
.hud-bars rect:nth-child(6){ animation-delay:.70s; }
.hud-bars rect:nth-child(7){ animation-delay:.20s; }
.hud-bars rect:nth-child(8){ animation-delay:.48s; }

.hud-scan{ opacity:.85; animation:hudScan 5.2s ease-in-out infinite alternate; }

.hud-spark{ fill:none; stroke:#CBFAFF; stroke-width:2; opacity:.92;
  stroke-dasharray:6 9; animation:hudFlow 2.4s linear infinite; }
.hud-dot{ fill:#E6FDFF; opacity:.9; animation:hudPing 2.6s ease-in-out infinite; }

.hud-radar circle{ fill:none; stroke:url(#hgLine); stroke-width:1.3; }
.hud-radar circle:nth-of-type(2){ opacity:.55; }
.hud-radar circle:nth-of-type(3){ opacity:.35; }
.hud-sweep{ transform-box:view-box; transform-origin:900px 700px;
  animation:hudSpin 9s linear infinite; }
.hud-sweep path{ fill:url(#hgLine); opacity:.16; stroke:none; }

.hud-wave{ fill:none; stroke:url(#hgLine); stroke-width:2; opacity:.6;
  stroke-dasharray:9 13; animation:hudFlow 3.6s linear infinite; }

/* 整屏缓慢下移的一道扫描光 */
.hud-scanline{
  position:absolute; left:0; right:0; top:0; height:26vh;
  background:linear-gradient(180deg,
    rgba(182,247,255,0) 0%, rgba(182,247,255,.05) 62%,
    rgba(182,247,255,.12) 92%, rgba(182,247,255,0) 100%);
  animation:hudDrop 14s linear infinite;
}

/* 鼠标交互提示：一圈脉冲 + 一个点，停在人物与屏幕之间 */
.hud-cursor{
  position:absolute; left:71%; top:63%; width:22px; height:22px;
  margin:-11px 0 0 -11px; border-radius:50%;
  border:1.5px solid rgba(198,250,255,.95);
  box-shadow:0 0 0 5px rgba(140,238,255,.10), 0 0 20px rgba(140,238,255,.35);
  animation:hudCursor 2.8s cubic-bezier(.2,.6,.3,1) infinite;
}
.hud-cursor::after{
  content:""; position:absolute; left:50%; top:50%; width:4px; height:4px;
  margin:-2px 0 0 -2px; border-radius:50%; background:rgba(214,252,255,.95);
}

/* 指针在首屏上时，面板亮一档 —— 这就是"鼠标交互"的全部（零脚本） */
.hero-photo:hover .hud-frame,
.hero-photo:focus-within .hud-frame{ opacity:.85; }

/* ---- 两块面板"只在该出现的档位出现"（2026-10-27 按实测定的三条） -------------
   面板坐标在 1600×900 的 viewBox 里，`slice` 的缩放/裁切随窗口比例变；`.hero-fade`
   却是页面像素（clamp(80px,15vh,200px)）⇒ **同一个 y 不可能在所有档位都对**。
   实测（`geo108.js` 逐档量 `.hud-a` / `.hud-b` 的矩形 vs 正文与 fade）：
     · 1280×900 / 1440×900            → B 底 785，fade 顶 788，✅ 清清爽爽
     · 1536×864 / 1600×900 / 1680×960 → 落进 fade 5~9px（fade 起始段是透明的，看不出）
     · 1920×1080                      → 落进 11px（同上）
     · 1280×800 / 1366×768            → **压到提示行**（正文块垂直居中，窗口一矮就往下贴）
     · 1920×900 / 2560 及以上         → 沉进 fade 70~170px（后面那段是 82% 白）
     · 3440×1440 / 3840×2160          → 面板被 `slice` 的纵向裁切**切掉 138~248px**
   所以按"放不下就不画"处理，⛔ 不是换个 y 再赌一次：
     · 矮窗口（<860）：B 会压提示行 ⇒ 只留 A（A 在右上角，任何高度都够）。
     · 又矮又宽（≥2.05:1）：B 沉进 fade ⇒ 同上。
     · 超宽（≥2000）：B 沉进 fade、A 被裁上沿 ⇒ 两块都不画，只留雷达/波形/扫描线。
   ⚠️ 这几条只关**装饰层**；`.hero-fade` 与正文一条都没动。 */
@media (max-height:859px), (min-aspect-ratio:2.05/1), (min-width:2000px){
  .hud-b, .hud-link{ display:none; }
}
@media (min-width:2000px){
  .hud-a{ display:none; }
}

@keyframes hudBar{ 0%,100%{ transform:scaleY(.55); } 50%{ transform:scaleY(1); } }
@keyframes hudScan{ from{ transform:translateY(0); } to{ transform:translateY(78px); } }
@keyframes hudSpin{ to{ transform:rotate(360deg); } }
@keyframes hudFlow{ to{ stroke-dashoffset:-30; } }
@keyframes hudPing{ 0%,100%{ opacity:.35; } 50%{ opacity:1; } }
@keyframes hudDrop{
  0%{ transform:translateY(-30vh); opacity:0; }
  8%{ opacity:1; } 92%{ opacity:1; }
  100%{ transform:translateY(105vh); opacity:0; }
}
@keyframes hudCursor{
  0%{ transform:scale(.75); opacity:0; }
  15%{ opacity:1; }
  65%{ transform:scale(1.45); opacity:0; }
  100%{ transform:scale(1.45); opacity:0; }
}

@media (max-width:1023px){
  .hero-copy{ max-width:min(560px, 100%); }
  .hero-media{ background-position:84% center; }
}
@media (max-width:767px){
  .hero-photo{
    display:block; min-height:auto;
    padding:calc(var(--sp-8) + var(--sp-5)) 0 calc(var(--sp-9) + var(--sp-6));
  }
  .hero-photo::before{
    background:linear-gradient(180deg,
      rgba(7,20,38,.90) 0%, rgba(7,20,38,.80) 46%, rgba(7,20,38,.60) 100%);
  }
  .hero-media{ background-position:84% center; }
  /* 小屏只留那道扫描光：面板会压字、光标会跟手指抢位置 */
  .hud-svg{ display:none; }
  .hud-cursor{ display:none; }
  .hero-fade{ height:72px; }
}
/* 系统里关掉动效就一定关掉（前庭失调 / 省电） */
@media (prefers-reduced-motion:reduce){
  .hero-photo .hud-bars rect,
  .hero-photo .hud-scan,
  .hero-photo .hud-sweep,
  .hero-photo .hud-spark,
  .hero-photo .hud-wave,
  .hero-photo .hud-link,
  .hero-photo .hud-dot,
  .hero-photo .hud-scanline,
  .hero-photo .hud-cursor{ animation:none; }
}


/* ⑤ 首屏摄影底（end） */
