/* ============================================================
   展示字体：Playfair Display（SIL OFL 1.1，原文见 vendor/fonts/OFL.txt）
   为什么要自带：本机实测没有 Didot / Bodoni / Playfair 这类高对比衬线，
   只有 Georgia / Garamond / Palatino——都算不上「高级」。要那个质感只能自带。
   与 three.js 是同一个先例：一次性取回、随仓库提交，站点运行时仍然离线。
   只取 latin 子集（各 ~22KB）；中文标题会回落到 --font-cjk。
   ============================================================ */
@font-face {
  font-family: 'Playfair Display';
  font-style: normal; font-weight: 400; font-display: swap;
  src: url('vendor/fonts/playfair-display-latin-400-normal.woff2') format('woff2');
}
@font-face {
  font-family: 'Playfair Display';
  font-style: normal; font-weight: 700; font-display: swap;
  src: url('vendor/fonts/playfair-display-latin-700-normal.woff2') format('woff2');
}
/* 400 italic 是 2026-10-13 为字标补回来的：用户要求「Career 用衬线字体、倾斜」。
   为什么必须是真斜体而不是让浏览器伪斜：Playfair 是高对比 Didone，伪斜只是把正体
   整体剪切一个角度，衬线仍然立着、字腔也没有变化，19px 下看着像排版事故。
   只补 400——字标里只有 Career 那半个词用它。
   注意 @font-face 的 font-style 必须写 italic：写成 normal 的话它会被当成
   「400 正体的另一个来源」，.career 上的 font-style:italic 照样走伪斜。 */
@font-face {
  font-family: 'Playfair Display';
  font-style: italic; font-weight: 400; font-display: swap;
  src: url('vendor/fonts/playfair-display-latin-400-italic.woff2') format('woff2');
}
/* 700 italic 是 2026-10-15 为首页大标题补的（用户：「首页顶部的大标题里的『FDE』使用倾斜」）。
   ⚠️ 为什么不能只写 `font-style: italic` 了事：h1 是 `font-weight: 700`，而之前只带了
      400 那一支斜体——浏览器找不到 700 斜体，就**拿 700 正体伪斜**。高对比 Didone 的伪斜
      只是整体剪切，衬线仍立着、字腔不变，和字标那边是同一个坑（见上面 400-italic 的注释）。
   ⚠️ 同理 `font-style` 必须写 `italic`：写成 `normal` 会被当成「700 正体的另一个来源」。 */
@font-face {
  font-family: 'Playfair Display';
  font-style: italic; font-weight: 700; font-display: swap;
  src: url('vendor/fonts/playfair-display-latin-700-italic.woff2') format('woff2');
}

/* ============================================================
   ① 与主题无关的 token：字体、字号阶梯、栅格、圆角
   ============================================================ */
:root {
  /* 中文一律走系统自带的黑体——2026-10-13 起不再用宋体（用户要求「所有文字使用系统字体」）。
     微软雅黑在 Windows 上必装；mac 回落 PingFang，Linux 回落 Noto Sans SC。
     名字里不带 serif 是刻意的：它已经不再装衬线，叫 --font-serif-cjk 就是撒谎。 */
  --font-cjk: "Microsoft YaHei", "微软雅黑", "PingFang SC", "Hiragino Sans GB", "Noto Sans SC", "Source Han Sans SC", system-ui, sans-serif;
  /* 展示层：拉丁走自带的 Playfair，中文回落到上面的黑体链（Playfair 没有汉字，
     浏览器会逐字回落，所以同一个元素里中英混排也各有各的字体） */
  --font-display: 'Playfair Display', Georgia, var(--font-cjk);
  --font-body: var(--font-cjk);
  /* 阅读正文：拉丁走 Georgia，中文走黑体。小字号长文里衬线汉字太累眼，西文衬线还好 */
  --font-read: Georgia, var(--font-cjk);
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  /* 字号阶梯：只有这几档，页面里不再各写各的 */
  --fs-display: clamp(34px, 5.6vw, 68px);
  --fs-h1:      clamp(30px, 3.8vw, 48px);
  --fs-h2:      clamp(23px, 2.6vw, 34px);
  --fs-h3:      19px;
  --fs-lede:    clamp(15px, 1.4vw, 18px);

  --maxw: 1180px;
  --r: 6px;
  --r-lg: 10px;

  /* 吸顶导航的高度（实测 63px = 12 上下内边距 + 38 的搜索框 + 1 的下边框）。
     凡是「要躲开导航」的地方都读它：阅读器左侧目录的 `top`、正文标题的
     `scroll-margin-top`（点目录跳过去时，标题不能被导航盖住）。
     ⚠️ 只在 **>900px** 成立——≤900px 导航会折成两行、涨到一百多像素，
        而那一档阅读器目录已经是 `position: static`，正好不需要躲。
     改导航的内边距/控件高度时，这个数要跟着量一遍。 */
  --nav-h: 63px;

  /* 不随主题变的语义色：它们只出现在自己那一类底上，所以两档同值。
     放进这个块（而不是主题块）是刻意的——写在这里就等于声明「它不该随主题变」。 */
  --accent-fill:        #d21f2c;                 /* 实底按钮 / 徽标的品牌红。白字在它上面约 5.3:1 */
  --fg-on-accent:       #ffffff;
  --fg-on-accent-dim:   rgba(255, 255, 255, .9);
  /* ⚠️ 强调带上的三支（`--fg-dim-on-emphasis` / `--line-on-emphasis` /
     `--hover-on-emphasis`）**2026-10-14 从这里搬走了**，各档在自己那块里定义。
     原来这里写着「强调带在两档里都偏深，所以这行字两档都是浅的」——那句话的前提没了：
     浅色档的带子现在是**淡紫浅底**（用户：「这个模块没有使用浅色模式，需要适配浅色模式」），
     白字放上去就是隐形。搬进主题块的代价是键集要多对一次（theme.test.mjs 第 1 条盯着）。 */
  /* 头像色相的默认值。真正用的是每个 svg 内联的那一个（见 views/layout.js），
     这里只是让样式表自洽——不然「var(--av-h) 有没有定义」这条护栏会红。 */
  --av-h: 220;

  /* 图表色相。**直接复用流程图那三组**——同一套色板、浅深两档都调过对比度，
     另起一套只会多出一处会漂的地方。
     名字按「第几组」叫而不是按语义：流程图里 scene/src 指的是「场景 / 数据源」，
     图表这边没有那层意思，借名字会把两边的语义绑在一起。
     ⚠️ 2026-10-13 用户点名「比较脏的蓝色去掉，使用橙色」——原来 --chart-2 指的是
     流程图里那支靛青（--svg-src），现在换成琥珀。所以这张表里**没有蓝色**，
     需要更多颜色时用 color-mix 从这三支里兑，不要再引进第四支色相。 */
  --chart-1: var(--svg-mid);    /* 品牌紫 —— 主色 */
  --chart-2: var(--svg-scene);  /* 琥珀橙 */
  --chart-3: var(--svg-out);    /* 松绿 */

  color-scheme: light;
}

/* ============================================================
   ② 主题色 token。两档的键集必须完全一致——漏定义一个不会报错，
   只会静默继承成另一档的值，然后某一处文字在某个模式下隐形。
   护栏：tests/theme.test.mjs 比对这两个块的键集。
   浅色档挂在 `:root, [data-theme="light"]` 上：没设属性时（无 JS）也要生效。
   ============================================================ */
:root,
[data-theme="light"] {
  --bg:             #f7f7fb;
  --bg-2:           #eeeef5;
  --bg-3:           #e5e5ee;
  --bg-wash:        rgba(210, 31, 44, .055);  /* 页面角上那层极淡的红光，免得整页死平 */
  --panel:          #ffffff;   /* 卡片与输入框的底 */
  --nav-bg:         rgba(247, 247, 251, .86); /* 吸顶导航的毛玻璃底 */
  --fg:             #14141f;
  /* 次级文字三档。⚠️ 2026-10-14 整体调暗一档——用户：「备注的文字或者次级文字需要透明度低些，
     让重要的信息更明显一些，次要信息更弱化一些」。
     这三档**不再当纯色改**，而是按「对卡片底（--panel #ffffff）的目标对比度」定的：
       --fg        18.3:1  正文，不动
       --fg-2       6.73 → 5.61
       --fg-3       5.22 → 4.22
       --fg-placeholder 4.83 → 3.98
     保住 WCAG AA 正文线（4.5:1）的是 --fg-2——它是 .lede / .note / 卡片摘要这些
     **成句的**次级文字，掉到 4.5 以下就是拿可读性换层次。--fg-3 是 .label / .cap
     这类**标签**（10px 宽字距、本来就在次要层），4.2 够用。
     ⚠️ 三档的区别不只是深浅，**字号也一起决定了它属于哪档**：字号越小越该用更暗的那档，
     反过来（小字用 --fg-2、大字用 --fg-3）会把「谁更重要」读反。 */
  --fg-2:           #666679;
  --fg-3:           #7a7a8a;
  --fg-placeholder: #7e7e91;
  --line:           #e3e3ec;
  --line-strong:    #cfcfdc;   /* 比 --line 重一档：卡片/按钮/图形描边、分隔轨 */
  /* 行业流程图的四列色相。参考稿的节点是「一列一个色」，这里照做：
     场景=琥珀、数据源=靛青、本体·对象=品牌紫、结果=松绿。
     四张图共用同一套——它们是并列的四种现场，不是四套配色。
     三样一组：底色（节点填充）、色相（描边与状态点）、辉光（那一圈光晕）。
     浅色档一律取深色号，理由同 --accent：中浅的色在白底上根本立不住。 */
  --svg-scene:      #b04e03;
  --svg-scene-bg:   #fdf3e3;
  --svg-scene-glow: rgba(180, 83, 9, .22);
  --svg-src:        #02669d;
  --svg-src-bg:     #e6f2fa;
  --svg-src-glow:   rgba(3, 105, 161, .22);
  /* 中列直接取品牌紫的三个 token，不另抄一份——它是全站唯一的强调色，
     抄出来就会在下次改主题时漂掉，而漂掉的那一列不会有任何报错。 */
  --svg-mid:        var(--accent);
  --svg-mid-bg:     var(--accent-soft);
  --svg-mid-glow:   var(--accent-glow);
  --svg-out:        #088136;
  --svg-out-bg:     #e7f4ea;
  --svg-out-glow:   rgba(21, 128, 61, .22);
  /* 甜甜圈环上那三支色相。**和 --chart-* 分开，不是重复。**
     ⚠️ --chart-2/--chart-3 在浅色档就是上面那两支 #b04e03 / #088136 —— 它们是**按描边调的**：
     画在近白纸上当细线要够暗才看得清。可甜甜圈的环是**一整块填充**，
     同一个色号画成色块就发暗发浊（2026-10-23 用户：「色彩需要更加明亮一些」）。
     填充与描边对明度的要求正好相反，一套色号做不到两头都好看，所以给甜甜圈单独一支
     ——**不动全站共用的那三支**（柱状图、流程图、行业分布都还在用）。
     ⚠️ 它们**必须定义在两个主题块里**，不能像 --chart-* 那样住在 :root 靠别名解析：
     这两档要的是两个**不同的实值**（theme.test.mjs 第 1 条会比对键集，缺一边就红）。
     段与段之间的两支（品红 / 青柠）由这三支用 hsl 兑出来，见 .dn-seg 那组规则。 */
  --dn-1: #5d34f4;
  --dn-2: #d87600;
  --dn-3: #10a95c;
  /* 操作台「本体图」的五个类目色。2026-10-15 用户：「内部的本体的节点配色需要参考项目
     D:OSFDE，需要让用户看的清楚」。参考的是 FDE 的 CANVAS_KINDS（本体画布上的元素
     类别）——只把 agent 换成它另一张表 SUGGESTION_TYPE_COLOR 里的绿：FDE 那套里
     action 与 agent 是两种紫、在图上分不开，而这五类在我们这张图上是一起出现的。
     五色两两不相邻：蓝 / 紫 / 红 / 绿 / 灰。⚠️ 底色仍走 --panel，类目色只上描边 +
     一层很淡的同色相填充（见 .ops-node.k-*），别把卡片底掀翻。 */
  --ops-object:     #2563eb;
  --ops-action:     #7c3aed;
  --ops-event:      #dc2626;
  --ops-agent:      #047857;
  --ops-orphan:     #64748b;
  --svg-wire:       #b9b9cf;   /* 连线与箭头的底色。四列都上色之后，
                                  线要够重才读得出是「连接」而不是「分隔」 */
  /* ⚠️ 2026-10-15 用户：「系统内色彩模式都需要高饱和度色彩」。这一轮**只提强调色与
     图表色**的饱和度（品牌紫 / 琥珀 / 靛青 / 松绿），底色、面板、文字、发丝线一律不动
     ——所以整站的明暗层次没变，只是那几支颜色更「艳」了。

     改法是**保持色相与明度、只把 S 推上去**（浅档紫 66→85、松绿 72→88、琥珀 90→97…），
     明度不动则对比度基本不动，护栏（WCAG AA）自然还站得住。实测：
       浅 accent 对白底 6.32 → **7.37**、白字压 accent-fill 5.34 → **6.44**
       深 accent 对面板 6.72 → **5.35**、对强调带 6.07 → **4.83**（仍在 AA 线之上）
     ⚠️ 深档的紫原本就是 S100/L77——**饱和度已经到顶，只能把明度压下来才显得艳**
        （77→73）。压太多会掉对比度，73 是实测能保住 4.5:1 的那一档，别再往下调。
     ⚠️ 浅档的 `--svg-out` / `--svg-scene` 提饱和会**掉**对比度（绿与琥珀在低明度下
        提饱和反而变暗），所以它们同时把 L 降了一两档补回来。这几支只当描边与填充用
        （`.svg-t` 的文字走 `--fg`），门槛是图形的 3:1，不是正文的 4.5:1。 */
  --accent:         #b3131f;   /* 职坐标品牌红：白底 6.9:1 */
  --accent-hover:   #8f0d17;
  --accent-soft:    rgba(210, 31, 44, .14);
  --accent-glow:    rgba(210, 31, 44, .24);
  /* 出身头像那圈柔和投影。⚠️ 必须是**中性黑**、不能走 --fg：深色档的 --fg 是近白，
     拿它当投影会变成一圈发光，不是投影。 */
  --face-shadow:    rgba(20, 20, 31, .20);
  /* 场景配图上压的那行海报字。图是黑白的、字压在一层深色蒙版上，
     所以这一组**基本与主题底色无关**——浅深两档只差一点亮度。
     红必须够亮：深红压在深蒙版上会糊成一团，读不出来。 */
  --scene-red:      #ff5a5c;
  --scene-ink:      #ffffff;
  /* ⚠️ 浅深两档同值：蒙版压的是**照片**不是主题底色，本来就与主题无关。
     差一点点会出事——.72 那档红字实测掉到 2.8:1（红天生亮度低）。 */
  --scene-scrim:    rgba(0, 0, 0, .78);
  /* 压在深色照片上的强调紫。**不能复用 --accent / --accent-on-emphasis**：
     浅色档那两支是 #472bee，压在深图上只有 1.x:1，读不出来。
     这一支跟 --scene-* 一样，与主题无关，两档同值。 */
  --scene-accent:   #a08cff;
  /* 强调带（收尾那一条）。2026-10-14 从「深带」改成**淡紫浅底**——
     用户看首页截图：「这个模块没有使用浅色模式，需要适配浅色模式」。
     原来浅色档也是深底 #14141f（当时的理由是「浅色页面上来一道深带才有对比」），
     在浅色页面里读出来就是一块黑，和整站不是一套语言。
     ⚠️ 带子上那五支**必须**跟着翻（它们是这套 token 里唯一「假设底是深的」的）：
     白字放在 #ece9fb 上就是隐形。所以三支 `*-on-emphasis` 从 :root 搬了下来。 */
  --emphasis-bg:    #fbeaec;   /* 极淡的品牌红，比 --bg-2 更有温度 */
  --emphasis-fg:    #14141f;
  --accent-on-emphasis:  #b3131f;                  /* 浅底不用提亮，直接用普通强调色 */
  --fg-dim-on-emphasis:  rgba(20, 20, 31, .62);
  --line-on-emphasis:    rgba(20, 20, 31, .26);
  --hover-on-emphasis:   rgba(20, 20, 31, .06);
  --fg-dim-on-invert: rgba(255, 255, 255, .66);  /* 反色小件（--fg 当底）上的次级文字 */
  /* 头像的明度。色相由 avatarSeed 算（内联 --av-h），明度得跟主题走：
     浅色档是浅底深字，深色档翻成深底浅字。 */
  --av-l-bg: 88%;
  --av-l-fg: 74%;
  --av-l-t:  22%;
}

/* 深色档。此刻它是浅色档的逐字节复制：这一步只验「机制」——切换、持久化、
   刷新后记住、无闪白。换色留到下一步，这样机制出问题时不会和配色改动混在一起查。
   刻意**不写** `color-scheme: dark`：值还是浅色的，声明 dark 会让表单控件和滚动条
   先变黑，那就不是零视觉变化了。换值时再补上。 */
[data-theme="dark"] {
  color-scheme: dark;
  --bg:             #0a0a12;
  --bg-2:           #0f0f1a;
  --bg-3:           #16161f;
  --bg-wash:        rgba(255, 107, 107, .10);
  --panel:          #12121c;
  --nav-bg:         rgba(10, 10, 18, .82);
  --fg:             #ececf5;
  --fg-2:           #8e8ea4;
  --fg-3:           #72728a;
  --fg-placeholder: #6f6f85;
  --line:           #23233a;
  --line-strong:    #33334f;
  /* 四列色相的深色档：同一个色相提亮（浅底深字 → 深底浅字）。
     底色不是「更浅的色」而是「掺了黑的色」——深色档里节点底得比 --panel 略亮一点，
     才看得出是个「块」；直接用浅色档那种淡底会亮成一排白条。 */
  --svg-scene:      #faa138;
  --svg-scene-bg:   #2a1f14;
  --svg-scene-glow: rgba(240, 164, 74, .40);
  --svg-src:        #4ab4f7;
  --svg-src-bg:     #0f2231;
  --svg-src-glow:   rgba(100, 181, 232, .40);
  --svg-mid:        var(--accent);
  --svg-mid-bg:     var(--accent-soft);
  --svg-mid-glow:   var(--accent-glow);
  --svg-out:        #35e979;
  --svg-out-bg:     #0e2417;
  --svg-out-glow:   rgba(95, 208, 138, .40);
  /* 甜甜圈环色（深色档）。浅色档那三支按填充调过，深色档这里同样取「填充用」的一档
     —— 比 --svg-* 那几支再亮一点，压在近黑底上才不发闷。理由见浅色块那段注释。 */
  --dn-1: #9b8cff;
  --dn-2: #ffb04d;
  --dn-3: #45f58c;
  /* 同上一组，深色档取 400 那一档（够亮）。色相一一对应，类目含义不变。 */
  --ops-object:     #4c8dff;
  --ops-action:     #a78bfa;
  --ops-event:      #f87171;
  --ops-agent:      #34d399;
  --ops-orphan:     #94a3b8;
  --svg-wire:       #45456b;
  --accent:         #ff6b6b;
  --accent-hover:   #ff8585;
  --accent-soft:    rgba(255, 107, 107, .14);
  --accent-glow:    rgba(255, 107, 107, .30);
  /* 深色档投影要比浅色档重：底色本来就黑，淡投影等于没有 */
  --face-shadow:    rgba(0, 0, 0, .60);
  /* 场景配图上的海报字：深色档只把红再提亮一档，蒙版与浅色档同值 */
  --scene-red:      #ff7a7a;
  --scene-ink:      #ffffff;
  --scene-scrim:    rgba(0, 0, 0, .78);
  --scene-accent:   #a08cff;
  --accent-on-emphasis:  #ff8585;   /* 深色档的带子是「抬一档」的近黑，强调色照旧要提亮 */
  --emphasis-bg:    #211416;   /* 深色档里强调带不是「翻成浅色」而是「抬一档」：整页近黑时
                                  来一条整幅的浅带太扎眼，抬一档才有层次。
                                  ⚠️ 浅色档 2026-10-14 起是**淡紫浅底**，两档不再是同一种深带 */
  --emphasis-fg:    #ececf5;
  --fg-dim-on-emphasis:  rgba(255, 255, 255, .66);  /* 深底上的次级字，浅色档那边是深字 */
  --line-on-emphasis:    rgba(255, 255, 255, .38);
  --hover-on-emphasis:   rgba(255, 255, 255, .10);
  --fg-dim-on-invert: rgba(20, 20, 31, .62);
  --av-l-bg: 26%;
  --av-l-fg: 40%;
  --av-l-t:  88%;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  color: var(--fg);
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

/* ⚠️ 2026-10-19：收尾带那片海要铺满视口宽，用的是 `100vw`，而 `100vw` **含滚动条宽度**——
   本机 1440 视口下它比可用宽度多 15px，实测撑出 8px 的横向溢出（`scrollWidth` 1433 >
   `clientWidth` 1425）。这里裁掉。
   ⚠️ 必须是 `clip` 而不是 `hidden`：`hidden` 会**建出一个滚动容器**，于是 `position: sticky`
   的参照物从视口变成这个容器，吸顶导航与文章左侧目录会一起失效（而且不报错）。
   `clip` 不建滚动容器，正是为这件事造的。另一轴留 `visible`——`clip` 是唯一允许和
   `visible` 配对的取值。
   ⚠️ 这是全站唯一一处横向裁切，所以**别处不许再有故意横向溢出的东西**：
   需要横向滚的地方（表格、tab 条）自己带 `overflow-x: auto`，裁不到它们。 */
html { overflow-x: clip; }
body { overflow-x: clip; }

/* 纸面：底色之上压一层极淡的紫光，避免整页死平 */
body {
  background-color: var(--bg);
  background-image: radial-gradient(1100px 520px at 84% -8%, var(--bg-wash), transparent 62%);
  background-repeat: no-repeat;
  background-attachment: fixed;
}

a { color: inherit; text-decoration: none; }
a:hover { color: var(--accent); }

.wrap { max-width: var(--maxw); margin: 0 auto; padding: 0 24px; }

/* 标签 / 元信息 */
.label {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--fg-3);
}

/* 顶部导航：吸顶，滚动时留一条毛玻璃 */
.nav {
  position: sticky; top: 0; z-index: 30;
  background: var(--nav-bg);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--line);
}
/* 只写上下内边距，别写简写：`.wrap` 给的 `padding: 0 24px` 会被 `padding: 15px 0`
   整个覆盖掉，于是导航内容贴到视口边缘、和下面的正文差 24px 对不齐（一直是错的）。 */
.nav .nav-in { display: flex; align-items: center; gap: 22px; padding-top: 12px; padding-bottom: 12px; }
/* 字标：两段式。Career 走衬线斜体、只有首字母大写；FDE 无衬线粗体、不倾斜。
   源文本保持 "Career FDE" 原样——读屏软件念的是正常的两个词，不是拼在一起的缩写。
   .fde 显式写 var(--font-body)：.brand 上不设字体族，
   免得被别处的 --font-display 继承进来（回车到衬线在汉字回退时尤其乱）。 */
/* ⚠️ 这里**不用 gap**，改成逐项 margin：方印与 Career 之间要留够（印转了 8°，
   角会探出来，留窄了会顶到字），而 Career 与 FDE 要贴紧——它们是一个词的两半。 */
.nav .brand {
  display: flex; align-items: center; white-space: nowrap;
  font-family: var(--font-body);
  /* 2026-10-14 用户要求「CareerFDE 需要放大」：19 → 23px。导航那一条的高度不受影响——
     `.nav .nav-in` 的高度由 32px 的 tab / 搜索框决定，字标在这一行里是最矮的那个。 */
  font-size: 23px; letter-spacing: .01em;
  color: var(--fg);
}
/* Career 2026-10-13 改的：原来是「300 细体无衬线」，现在要衬线 + 倾斜。
   走 --font-display（Playfair → Georgia）而不是写死 Playfair——两个都是衬线，
   谁先命中都不算跑题；写死反而会在字体没到位时掉回无衬线。
   font-weight 收成 400：Playfair 只带了 400/700 两档，300 会触发浏览器合成，
   在衬线上表现为笔画被削细、粗细对比失真。
   字号用相对值：衬线的 x-height 比无衬线小，同字号下衬线那半个词看着更小，
   所以补 5%。⚠️ 不能写死 px——窄屏那条 `.nav .brand { font-size: 16px }` 就管不到它了，
   字标会变成 Career 20px 配 FDE 16px。 */
.nav .brand .career {
  font-family: var(--font-display);
  font-style: italic; font-weight: 400;
  font-size: 1.05em; letter-spacing: .02em;
  /* 印转到 8° 之后角探出来的更少（原来是 15°），但字号放大了，留的间距跟着调宽一档。 */
  margin-left: 13px;
  /* 2026-10-13：原来是 --fg-2（比 FDE 暗一档），用户要求「色彩统一」——
     两半现在是同一个 --fg，只有 FDE 那道紫晕还留着。 */
  color: var(--fg);
}
.nav .brand .fde {
  font-weight: 800; letter-spacing: .06em;
  /* 与 Career 贴紧：它们是一个词的两半，原来 10px 的间距把它拆成了两段 */
  margin-left: 2px;
  /* 「光」的第一处：字标右侧那半个词带一层极淡的紫晕，像是被灯照到。
     用 text-shadow 而不是 filter——前者不产生新的合成层，字标每页都在首屏。 */
  color: var(--fg);
  text-shadow: 0 0 22px var(--accent-glow);
}
/* 方印里的「前」是汉字，Playfair 没有字形，所以显式指定中文字体栈 */
.nav .brand .mk {
  width: 24px; height: 24px; border-radius: 3px; background: var(--accent-fill); color: var(--fg-on-accent);
  display: inline-flex; align-items: center; justify-content: center;
  font-family: var(--font-cjk); font-size: 13px; font-weight: 700;
  /* 斜着盖下去的印。2026-10-14 用户把角度从 15° 收到 **8°**（「倾斜角度调整为 8 度」）。
     ⚠️ transform 不改布局盒——斜出去的那几个角仍然算在 24px 见方里，
     所以四周的间距得自己留够（见上面 .career 的 margin-left）。 */
  transform: rotate(8deg);
  /* 「光」的第二处：印章自己发一点光，跟右侧字标的晕呼应。
     参考稿里那个方块标记本身就是发光的，这里是最该有光的位置。 */
  box-shadow: 0 0 14px var(--accent-glow);
}
/* margin-top 必须显式归零：第 400 行那个给页面内 tab 用的 .tabs 带 margin-top:26px，
   这里只覆盖了 display 和 gap，于是导航里这四个 tab 被下推 26px、还把导航撑高 26px */
.nav .tabs { display: flex; gap: 3px; margin-top: 0; }
.nav .tab {
  /* 2026-10-13：统一 32px 高（与搜索框、主题按钮一致）。
     原来靠 padding 撑出来是 35px，比搜索框高一截，一行里三个高度看着就散。
     改成固定高 + inline-flex 居中：字号或字体一换，高度不会再跟着飘。 */
  display: inline-flex; align-items: center; height: 32px; padding: 0 14px;
  font-family: var(--font-mono); font-size: 12px; font-weight: 600; white-space: nowrap;
  border-radius: 4px; color: var(--fg-2);
}
.nav .tab.on { background: var(--fg); color: var(--bg); }
.nav .right { margin-left: auto; display: flex; align-items: center; gap: 14px; }
/* 导航里的按钮也统一 32px（.btn 本身是 padding 撑的，全站共用，不能动它） */
.nav .right .btn { display: inline-flex; align-items: center; height: 32px; padding: 0 16px; }

/* 搜索框 */
.search {
  width: 190px; height: 32px; border: 1px solid var(--line); background: var(--panel);
  border-radius: 5px; padding: 0 11px; font-family: var(--font-mono); font-size: 11px;
  color: var(--fg);
}
.search::placeholder { color: var(--fg-placeholder); }

/* 主题切换。只显示「点了会去哪儿」那个图标：浅色下给月亮，深色下给太阳。
   尺寸与搜索框同高（32px），免得把导航撑歪。 */
.theme-toggle {
  width: 32px; height: 32px; flex: none; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--line); border-radius: 5px;
  background: var(--panel); color: var(--fg-2);
  font-size: 14px; line-height: 1; cursor: pointer;
  transition: border-color .15s ease, color .15s ease;
}
.theme-toggle:hover { border-color: var(--line-strong); color: var(--fg); }
.theme-toggle .tt-sun { display: none; }
[data-theme="dark"] .theme-toggle .tt-sun { display: inline; }
[data-theme="dark"] .theme-toggle .tt-moon { display: none; }

/* 表单控件。全站只有 .search 是显式写过样式的，其余输入框一直吃浏览器默认长相——
   换个浏览器就换一副脸，而且默认聚焦环是蓝的，跟紫主色打架。
   用 :where() 把特异性压成 0：这样 .search 和 views 里的内联 style 都能照常覆盖它。 */
:where(input:not([type="checkbox"]):not([type="radio"]):not([type="submit"]), textarea, select) {
  font-family: var(--font-body); font-size: 13.5px; color: var(--fg);
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--r);
  padding: 9px 11px;
  transition: border-color .15s ease, box-shadow .15s ease;
}
:where(input, textarea, select):focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
:where(input, textarea)::placeholder { color: var(--fg-placeholder); }
textarea { line-height: 1.7; resize: vertical; }

/* 头像（由 avatarSeed 确定性生成，见 views/layout.js 的 avatar()）
   色相内联在 svg 的 --av-h 上，明度取主题 token。
   颜色必须写在规则里而不是 fill="…" 上：表现属性不接受 var()。
   --av-h 在 :root 里有个默认值，每个 svg 再内联覆盖成自己的色相。 */
.av { border-radius: 50%; display: inline-block; flex: none; }
.av-bg { fill: hsl(var(--av-h) 30% var(--av-l-bg)); }
.av-fg { fill: hsl(calc(var(--av-h) + 40) 34% var(--av-l-fg)); }
.av-t  { font-family: var(--font-display); font-weight: 700; fill: hsl(var(--av-h) 30% var(--av-l-t)); }

/* 面包屑：导航之下、正文之上，与正文同宽。从路径自动推（见 views/layout.js 的 crumbsFrom），
   所以每个二级页面都有，不用各处手写。
   整条走大写宽字距，但**当前项还原**——它常是文章/案例标题，大写会把中英混排弄得很怪。 */
.crumbs {
  display: flex; flex-wrap: wrap; align-items: center; gap: 7px;
  padding-top: 18px;
  font-family: var(--font-mono); font-size: 10.5px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--fg-3);
}
.crumbs a { color: var(--fg-3); }
.crumbs a:hover { color: var(--accent); }
.crumbs .crumb-sep { color: var(--line-strong); }
.crumbs [aria-current] { color: var(--fg); text-transform: none; letter-spacing: 0; }

/* 按钮。主按钮走品牌紫 + 一层辉光；ghost 是描边款。
   .btn.accent 现在和 .btn 是同一件事，保留选择器只是免得去改一堆调用点。 */
.btn {
  display: inline-block; font-family: var(--font-mono); font-size: 11.5px; font-weight: 600;
  padding: 9px 16px; border-radius: var(--r); border: 1px solid var(--accent-fill);
  background: var(--accent-fill); color: var(--fg-on-accent); cursor: pointer; white-space: nowrap;
  transition: transform .12s ease, box-shadow .15s ease, background .15s ease, border-color .15s ease;
}
.btn:hover { transform: translateY(-1px); box-shadow: 0 6px 18px -6px var(--accent-glow); }
.btn.ghost { background: transparent; color: var(--fg); border-color: var(--line-strong); }
.btn.ghost:hover { background: var(--bg-2); border-color: var(--accent); color: var(--accent); box-shadow: none; }
.btn.accent { background: var(--accent-fill); border-color: var(--accent-fill); color: var(--fg-on-accent); }
.btn.accent:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
.btn.lg { padding: 14px 26px; font-size: 12.5px; letter-spacing: .04em; border-radius: var(--r-lg); }

/* 卡片 */
.card {
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--r);
  padding: 14px;
  transition: border-color .15s ease, transform .15s ease, box-shadow .15s ease;
}
a.card:hover {
  border-color: var(--accent);
  transform: translateY(-1px);
  box-shadow: 0 8px 22px -10px var(--accent-glow);
}

/* 键盘焦点环。用 :focus-visible 而不是 :focus —— 鼠标点击不该留下一个圈。
   没有它的话，键盘用户在整站找不到自己在哪儿。 */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 3px;
}

/* 首页 hero */
.hero { position: relative; padding: 52px 0 0; }
/* 首屏背后那片环境光——参考稿里最抢眼的一层，也是这版「光」的主要来源。
   单独用 ::before 铺，不碰任何既有元素；pointer-events:none 保证它不吃点击。
   两档主题共用 --accent-glow（浅色档本来就弱一档，正合适）。 */
/* ⚠️ 宽度必须按容器算（100%），不能用 vw：绝对定位的元素照样计入 scrollWidth，
   132vw 在窄屏会把页面撑出一条横向滚动（408px 下实测溢出 58px）。 */
.hero::before {
  content: ''; position: absolute; z-index: 0; pointer-events: none;
  left: 50%; top: -150px; transform: translateX(-50%);
  width: min(1240px, 100%); height: 780px;
  background: radial-gradient(ellipse 50% 50% at 50% 50%, var(--accent-glow), transparent 70%);
  transition: opacity .6s ease;
}
/* 内容一律压在光之上 */
.hero > * { position: relative; z-index: 1; }

/* 首屏背后那层动效（`public/gl/colorbends.js`——React Bits 的 Color Bends，只用紫色范围）。
   ⚠️ 类名刻意叫 `hero-bg` 而不是按效果命名：这一格 2026-10-23 一天里换过两回
   （先 Aurora、同日改成 Color Bends），效果名进类名的话每换一次都要连 CSS 与测试一起改。
   ⚠️⚠️ 选择器必须是 `.hero > .hero-bg`，**不能只写 `.hero-bg`**。
   文件后面（gl 那一段）有一条全局的 `[data-scene] { position: relative }`——
   它的特异性和 `.hero-bg` 一样是 (0,1,0)，但**排在后面**，于是 `position: absolute`
   被顶掉、这一层落回文档流：它变成一个几百 px 高的块，把页头整段文字**往下推**，
   结果就是「动效在文字上面、文字被挤到它下面」。2026-10-23 用户截图圈的正是这个。
   多一层 `.hero >` 把特异性抬到 (0,2,0) 就压住了。
   （`.hero > *` 那条同理——它也是 (0,1,0)，也在这个文件更前面。） */
.hero > .hero-bg {
  position: absolute; z-index: 0; pointer-events: none;
  top: 0;
  /* 通栏：`left:50%` 从容器中心起、`margin-left:-50vw` 拉回半个视口宽。
     ⚠️ 用 `vw` 而不是 `100%` 就是因为它要**超出 `.wrap`**（wrap 只有 1180px）。
     `.hero::before` 那层光晕当年刻意避开 `vw`（绝对定位元素也计入 `scrollWidth`），
     这里同样盯着这一点：量过 380 / 900 / 1440 三档 `scrollWidth == clientWidth`，没有横向滚动条。 */
  left: 50%; margin-left: -50vw; width: 100vw;
  /* 高度覆盖整个页头（kicker → h1 → 导语 → 四格数字），在下面那两块入口面板之前收住。
     ⚠️ 这个高度**是内容高度的尺子**，不是「画多高」——Color Bends 的画面是自己铺满整块容器的，
     不像上一版 Aurora 那样还有一道 `S.height` 决定光幔垂到哪儿。
     ⚠️⚠️ 但**光带是有颜色的、容器是有底的**，所以到这个高度它会**齐刷刷断掉**，
     在页面上留一条横贯全宽的硬边（2026-10-23 用户圈的就是这条：「这底部紫色需要和黑色有过渡，
     不要有明显的边」）。解法是给这一层加一道**蒙版**把下沿化开——见下面那条 `mask-image`。
     不用「把容器加高」来糊：加高只是把那条边推到更低的地方，边还在。 */
  height: clamp(520px, 58vh, 680px);
  /* 下沿淡出：最后那一小段把 alpha 收到 0，收尾是渐变而不是一条线。
     ⚠️ 这里的 `black` **不是颜色**，是蒙版的 alpha 通道（蒙版只认明度：黑=全透、白=全留），
     所以它不跟主题走、也不该换成 token——两档主题共用这一份。
     用关键字而不是十六进制是因为 `tests/theme.test.mjs` 那条「token 块之外不许出现颜色字面量」
     会把 `#000` 当成漏网的颜色抓出来（它确实是这个意思：散落的字面量不跟主题走）。
     ⚠️ 90% 是量出来的，**两头都卡着**：
       · 往上——正文（kicker→四格数字）固定占到离层顶 485px 处，而这一层的高度是
         `clamp(520px, 58vh, 680px)`。最小那档 520px 时 485/520 = **93%**，
         所以再往 93% 以上靠，fade 就会爬进四格数字那一带；
       · 往下——fade 太短就等于没做，边还在。
     取 90%：四格数字本身是不透明的卡片，它底下被压淡看不出来；真正会被压到的
     只有卡片之间那 14px 缝与两侧留白，那正是该淡下去的地方。
     ⚠️ 两档主题共用同一条：浅色档的底是近白、深色档是近黑，但这条边**两档都**看得见。 */
  -webkit-mask-image: linear-gradient(to bottom, black 90%, transparent 100%);
  mask-image: linear-gradient(to bottom, black 90%, transparent 100%);
}
/* 动效真起来之后，把这层 CSS 光晕让出去——两层紫叠在一起会糊成一块，读不出层次。
   ⚠️ 判据用 `:has()` 而不是给 `.hero` 加个类：那要 JS 往**父级**上挂，而 core.js 的
   约定是只往挂载点（`.hero-bg`）自己身上挂 `gl-on`，为这一个特例破约定不值当。
   ⚠️ 降级是安全的：不认 `:has()` 的浏览器当这条不存在，两层紫同时出现——
   难看一点，但什么都不缺。 */
.hero:has(.hero-bg.gl-on)::before { opacity: 0; }
/* 报头一行：居中（参考稿的报头是居中的）。印章从文档流里挪出来钉在右上角——
   留在行内会把居中的标题挤偏。 */
.hero-top { display: flex; justify-content: center; align-items: center; gap: 24px; }
.hero .kicker {
  font-family: var(--font-mono); font-size: 11px; letter-spacing: .2em; text-transform: uppercase;
  color: var(--accent); display: inline-flex; align-items: center; gap: 10px;
  padding: 7px 16px; border: 1px solid var(--line-strong); border-radius: 999px;
  background: var(--panel);
}
/* 胶囊左侧那枚小圆点——参考稿里每个标签都带一个，且是发光的 */
.hero .kicker::before {
  content: ''; width: 6px; height: 6px; border-radius: 50%; background: var(--accent);
  flex: none; box-shadow: 0 0 10px var(--accent-glow);
}
.seal {
  position: absolute; right: 0; top: 46px; z-index: 2;
  flex: none; width: 96px; height: 96px; border: 3px solid var(--accent); border-radius: 9px;
  display: flex; align-items: center; justify-content: center; text-align: center;
  color: var(--accent); font-family: var(--font-display); font-weight: 700;
  font-size: 28px; line-height: 1.08; letter-spacing: .04em;
  transform: rotate(-6deg); opacity: .92;
  /* 「光」的第三处：这枚印是首屏唯一孤立的图形，值得被点亮。
     外发光 + 极淡的内发光一起给，读起来像盖下去之后还留着余温。
     它不在卡片网格里，所以发光不会顶开任何缝。 */
  box-shadow: 0 0 34px -4px var(--accent-glow), inset 0 0 20px -8px var(--accent-glow);
}
.hero h1 {
  font-family: var(--font-display); font-weight: 700;
  font-size: var(--fs-display); line-height: 1.06; letter-spacing: -.03em;
  margin: 26px 0 0; text-align: center;
}
.hero h1 .l2 { display: block; }
.hero h1 em {
  /* 2026-10-15 用户：「首页顶部的大标题里的『FDE』使用倾斜」。
     ⚠️ 这里要的是**真斜体**（Playfair 700 italic，2026-10-15 补进 vendor/fonts/），
        不是浏览器伪斜——高对比衬线的伪斜一眼假，理由与字标那边同一套。 */
  font-style: italic; color: var(--accent);
  box-shadow: inset 0 -0.16em 0 var(--accent-soft);
  /* 强调词自带一点辉光——参考稿里最亮的就是这类被点亮的关键词 */
  text-shadow: 0 0 26px var(--accent-glow);
}
.hero .lede {
  font-family: var(--font-read); font-size: var(--fs-lede); line-height: 1.78;
  color: var(--fg-2); max-width: 46ch; margin: 24px auto 0; text-align: center;
}
.cta { display: flex; gap: 12px; flex-wrap: wrap; }
.hero .cta { margin: 34px 0 46px; }

/* 大数字条（案例卡与详情用）。形状走上面的宫格；数字走品牌紫——参考稿里数字最抢眼 */
.metrics .num { font-family: var(--font-display); font-weight: 700; font-size: 30px; line-height: 1; letter-spacing: -.02em; color: var(--accent); }
/* 缺失读数（「未写明」/「面议」）。见 views/cases.js 的 MISS：照旧显示，但不该抢眼。 */
.metrics .num.miss { color: var(--fg-3); font-size: 22px; font-weight: 400; }
.metrics .cap { font-family: var(--font-mono); font-size: 9.5px; color: var(--fg-3); margin-top: 8px; letter-spacing: .12em; text-transform: uppercase; }

/* ---------------- 图表 ----------------
   2026-10-13 照参考稿重做。三样东西撑起「呼吸感」：
   ① 每张图有自己的卡片（大圆角 + 足量内边距）；② 行与行之间留够间隔；
   ③ **没有辅助线**——原来 .vbars 底下那条轴线是这张图里唯一的硬线，去掉了。

   色相走 --ch：每张 .chart 按 chart--N 定一次，里面所有图形读它。
   于是「换一张图的颜色」是改一个数字，不是改一串选择器。
   形状以前散在 views/trends.js 的内联样式里，现在归这里——两档主题与后续调形都只动一处。 */
.charts { display: flex; flex-direction: column; gap: 18px; }
.chart {
  --ch: var(--chart-1);
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--r-lg);
  padding: 22px 24px 24px;
}
.chart--1 { --ch: var(--chart-1); }
.chart--2 { --ch: var(--chart-2); }
.chart--3 { --ch: var(--chart-3); }
/* 题头：一枚色点 + 一行小字，和参考稿里每个图表的题头一致 */
.ctitle {
  display: flex; align-items: center; gap: 8px;
  font-family: var(--font-mono); font-size: 10.5px; letter-spacing: .14em;
  text-transform: uppercase; color: var(--fg-2);
}
.cdot { width: 7px; height: 7px; border-radius: 50%; background: var(--ch); flex: none; }

/* 行：名称 + 说明，或一条链接行 */
.rows { display: flex; flex-direction: column; gap: 15px; margin-top: 20px; }
.row { display: block; }
.row--link { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; text-decoration: none; }
.row--flat { display: flex; gap: 10px; }
.rname { font-size: 13px; color: var(--fg); }
.row--link:hover .rname { color: var(--accent); }

/* ---------------- 折线图 ----------------
   两条序列（中文 / 英文）逐年新增。坐标是服务端算好写进 path 的，
   这里只管上色与线型。⚠️ stop-color / stop-opacity 是 CSS 属性，能读 var()；
   而 SVG 的「表现属性」（写在标签上的 stop-color="…"）**不吃 var()**——
   所以渐变的颜色走 .lc-s1/.lc-s2 这两个类，别往属性里塞变量。 */
.legend { display: flex; gap: 16px; margin-top: 16px; }
.lg { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--fg-2); }
.lg-dot { width: 8px; height: 8px; border-radius: 50%; flex: none; }
.lg-dot.s1 { background: var(--chart-1); }
.lg-dot.s2 { background: var(--chart-2); }
.linechart { display: block; width: 100%; height: auto; margin-top: 8px; }
.linechart .lc-s1 { stop-color: var(--chart-1); }
.linechart .lc-s2 { stop-color: var(--chart-2); }
.linechart .lc-line { fill: none; stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; }
.linechart .lc-line.s1 { stroke: var(--chart-1); }
.linechart .lc-line.s2 { stroke: var(--chart-2); }
.linechart .lc-dot.s1 { fill: var(--chart-1); }
.linechart .lc-dot.s2 { fill: var(--chart-2); }
.linechart .lc-tick, .linechart .lc-x {
  font-family: var(--font-mono); font-size: 11px; fill: var(--fg-3);
}

/* ---------------- 甜甜圈 ----------------
   用 stroke-dasharray 画环，纯 SVG，无 JS。
   2026-10-13 照参考稿优化：**圆头端点** + 段间留白 + **标签在外圈带引线**。
   圆头是这张图最好认的特征；引线的落点在 JS 里算好（同一侧的标签会按 y 排开）。

   六片颜色**全部从三支色相里兑**——不再引进第四支，紫色占大头。
   ⚠️ s6 不能用「紫兑绿」：那两支混出来是石板蓝，正是用户点名要去掉的「比较脏的蓝色」。
   第六片改用**淡紫**（同色相降透明度），浅底深底都成立，也明确留在紫色家族里。 */
/* ⚠️ 加宽度上限：SVG 是等比缩放的，不限宽的话宽屏上标签会跟着环一起放大到 17px，
   而窄屏上又缩到 8.5px。定在 420 之后，两端分别是 ~9.6px 和 ~14.6px。 */
.donut { display: block; width: 100%; max-width: 420px; margin: 16px auto 0; height: auto; overflow: visible; }
/* 端头圆角是**画出来的**：views/trends.js 里的路径自己带四段半径 CR 的倒角圆弧，
   与端面、与内外弧都相切。所以这里只需要一个纯色填充。
   （不用 stroke-linecap: round —— 那个只能给半圆，半径恒等于线宽的一半。
     也不要用「内缩一圈 + 2×CR 描边撑回来」那一招——描边推出来的边离圆心恒有 CR 的距离，
     相邻两段对着的就是两条平行线，空隙看着是一条没有角度的直槽，用户就是这么打回的。） */
.dn-seg { fill: currentColor; }
/* ⚠️ 环色走 --dn-*（按**填充**调过的三支），不是 --chart-*（按描边调的）——理由见 token 块。
   ⚠️ s4 / s5 是两支色相之间的过渡段，**必须用 hsl 空间兑**：srgb 里紫兑橙是一坨发灰的梅子色、
   橙兑绿是一坨橄榄色（2026-10-23 用户就是看着这两段说「色彩需要更加明亮一些」）。
   ⚠️ s5 的配比是 **40/60 而不是 50/50**，这个数也是量出来的：50/50 兑出来是 rgb(152,149,63)
   的橄榄色（就是用户嫌浊的那一支），40/60 偏绿一点是 rgb(60,187,10) 的青柠，干净得多。
   ⚠️ **别换成 oklab**：oklab 兑紫+橙出 rgb(148,107,160) 的灰紫（又是浊的），
   兑橙+绿出橄榄——两组都往灰里走。这个坑试过了。
   ⚠️ s4 的配比是 **30/70 不是 50/50**：配比压向橙那头 = 色相往红推。
   50/50 落在 322°（品红 rgb(248,8,157)），30/70 落在 350°（正红 rgb(236,4,39)）——
   2026-10-23 用户次日又说「这里的粉色需要更红一些」，就是这一改，顺带对比度 3.81 → 4.55。
   ⚠️ 也**别拿绿兑紫**：那兑出来是 rgb(17,136,230) 的蓝，用户定过「配色主要紫色，
   其他用绿色和橙色」，蓝色是明确排除的。 */
.dn-seg.s1 { color: var(--dn-1); }
.dn-seg.s2 { color: var(--dn-2); }
.dn-seg.s3 { color: var(--dn-3); }
.dn-seg.s4 { color: color-mix(in hsl, var(--dn-1) 30%, var(--dn-2)); }
.dn-seg.s5 { color: color-mix(in hsl, var(--dn-2) 40%, var(--dn-3)); }
/* 第 6 段是兜底位（现在只有 5 段，用不到）。给它品牌紫的**浅一档**，而不是压透明度：
   压透明度会让它在深色档里几乎消失。 */
.dn-seg.s6 { color: color-mix(in hsl, var(--dn-1) 55%, var(--panel)); }
/* 引线：折线（肘形）——从环上斜出去、拐一下、再水平接到标签（见 views/trends.js 的 labels）。
   2026-10-23 用户：「连接线需要示意的」。⚠️ 线宽 1.4 而不是 1.2：这条线是 1.17 倍缩放下的
   发丝线，再细就只剩个影子，「示意」也就无从谈起。 */
.dn-lead { fill: none; stroke-width: 1.4; stroke-linejoin: round; }
.dn-lead.s1 { stroke: var(--dn-1); }
.dn-lead.s2 { stroke: var(--dn-2); }
.dn-lead.s3 { stroke: var(--dn-3); }
.dn-lead.s4 { stroke: color-mix(in hsl, var(--dn-1) 30%, var(--dn-2)); }
.dn-lead.s5 { stroke: color-mix(in hsl, var(--dn-2) 40%, var(--dn-3)); }
.dn-lead.s6 { stroke: color-mix(in hsl, var(--dn-1) 55%, var(--panel)); }
/* ⚠️ 标签**文字**走中性的 --fg，**不跟环的颜色**。2026-10-23 把环调亮之后试过让标签跟着走，
   实测浅色档里青柠 2.06 / 橙 2.38 / 绿 3.06 / 品红 3.75——12.5px 的小字要 4.5:1，四支全不达标。
   文字与填充对明度的要求是**相反**的（同 .fde-who-tags 那句「热量交给底色去表达，字只管读得清」）。
   颜色关联改由**引线**承担：它现在有拐角、1.4px 粗、且逐段同色——那才是「这条标签说的是哪一段」
   该回答的问题，不该压在文字颜色上。 */
.dn-tag { font-family: var(--font-body); font-size: 12.5px; fill: var(--fg); }
.dn-total { font-family: var(--font-display); font-size: 27px; font-weight: 700; fill: var(--fg); }
.dn-cap { font-family: var(--font-mono); font-size: 9.5px; letter-spacing: .12em; fill: var(--fg-3); }

/* ---------------- 柱状图（站里所有柱状图都走这一套） ----------------
   2026-10-14：用户指着「岗位需求 · 逐年」那张说「所有柱状图，使用这个风格」。
   原来四张「岗位需求」卡与行业/薪酬卡走的是横向比例条（.bar/.pill/.rline），
   现在统一成这一套：**胶囊竖柱 + 柱顶数值 + 柱下名字**。构造在 trends.js 的 vBars()。 */
.vchart { margin-top: 20px; }
/* 高度 150 = 柱区 132 + 顶部 18 的留白。那 18px 是给柱顶数字的：最高那根柱子的
   数字会落到留白里，不留就会被裁掉（柱子是按内容盒算百分比的）。 */
.vbars { display: flex; align-items: flex-end; gap: 10px; height: 150px; padding-top: 18px; }
.vbar { flex: 1; height: 100%; display: flex; align-items: flex-end; justify-content: center; }
/* 柱子收窄成胶囊：只占格子六成宽，两边留白——参考稿里柱子之间是很松的。
   全走本图色相；`.on` 只是更亮一档（原来这里是「灰 vs 紫」的二分，那版看着一半是空的）。
   ⚠️ `max-width: 72px` 是给**项数少**的那几张卡上的保险：城市只有 2 项、薪酬 5 项，
      按 62% 摊下来每根会有 130~150px 宽，胶囊胖成了药丸，和参考稿里那种「细柱大间距」
      完全不是一回事。封顶之后 4 项以上都还是按比例走，只有 1–3 项时才收住。
   ⚠️ 2026-10-15 圆角从 `999px`（胶囊）收到 **20px**：用户先要 12px，看过之后 18 → 22 → 定在 20。
      `999px` 在细柱上会把两端拉成半圆，跟右边那张带刻度的图（原来是 2px 平顶）放一起
      不像一套；20px 两处一样，两档主题下都是同一个形状。
   ⚠️ 柱子多数只有 33~72px 宽，**20px 半径在高柱上已经接近半宽**（68px 柱的半宽是 34px）——
      浏览器遇到半径超过半宽会四角**等比缩**，所以矮柱（`min-height: 10px` 那批）会被直接
      拉成胶囊。这不是 bug，是 20px 在那个柱宽下的必然结果。 */
.vbar > i {
  position: relative; display: block; width: 62%; max-width: 72px; min-height: 10px; border-radius: 20px;
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--ch) 38%, transparent),
    color-mix(in srgb, var(--ch) 72%, transparent));
}
.vbar > i.on {
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--ch) 68%, transparent), var(--ch));
}
/* 数字贴着柱顶。逐年那组从 1 涨到 690，小柱子的数字落在基线附近、大柱子的一路抬上去——
   那个落差本身就是那张图要说的话，所以**不要**把它排成整齐的一行。
   （这里用文字不用药丸：柱子挨着排，每根顶上一枚胶囊就挤成一串了。） */
.vbar .v { position: absolute; left: 0; right: 0; bottom: calc(100% + 6px); text-align: center;
  font-size: 11px; font-weight: 700; line-height: 1; color: var(--fg); font-variant-numeric: tabular-nums; }
.vlabels { display: flex; gap: 10px; margin-top: 12px; }
/* ⚠️ 名字走 .vlab，**不要**用 .label：那个类带着 `text-transform: uppercase` 与 `.18em`
   字距，是给英文小标签的，套在中文与年份上会散得读不成词（「互联网与平台」会拉成一长条）。 */
.vlab { flex: 1; min-width: 0; text-align: center; font-size: 11.5px; line-height: 1.45; color: var(--fg-2);
  overflow-wrap: break-word; }
/* ⚠️ 2026-10-20 用户：「红框的信号源路径按钮去掉」——逐年那组柱底下的 `.vsrc`
   （`s1047 s1048 …` 那排源链接）撤了，这条规则随之删掉。要恢复的话从 git 历史里捞。 */

/* 项数多的一组（>6）撑不开：11 根柱子平摊到半栏宽里每根不到 50px，中文名字会一列一个字。
   `min-width` 给柱格一个地板、容器横向可滚——柱宽锁住，名字放得下，卡不撑破页面。
   ⚠️ `.vbars` 与 `.vlabels` 是两个平行的 flex 容器，**地板与 gap 必须一模一样**，
      否则滚动起来柱子与名字会错位（各自按自己的宽度算列）。 */
.vchart--wide { overflow-x: auto; overflow-y: hidden; }
.vchart--wide .vbar, .vchart--wide .vlab { min-width: 58px; }

/* ⚠️ 2026-10-21 **删掉了「带纵轴刻度」那一整套 CSS**（`.vchart--axis` / `.va-grid` /
   `.va-ticks` / `.va-plot` / `.va-lines` / `.va-pad`，约 60 行）。
   它是 2026-10-14 照用户发来的参考稿做的（「岗位市场模块的柱状图，使用这个方式」），
   唯一的使用者就是岗位市场那五张卡；那天用户改口要「风格参考岗位需求 · 逐年 的展示」，
   五张卡全换回胶囊柱，这套样式一个选择器都不再命中。
   要恢复的话从 git 历史里捞（连同 views/trends.js 的 axisBars / niceScale）。 */

/* ---- 「平均薪资」那张卡里的大数字 ----
   2026-10-21 用户：「右侧放薪资，右侧增加一个平均薪资」。
   整段唯一一个「一个数说完一件事」的地方，所以给它衬线斜体的大字——
   与 4C 中心那个 FDE、六类现场的大数字同一支字形（`--font-display` 的真斜体）。
   ⚠️ 单位 K 单独缩一档、走 --accent：数字本身是主角，单位是附注，同号同色会读成一串。 */
.jd-avg { display: flex; align-items: baseline; gap: 16px; margin-top: 6px; }
.jd-avg-n {
  font-family: var(--font-display); font-style: italic; font-weight: 700;
  font-size: 54px; line-height: 1; color: var(--fg);
}
.jd-avg-n span { font-size: 24px; margin-left: 1px; color: var(--accent); }
.jd-avg-cap { font-size: 11.5px; line-height: 1.6; color: var(--fg-3); }

/* ---- 「岗位要求」那张卡里的排名列表 ----
   2026-10-22 用户：「在招的 FDE 岗位，要什么样的人这个模块内的右侧增加一个模块，
   就是 FDE 岗位的岗位要求，罗列排名靠前的 10 个能力要求（JD）」。
   ⚠️ 这条细横杠**不是**当年那批被删掉的 `.bar` / `.pill`（那是「每张图都是横向比例条」的
   时代，2026-10-14 全站换成了竖柱）。这里是一条**列表行里的进度条**：给「哪几条明显更高」
   一个一眼能看出来的落差，读数还是右边那个百分比。

   ⚠️ 2026-10-22 用户：「进度线太长，短一些，放到比例的左侧，紫色，圆润」。
   于是从「名字底下一条通栏」改成**名字与百分比之间的一小段**（定宽，不跟栏宽走）：
   通栏那条在 552px 的卡里能拉到 470px，十行看下来是一堵横线墙、把名字压成了附注；
   收到一小段之后它是**排名条**——只比长短，不占版面。
   颜色从卡片的 `--ch`（本卡是琥珀橙）改成品牌紫 `--accent`。

   ⚠️ 同一天稍后又问了一次「进度线」那一格要多大，答复是
   **「大概需要100px宽度即可，高度16px」**——先按 56×5 收过一版，太细。
   紧接着第三次：「宽度调整到 50，和百分比一行，放在百分比的左侧，百分比放大明显」，
   于是**定稿**是：轨道 50×16，柱身（`i`）在轨道**里面垂直居中、只占 6px**；
   百分比 `em` 从 11px/--fg-3 提到 **14px/--fg、600 字重**——它是这张卡真正的读数，
   之前那个字号把它降格成了脚注。
   ⚠️ 柱身**不是**占满 16px 高：那样第一行就是一整块实紫，
   十行里最长的几条连成一片色块，反而分不出「谁比谁长」——排名条要的是**长度差**，
   高度给的是让这条线在两行字之间呼吸的空间，不是让柱身变粗。 */
.jd-reqs { list-style: none; margin: 2px 0 0; padding: 0; display: grid; gap: 11px; }
/* ⚠️ 用 center 而不是 baseline：基线对齐时这一格（一个没有文字的 div）按**底边**算，
   短的那几条会挂在名字的腰上。
   ⚠️ 后两列（条 + 百分比）挨着、**离名字远**：所以 gap 分两档——前两档 9px，
   条与百分比之间收到 6px，让「一条线 + 一个数」读成**一个读数单元**，
   而不是四个等距的格子。 */
.jd-req-top {
  display: grid; grid-template-columns: 22px minmax(0, 1fr) 50px auto;
  align-items: center; gap: 9px 6px;
}
.jd-req-no { font-family: var(--font-mono); font-size: 10.5px; color: var(--fg-3); }
.jd-req-top b { font-size: 12.5px; font-weight: 500; color: var(--fg); }
.jd-req-top em {
  font-family: var(--font-mono); font-size: 14px; font-style: normal; font-weight: 600;
  color: var(--fg); font-variant-numeric: tabular-nums;
}
.jd-req-bar {
  display: flex; align-items: center;
  height: 16px; border-radius: 999px;
  background: color-mix(in srgb, var(--accent) 15%, transparent);
}
.jd-req-bar > i {
  display: block; height: 6px; border-radius: 999px;
  background: var(--accent);
}

/* ---------------- 「什么是 FDE」五张图 ----------------
   2026-10-13 新增。首页首屏之后那一整段（views/fde.js）。
   五张图都住在 .chart 卡片里、共用 trends.js 的 --ch 色相——所以这里面
   **一个颜色字面量都不写**，全部走 var(--ch) / var(--line*) / color-mix。
   （theme 护栏扫的是整份样式表，手写一个 #… 就会红。）
   ⚠️ 类名一律 fde- 前缀：.svg-* / .arch-* 那些被 tests/biz.test.js 逐个数过，
      在这里复用会把「加一张首页图」变成「改一批行业图的测试」。 */
.fde-4c, .fde-ring, .fde-radar { display: block; width: 100%; height: auto; margin: 14px auto 0; }
.fde-4c { max-width: 560px; }
.fde-ring { max-width: 520px; }
.fde-radar { max-width: 420px; }

/* ① 四瓣：一个色相的四档。四瓣是同一个责任主体的四个面，不是四种东西——
   上四支色相会把「缺一不可」读成「四选一」。 */
.fde-spoke { stroke: var(--line-strong); stroke-width: 1.5; }
/* ⚠️ 图上两种连线分工不同，不能同一个中性色（2026-10-15 用户「需要顺序箭头」）：
     辐条 = 四个面都收进中心，讲「缺一不可」这个结构 → 中性灰
     弧线 = 一瓣走到下一瓣，讲「按这个顺序」这个过程 → 取色相
   两条都做成灰的，读者分不出哪条是哪条。 */
/* ⚠️ 2026-10-16 用户：「所用的连接线使用滚动的虚线，朝着箭头的方向」——
   弧线改成会走的虚线。三条实现上的约束：
     1. **虚线段长要能整除位移量。** 这里是 `6 6`（一个周期 12），关键帧就走 −12，
        首尾正好接上；写别的数（比如 −10）每循环一次会跳一下。
     2. **位移量是 user unit，所以每条弧的线速度一致**——四条弧长度不同，但不用各配时长。
        速度 = 12 / 0.5s = 24 user unit/s。
     3. **负号才是朝着箭头。** `stroke-dashoffset` 从 0 走到 −12 是把虚线往**终点**推，
        而终点就是 `marker-end` 那个箭头的位置；写成 +12 虚线会倒着朝起点走。
   ⚠️ 辐条**不**做虚线：它是「收进来」的静态结构，让它动会跟弧线抢「哪个才是顺序」。 */
.fde-order {
  fill: none; stroke: var(--ch); stroke-width: 1.6; opacity: .72;
  stroke-dasharray: 6 6;
  animation: fde-march .5s linear infinite;
}
@keyframes fde-march { to { stroke-dashoffset: -12; } }
/* 环上那一档：段更长，所以一个周期的位移也更长。1s 走 24 个单位，与上面同速。 */
@keyframes fde-march-lg { to { stroke-dashoffset: -24; } }
.fde-arw-head { fill: var(--ch); }
.fde-lobe { fill: color-mix(in srgb, var(--ch) 10%, var(--panel)); stroke: var(--ch); stroke-width: 1.4; }
.fde-lobe.l2 { fill: color-mix(in srgb, var(--ch) 18%, var(--panel)); }
.fde-lobe.l3 { fill: color-mix(in srgb, var(--ch) 26%, var(--panel)); }
.fde-lobe.l4 { fill: color-mix(in srgb, var(--ch) 34%, var(--panel)); }
/* 瓣左上角的序数：弧线本身说明了先后，但线一细就说不清是 1→2 还是 2→1，
   落一个数字在一角，顺序就没有解释余地了。 */
.fde-lobe-no { font-family: var(--font-mono); font-size: 10px; font-weight: 600; fill: var(--ch); opacity: .8; }
.fde-lobe-name { font-family: var(--font-body); font-size: 16px; font-weight: 600; fill: var(--fg); }
.fde-lobe-en { font-family: var(--font-mono); font-size: 9px; letter-spacing: .16em; fill: var(--fg-3); }
.fde-hub { fill: var(--ch); }
/* ⚠️ 40px → 30px、并且走**真斜体**（2026-10-21 用户圈着中心圆说「缩小一点，倾斜一些」）。
   斜体是 `font-style: italic`，不是 `skew`——与「Career」字标、海报标题同一条规矩。
   ⚠️ 34px 试过一版：`getBBox()` 量出来 74px 宽、在 96px 的圆里左右各剩 11px，**数字上够**，
   可斜体的 E 右上角是往外斜的，`getBBox()` 不含那点外挑，实际看着两边几乎贴着圆边。
   降到 30px（约 65px 宽）才真的留出气。 */
.fde-hub-t { font-family: var(--font-display); font-style: italic; font-size: 30px; font-weight: 700; fill: var(--fg-on-accent); }

/* 四瓣的释义：图下面四列。窄屏折成两列再折成一列。 */
.fde-faces { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; margin-top: 20px; }
.fde-faces > div { display: flex; flex-direction: column; gap: 3px; }
.fde-faces b { font-family: var(--font-display); font-size: 14px; }
.fde-face-d { font-size: 11.5px; line-height: 1.6; color: var(--fg-2); }

/* ② 环：底轨 + 两段弧 + 十枚节点。09/10 走 .on（强调色），其余走色相的浅档。 */
.fde-track { fill: none; stroke: var(--line); stroke-width: 2; }
/* 环上那两段弧也是「连接线」，与四瓣那四条同一条规矩：滚动的虚线，朝着行进方向
   （2026-10-16 用户「所用的连接线使用滚动的虚线，朝着箭头的方向」）。
   环上没有箭头，方向就是**编号走的那头**（01→10，顺时针）。
   ⚠️ 虚线要**比四瓣那套长一档**（`14 10` 对 `6 6`）：环的周长是四瓣那条弧的六倍多，
      同一套 `6 6` 铺上去要排七十多段，看着是一条点线而不是虚线。两档用两个关键帧
      （`fde-march` / `fde-march-lg`），**线速度一样**（都是 24 user unit/s），
      只是段长不同——所以它们看上去是同一个动作。
   ⚠️ 两段弧在 08/09 之间接缝，各自从自己的起点开始排虚线，接缝处相位对不上——
      但那儿正压着一个节点圆（09），看不出来。别为这个去配 animation-delay。
   ⚠️ `.fde-track`（底下那条整圈的实线轨道）**保持实线**：它是「轨道」不是「连接线」，
      虚线跑在它上面才看得出是在一条线上走。 */
.fde-arc {
  fill: none; stroke: var(--ch); stroke-width: 2.4; opacity: .34;
  stroke-dasharray: 14 10;
  animation: fde-march-lg 1s linear infinite;
}
.fde-arc.a2 { opacity: 1; }
.fde-node circle { fill: color-mix(in srgb, var(--ch) 22%, var(--panel)); stroke: var(--ch); stroke-width: 1.4; }
.fde-node text { font-family: var(--font-mono); font-size: 14px; font-weight: 600; text-anchor: middle; fill: var(--fg-2); }
.fde-node.on circle { fill: var(--ch); }
.fde-node.on text { fill: var(--fg-on-accent); }
.fde-step { font-family: var(--font-body); font-size: 17px; fill: var(--fg-2); }
.fde-step.on { fill: var(--accent); font-weight: 600; }
.fde-center-t { font-family: var(--font-display); font-size: 40px; font-weight: 700; fill: var(--fg); }
/* 「步」是单位不是数字，单独收小（2026-10-14 用户「这里的『步』换成 17px」）——
   大数字 + 小单位。拆 tspan 才能给它自己的字号，否则整串一起 40px。 */
.fde-center-u { font-size: 17px; font-weight: 600; }
.fde-center-s { font-family: var(--font-mono); font-size: 14px; letter-spacing: .16em; fill: var(--fg-3); }

/* 十步的全名清单：环上只放得下三个字的短名，全名在这里按顺序列。
   两列排——十行排一列会把这张卡拉得比旁边那张高一倍。 */
.fde-steps { list-style: none; margin: 18px 0 0; padding: 0; display: grid; grid-template-columns: 1fr 1fr; gap: 5px 18px; }
.fde-steps li { display: flex; gap: 9px; align-items: baseline; font-size: 12px; color: var(--fg-2); }
.fde-steps b { flex: none; font-family: var(--font-mono); font-size: 10.5px; font-weight: 600; color: var(--fg-3); }
/* 09 / 10 是原文点名的分水岭，和环上那两枚节点同一档强调 */
.fde-steps li.on b, .fde-steps li.on span { color: var(--accent); }
.fde-steps li.on b { font-weight: 700; }

/* ③ 六类问题：宫格卡片。
   2026-10-14 用户：「需要宫格展示，需要表现出是问题呈现的感觉，需要有大数字，倾斜」。
   原来是一列六根横向条——那读起来像一份统计报表。改成三列宫格之后它才「摊开」成
   六个并列的症状，这也正是这一段要说的话（这些问题同时存在于不同的现场）。

   「问题感」的三样：
     ① 大数字走**衬线斜体**并旋转 −6°——它是「这有多常见」的计量，斜着写才像被标出来的
        症状，而不是报表里的一格。字形是 Playfair 400-italic，与字标里的 Career 同一支；
        ⚠️ 不能靠浏览器的伪斜（Playfair 是高对比 Didone，伪斜会把衬线整体剪切，一眼假），
        所以走 `--font-display` + `font-style: italic`，拿的就是那个真斜体文件。
     ② 每张卡左上角一道**色相**（border-top），六类各一个，卡片不再是六张一样的白片。
        色相 token 直接复用流程图的 `--svg-*` 四组，不新增。
     ③ 卡与卡之间留 14px 缝，各自独立，而不是共用一条线排成一张表。 */
/* ⚠️ `margin-top` 不是随手加的：`.rows` 那些图卡是 20px，但这张是宫格、格子里还叠着一枚
   62px 的大数字，题头贴太近时第一行卡片会顶到标题上。28px 是量出来的——再小就显挤。 */
.fde-probs { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; margin-top: 28px; }
.fde-prob {
  position: relative; overflow: hidden;
  padding: 16px 18px 18px;
  border: 1px solid var(--line); border-top: 2px solid var(--accent);
  border-radius: var(--r); background: var(--panel);
  transition: border-color .18s ease, box-shadow .18s ease, transform .18s ease;
}
.fde-prob:hover { box-shadow: 0 0 28px -12px var(--accent-glow); transform: translateY(-2px); }
/* ⚠️ 2026-10-21 用户：「色彩都使用紫色」。这里原来有**四支色相**（`.fde-prob.t-scene` /
   `.t-src` / `.t-mid` / `.t-out` 各给一个 `--c`，六张卡轮着用），**连同 `views/fde.js`
   的 `PROBLEM_TONES` 一起撤了**——六张卡现在共用品牌紫一支。
   所以下面几条也不再走 `var(--c)`：那个变量是别的组件（层级 / 扇门 / 分组）在用的，
   这里留着只会让人以为还能分色。 */

/* 大数字：卡片右上角，衬线斜体 + 旋转。压得很淡（`color-mix` 兑到 55%）——
   它是背景里的一个量级，不该和名字抢注意力；hover 时才亮起来。

   ⚠️ 2026-10-15 用户：「数字换成123456，当前数字不明白意义」。
   原来印的是**该类有几个现场**（13 / 9 / 4 …），读者没处知道 13 是什么的 13——
   既不是排名也不是比例。现在大数字是**第几类**（1~6），谁看得懂；
   真实成员数降解成名字旁边那枚小药丸（`.fde-prob-n`），判据照旧盯着它。

   ⚠️ **是 `float: right` 不是 `position: absolute`**。2026-10-14 用户报「宫格里的文字
   不用重叠」——绝对定位的数字浮在文字层上面，说明文字照旧铺满整行，最后那几个字
   （「…就是全部的流程。」的「流程」）正好被压在数字底下。改成浮动之后，跟着它的
   行盒自己会缩短绕开数字：**只有数字所在的那一两行变窄，下面的行还是全宽**。

   ⚠️ `margin-bottom: 8px` 不是随手给的：旋转以右上角为原点，62px 的行盒转 −6° 后
   左下角会往下探出约 6.5px，不留这点余量，被绕开的那行仍然会被斜着的数字尖角碰到。 */
.fde-prob-no {
  float: right; margin: -2px -4px 8px 22px;
  font-family: var(--font-display); font-style: italic; font-weight: 400;
  font-size: 62px; line-height: 1;
  color: color-mix(in srgb, var(--accent) 55%, var(--panel));
  transform: rotate(-6deg); transform-origin: 100% 0;
  user-select: none; pointer-events: none;
}
.fde-prob:hover .fde-prob-no { color: var(--accent); }
/* 名字不再需要 `padding-right: 44px`——那是给绝对定位的数字让位的，浮动之后
   行盒自己会绕，留着等于把名字又压窄一次。 */
.fde-prob-name { font-family: var(--font-display); font-weight: 700; font-size: 15px; line-height: 1.35; margin: 0; }
/* 六类现场各一枚线稿图标（`views/fde.js` 的 PROBLEM_ICONS），装在一枚**圆角方片**里
   ——2026-10-21 用户：「色彩都使用紫色，图标都用方形圆弧角」。
   方片就是 `.fde-ico` 自己：28×28、圆角 8px、淡紫底，描边走品牌紫。
   ⚠️ 线稿本身**不填充、不带颜色**（`strokeIcon` 的根 svg 写了 `fill="none"`、
   `stroke="currentColor"`），所以这里给的是**容器的底色**，不是给图形上色——
   改这一条不会碰到那六枚图形，它们一个都没重画。
   ⚠️ 用 `padding` 而不是把 svg 画小：`box-sizing: border-box` 是全站统一的
   （`* { box-sizing: border-box }`），24×24 的 viewBox 会自己缩进 16×16 的内容框，
   六枚的相对大小因此不会各走各的。
   ⚠️ 对齐用 `vertical-align` 而不是 `flex`：图标在 `<h3>` 里、和名字同一行，
   改成 flex 会把名字后面的「n 个现场」药丸也一起拽成 flex 子项。 */
.fde-ico {
  width: 28px; height: 28px; padding: 6px; border-radius: 8px;
  vertical-align: -9px; margin-right: 9px;
  background: color-mix(in srgb, var(--accent) 11%, transparent);
  color: var(--accent);
}
/* 真实成员数：名字后面那枚药丸。它说的是「这一类有几个现场」，
   口径与图注一致，两条不能各说各的。 */
.fde-prob-n {
  display: inline-block; vertical-align: 1px; margin-left: 7px;
  padding: 2px 8px; border-radius: 999px;
  font-family: var(--font-mono); font-size: 10px; font-weight: 500; font-style: normal;
  color: var(--fg-3); background: color-mix(in srgb, var(--accent) 12%, transparent);
  white-space: nowrap;
}
.fde-prob-one { font-size: 11.5px; line-height: 1.65; color: var(--fg-2); margin: 9px 0 0; }
/* 说明下面的两条链接要**清掉浮动**：数字矮的时候（个位数）它的行盒可能还盖着
   `.fde-prob-cases`，不清的话两条链接会缩排到数字左边。 */
.fde-prob-cases { display: flex; flex-direction: column; gap: 4px; margin-top: 12px; clear: both; }
.fde-prob-cases a { font-family: var(--font-mono); font-size: 10.5px; color: var(--fg-3); }
.fde-prob-cases a:hover { color: var(--accent); }

/* 「企业为什么一定需要 FDE」（`views/fde.js` 的 WHY_FDE）。
   ⚠️ 四列挤在**一张面板**里、用发丝线分隔，刻意不做成四张独立卡片——
   上面「六类现场」已经是宫格卡片了，再来一组同样的宫格，读者会把它读成
   「同一类东西的延续」，而不是「换了个问题在问」。
   2026-10-21 用户：「这个模块也需要一张黑白的配图，文字往下挪」——所以面板顶上
   先来一张出血的横带图，四列落到它下面。
   ⚠️ 面板持边框 / 圆角 / `overflow: hidden`（把图裁到圆角里），`.fde-why` 只管宫格。
   图**不能**放进 `.fde-why` 当第一个 grid 子项：那样 `:first-child` 会指到图上，
   第一条的左边线就删不掉了。 */
.fde-why-panel {
  margin-top: 24px; border: 1px solid var(--line); border-radius: var(--r);
  background: var(--panel); overflow: hidden;
}
/* 图不再是「一张照片」，而是**一张压在照片上的版式**——2026-10-21 用户：「需要换参考图
   这样的，流水线的感受，有文字的排版，深色风格，高级感」。
   ⚠️ 图本身在落盘前已经压暗去色（「深色」是烘进像素的），这里只加**左侧一层横向蒙版**：
   文字占左边，右边留给照片——照片的纵深（产线向远处收）就在右边那一半。 */
.fde-why-fig { position: relative; display: block; margin: 0; }
.fde-why-fig img { display: block; width: 100%; height: 300px; object-fit: cover; }
.fde-why-cap {
  position: absolute; inset: 0; display: flex; flex-direction: column; justify-content: center;
  padding: 0 34px;
  background: linear-gradient(90deg,
    var(--scene-scrim) 0%,
    color-mix(in srgb, var(--scene-scrim) 88%, transparent) 40%,
    color-mix(in srgb, var(--scene-scrim) 42%, transparent) 58%,
    transparent 76%);
}
/* ⚠️ 两行**刻意不是同一支字体**，跟左边那张海报是同一套对照：
   第一行走衬线斜体（Playfair 的真斜体），第二行落回无衬线正体 + 强调紫。
   2026-10-21 用户先要「有文字的排版，深色风格，高级感」（照参考图的工业看板），
   随后点名：「THE MODEL IS 使用衬线字体，倾斜，字体和 the gap 一致」。
   ⚠️ em 必须自己写 font-style: normal 与 font-family——父级已经是斜体衬线，
   不写的话第二行会跟着斜，两行就成了一种字体。
   ⚠️ 鸿蒙后面挂 var(--font-cjk) 兜底（鸿蒙是系统字体、不随仓库分发）。 */
.fde-why-cap-t {
  font-family: var(--font-display); font-style: italic;
  font-weight: 700; font-size: 34px; line-height: 1.12; letter-spacing: .005em;
  margin: 0; color: var(--scene-ink);
}
.fde-why-cap-t em {
  display: block; font-style: normal; color: var(--scene-accent);
  font-family: "HarmonyOS Sans SC", var(--font-cjk);
}
/* 标题下那道短横：参考图里是一道蓝条，这里换成强调紫 */
.fde-why-cap-bar { display: block; width: 46px; height: 3px; margin: 15px 0 0; background: var(--scene-accent); }
.fde-why-cap-cn {
  font-size: 12px; line-height: 1.6; margin: 13px 0 0;
  color: color-mix(in srgb, var(--scene-ink) 82%, transparent);
}
/* 底下那排小标签：用发丝竖线隔开，参考图里是一排带图标的小格 */
.fde-why-cap-l { list-style: none; display: flex; flex-wrap: wrap; margin: 17px 0 0; padding: 0; }
.fde-why-cap-l li {
  font-family: var(--font-mono); font-size: 9.5px; letter-spacing: .14em;
  color: var(--scene-ink);
}
.fde-why-cap-l li + li {
  margin-left: 11px; padding-left: 11px;
  border-left: 1px solid color-mix(in srgb, var(--scene-ink) 38%, transparent);
}
.fde-why {
  display: grid; grid-template-columns: repeat(4, 1fr);
  border-top: 1px solid var(--line);
}
.fde-why-i { padding: 20px 20px 22px; border-left: 1px solid var(--line); }
.fde-why-i:first-child { border-left: 0; }
.fde-why-n { font-family: var(--font-mono); font-size: 11px; letter-spacing: .08em; color: var(--accent); }
.fde-why-t { font-family: var(--font-display); font-weight: 700; font-size: 14.5px; line-height: 1.4; margin: 9px 0 0; }
.fde-why-d { font-size: 11.5px; line-height: 1.75; color: var(--fg-2); margin: 9px 0 0; }
/* 「这几个场景，你中过几个」：2×2 的场景卡。
   ⚠️⚠️ 刻意**不**做成「大数字 + 标题」那一路——那正是用户说的「宏观、跟我没关系」。
   这一段的主角是一句能让人对号入座的话，数字全降到脚注。
   2026-10-21 用户：「当前企业这个部分，太多是从宏观讲解，企业老板没有太多感觉……
   没有那种感同身受的认同感，迫切感，焦虑感，需要重构。」 */
.fde-scenes { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin-top: 24px; }
.fde-scene { border: 1px solid var(--line); border-radius: var(--r); background: var(--panel); padding: 0 20px 20px; overflow: hidden; }
.fde-scene-n { font-family: var(--font-mono); font-size: 10.5px; letter-spacing: .08em; color: var(--fg-3); }
.fde-scene-t { font-family: var(--font-display); font-weight: 700; font-size: 16px; line-height: 1.4; margin: 8px 0 0; }
.fde-scene-d { font-size: 12.5px; line-height: 1.85; color: var(--fg-2); margin: 10px 0 0; }
/* 「然后呢」那一句走左边一道竖线：它是**转折**，不是又一句描述——
   跟上面那句同一字号同一行高的话，读者会当成一段话读下去，那口气就泄了。 */
.fde-scene-w { font-size: 12px; line-height: 1.8; color: var(--fg); margin: 12px 0 0; padding-left: 12px;
  border-left: 2px solid color-mix(in srgb, var(--accent) 45%, transparent); }
/* 四张场景配图：Unsplash 照片（Unsplash License，可商用免署名）。
   ⚠️ 2026-10-21 用户：「不用生图，从可商用的平台调」——原来那版是手写 SVG 场景图，
   已撤。生图这条路当时也断着（ModelVerse 两条 key 一条失效一条配额用尽）。
   ⚠️ 图**出血**贴到卡片上沿：卡片的 padding 顶部改成 0，图用负外边距顶出去，
   否则一张照片四周留一圈白边，看着像贴纸而不是卡片的一部分。 */
.fde-scene-fig {
  position: relative; display: block; width: calc(100% + 40px); height: 190px;
  margin: 0 -20px 16px; overflow: hidden;
  border-radius: calc(var(--r) - 1px) calc(var(--r) - 1px) 0 0;
}
.fde-scene-fig img { display: block; width: 100%; height: 100%; object-fit: cover; }
/* 图上的海报字：红色英文大标题 + 小字中文副题。
   ⚠️ 底下那层蒙版是**必需**的，不是装饰：照片是黑白的、明暗不可控，
   红字碰上照片的亮部就糊掉（红的亮度天生低，实测压在亮部上只有 1:1）。
   ⚠️ 蒙版**必须"实底 + 尾部渐隐"**，不能一路线性渐隐到底：标题坐在副题上面，
   线性渐变到标题那一行只剩三成浓度，实测最坏处 rgb(148,148,148) 对红字 1:1——
   等于没盖。实底要一路铺到标题上沿之外，渐隐只留给照片那一侧。 */
.fde-scene-cap {
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: 22px 16px 12px;
  background: linear-gradient(to top,
    var(--scene-scrim) 0%,
    var(--scene-scrim) 78%,
    color-mix(in srgb, var(--scene-scrim) 45%, transparent) 90%,
    transparent 100%);
}
/* 2026-10-21 用户：「红色的英文，也使用开头字母大写，其他都是小写，英文倾斜的做法」。
   ⚠️ 大小写**写在数据里**（OWNER_SCENES 的 en），不靠 CSS 的 text-transform——
   那样测试断言不到，也照不出「有人又写回全大写」。
   ⚠️ 斜体走 font-style: italic（Playfair 的真斜体），不要 skew。
   ⚠️ 行高不能太紧：斜体的 g / p 尾巴要留出位置，1.1 在单行标题上够用。 */
.fde-scene-cap b {
  display: block; font-family: var(--font-display); font-weight: 700; font-style: italic;
  font-size: 18px; line-height: 1.15; letter-spacing: .01em;
  color: var(--scene-red);
}
.fde-scene-cap span {
  display: block; margin-top: 4px; font-size: 11px; letter-spacing: .05em;
  color: var(--scene-ink);
}

/* 「哪些企业需要 FDE」（`views/fde.js` 的 whoNeeds）：两组宫格标签。
   2026-10-21 用户：「企业为什么一定需要 FDE 这个模块的上面需要增加一个模块，
   哪些具体企业需要FDE，用宫格标签展示。」
   ⚠️ 出的是行业与规模、不是公司名（企业名不上页面是本站的结构性约定），
   所以「具体企业」落成「哪几类企业」。
   方角小卡 + 发丝线，跟站上其它卡片同一套语言；数字走 mono + 强调色。 */
.fde-who-grp + .fde-who-grp { margin-top: 20px; }
.fde-who-h { font-family: var(--font-mono); font-size: 10.5px; letter-spacing: .12em; color: var(--fg-3); }
.fde-who-tags {
  list-style: none; display: grid; gap: 8px; margin: 11px 0 0; padding: 0;
  /* 2026-10-21 用户给了张贡献图那种热力图当参考（小圆角方块、由浅到深成行成列），
     要求「热力图是参考这个，优化增加」。所以格子从宽条改成**方片**：
     auto-fill + minmax 让宽屏一行放七八个、窄屏自动落到三四个，不用写断点。 */
  grid-template-columns: repeat(auto-fill, minmax(118px, 1fr));
}
.fde-who-tags li {
  /* `--w` 是这一格的热量比值（0–1），由视图逐格写在内联 style 上。
     这里给一个 0 的默认值：既让 `var(--w)` 有定义可查
     （theme.test.mjs 那条「var() 引用的每个 token 都要有定义」），
     也让漏写的那一格自然地落到最浅，而不是继承上一个元素的值。 */
  --w: 0;
  aspect-ratio: 1;
  border-radius: var(--r);
  /* 热力图：底色按热量混 --accent，越深 = 越多。
     ⚠️ 一把尺量两组（比值在 views/fde.js 里现算，两组共用同一个最大值），
     不各算各的——各算各的会让「哪一格更深」失去意义，而那是这张图唯一的读数方式。 */
  background: color-mix(in srgb, var(--accent) calc(var(--w) * 45%), var(--panel));
  border: 1px solid color-mix(in srgb, var(--accent) calc(var(--w) * 55%), var(--line));
}
/* 格子里的内容整体是一枚链接：点进去是岗位库按这一格筛出来的真实岗位，再点一条就是那条 JD
   （2026-10-23 用户：「可以点进去看到真实的对应的岗位和岗位JD详情」）。
   ⚠️ **flex 与内边距必须挂在 `a` 上、不能留在 `li` 上**：`li` 原本是 `space-between` 的
   三段式（图标 / 名字 / 数字），内容搬进 `a` 之后 `li` 只剩一个子元素，三段式立刻失效——
   图标、名字、数字会被挤成一坨贴在顶上。 */
.fde-who-tags li > a {
  display: flex; flex-direction: column; justify-content: space-between;
  height: 100%; padding: 11px 12px 12px;
  color: inherit; text-decoration: none;
}
/* 悬停只动描边：底色深浅是数据（热量），不能拿它当交互反馈，否则「鼠标底下那一格更深」
   会被读成「它涨了」。描边加深是全站可点卡片的同一套信号。 */
.fde-who-tags li:hover { border-color: var(--accent); }
/* 每格顶上那枚线稿图标（2026-10-21 用户：「哪些企业需要 FDE · 真实在招岗位，
   这个模块都需要线稿图标」）。图形见 views/fde.js 的 WHO_ICONS / sizeBars。
   ⚠️ 这里是**裸线稿**，没有再套一层圆角方片——六类现场那边套方片，是因为那张卡本身
   是素白面板；这一格自己已经是一块带热力底的方片了，再套一层就是「方片里套方片」。
   ⚠️ 颜色走 --accent（紫），字走 --fg：底色越深字越难读，所以字不碰强调色；
   图标是**描边**、面积小，紫色压在 45% 的紫底上仍然看得清（最深处实测 4.6:1）。 */
/* 尺寸 24px：18px 那版在 132px 的方片里偏小（2026-10-21 用户「图标需要再大一点」）。
   24 是**方片内容宽（约 108px）的 1/4.5**——再大就开始跟上面那行名字抢位置了。 */
.fde-who-ico { display: block; width: 24px; height: 24px; color: var(--accent); }
/* ⚠️ 字一律走 --fg，**不要**用 --accent：底色本身就是强调色混出来的，
   强调色的字压在同色底的深处只剩 2.6–3.2:1（实测），而 --fg 在同样的底上有
   7.8–10.7:1。热量交给底色去表达，字只管读得清。 */
/* ⚠️ `margin-bottom: auto` 是给「图标 + 名字」这一组兜底的：`.fde-who-tags li` 是
   `space-between` 的三段式（图标 / 名字 / 数字），不写它的话三样会**等距摊开**，
   图标和名字被拉开一大截。auto 把那点余量全推到名字与数字之间——
   上面两样贴着，数字仍旧沉在底上。 */
.fde-who-tags b {
  font-size: 12px; line-height: 1.35; font-weight: 500; color: var(--fg);
  margin-top: 7px; margin-bottom: auto;
}
/* 数字放大到 20px：方片里它是主角（参考图里也是「格子本身」在说话） */
.fde-who-tags em {
  font-family: var(--font-mono); font-size: 20px; line-height: 1; font-weight: 600; font-style: normal;
  color: var(--fg);
}
/* 结论那一句：整段唯一一处强调底色，跟 `.fde-why-sum` 同一族，只是浅一档 */
.fde-who-punch {
  margin: 22px 0 0; padding: 14px 18px; border-radius: var(--r);
  background: color-mix(in srgb, var(--accent) 8%, transparent);
  font-size: 12.5px; line-height: 1.8; color: var(--fg-2);
}
.fde-who-punch b { color: var(--fg); }

/* 「拖下去，这道沟只会更深」：左边一张排版海报，右边四条通栏（左标题右说明）。
   ⚠️ 跟上面 2×2 的场景卡**换一种排法**：这一段是「清单」不是「故事」，
   四张卡摆两行会让人以为是同一种东西的续集。
   2026-10-21 用户：「这里文字靠右，左侧也需要一张图，且图上需要排版，类似我发你的海报，
   色彩风格同系统UI，主要讲解右侧的内容。场景是企业内的某个场景。也是大面积留白。」 */
/* 海报那一栏用 clamp 跟着视口缩，**不另设断点**：定宽 400px 在 1000px 上下的视口里
   会把右栏的说明挤到 200px 出头（一行只放得下七八个字）。
   标题也必须跟着缩——「IS WIDENING」在 44px 下要 290px 宽，栏一窄就撑破卡片。
   两者用同一个 30vw / 3.4vw 的斜率，缩起来是同步的，不会出现「栏窄了字没小」。 */
.fde-urg { display: grid; grid-template-columns: clamp(300px, 30vw, 400px) 1fr; gap: 20px;
  margin-top: 24px; align-items: start; }
.fde-urg-list { border: 1px solid var(--line); border-radius: var(--r); background: var(--panel); }
.fde-urg-i { display: grid; grid-template-columns: 236px 1fr; gap: 6px 22px;
  padding: 18px 22px; border-top: 1px solid var(--line); }
.fde-urg-i:first-child { border-top: 0; }
.fde-urg-t { grid-row: 1 / span 2; font-family: var(--font-display); font-weight: 700;
  font-size: 14.5px; line-height: 1.5; margin: 0; }
.fde-urg-d { font-size: 12px; line-height: 1.85; color: var(--fg-2); margin: 0; }
/* 「所以呢」那一句走强调色——四条各收一句，收的正是焦虑那一口气 */
.fde-urg-x { font-size: 12px; line-height: 1.8; color: var(--accent); margin: 2px 0 0; }

/* 海报。版式照用户给的那张参考：顶部一排小标签、超大标题、一张场景图、左下角一小列。
   ⚠️ 颜色一律走 token（--fg / --accent / --fg-3 / --line）：theme.test.mjs 第 3 条
   扫的是整份 app.css，这里写死任何 hex 都会让那条守卫变红。
   ⚠️ 图上的字是 **HTML 叠层**不是烤进 JPEG 的——字能选中、能跟主题走，换图不用重做字。
   留白是这张海报的主意：内边距给到 26px，标题与图之间 26px，图与列表之间 24px。 */
.fde-poster {
  border: 1px solid var(--line); border-radius: var(--r);
  background: var(--panel); padding: 24px 26px 26px;
}
/* 顶部三格小标签：用 space-between 顶到两端，中间那个自然落在正中 */
.fde-poster-top { display: flex; justify-content: space-between;
  font-family: var(--font-mono); font-size: 9.5px; letter-spacing: .16em; color: var(--fg-3); }
/* 超大标题。Playfair 只 vendor 了 400 / 700 两档（正体与斜体各一），没有 900——
   想要更重的观感只能靠字号与字距，别再往 font-weight 上写 900。
   ⚠️ 斜体走 font-style: italic（Playfair 的**真斜体**，仓库里 italic-700 那个 woff2）；
   不要用 skew 去掰正体——那会把衬线一起拧歪。字距跟着收：斜体的字身本来就带倾角，
   再按正体的负字距排会挤到一起。
   ⚠️ 行高**不能跟着正体那套 .96 用**：第一行「The gap」的 g / p 尾巴会垂到下一行的
   大写字母里，实测放大后 g 的尾钩正戳在 W 上。斜体要留 1.08 才分得开。 */
.fde-poster-t { font-family: var(--font-display); font-weight: 700; font-style: italic;
  font-size: clamp(34px, 3.7vw, 48px); line-height: 1.08; letter-spacing: -.01em;
  margin: 22px 0 0; color: var(--fg); }
/* ⚠️ 次行**不跟着倾斜**，而且换成鸿蒙（HarmonyOS Sans），不是 Playfair。
   2026-10-21 用户：「IS WIDENING，依然使用鸿蒙字体，不要倾斜」。
   两行一衬线斜体、一无衬线正体，是刻意的对照——首行是编辑感的标题，
   次行落回界面字体，读起来像同一张海报上的两种声音。
   ⚠️ 鸿蒙后面必须挂 var(--font-cjk) 兜底：鸿蒙是**系统字体**，不随仓库分发
   （中文全量要好几 MB，vendor 进来和零依赖这条冲突）。没装的机器上要能落回雅黑 / 苹方，
   所以这里只把它排在前面，不写死成一个孤立的名字。 */
.fde-poster-t em { display: block; font-style: normal; color: var(--accent);
  font-family: "HarmonyOS Sans SC", var(--font-cjk); letter-spacing: .02em; }
.fde-poster-fig { display: block; width: 100%; height: auto; margin: 22px 0 0; border-radius: 2px; }
/* 左下角那一列：左边一道强调色竖条，四行与右边那四条一一对应 */
.fde-poster-l { list-style: none; margin: 22px 0 0; padding: 0 0 0 13px;
  border-left: 2px solid var(--accent); display: grid; gap: 7px; }
.fde-poster-l li { font-size: 11.5px; line-height: 1.5; color: var(--fg-2); }

/* 结论条：整段里唯一一处强调底色。它装的是那篇的结论句，不是又一条理由——
   所以样式也跟上面四列**分开**，不混在格子里。
   ⚠️ 「不转型的风险」那一块也用它装那句「什么都没动」——那是引用一位 COO 的原话，
   不是本站的论断，用同一个「引文条」样式反而正好。 */
.fde-why-sum {
  margin-top: 14px; padding: 15px 18px;
  border: 1px solid color-mix(in srgb, var(--accent) 26%, transparent);
  border-radius: var(--r);
  background: color-mix(in srgb, var(--accent) 8%, var(--panel));
  font-size: 12.5px; line-height: 1.8; color: var(--fg);
}

@media (max-width: 900px) {
  /* 窄屏宫格折成两列；再窄就一列——`.fde-probs` 只有 6 张，两列刚好三行 */
  .fde-probs { grid-template-columns: 1fr 1fr; }
  /* 「企业为什么一定需要 FDE」四列折成两列：每行第一列去掉左边线，第二行起加上边线 */
  .fde-why { grid-template-columns: 1fr 1fr; }
  .fde-why-i:nth-child(2n+1) { border-left: 0; }
  .fde-why-i:nth-child(n+3) { border-top: 1px solid var(--line); }
  /* 趋势 / 风险那两块同一套折法 */
  .fde-scenes { grid-template-columns: 1fr; }
  /* 海报折到上面、四条落到下面。海报宽度此时是整栏，
     标题字号不用再管——上面那个 clamp 的下限 32px 就是给这种窄栏准备的。 */
  .fde-urg { grid-template-columns: 1fr; }
  .fde-urg-i { grid-template-columns: 1fr; gap: 8px; }
  .fde-urg-t { grid-row: auto; }
}
@media (max-width: 560px) {
  .fde-probs { grid-template-columns: 1fr; }
  .fde-prob-no { font-size: 52px; }
  .fde-why { grid-template-columns: 1fr; }
  .fde-why-i { border-left: 0; }
  .fde-why-i:nth-child(n+2) { border-top: 1px solid var(--line); }
  /* 单列时面板只剩四百多 px 宽。图压到 230px，标题收到 22px——不压的话
     蒙版那一层会把整张图盖满，右边的照片一点都露不出来，「深色 + 照片」就只剩深色了。 */
  .fde-why-fig img { height: 230px; }
  .fde-why-cap { padding: 0 20px; }
  .fde-why-cap-t { font-size: 22px; }
  .fde-why-cap-l li { font-size: 9px; letter-spacing: .1em; }
  .fde-why-cap-l li + li { margin-left: 8px; padding-left: 8px; }
}

/* ④ 雷达：网格与轴走中性线，多边形与顶点走色相。 */
.fde-grid { fill: none; stroke: var(--line); stroke-width: 1; }
.fde-axis { stroke: var(--line); stroke-width: 1; }
.fde-poly { fill: color-mix(in srgb, var(--ch) 20%, transparent); stroke: var(--ch); stroke-width: 2; stroke-linejoin: round; }
.fde-pt { fill: var(--ch); }
.fde-axis-name { font-family: var(--font-body); font-size: 12px; fill: var(--fg); }
.fde-axis-n { font-family: var(--font-mono); font-size: 11px; fill: var(--fg-3); }

/* ④ 从「半栏」变成「整行」之后，里面拆成两栏：左雷达、右七个域的逐条说明。
   ⚠️ 这里**不**用 `.split`——那一条是给「两张等宽卡」用的（见文件头那段），
      这里是一张卡内部的左右分栏，两件事，写在一起将来改一个会带走另一个。
   左栏固定 420px（雷达自己的 max-width 就是这个数），右栏吃掉剩下的。
   逐条说明左对齐之后雷达不用居中——它俩在视觉上是「图 + 注」的关系，不是对称。 */
.fde-two { display: grid; grid-template-columns: 420px 1fr; gap: 28px; align-items: start; }
.fde-two .fde-radar { margin-top: 0; }

/* 七个域的逐条：序号 + 域（粗）+ 那句 summary + 该域的能力点数。
   ⚠️ 排成 `<ol>` 而不是 flex 栅格，因为**顺序本身是内容**（原文说顺序不能跳）。 */
.fde-domlist { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
.fde-domlist li { display: grid; grid-template-columns: 18px 1fr auto; gap: 10px; align-items: baseline; padding: 7px 0; border-top: 1px solid var(--line); }
.fde-domlist li:first-child { border-top: none; }
.fde-domlist b { font-family: var(--font-mono); font-size: 10px; font-weight: 600; color: var(--fg-3); }
.fde-domlist span { font-size: 12px; line-height: 1.7; color: var(--fg-2); }
.fde-domlist span i { font-style: normal; font-weight: 600; font-family: var(--font-body); font-size: 12.5px; color: var(--fg); margin-right: 8px; }
.fde-domlist em { font-family: var(--font-mono); font-style: normal; font-size: 10.5px; color: var(--fg-3); white-space: nowrap; }

@media (max-width: 900px) {
  /* 窄屏上两栏叠成一栏；雷达本来就居中，说明跟着左对齐 */
  .fde-two { grid-template-columns: 1fr; gap: 18px; }
}

/* ⑤ 知识图谱 —— 一张 Obsidian 式的关系图。
   2026-10-15 用户：「知识图谱模块需要使用 Obsidian 图知识库的视觉感受，
   让用户一眼就能看懂 FDE 相关的图知识体系」。参考的是本机那个 Obsidian-Neo4j
   驾驶舱（布局规则见 views/kgraph.js 的文件头）。这里抄的是它的**读图规则**：

     点：按「是同类」分色，半径按度开方（长尾压平一档）
     线：细、低不透明度，互不抢戏
     字：先描一圈底色再填字（`paint-order`），压着线也读得清
     悬停：同层亮起、其余退到 14% —— 这是「一眼看懂」的主要来源

   ⚠️ 2026-10-20 起图的内容是**五层技术栈 + 53 个技术点**（原来 7 个域 + 58 个能力点）。
      五支色相直接复用上一段那张技术栈卡的（紫 / 琥珀 / 松绿 / 玫红 / 黄绿），
      定义在下面「五层技术栈的五个色相」那一段，**与卡片共用一组选择器**——
      读者刚看过卡片，图上同色才能一眼对上。

   ⚠️ 「谁该亮」这件事**判据只在 CSS 里这一份**（`:has()` 那一组，见下）。
      2026-10-15 之前这里是「一行 JS 不写」；那天加了「点击选中」，
      JS 只负责往被点中的节点组上加一个 `.is-sel` 类，**不参与判断谁和谁关联**。
      代价值得记下来：`x0..x4` 那套类名是**层的顺序**，`.fde-d1..d5` 是**色相**，
      两者是两件事，别把它们并成一套（并了以后改色相就会把高亮带坏）。 */
.fde-gwrap { position: relative; margin: 18px 0 0; }
.fde-graph { display: block; width: 100%; height: auto; }

/* 右下角那组视野控件：缩小 / 放大 / 适应。
   ⚠️ 它们都是 `.fde-gwrap` 的**兄弟节点**、不是画在 SVG 里——画在里面就得跟着
      viewBox 一起缩放（放大之后按钮变成巨无霸）。放外面它们钉在卡片右下角。
   ⚠️ 挂在 `html.js` 下面显示：没有 JS 时它们按不动，摆在那儿就是三枚假按钮。
   ⚠️ 2026-10-16 从「只有适应一枚」变成三枚一排。**「适应」仍在最右、位置没动**，
      新的两枚排在它左边——右下角不会让人觉得被换过地方。 */
.fde-gctl { display: none; }
.js .fde-gctl {
  position: absolute; right: 12px; bottom: 12px;
  display: flex; align-items: center; gap: 6px;
}
.js .fde-gfit {
  padding: 5px 12px; border-radius: 999px;
  border: 1px solid var(--line-strong); background: var(--panel);
  color: var(--fg-3); font: inherit; font-size: 11.5px; line-height: 1; letter-spacing: .04em;
  cursor: pointer;
  transition: color .15s ease, border-color .15s ease;
}
.js .fde-gfit:hover { color: var(--accent); border-color: var(--accent); }

/* 放大 / 缩小。圆形（999px 用在 26px 的方上就是圆），与「适应」那枚药丸同一族：
   同底色、同描边、同 hover。⚠️ 字号 15px 而不是 11.5px——「+ −」这两个字形本身
   就比汉字小一圈，跟着 11.5px 会显得没对齐、点着也别扭。 */
.js .fde-gz {
  display: grid; place-items: center;
  width: 26px; height: 26px; padding: 0;
  border-radius: 999px;
  border: 1px solid var(--line-strong); background: var(--panel);
  color: var(--fg-3); font: inherit; font-size: 15px; line-height: 1;
  cursor: pointer;
  transition: color .15s ease, border-color .15s ease;
}
.js .fde-gz:hover { color: var(--accent); border-color: var(--accent); }

/* 到了缩放上下限就真的禁用，不留一枚按下去没反应的按钮。
   ⚠️ 禁用态要把 hover 那两条也压住——不然鼠标扫过去还会变紫，看着像是能按。 */
.js .fde-gz[disabled] { color: var(--fg-3); border-color: var(--line); opacity: .4; cursor: default; }
.js .fde-gz[disabled]:hover { color: var(--fg-3); border-color: var(--line); }

/* 可交互的图谱（`.fde-gi` 由 public/kgraph.js 加上去）。
   ⚠️ `touch-action: none` 是**必须**的：不给的话触屏上单指拖动会被浏览器当成
      页面滚动，图谱一动不动。代价是 760px 以上、触屏设备在图谱区域内不能滚动页面
      ——760px 以下整块已经藏了，所以真正受影响的只有平板。 */
.fde-gi { cursor: grab; touch-action: none; }
.fde-gi.fde-gpan, .fde-gi.fde-gpan .fde-gn { cursor: grabbing; }
.fde-gi .fde-gn { cursor: grab; }
.fde-gi .fde-gn:active { cursor: grabbing; }

/* 球体的**轮廓**（2026-10-20 加）。球面上的点全都在同一个半径上、投影又是正交的
   （没有透视），所以不画这一圈的话，整张图读起来是一团散点，而不是一颗球——
   用户要的正是「一个球的效果」。半径就是 `SCALE`（服务端算的），所以层里那些
   正好转到侧面的点会落在这一圈上，反过来又印证了「它们都在同一个球面上」。
   ⚠️ 它**不**参与悬停压暗（那套只打在 `.fde-gn` 与三类 `.fde-gedge-*` 上）——
   它是参照系，不是内容。 */
/* ⚠️ 球体轮廓（`.fde-gball`）**2026-10-20 撤了**。它只活了半天：
   上午加它是为了「一眼看出这是个球」（点全在同一半径上、投影又是正交的，
   不画那一圈确实容易读成散点），下午用户看完说「外圈的圆圈，隐藏」。
   撤掉的是画布上那一圈**线**，球本身没变——「近大远小 + 近实远虚」那两档还在，
   层节点外面那圈描边环也还在。要恢复就是加回一行
   `<circle class="fde-gball" cx cy r={SCALE}/>` 加一条 `fill:none; stroke:var(--fg-3)`。 */

.fde-gedge-ab { stroke: var(--c, var(--line-strong)); stroke-width: 1.1; stroke-opacity: .5; }
/* 技术点之间的**交叉关系**（2026-10-20 加）。中性、更细更淡——它说的是
   「这两件事是一回事」，不是「谁属于谁」，所以不取任何一层的色相，也不能跟上面两类一样重。
   ⚠️ 用 `stroke-opacity` 而不是 `opacity`：`opacity` 要留给下面那套悬停明暗
   （压暗到 .14 / 点亮回 1），两条是相乘的。写成 `opacity` 的话，悬停时这条边
   要么跟着全灭、要么把明暗规则盖掉。
   ⚠️ 它带**两端各自那一层**的 `xN` 类（见 views/kgraph.js 的 build()），
   所以悬停任一端所属的层它都会亮——这就是想要的，不必另写点亮规则。 */
.fde-gedge-rel { stroke: var(--fg-3); stroke-width: .8; stroke-opacity: .2; }
/* 中心 FDE → 五层。中性灰、比层边更淡——它说的是「都收在这儿」这个结构，
   与 ① 四瓣图那四条辐条同一个角色。⚠️ 2026-10-16 之前这条是 `fde-gedge-dr`
   （能力点 → 四扇门），门从画布上撤了之后换成这一条。 */
.fde-gedge-hub { stroke: var(--line-strong); stroke-width: 1; stroke-opacity: .55; }
.fde-gdot { fill: var(--c, var(--accent)); }
.fde-gring { fill: none; stroke: var(--c, var(--accent)); stroke-width: 1.2; stroke-opacity: .4; }
/* 透明的「手感圈」。真正画出来的点只有 2~5px 半径，指着它悬停/点击太考验人；
   这一枚半径 11（屏上约 24px 见方）的透明圆负责接指针。
   ⚠️ `fill: none` + `pointer-events: all` 才有效——默认的 `visiblePainted` 要「画到了」
      才接事件，透明就等于不存在（这正是它该有的样子：看着没有、摸着有）。
   ⚠️ 它**不参与拖点**：从它上面按下仍然是转球（判据在 public/kgraph.js 的 `soft`），
      否则球会几乎转不动。 */
.fde-ghit { fill: none; pointer-events: all; }
.fde-gn { transition: opacity .16s ease; }

/* 域标题：画在点上，所以先描一圈卡片底色再填字——
   `paint-order: stroke` 让描边画在填充**下面**，字才不会被自己的描边吃掉。 */
.fde-gl { font-family: var(--font-body); font-weight: 600; fill: var(--fg); paint-order: stroke; stroke: var(--panel); stroke-width: 3.5px; stroke-linejoin: round; }
.fde-gdl { font-size: 14px; }

/* 技术点的名字。**2026-10-20 起常显**（用户：「里面的所有的节点旁边的小字需要显示」）——
   原来是 `opacity: 0`、只在同层高亮时露出来。常显的代价量过：59 个名字同屏只有 6 对重叠，
   而且 6 对全是「撞上它那一层的标题」，靠 spreadLabels 把标题当占位已经全部消掉（现在 0 对）。
   名字一律挂在点的**球外那一侧**（左半边的靠左、右半边的靠右，判据在 views/kgraph.js 的
   graphSvg 与 public/kgraph.js 的 placeLabel 里各有一份、必须一致），dy 用 0.32em（视觉居中）。
   ⚠️ `pointer-events` 保持默认（可点）——指着名字也能悬停/选中它那个点，
      按下时按「软目标」处理（拖=转球，点=选中），见 public/kgraph.js。
   ⚠️ 那圈 3px 的底色描边不只是好看：名字挨着线走，不描底就与连线糊在一起。 */
.fde-gal { font-family: var(--font-body); font-size: 10.5px; fill: var(--fg-2); paint-order: stroke; stroke: var(--panel); stroke-width: 3px; stroke-linejoin: round; }

/* 五层技术栈的**五个色相，全站只在这里定义一次**。两条链路共用同一组选择器：
     · 首页「我该怎么走」的 ① 五层技术栈卡  → `.rm-layer.t1–t5`
     · 首页「什么是 FDE」的 ⑤ 图知识图谱    → `.fde-gd.x0–x4`（层）/ `.fde-ga.x0–x4`（技术点）
   两处各写一份的话，「卡片上那个紫」和「图上那个紫」会慢慢漂开。
   `--c` 同时喂给点、描边环、属于它的那条边，以及图例上那枚色块。
   ⚠️ `.fde-ga.x{N}` 这一半**不能省**：技术点的 `--c` 只能从这里拿（它的父级带的是
   `.fde-ga` 而不是 `.fde-gd`），少了它 53 个点会一起回落到 `var(--accent)`——
   图例承诺了五种颜色、点却全是同一个紫。2026-10-20 之前就是这么错的。
   ⚠️ t4/t5 必须走 **hsl 空间**兑色：srgb 里紫兑琥珀是一坨发灰的梅子色、
   松绿兑琥珀是一坨橄榄色（四套方案比对过，只有 hsl 兑出来的玫红与黄绿是干净的）。 */
.rm-layer.t1, .fde-gd.x0, .fde-ga.x0 { --c: var(--chart-1); }
.rm-layer.t2, .fde-gd.x1, .fde-ga.x1 { --c: var(--chart-2); }
.rm-layer.t3, .fde-gd.x2, .fde-ga.x2 { --c: var(--chart-3); }
.rm-layer.t4, .fde-gd.x3, .fde-ga.x3 { --c: color-mix(in hsl, var(--chart-1) 55%, var(--chart-2)); }
.rm-layer.t5, .fde-gd.x4, .fde-ga.x4 { --c: color-mix(in hsl, var(--chart-3) 50%, var(--chart-2)); }
/* 中心那个 FDE。2026-10-16 加（用户「需要中间增加 FDE 节点」），同时四角那四扇门撤了。
   它不用任何一支域的色相——它是所有域的公共中心，染上谁的色都会被读成「属于那个域」。
   与 ① 四瓣图的中心圆（`.fde-hub`）同一个意思：整张图收在这一个东西上。 */
.fde-ghub .fde-gdot { fill: var(--panel); stroke: var(--accent); stroke-width: 2; }
.fde-ghub-t { font-family: var(--font-display); font-size: 17px; font-weight: 700; fill: var(--accent); }

/* 「谁该亮」——**2026-10-20 从 CSS 搬到了客户端**（`public/kgraph.js` 的 spotlight）。
   ⚠️ 为什么非搬不可：原来是一组 `:has(.fde-gd.x1:hover) …`，它只能表达「这一层」——
      层节点在 DOM 里正好是它那些技术点的父级，`:has()` 够得着。但用户要的是
      「选中后**关联**的高亮」，而「关联」是**图上的邻接关系**（83 条交叉关系连起来的那批）：
      邻居之间在 DOM 里没有共同祖先，**没有任何选择器能表达「与某个节点相连的那些」**。
   ⚠️ 分工：JS 只**挂类**（`.is-nb` 挂在该亮的节点与边上、`svg.has-spot` 表示「有人在聚光」），
      一个 opacity 都不设；明暗全在这里。悬停与选中走的是同一条 spotlight，不可能漂开。
   ⚠️ 压暗那条必须**排在点亮之前**：两者特异性相同时靠后的一条赢（`.has-spot .fde-gn`
      与 `.has-spot .is-nb` 都是 (0,2,0)）。
   ⚠️ `.fde-gball`（球体轮廓）**不在**这两条里——它是参照系，不该跟着暗。 */
.fde-graph.has-spot .fde-gn,
.fde-graph.has-spot .fde-gedge-hub,
.fde-graph.has-spot .fde-gedge-ab,
.fde-graph.has-spot .fde-gedge-rel { opacity: .12; }
.fde-graph.has-spot .fde-gn.is-nb,
.fde-graph.has-spot .fde-gedge-hub.is-nb,
.fde-graph.has-spot .fde-gedge-ab.is-nb,
.fde-graph.has-spot .fde-gedge-rel.is-nb { opacity: 1; }

/* 选中的那个点自己描一圈更重的环——悬停是「路过」，选中是「钉住了」，
   两者虽然亮同一批东西，但钉住的那个该看得出是它。 */
.fde-graph .fde-gn.is-sel .fde-gdot { stroke: var(--fg); stroke-width: 1.6; }

/* 图例：五层 + 四扇门。色块与图上的点取的是同一个 `--c`——
   两处各写一份的话，「图例上那个紫」和「图上那个紫」会慢慢漂开。 */
.fde-legend { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 14px; margin-top: 14px; }
.fde-legend .lb { font-family: var(--font-mono); font-size: 10px; letter-spacing: .16em; color: var(--fg-3); text-transform: uppercase; }
.fde-lg { display: inline-flex; align-items: center; gap: 6px; font-size: 11.5px; color: var(--fg-2); }
.fde-lg i { width: 9px; height: 9px; border-radius: 50%; background: var(--c, var(--accent)); flex: none; }
.fde-lg-hint { font-size: 11px; color: var(--fg-3); }

/* 一句话定义：图在前、话在后——它是图的注脚，不是这一段的开场白。 */
.fde-def { font-family: var(--font-read); font-size: 14px; line-height: 1.85; color: var(--fg-2); margin: 16px 0 0; }
/* 口径行：技术点之间那 83 条交叉关系是**手写的**，得说清它从哪来——
   与 ③ 六类问题同一种做法（人工归纳 + 页面标口径 + 测试钉住）。 */
.fde-relnote { font-size: 11.5px; line-height: 1.8; color: var(--fg-3); margin: 10px 0 0; }

/* 图注：每张图下面那句「这张图要说的话」。字号比正文小一档，颜色退一档——
   它是一句结论，不是又一段正文。 */
.fde-note { font-size: 12px; line-height: 1.75; color: var(--fg-2); margin: 16px 0 0; }
.fde-note + .label { margin-top: 10px; }

/* ---------------- 首页「我该怎么走」（views/roadmap.js） ----------------
   2026-10-19 加的第四段。只复用既有的 .chart / .rows / .row / .rname / .ladder-* /
   .grid3 / .enter 这几套，新形状一律 rm- 前缀——与 .fde-*（知识图谱那一套）零耦合。
   这一段里没有 SVG、没有颜色字面量，色相只从 .chart--N 给的 --ch 与 --accent 取。 */
/* ---- ① 五层技术栈：每一层是一张自己色相的卡 ----
   2026-10-19 用户：「要会什么 · 五层技术栈 这里的每个类型不明显，需要设计一下。」
   改之前五层的标题都是 13px 灰字、53 枚标签长得一模一样、层与层只隔 15px，
   读出来是一串标签被五条小字切断。现在每层自带色条 + 同色描边 + 极淡同色底，
   里面的技术点也跟着这一层的色相走——五个类型一眼分得开。

   ⚠️ `--c` 只在这五条里定义，值全部从 --chart-1/2/3 兑出来，**没有一个颜色字面量**
   （所以自动过「token 块之外不许有颜色字面量」那条护栏，也自动跟着换主题走）。
   ⚠️ 兑色必须走 **hsl 空间**：srgb 空间里紫兑琥珀会得到一坨发灰的梅子色、
   松绿兑琥珀是一坨橄榄色——四套方案比对过，只有 hsl 兑出来的玫红与黄绿是干净的。
   ⚠️ 层的**相邻色差是刻意拉大的**，不是按色轮顺排：顺排好看，但 03 琥珀与 04 黄绿
   会挨在一起，而这一段要解决的正是「每个类型不明显」。 */
.rm-stack { display: flex; flex-direction: column; gap: 14px; margin-top: 20px; }
.rm-layer {
  padding: 15px 18px; border-radius: var(--r);
  border: 1px solid color-mix(in srgb, var(--c, var(--line)) 26%, var(--line));
  border-left: 4px solid var(--c, var(--line));
  background: color-mix(in srgb, var(--c, var(--panel)) 5%, var(--panel));
}
/* ⚠️ 五层的色相**不在这里定义**——`.rm-layer.t1–t5` 与图谱那套
   `.fde-gd.x0–x4` / `.fde-ga.x0–x4` 共用一组选择器，定义在下面
   「五层技术栈的五个色相」那一段（与 `.fde-*` 放在一起，因为 `--c` 的消费方在那儿）。
   在这里再写一份的话，卡片与图谱会慢慢漂成两个颜色。 */
.rm-lhead { display: flex; align-items: baseline; gap: 10px; }
.rm-lno { font-family: var(--font-mono); font-size: 11px; font-weight: 700; color: var(--c, var(--fg-3)); }
.rm-lname { font-family: var(--font-display); font-weight: 700; font-size: 17px; }
.rm-ln { margin-left: auto; font-family: var(--font-mono); font-size: 11px; color: var(--fg-3); flex: none; }
.rm-what { font-size: 12.5px; line-height: 1.7; color: var(--fg-2); margin: 7px 0 0; }
.rm-tags { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 12px; }
/* ⚠️ `--c` 的兜底值不能省：这两条 color-mix 里一旦拿不到 `--c`，整条声明会被丢掉，
   标签就退回「没描边没底色」——不报错，只是看着像坏了。 */
.rm-tag {
  font-family: var(--font-mono); font-size: 11px; line-height: 1;
  padding: 5px 8px; border-radius: var(--r);
  border: 1px solid color-mix(in srgb, var(--c, var(--line)) 40%, var(--line));
  background: color-mix(in srgb, var(--c, var(--panel)) 13%, var(--panel));
  color: var(--fg);
}

/* 口径行：每张图下面那句「这数字/这套分类是哪来的」。它不是正文，是一张收据。 */
.rm-src { font-size: 11.5px; line-height: 1.8; color: var(--fg-2); margin: 18px 0 0; }
.rm-src a { color: var(--accent); }
.rm-src b { color: var(--fg); font-weight: 600; }
.rm-src code {
  font-family: var(--font-mono); font-size: 11px; padding: 1px 4px;
  border: 1px solid var(--line); border-radius: 4px; background: var(--bg-2);
}

/* ② 三档掌握程度 */
.rm-levels { margin-top: 20px; }
.rm-level {
  border: 1px solid var(--line); border-radius: var(--r);
  background: var(--bg-2); padding: 16px 18px;
}
.rm-level-top { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
/* ⚠️ 2026-10-22 用户：「文字紫色」。档名从卡片的 `--ch` 改成品牌紫 `--accent`——
   这张卡是 `chart--2`（琥珀橙），档名跟着卡片走的话是橙的。
   ⚠️ 只改**档名**：正文那两行（`.rm-level-one` / `.rm-level-out`）保持灰，
      整张卡的色相也没动——题头那枚圆点仍是琥珀。
   ⚠️ 别再改回 `var(--ch)`：那会跟着卡片色相漂，换一次 chart--N 就变一次色。 */
.rm-level-name { font-family: var(--font-display); font-weight: 700; font-size: 19px; color: var(--accent); }
/* 三档右上角那三枚线稿（地图 / 扳手 / 插头）。2026-10-22 替掉了原来的 01 / 02 / 03 编号。
   颜色与档名同色（品牌紫），两者一起读成「这一档」的标记。
   ⚠️ 类名是 `rm-lico` 不是 `rm-ico`：那个类名被 tests/roadmap.test.js 数着（== 阶段数），
      借过来会让「五阶段」那条多出三枚。 */
.rm-lico { display: block; flex: none; width: 22px; height: 22px; color: var(--accent); }
.rm-level-one { font-size: 12.5px; line-height: 1.75; color: var(--fg); margin: 10px 0 0; }
.rm-level-out { font-size: 12px; line-height: 1.7; color: var(--fg-2); margin: 10px 0 0; }
.rm-level-out b { color: var(--fg-3); font-weight: 600; }

/* ③ 五阶段阶梯：壳用 .ladder-*，这里补右上角那枚「几周 · 到哪一档」。
   ⚠️ `.ladder-*` 是**个人线共用的**（`views/path.js` 的七域阶梯也是这套壳），
      所以这一段的改动一律挂在 `.rm-stages` 下面，不动基类。 */
.rm-when { font-family: var(--font-mono); font-size: 11px; color: var(--fg-3); flex: none; }

/* 2026-10-20 用户：「怎么走 · 90 天五阶段，需要图标，需要色彩」。
   配色在三个方案里选了**「一个色相由浅到深」**——五个阶段是**一条路**，不是一个拼盘，
   所以不该上五支色相（那是上面「五层技术栈」那张卡的规矩：五个不同类型才配五个色）。

   两段渐变，分工不同：

   **① `--c`（前景：图标、序号、左边框）** —— 兑的是 **`--fg-3`（中间灰）**，不是白。
   ⚠️ 兑白的话五档里最浅的那档根本读不出来：品牌紫兑白到 45% 就只剩 2.6:1、兑 28% 只剩 1.3:1。
      兑中间灰则是「**发灰的紫 → 纯紫**」——亮度几乎不动、饱和度一路上去，
      五档全部稳在 4:1 以上，而「浅 → 深」照样看得出来（「浅」在中文里本来就兼指「淡」）。

   **② `--t`（卡片底色）** —— 面积最大的一块，所以深浅差在这里给足：
   从几乎看不见的 4% 一路走到 26%。前景那一段不敢拉这么开（拉了就读不出来），
   底色敢——它是背景，弱一点不影响任何东西的可读性。 */
.rm-stages .ladder.t1 { --c: color-mix(in hsl, var(--chart-1) 40%, var(--fg-3)); --t: color-mix(in srgb, var(--chart-1) 4%, var(--panel)); }
.rm-stages .ladder.t2 { --c: color-mix(in hsl, var(--chart-1) 55%, var(--fg-3)); --t: color-mix(in srgb, var(--chart-1) 9%, var(--panel)); }
.rm-stages .ladder.t3 { --c: color-mix(in hsl, var(--chart-1) 70%, var(--fg-3)); --t: color-mix(in srgb, var(--chart-1) 15%, var(--panel)); }
.rm-stages .ladder.t4 { --c: color-mix(in hsl, var(--chart-1) 85%, var(--fg-3)); --t: color-mix(in srgb, var(--chart-1) 21%, var(--panel)); }
.rm-stages .ladder.t5 { --c: var(--chart-1);                              --t: color-mix(in srgb, var(--chart-1) 28%, var(--panel)); }

/* 卡片：左侧一道 4px 色条 + 同色底——与「五层技术栈」那五张卡同一套做法，
   只是那边一条一个色相、这边一条比一条深。 */
.rm-stages .ladder-card {
  border-color: color-mix(in srgb, var(--c, var(--line)) 30%, var(--line));
  border-left: 4px solid var(--c, var(--line));
  background: var(--t, var(--panel));
  padding-left: 17px;   /* 左边那道色条吃掉 4px 行宽，补回来 */
}
.rm-stages .ladder-head { display: inline-flex; align-items: center; gap: 9px; min-width: 0; }
.rm-stages .ladder-head .rm-ico { color: var(--c, var(--accent)); }
/* ⚠️ 卡片底上了色之后，正文**不能**再走 `--fg-2`：最深那档（28%）下它对卡底的对比度只有
   **3.4:1**（浅色档算的，深色档 3.9:1），低于 AA 的 4.5——而 `--fg-2` 这一支全站就是
   守着 4.5 线的（见「次级文字三档」那段）。改成 `--fg`：浅档 **10.8:1**、深档 **10.4:1**，
   而且与上面「三档掌握程度」那张卡一致（`.rm-level-one` 本来就是 `--fg`）。 */
.rm-stages .ladder-sum { color: var(--fg); }

/* 左边那条竖轨上的序号也跟着色相走——轨是这一段的脊柱，五段走下来
   序号由淡到浓，等于把「这条路越走越深」又写了一遍。 */
.rm-stages .ladder-rail i { border-color: var(--c, var(--line)); color: var(--c, var(--fg-3)); }

/* ④ 出身分岔 */
.rm-groups { display: flex; flex-direction: column; gap: 14px; margin-top: 20px; }
.rm-group {
  border: 1px solid var(--line); border-radius: var(--r);
  background: var(--bg-2); padding: 18px 20px;
}
.rm-group.open { border-color: var(--accent); background: var(--panel); }
.rm-ghead { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.rm-gname { font-family: var(--font-display); font-weight: 700; font-size: 17px; }
.rm-group.open .rm-gname { color: var(--accent); }
.rm-gn { font-family: var(--font-mono); font-size: 11px; color: var(--fg-3); }
.rm-gone { font-size: 12.5px; line-height: 1.75; color: var(--fg-2); margin: 8px 0 0; }
.rm-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 14px; }
.rm-chip {
  display: inline-flex; align-items: center; gap: 8px;
  /* 头像那一侧的内边距比文字侧小：圆形头像自己带底色，左边留 11px 会看着偏 */
  font-size: 12px; line-height: 1; padding: 5px 11px 5px 6px; text-decoration: none;
  border: 1px solid var(--line); border-radius: var(--r);
  background: var(--panel); color: var(--fg-2);
  transition: border-color .18s ease, color .18s ease;
}
.rm-chip:hover { border-color: var(--accent); color: var(--accent); }
.rm-chip.on {
  border-color: var(--accent); color: var(--accent);
  background: color-mix(in srgb, var(--accent) 10%, var(--panel));
}
/* 「收起」那三个字**一直在 DOM 里**，靠 .on 显隐——脚本原地切换时只要挪 .on，
   不用自己往标签里插/删节点。没有 JS 时效果与「只在选中那条里渲染」逐字节等价。 */
.rm-x { display: none; font-size: 10px; color: var(--fg-3); }
.rm-chip.on .rm-x { display: inline; }
/* 详情面板：十一条全在 DOM 里，未选中的靠 hidden 藏。⚠️ 这句显式写出来是因为
   `[hidden]` 的默认 `display:none` 会被任何一条 `display:` 覆盖掉（`.ops[hidden]`
   就是为此加的）——将来谁给 `.rm-panel` 加了 display，这里不会静默失效。 */
.rm-panel[hidden] { display: none; }

/* 出身头像（views/faces.js）：圆形卡通头像。
   ⚠️ 2026-10-20 用户：「你从哪出发 · 11 条出身，这个模块需要卡通头像，圆形。」
   ⚠️ 同日再改：**从手写 SVG 换成静态 PNG**（用户：「到小红书找这种风格的，统一一下，
      使用图片即可，矢量风格」）。手写那版是圆底 + 火柴人，到不了海报式扁平插画的完成度。
      图片由 `scripts/faces.mjs` 从 DiceBear 的 avataaars 风格出（可商用），
      落在 `public/img/faces/<key>.png`，**运行时零依赖、不联网**。

   圆底走 CSS 的 `--c`（分组色），**只在图片没加载出来时兜底**：现在是白底素材，
   图片铺满整个圆，分组色看不见了。

   ⚠️ 2026-10-20 用户：「头像的色彩描边去掉，改用投影，柔和。」
      原来图片缩进圆里、外圈露一圈分组色环——那圈环去掉了，改成**柔和投影**。
      ⚠️ 投影用 `box-shadow` 而不是 `border`：边框会占掉盒子，头像跟着缩一圈，
      11 枚标签会以肉眼可见的程度抖动（这条踩过）。 */
.rm-face {
  display: block; flex: none; width: 26px; height: 26px;
  border-radius: 50%; object-fit: cover;
  background: var(--c, var(--accent));
  /* 两层叠加才「柔」：一层贴着边收紧轮廓，一层铺开做晕。
     单层要么太硬（小 blur）要么糊成一团（大 blur）。 */
  box-shadow: 0 1px 3px var(--face-shadow),
              0 3px 10px color-mix(in srgb, var(--face-shadow) 55%, transparent);
}
.rm-face-lg {
  width: 44px; height: 44px;
  box-shadow: 0 2px 5px var(--face-shadow),
              0 6px 18px color-mix(in srgb, var(--face-shadow) 55%, transparent);
}

/* 四条出身各一支色相——「不同种类配不同色相」是全站的规矩（同「五层技术栈」那五张卡）。
   ⚠️ 第四组「不成为，只用」刻意用中性灰：它不是一类出身，是一组「不换 title」的对照。 */
.rm-group.g-code { --c: var(--chart-1); }
.rm-group.g-field { --c: var(--chart-2); }
.rm-group.g-product { --c: var(--chart-3); }
.rm-group.g-use { --c: var(--fg-3); }

/* 图标（`icon()`，仍给五阶段那几张卡用）：线稿、只描边、不填充。
   ⚠️ 颜色由这里给，图标自己不写颜色——所以它自动跟着主题走，也不碰「token 块之外
   不许有颜色字面量」那条护栏。 */
.rm-ico { display: block; flex: none; width: 16px; height: 16px; color: var(--fg-3); transition: color .18s ease; }
/* 卡片头部那一档（五阶段）。⚠️ 颜色不在这里定——五阶段那几张卡各自把它改成自己的 --c。 */
.rm-ico.rm-ico-md { width: 22px; height: 22px; }
.rm-dhead { display: flex; align-items: center; gap: 12px; margin-bottom: 18px; }
.rm-dname { font-family: var(--font-display); font-weight: 700; font-size: 19px; }

.rm-detail { margin-top: 18px; padding-top: 18px; border-top: 1px solid var(--line); }
.rm-dl { display: grid; grid-template-columns: 96px 1fr; gap: 11px 14px; }
.rm-dk { font-size: 11.5px; color: var(--fg-3); }
.rm-dv { font-size: 12.5px; line-height: 1.75; color: var(--fg); }
.rm-steps {
  list-style: none; margin: 16px 0 0; padding: 0;
  display: flex; flex-direction: column; gap: 11px;
}
.rm-steps + .rm-dl { margin-top: 16px; }
.rm-steps li { display: grid; grid-template-columns: 20px 84px 1fr; gap: 10px; align-items: baseline; }
.rm-step-n { font-family: var(--font-mono); font-size: 11px; color: var(--accent); }
.rm-step-w { font-family: var(--font-mono); font-size: 11px; color: var(--fg-3); }
.rm-step-b { font-size: 12.5px; line-height: 1.75; color: var(--fg); }
.rm-land { font-size: 12.5px; line-height: 1.7; color: var(--fg-2); margin: 16px 0 0; }
.rm-land b { font-family: var(--font-display); font-size: 14px; color: var(--accent); }
/* 「这条是本站补的」——凡是站上原本没有的内容，都在卡里留一句出处交代 */
.rm-own { font-size: 11.5px; line-height: 1.75; color: var(--fg-3); margin: 10px 0 0; }

/* ------------------------------------------------ 沙盘（views/sandbox.js）
   2026-10-22 用户：「这里加一个板块，接下来我们来见证一下，FDE 是如何作业的。介绍完，
   有按钮，进入 FDE 演示沙盘。」——首页上那一段引子（`#sec-sandbox`）+ 独立页 `/沙盘`。

   ⚠️ 这一页**不新增任何 token**，也**不新增任何架构类名**：作业台整个是 `.tabs` /
   `.tab-panel` / `.ops` 那三套现成的（`.ops*` 在 views/method.js 的 05 段已经用了），
   这里只补两处这一页独有的：首页那段的 CTA 卡、沙盘页的五步条与那行场景说明。 */

/* 首页那一段的落点。左边一段话、右边一枚按钮——**只有一个按钮**，
   沙盘里有什么写在 /沙盘 上，首页再铺一遍就是两份说明（见 views/sandbox.js 的文件头）。 */
.sbx-cta {
  display: flex; align-items: center; gap: 24px; flex-wrap: wrap;
  margin-top: 22px; padding: 20px 22px;
  border: 1px solid var(--line); border-top: 2px solid var(--accent);
  border-radius: var(--r-lg); background: var(--panel);
}
.sbx-cta-copy { flex: 1 1 380px; }
.sbx-cta-copy b { font-family: var(--font-display); font-weight: 700; font-size: 16px; }
.sbx-cta-copy p { margin: 7px 0 0; font-size: 12.5px; line-height: 1.8; color: var(--fg-2); }
/* ⚠️ 按钮**不许被压窄**：`flex-wrap` 让它换行之后仍然占满一行，
   否则长文案会把 `.btn` 挤成两行字（它内部不走 nowrap）。 */
.sbx-cta > .btn { flex: none; }

/* 沙盘那一段的配图（`public/gl/particles.js`）。它是这一段的主角，排在导语与 CTA 卡之间、横跨正文。

   ⚠️ **高度必须由这个容器自己给**：`[data-scene] > canvas` 是 `position:absolute; inset:0`，
   撑不出高度（页头那一格是靠 `<img>` 撑的、个人线那格是靠 `aspect-ratio` 撑的，
   这里走后者）。给 0 高的话整块塌掉、canvas 也一帧都画不出来。
   ⚠️ 比例 **1180 / 455 ≈ 2.59** 是一条**横带**：粒子网络铺满整个盒子，
   这个比例本身不再是「几何决定的」——但横带与这一段「文字在上、图在下」的版式是一套，
   换成竖的会让这一段占掉整屏。
   ⚠️ 底纹是**降级层**：这一段本来就没有配图文件，没有 WebGL 时也该是「一张线稿」而不是
   一块空白——与 `.line-fig--stage` 同一个做法。 */
.sbx-stage {
  aspect-ratio: 1180 / 455;
  margin: 26px 0 4px;
  border-radius: var(--r-lg);
  background:
    repeating-linear-gradient(115deg, var(--line) 0 1px, transparent 1px 11px),
    var(--bg-2);
}
/* ⚠️ 这里**不要**给 canvas 设 `cursor: grab`：粒子网络不可拖拽（原稿的拖拽式交互没移植），
   抓手的指针等于一个假承诺。 */
.sbx-stage canvas { pointer-events: none; }
/* 窄屏：一条 2.59 的横带挤进 340px 只有 131px 高，粒子网络那样就成了一条细缝。放到 4 / 3
   让它在竖屏里也铺得开。⚠️ 只改比例、别的一概不动——粒子是按盒子尺寸铺的，没有取景要重算。 */
@media (max-width: 760px) {
  .sbx-stage { aspect-ratio: 4 / 3; margin-top: 20px; }
}

/* 沙盘页的五步条。**不是可点的手风琴**——那是 03 段那一版的形态，
   这里只要一条横向索引，把「一个现场怎么走」这次序先摆出来，重点在下面那台作业台。
   ⚠️ 用 ol 而不是 div：五步是有序的，读屏软件要念得出「第几步」。 */
.sbx-steps {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(5, 1fr); gap: 10px;
  max-width: 960px; margin-left: auto; margin-right: auto;
}
.sbx-step {
  display: flex; flex-direction: column; gap: 5px;
  padding: 13px 14px; border: 1px solid var(--line); border-radius: var(--r);
  background: var(--panel);
}
.sbx-step i { font-family: var(--font-mono); font-size: 10.5px; font-style: normal; color: var(--accent); }
.sbx-step b { font-family: var(--font-display); font-size: 15px; }
.sbx-step span { font-size: 11.5px; line-height: 1.65; color: var(--fg-2); }

/* 每个行业面板顶上那行场景说明。操作台的标题写的是「装备制造」这类全称，
   而标签上写的是「制造」——不补这句，读者对不上号。 */
.sbx-one { margin: 0 0 14px; font-size: 13px; line-height: 1.8; color: var(--fg-2); }

@media (max-width: 760px) {
  /* 五步并排在窄屏上每格只剩 60px，标题会竖着折成一条。
     ⚠️ 折成两列留一格的排法（grid-auto-flow）在这一页不好看——第五步会孤零零占半行，
        直接折成一列更整齐。 */
  .sbx-steps { grid-template-columns: 1fr; }
  .sbx-cta { padding: 16px 16px; gap: 16px; }
  .sbx-cta > .btn { width: 100%; justify-content: center; }
}

@media (max-width: 760px) {
  /* 窄屏上「标签 / 值」两栏会把值挤成一条竖线，改成上下 */
  .rm-dl { grid-template-columns: 1fr; gap: 3px; }
  .rm-dl > .rm-dk { margin-top: 10px; }
  .rm-steps li { grid-template-columns: 18px 1fr; }
  .rm-step-w, .rm-step-b { grid-column: 2; }
  /* 五层那五张卡在窄屏上再收一档内边距，否则 4px 色条 + 18px 左内边距吃掉太多行宽 */
  .rm-layer { padding: 13px 14px; }
  .rm-lname { font-size: 15.5px; }
}

@media (max-width: 760px) {
  .fde-faces { grid-template-columns: 1fr 1fr; }
  /* 窄屏上图会缩到一半宽（980 的 viewBox 挤进 340px，球只剩 175px 直径），
     点与字都跟着糊成一团。这一张在窄屏上索性只留一条提示——图本身在桌面端看。
     ⚠️ 藏的是 `.fde-gwrap` 不是 `.fde-graph`：只藏 SVG 的话右下角那几枚
        视野控件（缩小 / 放大 / 适应）会孤零零浮在一片空白里。 */
  .fde-gwrap { display: none; }
}
@media (max-width: 460px) {
  /* 窄屏上环上的步名会挤在一起，索性去掉，留在圆环本身与图注里 */
  .fde-step { display: none; }
}

/* 岗位详情的职责/要求条目。每条是一整句话（不是短语），所以行距给足；
   圆点用强调色、不占太多左边距——条目本身很长，左边距大了读起来会散。 */
.jd-list { list-style: none; margin: 12px 0 0; padding: 0; }
.jd-list li { position: relative; padding-left: 17px; margin-top: 10px; font-size: 13px; line-height: 1.85; color: var(--fg-2); }
.jd-list li::before { content: ''; position: absolute; left: 2px; top: .78em; width: 5px; height: 5px; border-radius: 50%; background: var(--accent); }

/* 页脚。⚠️ 2026-10-19 用户圈着页脚那行字说「圆圈的部分的色彩都去掉」——
   原来那层「从页面底边透上来的紫光」（2026-10-16 加的，用户当时要「底部太单调，
   光效使用紫色」）撤掉了：收尾带底下现在有一整片会动的海在承担「底部不单调」这件事，
   页脚再压一层紫光就是同一句话说两遍，而且那行小字压在紫光上更糊。
   ⚠️ 撤的是**页脚这一层**，不是收尾带里那两条径向光——那两条还在。 */
.foot {
  border-top: 1px solid var(--line-strong); margin-top: 56px; padding: 28px 0 48px;
  font-family: var(--font-mono); font-size: 10.5px; line-height: 1.9; color: var(--fg-3);
}
.foot b { color: var(--fg-2); font-weight: 600; }

/* 区块。2026-10-14 用户：「线去掉，间距缩小」——
   原来是 `padding: 56px 0` + `border-bottom: 1px solid var(--line-strong)`，两个分段之间
   就是「56 + 1 + 56 = 113px 的空白」中间横一道发丝线。全站每个分段都在用这条规则，
   所以首页、/学 /做 /连 里每一处分段都长这样，截图里圈出来的那两处正是它。

   线去掉、上下留白对半收到 28px（两个相邻分段之间还剩 56px）——省下的正是那道
   线本来在承担的分隔作用：分段靠留白自己分开，而不是靠一条划在页面上的横线。 */
.block { padding: 28px 0; position: relative; }
/* 整幅铺满视口宽度的底色带（盒子阴影外扩 + 裁切到自身上下边界） */
.block.alt { background: var(--bg-2); box-shadow: 0 0 0 100vmax var(--bg-2); clip-path: inset(0 -100vmax); }
/* 强调带（收尾那一条）。2026-10-14 起**两档都是浅底**：
   浅色档是淡紫、深色档是「抬一档」的近黑——两者各自的理由写在 token 那边。
   它有自己的底/字 token，不跟着 --fg 反色。
   ⚠️ **带子上那几支 `*-on-emphasis` 必须跟着 `--emphasis-bg` 走**，
   它们是这个文件里唯一「假设底是什么颜色」的一组——底一翻，字不翻就是隐形。
   （对比之下，导航当前项、筛选当前项那些**小件**用 --fg 反色是对的：那是个醒目的「你在这儿」。）

   ⚠️ 2026-10-16 用户：「底部太单调，光效使用紫色」——当时在带子中间压过一层紫色的光
   （`background-color` 之外再压一层 `background-image`，两层径向渐变）。
   ⚠️ **2026-10-19 撤掉了**：用户圈着标题那一带说「圆圈的部分的色彩都去掉」。
      那处紫色正是这两层（内层小而亮的那个「芯」就在标题正后面）。现在带子底下有
      一整片会动的海在承担「不单调」，标题区再压一层紫晕就是同一句话说两遍，
      而且它把标题衬得发灰。
   ⚠️ 连带一条：**带子的底不再需要喂给 `box-shadow` 之外的东西**——上面那条
      「底色还得喂给 box-shadow 那条整幅铺开的规则，而 box-shadow 接不了渐变」
      是当年为了「底色 + 渐变并存」写的，现在没有渐变了，规则照旧（只用底色）。 */
.block.emphasis {
  background-color: var(--emphasis-bg);
  color: var(--emphasis-fg);
  box-shadow: 0 0 0 100vmax var(--emphasis-bg); clip-path: inset(0 -100vmax);
}
.block.emphasis { padding-bottom: 220px; }
.block.emphasis .foot-gl {
  position: absolute; top: 0; bottom: 0;
  left: calc(50% - 50vw); width: 100vw;
  z-index: -1;
}
.block.emphasis + .foot { margin-top: 0; }
@media (max-width: 820px) {
  .block.emphasis { padding-bottom: 150px; }
}
.block.emphasis .sec-no { color: var(--accent-on-emphasis); }
.block.emphasis h2 { color: var(--emphasis-fg); }
.block.emphasis .lede { color: var(--fg-dim-on-emphasis); }
.block.emphasis .btn.ghost { background: var(--emphasis-bg); }
.block.emphasis .btn.ghost { color: var(--emphasis-fg); border-color: var(--line-on-emphasis); }
.block.emphasis .btn.ghost:hover { background: var(--hover-on-emphasis); }

/* 小节标签做成胶囊（参考稿里每个标题上面都是一枚带圆点的胶囊）。
   原来只是一行大写小字，居中之后看着像没写完的标题。 */
.block .sec-no {
  font-family: var(--font-mono); font-size: 11px; letter-spacing: .18em; text-transform: uppercase;
  color: var(--accent);
  display: inline-flex; align-items: center; gap: 8px;
  padding: 6px 14px 5px; border: 1px solid var(--line-strong); border-radius: 999px;
  background: var(--panel);
}
.block .sec-no::before {
  content: ''; width: 5px; height: 5px; border-radius: 50%; background: var(--accent);
  flex: none; box-shadow: 0 0 8px var(--accent-glow);
}
.block h2 {
  font-family: var(--font-display); font-weight: 700;
  font-size: var(--fs-h2); line-height: 1.16; letter-spacing: -.022em; margin: 12px 0 0;
  max-width: 22ch;
}
.block .lede { font-family: var(--font-read); font-size: var(--fs-lede); line-height: 1.78; color: var(--fg-2); max-width: 58ch; margin: 16px 0 0; }
.block .bhead { display: flex; justify-content: space-between; align-items: baseline; gap: 16px; position: relative; z-index: 1; }
.block .bbody { position: relative; z-index: 1; margin-top: 24px; }
.block .bfoot { position: relative; z-index: 1; margin-top: 18px; }
/* 首页那种「营销段」的小节头：居中。
   应用页（列表、阅读器）仍左对齐——左对齐的列表更好扫，居中的反而难读。 */
.block.center .bhead { justify-content: center; text-align: center; }
/* ⚠️ 这里**不要**给 `.bhead > div` 加 `max-width`。
   2026-10-13 用户：「从什么是 FDE 开始，大标题全部一行」——量出来根因就是这一条：
   原来写着 `max-width: 30ch`，`ch` 按 15px 的正文字算出来是 **264px**，
   和视口多宽没关系；于是「五张图，看懂这个岗位」在 1440 下也被压成两行
   （h2 实测 79px = 2 行），下面每一段的标题跟着一起折。
   下面那条 `.block.center h2 { max-width: none }` 本来就是想说「居中段的标题不限宽」，
   被父级这 264px 直接顶掉了——两条规则互相打架，赢的是父级。
   去掉之后标题用满整栏、一行放下（1440 下最长的「把问题交出来，我们看看能不能做。」
   实测 544px，单行）。.lede 有它自己的 `52ch`，不受影响。 */
.block.center h2 { max-width: none; }
.block.center .lede { max-width: 52ch; margin-left: auto; margin-right: auto; text-align: center; }
.block.center .bbody { margin-top: 34px; }
/* ⚠️ 这条是「居中段里的 .label 一律居中」。段头下面那种**独立的**标签行
   （「想按能力点找？…」）居中是对的，但它会连同卡片**内部**的标签一起居中——
   卡片里的 CONTEXT / CODE、`17 篇`、`制造业 · 指标准确率` 这些全部跟着歪，
   用户在「什么是 FDE」那一屏看到的就是四列标签各自居中、彼此左端不对齐（截图打回）。
   2026-10-14 收窄：只管直接挂在 .bbody 上的那一层，卡片里的还原成左对齐。
   ⚠️ `:scope >` 不能省——写成 `.bbody .label` 会把子级一起放行，等于没改。 */
.block.center > .bbody > .label { text-align: center; }
.block.center .chart .label,
.block.center .card .label { text-align: left; }
/* 每段顶部一层很淡的紫晕，像被灯从上方扫到——参考稿里每个大容器顶端都有。
   只给 .block.center（首页与两条线页那种营销段）：应用页的列表又密又长，加了是噪声。
   ⚠️ 别调太亮：这层铺在卡片底下，一浓就把卡片和页底的对比压没了，
   卡片反而看着比周围更暗。 */
.block.center::before {
  content: ''; position: absolute; z-index: 0; pointer-events: none;
  left: 50%; top: 0; transform: translateX(-50%);
  width: min(1000px, 100%); height: 260px;
  background: radial-gradient(ellipse 50% 100% at 50% 0%, var(--accent-glow), transparent 72%);
  opacity: .3;
}
/* 内容压在光之上 */
.block.center > * { position: relative; z-index: 1; }

/* 窄屏把大标题收一档，让长标题也放得下**一行**（用户：「大标题全部一行」）。
   23px 在 375 宽下放不下最长的几条（「七个能力域，一条从入门到规模化的路」17 字
   × 23px = 391px，而可用宽度只有 327px）。实测全站 29 条 h2 在 19px 下**一条都不折**，
   20px 还有 2 条、23px 有 5 条。
   断点取 460：再宽一点（440+）23px 本来就放得下最长那条，不需要收。
   ⚠️ 改的是 `--fs-h2` 本身而不是 `.block h2`：线页的 `.line-head-copy h1` 也读它
   （见文件末尾那条窄屏规则），两处是同一档标题，得一起收。 */
@media (max-width: 460px) {
  :root { --fs-h2: 19px; }
}

/* 段落配图。上限比正文窄一档：它不是插画页，是一句「这段在讲什么」的图形化说法。
   ⚠️ height 必须写明 auto —— `<img width height>` 是表现性提示，
   CSS 不给 height 时它会顶掉 aspect-ratio，窄屏上竖着裁掉一截。 */
.sec-fig { margin: 30px auto 0; max-width: 720px; }
.sec-fig img { display: block; width: 100%; height: auto; }
@media (max-width: 640px) { .sec-fig { max-width: none; } }
/* 矮一档的区块：像「口径」那种只有两行字的收尾说明，用 56px 的上下留白太空 */
.block.tight { padding: 22px 0; }

/* ---------------- 卡片网格 ----------------
   2026-10-13：从「一整块描边 + 1px 缝」改成**独立圆角卡片**（用户给了新参考稿，
   要求卡片各自带圆角与辉光）。
   缝那套的好处是线共用、不用数「第几列去右边线」，但它和辉光是互斥的：辉光会从缝里
   溢出去，格子看着就散了。所以现在改成真实间隔 + 每格自己一条描边。
   ⚠️ 容器不能留 overflow: hidden，否则悬停的辉光会被裁在格子边界上，像贴了块补丁。
   ⚠️ 这一套是全站共用的（含 1251 条的信号库列表），改一处就是改所有列表页。 */
.grid2, .grid3, .roles, .graph-cells, .metrics, .cells {
  display: grid; gap: 14px;
}
.grid2 { grid-template-columns: 1fr 1fr; }
.grid3 { grid-template-columns: repeat(3, 1fr); }
.roles { grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); margin-top: 20px; }
.graph-cells { grid-template-columns: 1fr 1fr; }
.metrics { grid-auto-flow: column; grid-auto-columns: 1fr; }
.cells { grid-template-columns: 1fr; }

/* 格子：自己带底、描边与圆角 */
.grid2 > *, .grid3 > *, .roles > *, .graph-cells > *, .metrics > *, .cells > * {
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--r);
  min-width: 0;
  transition: background .15s ease, border-color .18s ease, box-shadow .18s ease, transform .18s ease;
}
/* 常驻的一圈极淡外发光：参考稿里的卡片是「被点亮」的，不是只靠边框分出来的。
   只给营销网格，**不给 .cells**——那是 1251 条的列表，每行都发光就是噪声。 */
.grid2 > *, .grid3 > *, .roles > *, .graph-cells > * {
  box-shadow: 0 0 24px -14px var(--accent-glow);
}
/* ⚠️ 2026-10-15 起趋势开头那四个数字是 `<a>`（用户：「数字也可以点击进入详情」）。
   链接的默认下划线与颜色要归零；悬停走下面那条通用规则——所以它的选择器列表里
   **必须带上 `.metrics > a`**，漏了的话这四格点上去一点反馈都没有（描边不转紫、
   也不抬起），而不会有任何报错。 */
.metrics > a { text-decoration: none; color: inherit; }

/* 悬停：描边转紫 + 一圈外发光 + 微微抬起。
   ⚠️ 这条 2026-10-13 之前是**反的**（明令不许有辉光，因为会把 1px 的缝顶开）。
   缝没了，这条限制也就随之解除——但只在卡片网格里，别处仍不许滥用。 */
.grid2 > a:hover, .grid3 > a:hover, .roles > a:hover, .cells > a:hover, .metrics > a:hover {
  background: var(--panel); border-color: var(--accent);
  box-shadow: 0 0 28px -8px var(--accent-glow); transform: translateY(-2px);
}
.grid2 > .card, .grid3 > .card { padding: 20px 22px; }
.cells > .card { padding: 15px 18px; }
/* .row-card 自带的 margin-bottom 是为「散列」写的；进了网格 gap 就是间隔，再留就双份 */
.cells > .row-card, .cells > .me-app { margin-bottom: 0; }
.metrics > * { padding: 16px 18px; }

/* ---------------- 两栏布局 ----------------
   带侧栏的两栏版式。原来是写在 views 里的内联 grid-template-columns，
   两个毛病：内联的**媒体查询覆盖不了**（窄屏不折行），而且 grid 子项默认
   min-width: auto——内容一宽就把列撑开。实测 /趋势 在 380px 下横向溢出 41px。

   2026-10-13 用户：「左右两个宫格，一样高度，左右两个宫格宽度也一样」。
   原来是 `1.2fr 1fr` + `align-items: start`——量出来 1274 宽下左 602 / 右 502，
   首页第 1 行左右各 666 / 589px 高，第 2 行差 11px。三处都要改：
     ① 列等宽：`1.2fr 1fr` → `1fr 1fr`（1.2:1 是「主栏 + 副栏」的写法，这里两栏地位相同）；
     ② 高度拉齐：`align-items: start` → `stretch`（start 让每栏各按内容高，短的那栏底下留空）；
     ③ 卡片补齐：栏高齐了，但**卡片自己**还按内容高——`.charts` 是纵向 flex，
        给最后一张 `flex: 1` 让它吃掉缩水出来的那截，卡片才真的等高。
   ⚠️ `.split.aside`（案例详情、需求详情的「正文 + 300px 侧栏」）**不改**：
      那里主次分明，侧栏本来就该窄、也本来就该短。 */
.split { display: grid; grid-template-columns: 1fr 1fr; gap: 28px; align-items: stretch; }
.split.aside { grid-template-columns: minmax(0, 1fr) 300px; gap: 36px; align-items: start; }
.split > * { min-width: 0; }
/* 栏等高之后，最后一张卡补齐剩余高度 */
.split .charts > .chart:last-child { flex: 1; }
/* ---- 岗位市场那一段的两栏（`.split--jobs`）----
   2026-10-21 用户：「这个部分全部缩小，放到一列，左侧，右侧放薪资，右侧增加一个平均薪资」。
   左栏四张分布卡（经验/学历/规模/城市）、右栏薪酬 + 平均薪资。
   ⚠️ **两栏张数不等（4 vs 2），所以那条「最后一张卡补齐高度」必须关掉**——
   它本来是给「两栏各两张」写的；套到这里会把「平均薪资」拉成一张 771px 高的空卡（实测过）。
   ⚠️ 左栏略窄（.86 : 1.14）：四张卡的柱形跟着收窄，就是用户说的「全部缩小」；
   宽出来的那点留给右栏薪酬那张五档的图。 */
.split--jobs { grid-template-columns: minmax(0, .86fr) minmax(0, 1.14fr); }
.split--jobs .charts > .chart:last-child { flex: 0 0 auto; }
@media (max-width: 900px) {
  .split, .split.aside { grid-template-columns: 1fr; gap: 22px; }
  /* 折成一列之后「等高拉齐」就没有意义了——两栏不再并排，`stretch` 只会让容器按
     最高的那一栏算，而最后一张卡的 `flex: 1` 会在纵向 flex 里把它撑高
     （实测第 1 行右栏由 622px 涨到 637px）。两条一起还原。 */
  .split { align-items: start; }
  .split .charts > .chart:last-child { flex: none; }
}

/* 其余 grid 容器也把子项的 min-width 归零：同一个坑，不一定哪天就踩上 */
.graph-layout > *, .arch3d > *, .forks > *, .cmp > *, .step-meta > *, .hero-split > * { min-width: 0; }

/* 知识图谱：域卡片栅格 + 右侧四扇门 */
.graph-layout { display: grid; grid-template-columns: 1.35fr 1fr; gap: 22px; align-items: start; }
/* 首页思维导图那栏要更宽，否则图被压小、字糊 */
.graph-layout.wide { grid-template-columns: 1.75fr 1fr; }
.graph-side { position: sticky; top: 86px; }
/* 能力点行：名字吃掉剩余宽度，成熟度标签永远不折行 */
.abil-row {
  display: flex; justify-content: space-between; align-items: baseline; gap: 8px;
  padding: 6px 9px; border-radius: 4px; font-size: 12.5px; background: var(--bg-2);
}
.abil-row.on { background: var(--fg); color: var(--bg); }
.abil-row.on .label { color: var(--bg); }
.abil-name { flex: 1; min-width: 0; }
.abil-meta { flex: none; white-space: nowrap; }
@media (max-width: 1000px) {
  .graph-layout { grid-template-columns: 1fr; }
  .graph-side { position: static; }
}
@media (max-width: 640px) {
  .graph-cells { grid-template-columns: 1fr; }
}

/* 07 思维导图：中心 FDE 能力图谱，七个域向两侧展开，每个域可点 */
.mm-wrap { min-width: 0; }
.mindmap { display: block; width: 100%; height: auto; }
.mm-link { fill: none; stroke: var(--line-strong); stroke-width: 1.5; }
.mm-node { cursor: pointer; }
.mm-node rect { fill: var(--panel); stroke: var(--line); transition: stroke .15s ease, fill .15s ease; }
.mm-node:hover rect, .mm-node.sel rect { stroke: var(--accent); fill: var(--bg-2); }
.mm-name { font-family: var(--font-display); font-weight: 700; font-size: 15px; fill: var(--fg); }
.mm-sub { font-family: var(--font-mono); font-size: 9.5px; letter-spacing: .1em; fill: var(--fg-3); }
.mm-center rect { fill: var(--accent-fill); }
.mm-center text { font-family: var(--font-display); font-weight: 700; font-size: 17px; fill: var(--fg-on-accent); }

@media (max-width: 900px) {
  .grid3 { grid-template-columns: 1fr; }
  /* 导航折成两行：上行字标与右侧控件，下行四个 tab。
     挤在一行里的话 465px 处导航会涨到 180px 高、还横向溢出（实测过）。 */
  .nav .nav-in { flex-wrap: wrap; gap: 10px 12px; padding-top: 12px; padding-bottom: 12px; }
  .nav .tabs {
    order: 3; width: 100%;
    overflow-x: auto; scrollbar-width: none;   /* tab 多了就横向滚，不折行 */
  }
  .nav .tabs::-webkit-scrollbar { display: none; }
  .nav .right { gap: 10px; }
  .nav .search { width: 130px; }
  /* 窄屏下大数字条从「一行 N 格」折成两行两列。用 grid 的改法，
     不再是 flex-wrap（那套在改成宫格之后已经不生效了）。 */
  .metrics { grid-auto-flow: row; grid-template-columns: 1fr 1fr; }
  /* 印章现在是绝对定位钉在右上角，而报头改成了居中——窄屏下两者会撞上。
     它是装饰不是信息，收掉。 */
  .seal { display: none; }
  .hero { padding-top: 34px; }
}

@media (max-width: 560px) {
  /* 手机上真的塞不下了。搜索框是这里最不关键的一个——字标、四个 tab、
     登录、主题切换都得留着，所以让它退场。 */
  .nav .search { display: none; }
  /* 字标跟着基准字号放大过（19 → 23px），这里的收尾值也抬一档：16 → 19px */
  .nav .brand { font-size: 19px; }
  /* 间距也得跟着收——窄屏下印缩到 20px，11px 的左边距就显得空了 */
  .nav .brand .career { margin-left: 9px; }
  .nav .brand .mk { width: 20px; height: 20px; font-size: 11px; box-shadow: 0 0 10px var(--accent-glow); }
}

/* 长文正文：阅读器用 */
/* ---------------- 阅读器 ----------------
   整块收窄并居中：舒适的行长是 70~80 字符，铺满 1132px 的正文读起来很累。
   标题、来源、目录、正文一起收进一个 1040px 的栏里，这样它们是齐的。 */
.reader { max-width: 1040px; margin: 0 auto; }
/* ⚠️ 这里**不能**写 `align-items: start`。写了的话左侧那一格（包着目录的 div）会缩到
   和目录一样高，而 `position: sticky` 只能在**父盒子**里移动——父盒子只有 222px 高，
   目录一点都动不了，滚动时跟着页面一起消失。2026-10-16 用户：「需要左侧顶部固定，
   不要随页面的滚动而消失」——实测滚 1200px 目录就跟着走了 1200px，就是这个原因。
   去掉之后左格撑满整行高度（= 正文高度），目录才有地方可停。 */
.reader-grid { display: grid; grid-template-columns: 220px minmax(0, 1fr); gap: 36px; margin-top: 28px; }

.prose-fde {
  font-family: var(--font-read);
  font-size: 15.5px; line-height: 1.85;
  min-width: 0;
  max-width: 74ch;   /* 行长上限。再宽，眼睛换行时会跟丢 */
}
.prose-fde h1, .prose-fde h2, .prose-fde h3, .prose-fde h4 {
  font-family: var(--font-display);
  line-height: 1.3;
  letter-spacing: -.01em;
  margin: 1.6em 0 .6em;
  /* 点左侧目录跳过来时，标题不能被吸顶导航盖住。原来是 20px——比导航（63px）还小，
     跳过去的标题正好藏在导航底下。 */
  scroll-margin-top: calc(var(--nav-h) + 20px);
}
.prose-fde h1 { font-size: 27px; }
.prose-fde h2 { font-size: 24px; }
.prose-fde h3 { font-size: 18.5px; }
.prose-fde p { margin: 0 0 1.05em; }
.prose-fde ul, .prose-fde ol { padding-left: 1.5em; margin: 0 0 1.05em; }
.prose-fde li { margin-bottom: .35em; }
.prose-fde blockquote {
  margin: 0 0 1.1em; padding: 2px 0 2px 16px;
  border-left: 2px solid var(--line); color: var(--fg-2);
}
.prose-fde table { width: 100%; border-collapse: collapse; margin: 0 0 1.2em; font-family: var(--font-body); font-size: 13px; }
.prose-fde th, .prose-fde td { border: 1px solid var(--line); padding: 7px 9px; text-align: left; vertical-align: top; }
.prose-fde th { background: var(--bg-2); font-weight: 600; }
.prose-fde code { font-family: var(--font-mono); font-size: .88em; background: var(--bg-2); padding: 1px 5px; border-radius: 3px; }
.prose-fde pre { background: var(--bg-2); padding: 14px; border-radius: var(--r); overflow-x: auto; margin: 0 0 1.2em; }
.prose-fde pre code { background: none; padding: 0; }
.prose-fde a { color: var(--accent); }
.prose-fde hr { border: 0; border-top: 1px solid var(--line); margin: 1.8em 0; }

/* 阅读器左侧的目录。此前只有 views/manual.js 里那行内联 sticky，没有任何外观——
   给它一个方框，和参考稿的「面板」语言一致。链接的缩进按标题层级算，仍是内联。
   ⚠️ `top` 要躲开吸顶的导航（`--nav-h` = 63px），不是随便写的 20px——
      写 20 的话目录顶上那一截会压在导航底下，看着像被裁掉了。
   ⚠️ 它能粘住的前提是**父盒子比它高**，那一条在 `.reader-grid` 上面（别加回 align-items: start）。 */
.toc {
  position: sticky; top: calc(var(--nav-h) + 16px);
  border: 1px solid var(--line); border-radius: var(--r); background: var(--panel);
  padding: 14px 16px;
  max-height: calc(100vh - var(--nav-h) - 32px); overflow-y: auto;
}
.toc .label { margin-bottom: 10px; }
/* ⚠️ 2026-10-16 用户：「字体同右侧」——原来这里是等宽字体（`--font-mono`，12px），
   和右边正文的 Georgia 完全是两路。目录是**正文的索引**，用正文那一套读起来才连得上。
   字号比正文（15.5px）小一档：220px 的栏里 15.5px 一行放不下几个字。 */
.toc a { display: block; font-family: var(--font-read); font-size: 13.5px; line-height: 1.55; padding: 4px 0; color: var(--fg-2); }
.toc a:hover { color: var(--accent); }

@media (max-width: 900px) {
  .prose-fde table { display: block; overflow-x: auto; }
  /* 窄屏把目录从侧栏挪到正文上方：220px 的侧栏在手机上会挤掉正文 */
  .reader-grid { grid-template-columns: 1fr; gap: 20px; }
  .toc { position: static; max-height: none; }
}

/* 列表页的筛选行与分页 */
.facets { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; }
.facets > .label { width: 52px; flex: none; padding-top: 4px; }
.facets a.card { padding: 4px 9px; font-size: 11.5px; }
.facets a.card.on { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }

/* 小字说明。它和 .lede 是两种声音，别混用：
   .lede 是页头导语——这页是干什么的（衬线、15–18px）；
   .note 是口径与免责——脱敏、不参与结算、版权归属（无衬线、12.5px）。
   原来这两套散在 10 个 view 的内联样式里，字号还有 12.5/13 两种。 */
.note { font-size: 12.5px; color: var(--fg-2); line-height: 1.8; max-width: 70ch; margin: 10px 0 0; }

/* 详情页的朗读段：正文字体、行高放宽 */
.prose-p { font-family: var(--font-read); font-size: 14.5px; line-height: 1.85; margin: 8px 0 0; }

/* 「所涉能力点」这类小标签行。chip 本身还是 .card，只是压小一号 */
/* 跨页入口：一行描边胶囊（views/bits.js 的 enter()）。
   这些入口原来大多写成一行小字里的内联链接，扫过去看不到「这里还有一整页可以进」——
   2026-10-14 统一成胶囊：一眼能看见，一行也能排几个。
   ⚠️ 强调带里另有一套配色（那一段的底不是 --panel），见 .block.emphasis .enter。 */
.enter { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 18px; }
.enter > a {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 7px 14px; border: 1px solid var(--line-strong); border-radius: 999px;
  background: var(--panel); font-size: 12.5px; line-height: 1.4; color: var(--fg);
  transition: border-color .18s ease, box-shadow .18s ease, transform .18s ease, color .18s ease;
}
.enter > a:hover {
  border-color: var(--accent); color: var(--accent);
  box-shadow: 0 0 24px -10px var(--accent-glow); transform: translateY(-1px);
}
.enter > a .n { font-family: var(--font-mono); font-size: 11px; color: var(--fg-3); }
.enter > a .arw { color: var(--fg-3); transition: color .18s ease, transform .18s ease; }
.enter > a:hover .n, .enter > a:hover .arw { color: var(--accent); }
.enter > a:hover .arw { transform: translateX(2px); }

.chiprow { display: flex; flex-wrap: wrap; gap: 6px; margin: 10px 0 20px; }
.chiprow > .card { padding: 5px 10px; font-size: 12px; }

.pager { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 18px; }
.pager a.card { padding: 5px 11px; font-family: var(--font-mono); font-size: 11.5px; }
.pager a.card.on { background: var(--fg); color: var(--bg); border-color: var(--fg); }

/* 列表卡片：整块可点 */
.row-card { display: block; margin-bottom: 9px; }
/* 卡片顶部一行：序号 / 标题 / 状态。标题吃掉剩余宽度，状态被顶到最右。 */
.rhead { display: flex; gap: 10px; align-items: baseline; }
.row-card .rt { flex: 1; font-family: var(--font-display); font-weight: 700; font-size: 15px; }
.row-card .rb { font-size: 12.5px; color: var(--fg-2); margin-top: 8px; line-height: 1.65; }
/* 行内标题与正文：用在「不是 .rhead 那一行」的场合（档案页的作品集与动态） */
.row-title { font-family: var(--font-display); font-weight: 700; font-size: 14px; }
.row-body { font-size: 12.5px; line-height: 1.6; }

/* ---------------- 管理后台 ----------------
   后台的列表行与站上其它列表同一套（.cells > .card.row-card），只多一行操作按钮。
   ⚠️ 这一行里的按钮是**表单里的提交按钮**，不是链接——删除与封禁都是 POST。
   所以这一格不能像别处那样整块做成 <a>：表单套进链接是非法嵌套。
   ⚠️ `form { display: contents }` 是为了让按钮直接参与上面那行的 flex 排布；
      不写的话每个 form 各自成为一个块，按钮会各占一行。 */
.adm-actions {
  display: flex; flex-wrap: wrap; gap: 8px; align-items: center;
  margin-top: 12px; padding-top: 11px; border-top: 1px solid var(--line);
}
.adm-actions form { display: contents; }
.adm-actions .btn { padding: 5px 12px; font-size: 12px; }

/* 信号库：溯源块（趋势页的「源」点进来时，那一条被单独提出来放在列表之前）。
   形状复用 .cells，只把描边换成品牌色。 */
.sig-focus { margin-bottom: 22px; }
.sig-focus-h { color: var(--accent); }
.sig-focus-box { margin-top: 8px; border-color: var(--accent); }
/* 「已核实」标记。它在一行里是 flex 子项，不能被标题挤没 */
.sig-ok { flex: none; color: var(--accent); }
/* 信号库来源提示：贴在搜索框上方，用品牌色左边线做轻提示，不抢列表内容。 */
.sig-notice {
  margin: 0 0 18px;
  padding: 10px 14px;
  border: 1px solid var(--line);
  border-left: 3px solid var(--accent);
  border-radius: var(--r);
  background: var(--accent-soft);
  color: var(--fg-2);
  font-size: 12.5px;
  line-height: 1.6;
}

/* ---------------- 私信 ----------------
   会话列表与气泡原来全是内联样式，气泡还把底色/文字写死成 var(--fg)/var(--bg)。
   两档主题下 --fg 会翻转，写死的白字在浅色档会消失，所以这里全部收进类。 */
.thread-row { display: flex; gap: 12px; align-items: center; }
.thread-main { flex: 1; min-width: 0; }
.thread-name { font-family: var(--font-display); font-weight: 700; font-size: 14.5px; }
.thread-last { margin-top: 5px; line-height: 1.6; }
.thread-meta { text-align: right; flex: none; }
.thread-meta > .label + .label { margin-top: 5px; }

/* 对话页的表头：头像 + 姓名/身份 + 右侧「看档案」 */
.thread-head { display: flex; gap: 12px; align-items: center; }
.thread-head-name { font-family: var(--font-display); font-weight: 700; font-size: 17px; }
.thread-head-meta { margin-top: 4px; }
.thread-back { margin-left: auto; }

.thread-body { max-width: 640px; }
.thread-form { display: flex; gap: 8px; margin-top: 16px; }

/* 气泡。自己那条的底是 --fg、字是 --bg，两档都会翻转——所以时间戳用
   --fg-dim-on-invert（它跟着 --fg 一起翻），不能用 --fg-2（那是页面底上的灰）。 */
.bubble-row { display: flex; margin-bottom: 10px; }
.bubble-row.mine { justify-content: flex-end; }
.bubble {
  max-width: 70%; padding: 9px 12px; border-radius: 8px;
  font-size: 13.5px; line-height: 1.7;
  background: var(--panel); border: 1px solid var(--line);
}
.bubble > .label { margin-top: 5px; }
.bubble-row.mine .bubble { background: var(--fg); border-color: var(--fg); color: var(--bg); }
.bubble-row.mine .bubble > .label { color: var(--fg-dim-on-invert); }

@media (max-width: 900px) {
  .facets > .label { width: 100%; }
}

/* ============ 首页讲解线：分流 / 对照 / 步骤 / 架构图 / 行业 / 学习路径 ============ */

/* 01 分流：老板和入行者各自找到自己的那一条 */
.forks { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; margin-top: 28px; }
.fork {
  display: block; padding: 28px 26px; border: 1px solid var(--line); border-radius: var(--r-lg);
  background: var(--panel); transition: border-color .15s ease, transform .15s ease, box-shadow .15s ease;
}
.fork:hover { border-color: var(--accent); transform: translateY(-2px); box-shadow: 0 10px 26px -12px var(--accent-glow); }
.fork.biz { border-top: 3px solid var(--accent); }
.fork.me { border-top: 3px solid var(--fg); }
.fork-who { font-family: var(--font-mono); font-size: 11px; letter-spacing: .2em; text-transform: uppercase; color: var(--fg-3); }
.fork-want { font-family: var(--font-display); font-weight: 700; font-size: clamp(20px, 2.2vw, 27px); line-height: 1.28; letter-spacing: -.02em; margin-top: 12px; }
.fork-desc { font-family: var(--font-read); font-size: 14px; line-height: 1.8; color: var(--fg-2); margin: 14px 0 0; }
.fork-cta { display: inline-block; margin-top: 20px; font-family: var(--font-mono); font-size: 12px; font-weight: 600; color: var(--accent); }
.fork.me .fork-cta { color: var(--fg); }

/* 02 传统交付 vs FDE 交付 */
.cmp { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; margin-top: 28px; }
.cmp-col { border: 1px solid var(--line); border-radius: var(--r); padding: 20px 22px; background: var(--panel); }
.cmp-col.bad { background: var(--bg-2); }
.cmp-col.good { border-color: var(--accent); }
.cmp-head { display: flex; align-items: baseline; gap: 10px; }
.cmp-t { font-family: var(--font-display); font-weight: 700; font-size: 18px; }
.cmp-col.bad .cmp-t { color: var(--fg-2); }
.cmp-col.good .cmp-t { color: var(--accent); }
.cmp-list { list-style: none; margin: 16px 0 0; padding: 0; }
.cmp-list li { display: flex; gap: 10px; font-size: 13.5px; line-height: 1.7; padding: 9px 0; border-top: 1px solid var(--line); }
.cmp-list li:first-child { border-top: 0; }
.cmp-mk { flex: none; font-family: var(--font-mono); font-weight: 700; }
.cmp-col.bad .cmp-mk { color: var(--fg-3); }
.cmp-col.good .cmp-mk { color: var(--accent); }

/* 03 五步：可点、带进度、走完给一句收尾 */
.progress { display: flex; align-items: center; gap: 14px; margin-top: 26px; }
.progress-bar { flex: 1; height: 4px; background: var(--line); border-radius: 2px; overflow: hidden; }
.progress-bar i { display: block; height: 100%; width: 0; background: var(--accent); transition: width .45s cubic-bezier(.4, 0, .2, 1); }
.progress-n { font-family: var(--font-mono); font-size: 11px; letter-spacing: .1em; color: var(--fg-2); white-space: nowrap; }
.progress-n b { color: var(--accent); font-size: 13px; }

.steps { list-style: none; margin: 24px 0 0; padding: 0; }
.step { display: flex; }

/* 左侧一条竖轨把五步串成一条路：走过的段染红，于是「走到哪儿了」
   是一眼看得见的，而不是只有顶上一个进度条在动。 */
.step-rail, .ladder-rail { flex: none; width: 44px; position: relative; display: flex; justify-content: center; }
.step-rail::before, .ladder-rail::before {
  content: ''; position: absolute; left: 50%; top: 0; bottom: 0; width: 2px;
  background: var(--line); transform: translateX(-50%);
}
/* 首尾两段不要伸出去，否则轨道会在列表两端各多出半截 */
.step:first-child .step-rail::before,
.ladder:first-child .ladder-rail::before { top: 26px; }
.step:last-child .step-rail::before,
.ladder:last-child .ladder-rail::before { bottom: calc(100% - 26px); }
.step.done .step-rail::before { background: var(--accent); }
.step-rail i, .ladder-rail i {
  position: relative; z-index: 1; margin-top: 15px; font-style: normal;
  width: 26px; height: 26px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--panel); border: 2px solid var(--line);
  font-family: var(--font-mono); font-size: 9.5px; color: var(--fg-3);
  transition: background .25s ease, border-color .25s ease, color .25s ease;
}
.step.open .step-rail i { border-color: var(--fg); color: var(--fg); }
.step.done .step-rail i { background: var(--accent); border-color: var(--accent); color: var(--bg); }

.step-card {
  flex: 1; min-width: 0; margin-bottom: 10px; overflow: hidden;
  border: 1px solid var(--line); border-radius: var(--r); background: var(--panel);
  transition: border-color .2s ease;
}
.step.done .step-card { border-color: var(--accent); }
.step-head { display: flex; align-items: center; gap: 14px; width: 100%; text-align: left; padding: 16px 20px; background: none; border: 0; cursor: pointer; font: inherit; color: inherit; }
.step-t { flex: 1; font-family: var(--font-display); font-weight: 700; font-size: 17px; letter-spacing: -.01em; }
.step-tick { flex: none; width: 22px; height: 22px; border-radius: 50%; border: 1px solid var(--line); color: transparent; display: flex; align-items: center; justify-content: center; font-size: 12px; transition: background .2s ease, color .2s ease, border-color .2s ease; }
.step.done .step-tick { background: var(--accent); border-color: var(--accent); color: var(--bg); }
.step-body { display: none; padding: 0 20px 18px; }
.step.open .step-body { display: block; animation: fadeUp .3s ease both; }
.step-d { font-family: var(--font-read); font-size: 14px; line-height: 1.8; margin: 0 0 14px; }
.step-meta { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.step-meta > div { background: var(--bg-2); border-radius: 5px; padding: 10px 12px; font-size: 12.5px; line-height: 1.65; }
.step-meta .label { display: block; margin-bottom: 5px; }
.steps-done { margin: 18px 0 0 44px; font-family: var(--font-read); font-size: 14px; color: var(--accent); }

/* 04 本体 → 对象 → 关系 → 数据 → Agent */
.arch-cap { display: flex; align-items: center; gap: 12px; padding: 15px 20px; border-radius: var(--r); background: var(--fg); color: var(--bg); font-family: var(--font-display); font-weight: 700; font-size: 16px; }
.arch-cap .label { color: var(--fg-dim-on-invert); }
/* 终点那枚用 --accent-fill 而不是 --accent：后者是「浅底上当文字用」的压深版，
   深色档里它反而变浅（#8875ff），白字压上去只有 1.6:1。--accent-fill 两档同值，
   白字才一直是 5.3:1。 */
.arch-cap.bot { background: var(--accent-fill); }
.arch-cap.bot .label { color: var(--fg-on-accent-dim); }
/* 连线：一竖线加一个箭头，把上下两层接起来 */
.arch-spine { width: 2px; height: 24px; margin-left: 36px; background: var(--line-strong); position: relative; }
.arch-spine::after { content: ''; position: absolute; left: 50%; bottom: -1px; transform: translateX(-50%); border-left: 5px solid transparent; border-right: 5px solid transparent; border-top: 7px solid var(--line-strong); }
.arch-layer { display: flex; align-items: center; gap: 18px; padding: 16px 20px; border: 1px solid var(--line); border-radius: var(--r); background: var(--panel); }
.arch-no { flex: none; width: 34px; font-family: var(--font-display); font-weight: 700; font-size: 26px; color: var(--accent); }
.arch-main { flex: 1 1 210px; min-width: 0; }
.arch-t { font-family: var(--font-display); font-weight: 700; font-size: 16px; }
.arch-d { font-size: 12.5px; color: var(--fg-2); margin-top: 5px; line-height: 1.6; }
.arch-chips { flex: 1 1 250px; display: flex; flex-wrap: wrap; gap: 6px; }
.arch-chips span { font-family: var(--font-mono); font-size: 10.5px; letter-spacing: .04em; border: 1px solid var(--line); border-radius: 4px; padding: 3px 8px; color: var(--fg-2); background: var(--bg); }

/* 05 行业切换 */
.tabs { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 26px; }
.tab-btn { font-family: var(--font-mono); font-size: 12.5px; font-weight: 600; padding: 9px 18px; border-radius: var(--r); border: 1px solid var(--line); background: var(--panel); color: var(--fg-2); cursor: pointer; transition: background .15s ease, color .15s ease, border-color .15s ease; }
.tab-btn:hover { border-color: var(--accent); color: var(--accent); }
/* 当前项与筛选行里的当前项同一套长相：紫描边 + 淡紫底 + 紫字。
   （导航那四个 tab 与分页当前页仍是 --fg 反色——那是另一种语境，别混。） */
.tab-btn.on { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); }
.tab-panels { margin-top: 18px; }
.tab-panel { display: none; }
.tab-panel.on { display: block; animation: fadeUp .35s ease both; }

/* 行业架构图（内联 SVG，跟着主题色走） */
.arch-svg { display: block; width: 100%; height: auto; }
/* 05 行业流程图：四列色相各不相同的圆角节点。
   每个盒子/圆点自己带一套 --c（色相），底下几条规则按它取色。
   这样加一列只要在 views/method.js 的 tone 里多写一个词、并在两个主题块里配一组 token，
   不必再来这里抄一遍 --svg-* 规则。
   --cbg 只有盒子用得上，但和 --c 写在同一条规则里，省得两个色相定义各写一次、日后漂开。 */
.svg-box.scene, .svg-dot.scene { --c: var(--svg-scene); --cbg: var(--svg-scene-bg); }
.svg-box.src,   .svg-dot.src   { --c: var(--svg-src);   --cbg: var(--svg-src-bg); }
.svg-box.mid,   .svg-dot.mid   { --c: var(--svg-mid);   --cbg: var(--svg-mid-bg); }
.svg-box.out,   .svg-dot.out   { --c: var(--svg-out);   --cbg: var(--svg-out-bg); }
.svg-box { fill: var(--cbg); stroke: var(--c); stroke-width: 1.6; }
/* 节点左端的状态点：取本列色相的实底。位置由 views/method.js 算，
   和文字的左内边距一起定义——改一个不改另一个就会重叠。 */
.svg-dot { fill: var(--c); }
.svg-t { font-family: var(--font-body); font-size: 12.5px; fill: var(--fg); }
.svg-h { font-family: var(--font-mono); font-size: 10px; letter-spacing: .14em; text-transform: uppercase; fill: var(--fg-3); }
/* 连线：虚线仍是虚线（那是刻意的——它画的是「这条路怎么走」，不是实体连接），
   但要比上一版重，四列各自上色之后线太细就读不出是连接了 */
.svg-link { fill: none; stroke: var(--svg-wire); stroke-width: 2; }
.svg-head { fill: var(--svg-wire); }
/* 连线跟着它**出发**那一列上色：x1 说了它是从哪一列出来的。
   写在这里而不是 method.js 的 stroke 属性，是因为颜色只允许住在 token 块里——
   theme 护栏扫的是这份样式表，method.js 里手写的 stroke="#…" 它看不见、会漏网。
   箭头仍是单色：marker 是 <defs> 里的一个图形，四列三段的箭头共用它，
   每张图只发 4 个 marker（见 tests/biz.test.js 那条「id 各不相同」）。 */
.svg-link[data-tone="scene"] { stroke: var(--svg-scene); }
.svg-link[data-tone="src"]   { stroke: var(--svg-src); }
.svg-link[data-tone="mid"]   { stroke: var(--svg-mid); }
/* 连接线：虚线曲线。dasharray 也写在 SVG 呈现属性上，CSS 挂了还是虚线 */
.svg-link { fill: none; stroke: var(--line-strong); stroke-width: 1.5; }
.svg-head { fill: var(--line-strong); }
/* 连线描边：进入视口时从左往右画出来 */
html.js .tab-panel.on .svg-arrow { stroke-dasharray: 260; stroke-dashoffset: 260; animation: drawLine .7s ease forwards; }
html.js .tab-panel.on .svg-arrow:nth-of-type(2) { animation-delay: .08s; }

/* 06 学习路径 */
.stages { list-style: none; margin: 30px 0 0; padding: 0; }
.stage { display: flex; gap: 20px; }
.stage-rail { flex: none; width: 40px; position: relative; display: flex; justify-content: center; }
.stage-rail i { width: 32px; height: 32px; border-radius: 50%; background: var(--fg); color: var(--bg); display: flex; align-items: center; justify-content: center; font-family: var(--font-mono); font-size: 13px; font-style: normal; position: relative; z-index: 1; }
.stage:not(:last-child) .stage-rail::after { content: ''; position: absolute; top: 32px; bottom: -26px; left: 50%; transform: translateX(-50%); width: 2px; background: var(--line-strong); }
.stage-card { flex: 1; min-width: 0; padding-bottom: 28px; }
.stage-top { display: flex; align-items: baseline; gap: 12px; }
.stage-name { font-family: var(--font-display); font-weight: 700; font-size: 21px; letter-spacing: -.015em; }
.stage-goal { font-family: var(--font-read); font-size: 14.5px; line-height: 1.8; margin: 10px 0 14px; }
.stage-facts { margin: 0; border: 1px solid var(--line); border-radius: var(--r); overflow: hidden; }
.stage-facts > div { display: flex; gap: 14px; padding: 10px 14px; border-top: 1px solid var(--line); }
.stage-facts > div:first-child { border-top: 0; }
.stage-facts dt { flex: none; width: 52px; padding-top: 2px; }
.stage-facts dd { margin: 0; font-size: 13.5px; line-height: 1.65; }
.stage-links { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
.stage-links a.card { padding: 6px 12px; font-family: var(--font-mono); font-size: 11.5px; }

/* 动画 */
@keyframes fadeUp { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
@keyframes drawLine { to { stroke-dashoffset: 0; } }
/* .reveal 只在 JS 可用时才隐藏——否则脚本挂了整页就白了 */
html.js .reveal { opacity: 0; transform: translateY(18px); }
html.js .reveal.in { opacity: 1; transform: none; transition: opacity .6s ease, transform .6s cubic-bezier(.2, .7, .3, 1); }

@media (max-width: 760px) {
  .forks, .cmp { grid-template-columns: 1fr; }
  .arch-spine { margin-left: 26px; }
}
@media (max-width: 640px) {
  .step-meta { grid-template-columns: 1fr; }
  .stage-facts > div { flex-direction: column; gap: 3px; }
}
@media (prefers-reduced-motion: reduce) {
  html.js .reveal { opacity: 1; transform: none; }
  html.js .reveal.in { transition: none; }
  html.js .tab-panel.on .svg-arrow { stroke-dasharray: none; stroke-dashoffset: 0; animation: none; }
  .tab-panel.on { animation: none; }
  /* 滚动虚线也停。⚠️ 只是**停住**，不要把 dasharray 去掉——那四条弧会变回实线，
     和辐条长得一样，「哪条是顺序」这件事就丢了。 */
  .fde-order, .fde-arc { animation: none; }
}

/* 认证等级徽章 */
.badge {
  display: inline-block; flex: none;
  font-family: var(--font-mono); font-size: 10px; letter-spacing: .08em;
  padding: 2px 9px; border-radius: 999px;
  border: 1px solid var(--accent); color: var(--accent);
}
.block.emphasis .badge { border-color: var(--accent-on-emphasis); color: var(--accent-on-emphasis); }
/* 强调带里的跨页入口：那一段的底不是 --panel，--line-strong 也不保证够亮，
   所以整组换成 *-on-emphasis 那四支（和 .lede / .btn.ghost 走的是同一套）。 */
.block.emphasis .enter > a {
  border-color: var(--line-on-emphasis); background: transparent; color: var(--emphasis-fg);
}
.block.emphasis .enter > a:hover { border-color: var(--accent-on-emphasis); color: var(--accent-on-emphasis); box-shadow: none; }
.block.emphasis .enter > a .n, .block.emphasis .enter > a .arw { color: var(--fg-dim-on-emphasis); }
.block.emphasis .enter > a:hover .n, .block.emphasis .enter > a:hover .arw { color: var(--accent-on-emphasis); }

/* 我的 · 档案页 */
.me-head { display: flex; gap: 18px; align-items: center; margin-top: 16px; flex-wrap: wrap; }
.me-head-main { flex: 1; min-width: 200px; }
.me-head-main h2 { margin: 0; }
.me-app { display: block; padding: 16px; }
.me-app-msg { font-family: var(--font-read); font-size: 13.5px; line-height: 1.7; margin-top: 10px; color: var(--fg); }
/* 认证进度：四项拆解 + 合计，右对齐的数字好对账。
   线原来靠 border-top + :first-child 去首行——那是宫格的手工版，行数一变就要重数。
   改成和 .cells 同一套：缝里透出容器底色。 */
.me-parts {
  margin-top: 22px; max-width: 520px;
  display: grid; gap: 1px;
  background: var(--line);
  border: 1px solid var(--line); border-radius: var(--r); overflow: hidden;
}
.me-part { display: flex; align-items: baseline; gap: 12px; padding: 11px 16px; background: var(--panel); }
.me-part-k { flex: 1; font-size: 13.5px; }
.me-part-calc { flex: none; font-family: var(--font-mono); font-size: 11px; color: var(--fg-3); letter-spacing: .06em; }
.me-part-v { flex: none; width: 62px; text-align: right; font-family: var(--font-display); font-weight: 700; font-size: 16px; }
.me-part.total { background: var(--bg-2); }
.me-part.total .me-part-k { font-family: var(--font-mono); font-size: 11px; letter-spacing: .14em; text-transform: uppercase; color: var(--fg-3); }
.me-part.total .me-part-v { color: var(--accent); }

/* ---------------- 首屏：左右双入口分屏 ---------------- */

/* 四个数字走宫格 + 大号数字（参考稿里数字是最抢眼的一层）。
   原来是四个内联的小字，排在标题下面，几乎看不见。
   2026-10-13：撤掉 max-width:640px，改成铺满整栏——和下面那些 .block 的正文同宽。
   顺带把字号与内边距按比例放大，不然一条 1130px 的带子里塞 26px 的字会显得很空。 */
.hero-stats {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px;
  margin-top: 30px;
}
/* ⚠️ 2026-10-15 起这四格是 `<a>` 不是 `<div>`（用户：「要增加点击数字可以点到对应的
   详情界面」）。选择器两边都写：**两条线页的页头用的是同一个组件**，那四格还没改成链接
   （`views/lines.js` 的 lineHead 仍出 `<div>`），只写 `> a` 的话线页那四格会**静默掉样式**
   ——没底色、没描边、没内边距。 */
.hero-stats > div, .hero-stats > a {
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--r);
  padding: 22px 24px; display: flex; flex-direction: column; gap: 8px; min-width: 0;
}
/* 链接的默认下划线与颜色要归零。悬停只动描边与标签色——数字本来就是强调色
   （`--accent`），再往它身上加「更亮」没有余地。 */
.hero-stats > a { text-decoration: none; color: inherit; transition: border-color .15s ease; }
.hero-stats > a:hover { border-color: var(--accent); }
.hero-stats > a:hover .k { color: var(--fg-2); }
.hero-stats .n { font-family: var(--font-display); font-size: 40px; font-weight: 700; line-height: 1; color: var(--accent); }
.hero-stats .k { font-family: var(--font-mono); font-size: 11px; letter-spacing: .1em; text-transform: uppercase; color: var(--fg-3); transition: color .15s ease; }
/* 窄屏四格并排时每格不到 90px，40px 的「1,251」会被宫格的 overflow:hidden 裁掉
   （实测 408px 下显示成「1,25」，标签也被挤成两行）。折成 2×2，字号收一档。 */
@media (max-width: 640px) {
  .hero-stats { grid-template-columns: repeat(2, 1fr); }
  .hero-stats > div, .hero-stats > a { padding: 18px 20px; }
  .hero-stats .n { font-size: 32px; }
}

.hero-switch { display: flex; align-items: center; gap: 8px; margin-top: 22px; }
.hero-switch .sw {
  font-family: var(--font-mono); font-size: 11.5px; letter-spacing: .06em;
  padding: 4px 13px; border: 1px solid var(--line-strong); border-radius: 999px;
  color: var(--fg-2); text-decoration: none;
}
.hero-switch .sw.on { border-color: var(--fg); background: var(--fg); color: var(--bg); }

.hero-split {
  position: relative; display: grid; grid-template-columns: 1fr 1fr;
  margin-top: 24px; border: 1px solid var(--line-strong); border-radius: var(--r);
  overflow: hidden; background: var(--bg);
}
/* 面板自己就是 flex 列并靠底对齐。别用「子元素 height:100%」——
   父元素只有 min-height、没有确定高度，子元素的 100% 会解析成 auto，
   justify-content 就白写了。 */
.hero-panel {
  position: relative; z-index: 1; display: flex; flex-direction: column; justify-content: flex-end;
  min-height: 54vh; border-right: 1px solid var(--line-strong); text-decoration: none; overflow: hidden;
  --scrim: var(--bg);
}
.hero-panel:last-child { border-right: 0; }
.hero-panel.on { background: var(--bg-2); --scrim: var(--bg-2); }

/* 静态降级层：没有 WebGL 时它就是画面本身。左边是网格（结构），
   右边是点阵（网络）——两种气质要一眼分得开。
   ⚠️ 2026-10-21：透明度原来分别是 .5 / .65，配 --line-strong（浅色档 #cfcfdc）
   叠在白底上**几乎看不见**，整块面板读出来就是「空的」——用户看到的就是这个，
   报的是「这里的动效丢了」。降级层的职责就是「没有 3D 时它顶上」，
   淡到看不见等于没有降级层。现在拉满，线色本身够轻，不会抢正文。
   ⚠️ 它只在 `.gl-live` 缺席时可见（`.hero-panel.gl-live .hero-gl-fallback` 那条会藏掉它），
   所以调它**不影响任何有 WebGL 的机器**。 */
.hero-gl-fallback {
  position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background:
    repeating-linear-gradient(0deg, transparent 0 23px, var(--line-strong) 23px 24px),
    repeating-linear-gradient(90deg, transparent 0 23px, var(--line-strong) 23px 24px);
  opacity: 1;
}
.hero-panel.me .hero-gl-fallback {
  background: radial-gradient(circle at 50% 45%, var(--line-strong) 0 1.5px, transparent 1.5px) 0 0/34px 34px;
  opacity: 1;
}
/* canvas 由 public/gl/hero.js 塞进**面板**里（不是塞进 [data-scene]）——
   这样它天然被 .hero-panel 的 overflow 裁住，尺寸也只跟这一半有关。
   两条规则的 [data-scene] > canvas 那条管不到它，得单独写。 */
.hero-panel > canvas {
  position: absolute; inset: 0; z-index: 0;
  display: block; width: 100%; height: 100%;
}
/* WebGL 挂上了就把静态层藏掉。⚠️ 判据在**面板**上而不是 .hero-split 上：
   左右两个效果是各自独立建场的，某一边建不起来时它得留着自己的降级层，
   挂在 .gl-on 上的话另一边失败会把这一边的图也一起藏掉，那一半就全空了。 */
.hero-panel.gl-live .hero-gl-fallback { display: none; }

/* 底部垫一层底色：3D 的网格线穿过文字会看不清，但又不该把画面整块盖住。
   ⚠️ 2026-10-20 用户：「这里的渐变需要再往上一些，可以让文字更明显一些」。
   原来渐变**只长在 `.hero-panel-copy` 里**（那一块高 179px），实底只到它 38% 处
   ——离面板底 68px；而标题「我是企业」的**顶端在离底 145px 处**，那里只剩 21% 的遮挡，
   网格线是直接压在字上的。
   现在拆成两段：文字那一块**整块实底**（分界线正好落在标题顶上），再靠 `::before`
   往上接一段长渐变。⚠️ 不能只把百分比调大——一个渐变同时管「字上要实」与
   「分界要缓」两件事，只能取其一：实底够高则分界硬，分界缓则字上还有线。 */
/* ⚠️ 2026-10-21 用户对着首屏那两块圈出 CTA：「也使用紫色按钮，圆头，靠对应的方格右侧」。
   ——「也」指的是「从哪开始」那两块（.hero-split--mini）：那里的 CTA 早就是右侧一枚
   实心紫药丸了。所以这次**把版式提到基础规则上**，两处从此同一套：左列 1fr 放
   标题 + 一句说明，右列 auto 放按钮、跨两行靠底对齐。
   下面那组 mini 规则因此**只覆盖尺寸**，不再覆盖版式——两处一旦长得不一样，
   「这就是首屏那个东西」这条线就断了，而那正是矮那一份存在的理由。
   ⚠️ 颜色全走站上既有的按钮 token（--accent-fill / --fg-on-accent / --accent-hover /
   --accent-glow），不新写色值——.btn 那一套就是这几支。 */
.hero-panel-copy {
  position: relative; z-index: 2; padding: 34px 30px 30px;
  background: var(--scrim);
  display: grid; grid-template-columns: 1fr auto; align-items: end; column-gap: 24px;
}
/* 往上接的那一段。高度跟着视口走——窄屏面板只有 40vh，固定 110px 会把画面盖掉八成。 */
.hero-panel-copy::before {
  content: ''; position: absolute; left: 0; right: 0; bottom: 100%;
  height: clamp(64px, 14vh, 110px);
  background: linear-gradient(to top, var(--scrim), transparent);
  pointer-events: none;
}
.hero-panel-who { grid-area: 1 / 1; font-family: var(--font-display); font-weight: 700; font-size: clamp(22px, 2.4vw, 30px); color: var(--fg); }
.hero-panel-want { grid-area: 2 / 1; font-size: 13.5px; color: var(--fg-2); margin-top: 7px; }
.hero-panel-cta {
  grid-area: 1 / 2 / 3 / 3; align-self: end; white-space: nowrap;
  padding: 12px 22px; border-radius: 999px;
  border: 1px solid var(--accent-fill);
  background: var(--accent-fill); color: var(--fg-on-accent);
  font-family: var(--font-mono); font-size: 12.5px; font-weight: 600;
  box-shadow: 0 6px 18px -6px var(--accent-glow);
  transition: transform .12s ease, background .15s ease, border-color .15s ease;
}
.hero-panel:hover .hero-panel-cta {
  background: var(--accent-hover); border-color: var(--accent-hover); transform: translateY(-1px);
}

/* 「从哪开始」那一段里的入口面板：**与首屏同一份标记，只是矮一半**。
   2026-10-21 用户：「不知道从哪下手，还是需要先确认用户的身份，下面给用户两个选项，
   我是企业主，我是个人，动效就用首页顶部的，这里再放一遍，但是高度要缩小一半，
   稍微展示即可，主要是入口识别」。
   首屏面板 54vh（窄屏 40vh），这里 27vh（窄屏 20vh）。
   ⚠️ 这一组**只覆盖尺寸**（高度、内边距、字号），版式与按钮样式全在基础规则里——
   两处一旦长得不一样，「这就是首屏那个东西」这条线就断了，而那正是这一块存在的理由。 */
.hero-split--mini { margin-top: 0; }
.hero-split--mini .hero-panel { min-height: 27vh; }
/* 2026-10-21 用户：「图片位置内容删除」——矮的那两块不要那张静态图案。
   它们的主意是「入口识别」，画面就该是那块动效本身；没有 WebGL 时宁可是干净的底，
   也不要一张看着像「动效坏了」的假图。
   ⚠️ 首屏那两块**保留**降级层：那里是大幅沉浸式分屏，没有 WebGL 时有一张静态图
   比一片空白强。两处的取舍不同，是有意的。 */
.hero-split--mini .hero-gl-fallback { display: none; }
/* 字也要收一档：27vh 里塞不下 30px 的标题 + 那两行说明 */
.hero-split--mini .hero-panel-copy { padding: 18px 22px 16px; column-gap: 16px; }
.hero-split--mini .hero-panel-who { font-size: clamp(17px, 1.7vw, 21px); }
.hero-split--mini .hero-panel-want { font-size: 12.5px; margin-top: 5px; }
.hero-split--mini .hero-panel-cta { padding: 10px 20px; font-size: 12px; }
/* ⚠️ 上面那段渐变**必须跟着收**。首屏那份是 `clamp(64px, 14vh, 110px)`，
   在 243px 的面板里它加文字块一共盖掉 210px——只剩三十几 px 露画面，
   动效等于没挂（实测过）。这里压到 `clamp(28px, 6vh, 48px)`，
   让矮面板里还能看见上半截。 */
.hero-split--mini .hero-panel-copy::before { height: clamp(28px, 6vh, 48px); }

/* ---------------- 首屏下面那支片子 ---------------- */
/* 2026-10-21 用户：「这个视频，放到项目里，放在『FDE 是什么，快速看懂这个岗位』这个模块上面，
   在我是企业我是个人下面」；随后又说「视频宽度和上下一样宽，一样距边」。
   所以这里**不再自己限宽**：`.wrap` 已经把内容列定在 `--maxw: 1180px` − 左右各 24px，
   首屏那两块面板、下面那七张卡都落在这个宽度里——视频跟着它走就是「一样宽、一样距边」。
   （原来写的 `max-width: 1000px; margin: 0 auto` 会比上下窄 132px，两侧各多出一截空白。）
   片子自己是 16:9 的深色画面（近黑底 + 橙/紫），所以这里只做容器的事：
   圆角 + 一道发丝线 + 一点紫辉光，让它读起来是「站上的一块」，而不是一段裸嵌的 iframe 感。
   ⚠️ `aspect-ratio` 与 HTML 上的 `width`/`height` 属性是**两件事**：属性负责在 CSS
   到位之前就占好位置（避免布局跳动），这条负责按比例定高。
   ⚠️ 底色走 `--bg`，不要改成 `--panel`：视频本身是黑的，`--panel` 在深色档下会在四周
   留出一圈比画面更亮的边（实测看着像描了个错误的框）。 */
.fde-video {
  display: block; width: 100%; height: auto; margin: 0;
  aspect-ratio: 16 / 9;
  /* ⚠️ 2026-10-23 用户：「所有内嵌视频需要圆角」。原来这里是 `--r`（6px）——
     在这块 1132px 宽的片子上根本看不出来，而且**暂停时那层遮罩是方的**
     （见 `.fde-video-wrap::after`），把本来那点圆角也盖平了。
     改用 `--r-lg`，与 `.chart` 那批大块同一档：站上「大块」的圆角语言就这一支。 */
  border: 1px solid var(--line); border-radius: var(--r-lg);
  background: var(--bg);
  box-shadow: 0 20px 50px -30px var(--accent-glow);
}
/* 片子上盖的两样：居中的播放键 + 右上角的时长（2026-10-21 用户：「右上角增加视频时长，
   视频中间增加播放按钮」）。原生控件给不出这两样——控件条在底部，整块画面上没有
   「按这里」的指示；时长要等元数据才知道。
   ⚠️ 两样都**挂在 `.js` 下面**：没有 JS 时它们按不动，摆在那儿就是两枚假按钮
   （与图谱页那三枚控制键同一条规矩，见 `.js .fde-gctl`）。
   ⚠️ 播放键是**一枚居中的圆**，不是铺满画面的遮罩——铺满的话会把底部那条原生控件条
   一起挡住，用户就按不到音量与全屏了。
   ⚠️ 播起来之后两样都撤（`.is-paused` 那个类由 app.js 跟着 play/pause 事件切），
   让画面干净；此时时间和进度由原生控件条负责。 */
.fde-video-wrap { position: relative; }
/* 暂停时压在画面上的一层：半透明黑 + 背景模糊
   （2026-10-21 用户：「视频上加一个透明度黑底，黑底下面做模糊处理」）。
   ⚠️ 颜色**不能写 `rgba(0,0,0,.55)`**——theme 那条护栏扫的是整份 app.css 里的颜色字面量。
   走 `color-mix` 把 `--scene-scrim`（它本来就是 rgba(0,0,0,.78)）兑淡，两档主题同一份。
   ⚠️ 这一层**必须也挂在 `.js` 下面**：它是「暂停时才盖」，而摘掉它的开关在 JS 手里；
   没 JS 时盖上来就永远揭不掉，播起来画面也是糊的。
   ⚠️ `pointer-events: none`——它是装饰，底下那条原生控件条（音量 / 全屏）还得按得到。
   ⚠️ 播放键与时长要 `z-index` 压过它：`::after` 是最后一个子盒，不抬的话按钮跟着一起糊。
   ⚠️ 用 `visibility` 而不是 `display`：`display` 换不来淡入淡出，而这一层一闪一灭很跳。
   ⚠️ **圆角必须自己写一份**：这一层是 `wrap` 的子盒，**不受 video 的 `border-radius` 裁剪**
      （`wrap` 没有 `overflow: hidden`——加了会把 video 那道紫辉光一起裁掉）。
      `inset: 0` 铺满 video 的 border-box，方角正好填掉 video 的四个圆角，
      于是**暂停时整块看着是方的**（2026-10-23 用户就是看到这个才说「所有内嵌视频需要圆角」）。
      ⚠️ 四个角都要给：底部让位 56px，那一条**底边是看得见的**，它的两个角也在画面里。 */
.fde-video-wrap::after {
  content: ''; position: absolute; inset: 0;
  border-radius: var(--r-lg);
  /* ⚠️ 底部**让出一条**：原生控件条就长在那儿，不让的话这一层会把它一起糊掉，
     音量 / 全屏 / 时间全糊成一团（第一版就是这样，截图里一眼看得出）。
     56px 是量出来的：1132×630 的片子上，控件条的播放键顶边离画面底 56px。
     ⚠️ 窄屏那条把值收小——Chrome 给小片子用的是更矮的一条控件条，
     照 56 让位会在手机上白让掉画面三成。 */
  bottom: 56px;
  background: color-mix(in srgb, var(--scene-scrim) 55%, transparent);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  pointer-events: none;
  opacity: 0; visibility: hidden;
  transition: opacity .22s ease, visibility 0s .22s;
}
.js .fde-video-wrap.is-paused::after {
  opacity: 1; visibility: visible;
  transition: opacity .22s ease;
}
.fde-video-play {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  z-index: 1;
  width: 78px; height: 78px; padding: 0; border: 0; border-radius: 999px;
  place-items: center; cursor: pointer;
  background: var(--accent-fill); color: var(--fg-on-accent);
  box-shadow: 0 10px 34px -10px var(--accent-glow);
  transition: background .15s ease, transform .14s ease;
}
.fde-video-play svg { width: 36px; height: 36px; fill: currentColor; margin-left: 4px; }
.fde-video-play:hover { background: var(--accent-hover); transform: translate(-50%, -50%) scale(1.06); }
.fde-video-play:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.fde-video-len {
  position: absolute; top: 12px; right: 12px;
  z-index: 1;   /* 压过暂停时那层模糊，否则它自己也糊了 */
  padding: 3px 9px; border-radius: 999px;
  font-family: var(--font-mono); font-size: 11.5px; letter-spacing: .04em;
  background: var(--scene-scrim); color: var(--scene-ink);
  pointer-events: none;
}
/* ⚠️ 这两条要写在 `.js` 那条**后面**，且选择器权重更高——
   否则「暂停时才显示」会被「有 JS 就显示」压过去。 */
.js .fde-video-play { display: grid; }
.js .fde-video-wrap:not(.is-paused) .fde-video-play { display: none; }
/* ⚠️ `[hidden]` 那条 UA 样式打不过作者写的 `display`，所以这里得自己写一遍：
   默认 none、JS 把 hidden 摘掉之后才 block（时长要等元数据，先占位会闪一下空药丸）。 */
.fde-video-len { display: none; }
.js .fde-video-len:not([hidden]) { display: block; }
.js .fde-video-wrap:not(.is-paused) .fde-video-len { display: none; }
/* 窄屏：Chrome 给矮片子换一条更矮的控件条，56px 的让位会白让掉画面三成 */
@media (max-width: 700px) {
  .fde-video-wrap::after { bottom: 40px; }
}

/* ---------------- 线的页头（/企业 · /个人） ---------------- */

/* 版式与 .hero 同源：同样的报头节奏、同样的数字带、同样的标题阶梯。
   唯一的不同是**右侧是图不是 canvas** —— 这两页上没有任何 [data-scene]，
   所以 gl/main.js 根本不会挂载，three.js 一个字节都不下。
   换句话说：「进去之后顶部没有动效框」不是靠一个开关关掉的，是这里没有那个属性。 */
.line-head {
  display: grid; grid-template-columns: minmax(0, 1.12fr) minmax(0, .88fr);
  gap: 44px; align-items: center; padding: 52px 0 44px;
}
.line-head-copy { min-width: 0; }
.line-head-copy .kicker {
  font-family: var(--font-mono); font-size: 11px; letter-spacing: .22em; text-transform: uppercase;
  color: var(--accent); display: flex; align-items: center; gap: 12px;
}
.line-head-copy .kicker::before { content: ''; width: 30px; height: 1px; background: var(--accent); display: inline-block; flex: none; }
.line-head-copy h1 {
  font-family: var(--font-display); font-weight: 700;
  font-size: var(--fs-h1); line-height: 1.1; letter-spacing: -.025em; margin: 22px 0 0;
}
.line-head-copy h1 .l2 { color: var(--accent); }
.line-head-copy .lede {
  font-family: var(--font-read); font-size: var(--fs-lede); line-height: 1.78;
  color: var(--fg-2); max-width: 44ch; margin: 20px 0 0;
}
/* 线页的报头里那条数字带铺满左栏（base 现在本来就是满宽，这里只需要调间距） */
.line-head-copy .hero-stats { margin-top: 26px; }
.line-head-copy .cta { margin-top: 30px; }

/* 配图是**正文的一部分**，不是装饰：gpt-image-2 生成的透明底 PNG，
   深色档下直接叠在 --panel 上，不需要做白底处理。
   ⚠️ height 必须写死 auto —— `<img width height>` 是表现性提示，
   CSS 不给 height 时它会顶掉 aspect-ratio，窄屏上竖着裁掉一半。 */
.line-fig { margin: 0; }
/* ⚠️ 2026-10-15 起企业线这张图底下多一层 canvas（`data-scene="topography"`，
   2026-10-16 从 Aurora 换成线稿、同日又从线稿格网换成等高线地形）。
   两者是叠着的：canvas 走 [data-scene] > canvas 那条（inset:0 绝对定位、z-index 0），
   所以 img 要抬到它上面。图**留在文档流里撑高度**——canvas 是绝对定位的，撑不出高度，
   图一 display:none 这一格就塌成 0。WebGL 起来之后藏的是可见性不是占位。 */
.line-fig img { position: relative; z-index: 1; display: block; width: 100%; height: auto; }
/* ⚠️ 两条一起看：`.gl` 是 layout.js 那个**内联探针在首次绘制前**加上的，`.gl-on` 是
   gl/core.js 把 canvas 建起来之后加的。为什么要两条：
   图上这一格是「静态图 + canvas 叠着」，图是 eager + 缓存命中，**会先画一帧**——
   等 gl-on 再藏就晚了，用户先看到的是那幅旧插画闪一下，然后才换成等高线
   （2026-10-22 用户报「进入企业详情，右上角的图片还是会缓存出来」）。
   探针提前把图按住，图就不会出现在第一帧里。
   ⚠️ 都用 `visibility` 不用 `display`：这一格的高度是图撑的，去掉占位整格会塌成 0。 */
.gl .line-fig img,
.line-fig.gl-on img { visibility: hidden; }

/* ⚠️ 个人线页头那一格**没有静态图**（2026-10-16 用户要求把这一页三张配图全删掉），
   所以它自己撑高度：比例取原来那张 me.webp 的 1200×649，版式不会跳。
   ⚠️ 别把这条并进 `.line-fig`——企业线那一格的高度是 <img> 给的，两边各管各的。
   底纹是**降级层**：没有 WebGL 时这一格不是一块空白，还是一张线稿。 */
.line-fig--stage {
  aspect-ratio: 1200 / 649;
  background:
    repeating-linear-gradient(115deg, var(--line) 0 1px, transparent 1px 9px),
    var(--bg-2);
}

@media (max-width: 900px) {
  .line-head { grid-template-columns: 1fr; gap: 30px; padding: 42px 0 34px; }
  .line-head-copy h1 { font-size: var(--fs-h2); }
}

/* ---------------- WebGL 画布容器 ---------------- */

/* canvas 由 public/gl/core.js 塞进 [data-scene] 里。绝对定位让它脱离
   文档流——首屏的 [data-scene] 是个 grid，canvas 若参与布局会把网格挤成三列。 */
[data-scene] { position: relative; }
[data-scene] > canvas {
  position: absolute; inset: 0; z-index: 0;
  display: block; width: 100%; height: 100%;
}

@media (max-width: 820px) {
  .hero-split { grid-template-columns: 1fr; }
  .hero-panel { min-height: 40vh; border-right: 0; border-bottom: 1px solid var(--line-strong); }
  .hero-panel:last-child { border-bottom: 0; }
  /* 矮的那一份跟着折成一列，高度也折半——不然窄屏上它比首屏还高 */
  .hero-split--mini .hero-panel { min-height: 20vh; }
}

/* 窄屏整块只有四百多 px，标题和按钮并排会挤——按钮落回下一行、靠左。
   ⚠️ 这条对首屏与矮的那一份**都生效**（版式与按钮都在基础规则里），
   所以这里不再各写一份；写两份的话改一处就会漏一处。 */
@media (max-width: 560px) {
  .hero-panel-copy { grid-template-columns: 1fr; }
  .hero-panel-cta { grid-area: 3 / 1; justify-self: start; margin-top: 12px; }
}

/* ---------------- 五层架构：3D 层堆叠 + 文字索引 ---------------- */
/* 索引不是配图说明——它是正文，也是 3D 挂不上时的降级层 */
/* ⚠️ 这里**不写 `align-items`**（默认 stretch）：左边那块 3D 舞台要跟右边那摞
   索引卡片一样高，否则 430px 的框旁边杵着 791px 的索引，底下空一大片。
   ⚠️ 代价是画框比例会从约 1.45:1 变成 0.79:1，而 `PerspectiveCamera` 的 fov 是
   **竖向**的——画框一变竖，横向视野跟着窄，五张纸片两边的角会被切掉。
   所以 `gl/arch.js` 的 `resize()` 会按八个角重算相机距离，见那边的注释。 */
.arch3d { display: grid; grid-template-columns: 1.3fr 1fr; gap: 26px; margin-top: 28px; }
/* min-height 只是个下限：单列那档（≤900px）用得上，两列时由行高说了算 */
.arch3d-stage {
  position: relative; min-height: 430px; border: 1px solid var(--line);
  border-radius: var(--r); background: var(--panel); overflow: hidden; touch-action: none;
}
.arch3d-stage canvas { cursor: grab; }
.arch3d-stage canvas:active { cursor: grabbing; }
.arch-index { min-width: 0; }
.arch-index .arch-layer { cursor: pointer; transition: border-color .15s ease; }
.arch-index .arch-layer:hover { border-color: var(--fg); }

@media (max-width: 900px) {
  .arch3d { grid-template-columns: 1fr; }
  .arch3d-stage { min-height: 330px; }
}

/* ---------------- 行业四场景：三列图 + 就地展开的操作台 ---------------- */
/* ⚠️ 2026-10-15 撤掉了这一段顶上的 3D 舞台（`.ind3d-stage`，680×300 的框里跑
   `data-scene="industry"`）——用户「这里去掉」。它画的是四组体素方块（`SPECS.mfg`
   五个方块排一行、一条 `--accent-fill` 的线横穿），跟下面那张 SVG 讲的是同一件事，
   而方块排布读不出行业差异（制造五个方块 / 零售三条横板，肉眼分不出谁是谁）。
   撤掉之后这一段直接从 tabs 开始，`.tabs` 自带 26px 上边距、不用再补。 */

/* ---------------- 企业链 ①：四级下钻 ---------------- */
.funnel-wrap { margin-top: 26px; }
.funnel { display: grid; grid-template-columns: 300px 1fr; gap: 26px; align-items: start; }

.funnel-companies { list-style: none; margin: 0; padding: 0; }
.funnel-co {
  display: block; width: 100%; text-align: left; cursor: pointer;
  border: 1px solid var(--line); border-radius: var(--r); background: var(--panel);
  padding: 12px 14px; margin-bottom: 8px; font: inherit; color: inherit;
  transition: border-color .15s ease, background .15s ease;
}
.funnel-co:hover { border-color: var(--fg); }
.funnel-co.on { border-color: var(--accent); background: var(--bg-2); }
.funnel-co-name { display: block; font-family: var(--font-display); font-weight: 700; font-size: 14.5px; }
.funnel-co .label { display: block; margin-top: 5px; }

.funnel-detail-panel { display: none; }
.funnel-detail-panel.on { display: block; animation: fadeUp .3s ease both; }
.funnel-row { border-left: 2px solid var(--line-strong); padding: 2px 0 2px 16px; margin-bottom: 20px; }
.funnel-scene { font-family: var(--font-display); font-weight: 700; font-size: 22px; margin-top: 6px; }
.funnel-problem { font-family: var(--font-read); font-size: 15px; line-height: 1.8; color: var(--fg); margin: 10px 0 0; }
.funnel-practice { font-family: var(--font-read); font-size: 15px; line-height: 1.8; color: var(--fg-2); margin: 8px 0 0; }
.funnel-results { display: flex; gap: 26px; flex-wrap: wrap; margin-bottom: 20px; }
.funnel-results b { display: block; font-family: var(--font-display); font-weight: 700; font-size: 30px; color: var(--accent); line-height: 1; }
.funnel-results .label { display: block; margin-top: 7px; }
.funnel-abil { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.funnel-abil .label { margin-right: 4px; }
.funnel-abil a { padding: 5px 11px; font-size: 12px; }

/* ---------------- 企业链 ②：谁来干 ---------------- */
.role-lede { font-family: var(--font-read); font-size: 15px; line-height: 1.8; color: var(--fg-2); margin: 24px 0 0; }
.role-lede b { color: var(--fg); }
.role-card { padding: 16px 18px; }
/* 宫格里描边被抹掉了，所以「主角」这一格改用底色来标（--accent-soft） */
.role-card.lead { background: var(--accent-soft); }
.role-top { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; }
.role-name { font-family: var(--font-display); font-weight: 700; font-size: 16px; }
.role-card.lead .role-name { color: var(--accent); }
.role-job { font-size: 12.5px; color: var(--fg-2); line-height: 1.7; margin: 9px 0 12px; }

/* ---------------- 五步现场：五格连环画 ----------------
   舞台与正文同宽（不 max-width + auto 居中），四边距和上下段落一致。
   每格上面一张实景照片、下面两行字。照片是 <img> 而不是 canvas 贴图：
   贴图没有 WebGL 就整块没了，<img> 跟文字一样离线也在。
   五张都是亮调大留白；未选中的压一点饱和度，选中/悬停回满——
   一屏里同时只有一格是「亮」的，眼睛知道该看哪儿。 */
.pipeline-stage {
  margin: 26px 0 0;
  background: var(--line);
  border: 1px solid var(--line); border-radius: var(--r);
  overflow: hidden;
}
.strip-cells {
  margin: 0; padding: 0; list-style: none;
  display: grid; grid-template-columns: repeat(5, 1fr); gap: 1px;
}
/* 五格也是宫格：线由容器的缝来画，不再给每格画 border-right */
.strip-cell {
  display: flex; flex-direction: column; cursor: pointer;
  background: var(--panel);
  transition: background .18s ease;
}
.strip-cell:hover { background: var(--bg-2); }
.strip-cell.on { background: var(--accent-soft); }

/* 高按 640:446 由宽度算出来——照片源文件就是这个比例，于是 cover 一格都不裁。
   写死高度（152px）在窄屏会变成 110×128 的竖裁，横向裁掉四成。
   `height: auto` 不能省：标签上的 height="446" 是「表现性提示」，CSS 不显式给高度时
   它就会生效，于是图片按 446px 高渲染（实测过，aspect-ratio 被它盖掉）。 */
.sc-img {
  display: block; flex: none; width: 100%; height: auto; aspect-ratio: 640 / 446;
  object-fit: cover; background: var(--bg-2);
  filter: saturate(.55) contrast(.97);
  transition: filter .3s ease;
}
.strip-cell:hover .sc-img,
.strip-cell.on .sc-img { filter: none; }

.sc-foot { flex: 1; display: flex; flex-direction: column; gap: 3px; padding: 12px 14px 14px; }
.sc-t { font-family: var(--font-display); font-weight: 700; font-size: 15px; letter-spacing: -.01em; }
.sc-t b {
  font-family: var(--font-mono); font-size: 11px; font-weight: 600;
  letter-spacing: .1em; color: var(--fg-3); margin-right: 7px;
}
.strip-cell.on .sc-t,
.strip-cell.on .sc-t b { color: var(--accent); }
.sc-o { font-family: var(--font-mono); font-size: 10px; letter-spacing: .04em; color: var(--fg-3); }

/* 窄屏：五格挤到 ~110px 一列，产出物那行就先收起来，保住短名 */
@media (max-width: 820px) {
  .sc-foot { padding: 10px 8px 12px; }
  .sc-t { font-size: 13.5px; }
  .sc-t b { margin-right: 5px; }
}
@media (max-width: 640px) {
  .sc-o { display: none; }
}

/* ---------------- 个人线 ---------------- */
.ladder-list { list-style: none; margin: 26px 0 0; padding: 0; }
.ladder { display: flex; }
.ladder-card { flex: 1; min-width: 0; border: 1px solid var(--line); border-radius: var(--r);
  background: var(--panel); padding: 14px 18px; margin-bottom: 10px; }
.ladder-top { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; }
.ladder-name { font-family: var(--font-display); font-weight: 700; font-size: 17px; }
.ladder-sum { font-size: 12.5px; color: var(--fg-2); line-height: 1.7; margin: 7px 0 0; }

.band-lede { font-family: var(--font-read); font-size: 15px; line-height: 1.8; color: var(--fg-2); margin: 24px 0 0; }
.bands { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; margin-top: 20px; }
.band { border: 1px solid var(--line); border-radius: var(--r); background: var(--panel); padding: 16px 18px; }
.band.top { border-color: var(--accent); background: var(--bg-2); }
.band-top { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; }
.band-name { font-family: var(--font-display); font-weight: 700; font-size: 16px; }
.band.top .band-name { color: var(--accent); }
.band-n { margin-top: 14px; }
.band-n b { font-family: var(--font-display); font-weight: 700; font-size: 30px; line-height: 1; }
.band-n .label { display: block; margin-top: 6px; }

.me-list { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin-top: 26px; }
.peers { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 10px; margin-top: 26px; }
.peer { display: block; border: 1px solid var(--line); border-radius: var(--r); background: var(--panel); padding: 12px 14px; }
.peer:hover { border-color: var(--fg); }
.peer-name { display: block; font-family: var(--font-display); font-weight: 700; font-size: 14.5px; }
.peer .label { display: block; margin-top: 5px; }

/* 宫格：形状由上面的 .grid2 宫格块给，这里没有别的了 */

@media (max-width: 900px) {
  .bands { grid-template-columns: 1fr 1fr; }
  .me-list { grid-template-columns: 1fr; }
}
@media (max-width: 820px) {
  .funnel { grid-template-columns: 1fr; }
  .grid2 { grid-template-columns: 1fr; }
}

/* ---- 05 段：可点的行业流程图 + 展开的操作台 ---- */
/* 整行 hover：四列各自亮一档，不是全涂成紫——那是上一版「全站紫」的残留，
   现在四列各有色相，涂成紫反而把「哪一列是什么」这条信息抹掉了。 */
.arch-row { cursor: pointer; }
/* 不用 transition: filter —— 从 none 到 drop-shadow 之间没有可插值的中间态，
   浏览器会直接跳变；只给描边宽度做过渡就够了。 */
.arch-row .svg-box { transition: stroke-width .15s ease; }
.arch-row:hover .svg-box { stroke-width: 2.4; }
.arch-row:hover .svg-box.scene { filter: drop-shadow(0 0 6px var(--svg-scene-glow)); }
.arch-row:hover .svg-box.src   { filter: drop-shadow(0 0 6px var(--svg-src-glow)); }
.arch-row:hover .svg-box.mid   { filter: drop-shadow(0 0 6px var(--svg-mid-glow)); }
.arch-row:hover .svg-box.out   { filter: drop-shadow(0 0 6px var(--svg-out-glow)); }
.arch-row:hover .svg-t { fill: var(--fg); }
.arch-cue { font-family: var(--font-mono); font-size: 11px; fill: var(--fg-3); }
.arch-row:hover .arch-cue { fill: var(--accent); }
/* ⚠️ 这处辉光是唯一被允许的：它在一张独立的内联 SVG 里，不在宫格中。
   宫格（.grid2/.grid3/.cells）里的格子依旧不许有辉光与浮起——那会把 1px 的缝顶开，
   这条不因为「参考稿的卡片有辉光」而放开。 */

.ops { margin-top: 22px; border: 1px solid var(--line); border-radius: var(--r); overflow: hidden; background: var(--panel); }
.ops[hidden] { display: none; }
.ops-head { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; padding: 12px 16px; border-bottom: 1px solid var(--line); }
.ops-title { font-size: 14px; font-weight: 600; color: var(--fg); }
.ops-title span { color: var(--fg-2); font-weight: 400; }
.ops-readout { font-family: var(--font-mono); font-size: 10px; letter-spacing: .06em; color: var(--fg-3); }
.ops-views { display: flex; gap: 4px; margin-left: auto; }
.ops-view, .ops-btn, .ops-bp {
  font-family: var(--font-body); font-size: 12px; color: var(--fg-2);
  background: none; border: 1px solid var(--line); border-radius: 999px;
  padding: 4px 12px; cursor: pointer;
}
.ops-view.on { color: var(--panel); background: var(--accent); border-color: var(--accent); }
.ops-view:hover, .ops-btn:hover, .ops-bp:hover { border-color: var(--accent); color: var(--accent); }
.ops-view.on:hover { color: var(--panel); }

.ops-bps { display: flex; gap: 6px; flex-wrap: wrap; padding: 10px 16px; border-bottom: 1px solid var(--line); background: var(--bg-2); }
.ops-bp.on { color: var(--panel); background: var(--fg); border-color: var(--fg); }
.ops-bp.on:hover { color: var(--panel); }

.ops-body { display: flex; align-items: stretch; }
.ops-vp {
  flex: 1 1 auto; min-width: 0; height: 540px; overflow: hidden; position: relative;
  cursor: grab; background: var(--bg-2); user-select: none; touch-action: none;
  background-image: radial-gradient(var(--line-strong) 1px, transparent 1px);
  background-size: 22px 22px;
}
.ops-vp:active { cursor: grabbing; }
.ops-canvas { position: absolute; left: 0; top: 0; transform-origin: 0 0; will-change: transform; }
.ops-svg { display: block; }
.ops-legend { padding: 9px 16px; border-top: 1px solid var(--line); }

.ops-rail { flex: 0 0 302px; border-left: 1px solid var(--line); max-height: 540px; overflow-y: auto; }
/* 收起右栏。⚠️ 必须用 display:none 而不是宽度归零：`.ops-vp` 的宽度是
   fitView() 现读 clientWidth 算出来的，display:none 会让它在同一帧里就变宽，
   收起之后画布能立刻重新适应；改 width 则要等下一帧，量到的还是旧宽度。 */
.ops-body.rail-off .ops-rail { display: none; }
.ops-agents { display: flex; gap: 6px; flex-wrap: wrap; padding: 10px 12px 0; }
.ops-agent { font-family: var(--font-body); font-size: 11.5px; color: var(--fg-2); background: none; border: 1px solid var(--line); border-radius: 999px; padding: 3px 10px; cursor: pointer; }
.ops-agent.on { color: var(--panel); background: var(--accent); border-color: var(--accent); }
.ops-agent-head { padding: 12px 12px 10px; border-bottom: 1px solid var(--line); }
.ops-agent-head b { font-size: 13px; }
.ops-agent-head p { margin: 6px 0 0; font-size: 12px; line-height: 1.6; color: var(--fg-2); }
.ops-module { border-bottom: 1px solid var(--line); }
.ops-module-head { display: flex; align-items: center; gap: 8px; width: 100%; text-align: left; background: none; border: 0; padding: 10px 12px; cursor: pointer; font-family: var(--font-body); }
.ops-caret { font-size: 10px; color: var(--fg-3); transition: transform .15s ease; }
.ops-module.open .ops-caret { transform: rotate(90deg); }
.ops-mt { font-size: 12px; font-weight: 600; color: var(--fg-2); }
.ops-hint { margin-left: auto; font-family: var(--font-mono); font-size: 10px; color: var(--fg-3); }
.ops-module-body { padding: 0 12px 12px; }
.ops-label { font-family: var(--font-mono); font-size: 9.5px; letter-spacing: .12em; color: var(--fg-3); margin: 8px 0 4px; }
.ops-pre { margin: 0; padding: 8px 10px; background: var(--bg-2); border: 1px solid var(--line-strong); border-radius: 6px; font-family: var(--font-mono); font-size: 11px; line-height: 1.6; color: var(--fg); white-space: pre-wrap; word-break: break-word; }
.ops-row { display: flex; gap: 10px; padding: 5px 0; border-bottom: 1px dashed var(--line-strong); font-size: 12px; }
.ops-row:last-child { border-bottom: 0; }
.ops-k { flex: 0 0 40%; color: var(--fg-2); }
.ops-v { flex: 1 1 auto; color: var(--fg); }
.ops-p { margin: 4px 0; font-size: 12px; line-height: 1.65; color: var(--fg-2); }
.ops-empty { margin: 4px 0; font-size: 12px; color: var(--fg-3); }

/* 画布元素 */
.ops-box { fill: var(--panel); stroke: var(--line-strong); stroke-width: 1.2; }
.ops-node.obj { fill: var(--panel); stroke-width: 1.4; }
.ops-node.agent { fill: var(--bg-2); stroke-width: 1.4; }
.ops-node.pill { fill: var(--bg-2); stroke-width: 1; }
.ops-node.wf { fill: var(--panel); stroke-width: 1.4; }
.ops-node.lane { fill: none; stroke: var(--line-strong); stroke-dasharray: 4 4; }
.ops-node.inst { fill: var(--panel); stroke: var(--line-strong); stroke-width: 1.1; }

/* 类目色。`--c` 由节点组上的 `k-<kind>` 定，卡片描边、图标、左侧那道色条都读它
   ——同一个类目在图上只有一个颜色来源。
   ⚠️ `--c` 定在 `.ops-el`（那个 `<g>`）上而不是 `<rect>` 上：色条是 rect 的**兄弟**
      节点，挂在 rect 上它读不到（症状是色条整条不见，而且不报错）。
   ⚠️ 下面 `var(--c, …)` 的**兜底值不能省**：链路图/泳道/实例没有 `k-<kind>`，
      `--c` 是空的，少了兜底描边会退成 none —— 卡片只剩白底，同样不报错。 */
.ops-el.k-object { --c: var(--ops-object); }
.ops-el.k-action { --c: var(--ops-action); }
.ops-el.k-event  { --c: var(--ops-event); }
.ops-el.k-agent  { --c: var(--ops-agent); }
.ops-el.k-orphan { --c: var(--ops-orphan); }
.ops-node.obj, .ops-node.agent, .ops-node.pill {
  stroke: var(--c, var(--line-strong)); stroke-width: 1.6;
  fill: color-mix(in srgb, var(--c, var(--panel)) 10%, var(--panel));
}
.ops-ic { fill: var(--c, var(--fg-2)); }
.ops-bar { fill: var(--c, var(--line-strong)); }
.ops-rule { stroke: var(--line); stroke-width: 1; }

.ops-t { font-family: var(--font-body); }
/* 卡片骨架：图标 + 标题 → 细线 → 键值表，照参考项目（D:\OS\FDE）那张卡。
   字号与行高对着它的 CSS 取（标题 12、键值表 12/16），卡片宽度两边都是 208。 */
.obj-title, .agent-title, .inst-title, .wf-title { font-size: 12px; font-weight: 600; fill: var(--fg); }
.pill-title { font-size: 11px; fill: var(--fg); }
.pill-tag { font-size: 10px; fill: var(--fg-3); text-anchor: end; }
.ops-kv-k { font-size: 11.5px; fill: var(--fg-3); }
.ops-kv-v { font-size: 11.5px; fill: var(--fg-2); }
.ops-band { font-family: var(--font-mono); font-size: 10px; letter-spacing: .14em; fill: var(--fg-3); }
.ops-band-hint { font-size: 10px; fill: var(--fg-3); }
.lane-title { font-size: 12.5px; font-weight: 600; fill: var(--fg); }
.lane-hint { font-size: 10.5px; fill: var(--fg-3); }
/* 流程图的类型标签：中性底 + 分类色文字（颜色由 render.mjs 内联给）。
   以前是「分类色实底 + 白字」，白字在那些色上只有 1.5~2:1，等于读不出来。
   2026-10-15 起它从右上角挪到细线下面（参考项目就是那个位置），所以左对齐。 */
.ops-wf-tag { fill: var(--bg-2); stroke: var(--line); stroke-width: 1; }
.ops-wf-tag-t { font-size: 10.5px; fill: var(--fg-2); text-anchor: start; }
.ops-edge { fill: none; stroke-width: 1.2; }
.ops-edge.link { stroke: var(--line-strong); stroke-dasharray: 5 4; }
/* bind 是「Agent 绑到某个对象上」，跟 agent 节点同色——它本来就在说同一件事。 */
.ops-edge.bind { stroke: var(--ops-agent); stroke-dasharray: 3 4; opacity: .55; }
.ops-edge.sat { stroke: var(--line-strong); stroke-dasharray: 2 3; }
.ops-edge.wf { stroke-width: 1.4; }
/* 数据图的关联线。⚠️ 这里**必须写 stroke**：`.ops-edge` 只给了 stroke-width，
   不写的话 SVG 的默认 stroke 是 none，三十多条线全在 DOM 里却一条都画不出来
   ——链路图没事只是因为它每条边都内联了 `style="stroke:…"` 把缺失盖过去了。
   ⚠️ 颜色取 `--fg-3` 而不是 `--line-strong`：后者本身就是描边用的浅灰，
   再乘 30% 的不透明度，合成出来约 `rgb(240,240,245)`——等于没画。 */
.ops-edge.inst { stroke: var(--fg-3); opacity: .45; }
/* 选中某条实例时，跟它相连的那几条恢复不透明并带出关系名，其余继续压着：
   「路径」是选中之后才完整显现的。 */
.ops-edge.inst.on { stroke: var(--ops-object); opacity: .95; }
/* 关系名压在线的中点上，所以要一圈面板色的描边当底，否则字和线糊在一起。
   `paint-order: stroke` 让描边画在字的**下面**——不写它字会被自己那圈描边吃掉。 */
.ops-inst-note {
  font-size: 9.5px; fill: var(--fg-3); text-anchor: middle;
  paint-order: stroke; stroke: var(--panel); stroke-width: 3px; stroke-linejoin: round;
}
.ops-edge-label { font-family: var(--font-mono); font-size: 10px; fill: var(--fg-3); }
.ops-arrow-head { fill: var(--line-strong); }
.ops-arrow-head.agent { fill: var(--ops-agent); }
.ops-el { cursor: pointer; }
.ops-el.dim { opacity: .13; }
.ops-lane-g { cursor: default; }

@media (max-width: 900px) {
  .ops-body { flex-direction: column; }
  .ops-rail { flex: 1 1 auto; border-left: 0; border-top: 1px solid var(--line); max-height: none; }
  .ops-vp { height: 340px; }
}

/* ---------------------------------------------------------------- 53 客服 */
.customer-service-modal {
  position: fixed;
  inset: 0;
  z-index: 10000010;
  visibility: hidden;
  opacity: 0;
  pointer-events: none;
  transition: opacity .2s ease, visibility .2s ease;
}
.customer-service-modal.is-open {
  visibility: visible;
  opacity: 1;
  pointer-events: auto;
}
.customer-service-backdrop {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  padding: 0;
  border: 0;
  background: color-mix(in srgb, var(--fg) 58%, transparent);
  cursor: default;
}
.customer-service-dialog {
  position: absolute;
  top: 50%;
  left: 50%;
  width: min(800px, calc(100vw - 48px));
  height: min(600px, calc(100vh - 48px));
  overflow: hidden;
  border: 1px solid var(--line-strong);
  border-radius: 10px;
  background: var(--panel);
  box-shadow: 0 24px 64px color-mix(in srgb, var(--fg) 32%, transparent);
  transform: translate(-50%, -48%);
  transition: transform .2s ease;
}
.customer-service-modal.is-open .customer-service-dialog {
  transform: translate(-50%, -50%);
}
.customer-service-dialog iframe {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
}
.customer-service-close {
  position: absolute;
  top: 10px;
  right: 10px;
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  padding: 0;
  border: 1px solid var(--line-strong);
  border-radius: 50%;
  background: color-mix(in srgb, var(--panel) 94%, transparent);
  color: var(--fg-2);
  cursor: pointer;
  box-shadow: 0 2px 8px color-mix(in srgb, var(--fg) 18%, transparent);
}
.customer-service-close:hover,
.customer-service-close:focus-visible {
  color: var(--accent);
  border-color: var(--accent);
  outline: none;
}
@media (max-width: 640px) {
  .customer-service-dialog {
    width: 100vw;
    height: 100dvh;
    border: 0;
    border-radius: 0;
  }
}
