/* ==========================================================================
   CodeRelay — 首页专属样式表（home.css）
   ==========================================================================

   为什么要有这个独立文件？
   ---------------------------------------------------------------------
   style.css 是「全站共享」的：features.html / download.html / 404.html 都
   在加载它。首页这次要做的是「像素风 + 终端风」的整体重排，里面会引入大量
   只属于首页的组件（终端面板、像素管线、LED 计数器、走马灯）。如果把这些
   直接塞进 style.css，另外三个页面的样式体积会被无谓地撑大，而且首页特有
   的类名还有可能和功能页既有类名撞车。

   所以：**所有新样式集中在本文件，且只被 index.html 引用**（在 style.css
   之后）。所有新类名统一 `hp-`（homepage）前缀，形成天然的命名空间，
   即便将来有人在别的页面误用了同名类，也还有前缀这一道保险。

   设计约束（来自本次需求，逐条对应）
   ---------------------------------------------------------------------
   1. 背景一个字都不能动。body::before 的网格光晕、.bgcode 的 3D 代码层、
      .bgpixels 的像素流星，全部原样保留。本文件**没有任何一条规则**会碰到
      它们，也不新增全屏背景层 —— 所有肌理都画在「我的组件内部」，不铺满
      视口，避免和既有的三层背景互相打架（那三层已经够满，再叠一层就是脏）。
   2. 不抄 features.html 的区块。没有 .feature-card、没有 .replica-wrap、
      没有 .ui-* 工作台复刻。首页的叙事是「体验」而不是「清单」：看一条请求
      真的跑起来、看它怎么流过去、看几个像素数字跳起来。
   3. 像素风 = 硬边。新组件一律 2px 实线边框、border-radius: 0、
      方角括号装饰、方形色块；不用阴影糊边、不用模糊滤镜、不用图片。
   4. 代码风 = 等宽。所有数字、路径、协议名、微标签一律 DM Mono +
      大写 + 字间距；唯一强调色是 --green。

   成本纪律（本次需求的另一半）
   ---------------------------------------------------------------------
   「花里胡哨」和「省资源」不是矛盾的，前提是**把开销挂到可见性上**：
   · 所有持续动画都挂在动画门控类 .hp-gate 之下，默认 animation-play-state
     为 paused；只有当 assets/js/home-fx.js 的 IntersectionObserver 判定
     该区块进入视口时，才会加上 .is-live 让它们跑起来。离开视口立刻回到
     paused —— 浏览器会把暂停的合成器动画开销降到接近零。
   · 动画只动 transform / opacity（合成器属性），绝不动 left/top/width/
     height，避免每帧重排。
   · 「打字机」不用 JS 逐字符改文本（那会触发逐帧重排），改用等宽字体的
     ch 单位 + steps() 做纯 CSS 宽度推进：零 JS、零重排。
   · 「粒子包」不用 rAF 逐帧算坐标，改用纯 CSS transform 平移。这样连
     「逐帧拼字符串」带来的 GC 压力都不存在。
   · 唯一的 rAF 出现在 home-fx.js 的数字计数器上（它必须逐帧算数值），
     且是一次性的、跑完即从调度表里移除。
   · prefers-reduced-motion 与 print 都有明确的终态兜底，见文件末尾。

   变量来源：只使用 style.css 已定义的 token，绝不新造颜色。
   纹理用 --bg-grid-line / --bg-dot / --bg-glow-a，它们本来就是为「极低
   透明度肌理」准备的，复用它们既统一又不会引入新色值。
   ========================================================================== */

/* ==================================================================
   0. 区块骨架
   ================================================================== */
/* 首页每个新段落共用的上下节奏。用 clamp 让它随视口呼吸：
   窄屏不至于把间距压没，宽屏也不会拉出一条过长的空白。
   注意没有写 .section 那个「相邻区块取消上内边距」的规则 —— 首页的四个
   段落是并列的、各自独立的体验单元，每一段都需要自己的上边距，而不是
   黏成一大堆。 */
.hp-sec {
  position: relative;
  padding: clamp(64px, 8.5vw, 116px) 0;
}

/* 段落标题组：左对齐。代码/终端审美里，居中的长段落会削弱「终端输出」
   的机械感；左对齐 + 等宽微标签才是这个语言该有的样子。
   （最后一节 .hp-cta 例外，它是收尾号召，居中更有力。） */
.hp-head { max-width: 720px; margin-bottom: clamp(26px, 3.4vw, 40px); }

/* 微标签：「01 / LIVE REQUEST」这种。
   等宽 + 大写 + 大字距是「终端标签」的三个识别特征，缺一个就不像了。 */
.hp-kicker {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 14px;
  color: var(--muted);
  font-family: 'DM Mono', ui-monospace, SFMono-Regular, Consolas, monospace;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: .22em;
  text-transform: uppercase;
}
/* 序号方块：实心绿底 + 深色字，是全页唯一的「反相」元素，
   用来在视觉上钉住每个段落的起点。方角、无圆角，符合像素语言。 */
.hp-idx {
  display: inline-grid;
  place-items: center;
  min-width: 22px;
  height: 18px;
  padding: 0 4px;
  background: var(--green);
  color: var(--app-bg);
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: .04em;
}

.hp-h2 {
  font-size: clamp(23px, 3.3vw, 34px);
  font-weight: 600;
  letter-spacing: -.035em;
  line-height: 1.22;
  color: var(--ink);
}
.hp-accent { color: var(--green); }

.hp-lead {
  max-width: 660px;
  margin-top: 14px;
  color: var(--muted);
  font-size: 15px;
  line-height: 1.74;
}

/* 行内代码：只做「底色 + 等宽」，不加圆角不加边框，
   避免在正文里长出一堆小盒子。 */
.hp-inline-code {
  padding: 1px 5px;
  background: var(--surface-soft);
  color: var(--ink-strong);
  font-family: 'DM Mono', ui-monospace, SFMono-Regular, Consolas, monospace;
  font-size: .92em;
}

/* ------------------------------------------------------------------
   像素边框基座
   ------------------------------------------------------------------
   一块 2px 实线框 + 两枚 L 形绿色角标。
   为什么只用两枚（左上 + 右下）而不是四枚：四角全画会变成一个「取景框」，
   把内容框死在中间，像截图；对角的两枚既点出了「像素取景」的语言，又让
   框线保持通透、不抢内容。伪元素实现，零额外 DOM。
   角标压在边框上（-2px 对齐外沿），所以父级绝不能 overflow: hidden。 */
.hp-frame {
  position: relative;
  border: 2px solid var(--line-strong);
  background-color: var(--surface-solid);
  /* 细扫描线肌理：4px 周期、1px 线宽，用的是背景层现成的 --bg-grid-line。
     静态绘制一次，不参与任何动画，因此没有逐帧成本。 */
  background-image: repeating-linear-gradient(
    to bottom,
    var(--bg-grid-line) 0 1px,
    transparent 1px 4px
  );
}

.hp-frame::before,
.hp-frame::after {
  content: '';
  position: absolute;
  width: 15px;
  height: 15px;
  border: 2px solid var(--green);
  pointer-events: none;
}
.hp-frame::before {
  top: -2px;
  left: -2px;
  border-right: 0;
  border-bottom: 0;
}
.hp-frame::after {
  right: -2px;
  bottom: -2px;
  border-top: 0;
  border-left: 0;
}

/* ==================================================================
   1. 走马灯（像素色带）
   ================================================================== */
/* 一条持续横向流动的等宽标签带，把产品的关键参数像终端滚动日志一样
   推过去。它是整页唯一「持续性」的视觉噪音，所以刻意压得很窄（38px）、
   很淡，只在段落之间做一次呼吸。
   无缝循环的做法：轨道里放**两份完全相同**的内容，整体位移 -50%，
   于是第一份走到头时第二份正好补上，接缝不可见。 */
.hp-ticker {
  position: relative;
  overflow: hidden;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  background: var(--surface-solid);
  /* 两端淡出：硬切边在深色底上会显得像是被截断的残片。 */
  -webkit-mask-image: linear-gradient(to right, transparent, #000 7%, #000 93%, transparent);
  mask-image: linear-gradient(to right, transparent, #000 7%, #000 93%, transparent);
}

.hp-ticker-track {
  display: flex;
  width: max-content;
  animation: hp-marquee 34s linear infinite;
  /* 只提示合成器，且整条轨道只有一层，不会为每个标签各开一层。 */
  will-change: transform;
}

/* 门控：默认暂停。既保证了「JS 没跑起来也不会有看不见的开销」，
   也保证了「滚出视口后立即停」——两条都由 .is-live 一个类统一切换。 */
.hp-gate .hp-ticker-track { animation-play-state: paused; }
.hp-gate.is-live .hp-ticker-track { animation-play-state: running; }

.hp-tick {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  padding: 9px 18px;
  color: var(--subtle);
  font-family: 'DM Mono', ui-monospace, SFMono-Regular, Consolas, monospace;
  font-size: 11.5px;
  letter-spacing: .12em;
  text-transform: uppercase;
  white-space: nowrap;
}
/* 分隔符单独成元素而不是用 content 伪元素：等宽字体下 `·` 与 `|`
   宽度不同，单独一个 span 才好精确控制间距。 */
.hp-tick-sep { color: var(--line-strong); }
/* 少量条目提亮成绿色，制造「偶发事件」的节奏感（429、切号、缓存命中）。 */
.hp-tick.is-hot { color: var(--green); }

/* ==================================================================
   2. 终端面板（hp-term）
   ================================================================== */
.hp-term {
  /* 终端内部比页面底色更暗一档：真实的终端窗口在暗色桌面上就是这样
     「陷进去」的。直接复用 --app-bg，不新造色值。 */
  background-color: var(--app-bg);
  background-image: repeating-linear-gradient(
    to bottom,
    var(--bg-grid-line) 0 1px,
    transparent 1px 4px
  );
}

/* ---- 标题栏 ---- */
.hp-term-top {
  position: relative;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 14px;
  border-bottom: 2px solid var(--line);
  background: var(--surface-soft);
  /* 只为了裁掉下面那条扫光的溢出部分。 */
  overflow: hidden;
}

/* 三个方形「窗口按钮」。用方点而不是圆点，是因为这里的整个语言是像素，
   圆形会立刻把观感拉回普通网页。 */
.hp-chrome { display: inline-flex; gap: 5px; }
.hp-chrome i {
  width: 7px;
  height: 7px;
  background: var(--line-strong);
}
.hp-chrome i:first-child { background: var(--green); }

.hp-term-title {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  color: var(--muted);
  font-family: 'DM Mono', ui-monospace, SFMono-Regular, Consolas, monospace;
  font-size: 11.5px;
  letter-spacing: .04em;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.hp-term-badge {
  flex: 0 0 auto;
  padding: 2px 7px;
  border: 1px solid var(--green);
  color: var(--green);
  font-family: 'DM Mono', ui-monospace, SFMono-Regular, Consolas, monospace;
  font-size: 10px;
  letter-spacing: .14em;
}

/* 标题栏底部的一条循环扫光：像终端在「等响应」。
   1px 的窄条，只动 transform，开销可以忽略；门控同样生效。 */
.hp-term-top::after {
  content: '';
  position: absolute;
  left: 0;
  bottom: 0;
  width: 34%;
  height: 2px;
  background: linear-gradient(90deg, transparent, var(--green), transparent);
  animation: hp-sweep 3.4s linear infinite;
  pointer-events: none;
}
.hp-gate .hp-term-top::after { animation-play-state: paused; }
.hp-gate.is-live .hp-term-top::after { animation-play-state: running; }

/* ---- 代码区 ---- */
.hp-term-body {
  /* 横向滚动而不是折行：折行会打乱等宽代码的缩进，读起来立刻失真。
     这和站点已有的 .replica-scroll 是同一个取舍。 */
  overflow-x: auto;
  overflow-y: hidden;
  padding: 18px 16px 20px;
  -webkit-overflow-scrolling: touch;
  /* 把重排/重绘锁在本元素内：外层背景层再怎么动也不会牵连到这里。 */
  contain: layout paint;
}

/* 用 div + display:block 的 span 而不是 <pre>：
   <pre> 里放不了 <span> 之外的块级结构，而这里需要逐行做错峰入场。
   white-space: pre 把等宽排版效果原样拿回来。 */
.hp-term-code {
  margin: 0;
  color: var(--ink-soft);
  font-family: 'DM Mono', ui-monospace, SFMono-Regular, Consolas, monospace;
  font-size: 12.5px;
  line-height: 1.95;
  /* ⚠ 这里是 normal 而不是 pre，且必须保持 normal。
     每一行 .hp-tl 自己才是 white-space: pre。原因：如果把 pre 放在容器上，
     HTML 源码里行与行之间的「换行 + 缩进」空白会全部被当成真实字符渲染，
     每一行前面都会多出一截缩进、行与行之间还会凭空多出空行。
     放在 .hp-tl 上则只保留「行内」的空白（也就是我们想要的对齐），
     而块与块之间的空白按 normal 规则被折叠掉。 */
  white-space: normal;
  tab-size: 2;
}

/* 单行。入场的初始隐藏态写在文件末尾的 reduced-motion 段落里 ——
   必须在 no-preference 且 html.hp-js 同时成立时才隐藏，否则禁用动效的用户
   或 JS 挂掉的场景会看到一片空白。 */
.hp-tl {
  display: block;
  /* 行内空白（对齐用的缩进）必须原样保留，行与行之间的源码空白则由
     父级 .hp-term-code 的 normal 折叠掉。 */
  white-space: pre;
  opacity: 1;
  transform: none;
}

/* 空行占位：<span> 里放空内容会被折叠成 0 高，必须给一个确定高度。 */
.hp-tl-gap { height: 1em; }

/* ---- 语法着色 ----
   只用了两种颜色：绿（强调）与三档灰（层次）。像素风的前提是克制，
   一段彩虹色的 JSON 会立刻失掉「终端」的味道。 */
.hp-p { color: var(--green); }             /* 提示符 $ / C:\> */
.hp-c { color: var(--subtle); }            /* 注释、次要信息 */
.hp-k { color: var(--ink); }               /* 键名 / 命令 */
.hp-s { color: var(--blue); }              /* 字符串 */
.hp-n { color: var(--ink-strong); }        /* 数字 */
.hp-ok { color: var(--green); }            /* 200 OK 这类成功标记 */

/* ---- 打字机 ----
   纯 CSS 实现：等宽字体里 1ch 恰好是一个字符宽，把容器宽度从 0 推到 12ch
   并配 steps(12)，浏览器就会一拍一格地「露」出文字。

   为什么不用 JS 逐字符改 textContent：那需要 12 次定时器回调 + 12 次整行
   重新排版，而且必须自己把定时器句柄管起来（组件滚出视口或页面卸载后
   回调还在跑，正是「忘了回收」的典型来源）。这里换成一个 CSS 动画：
   steps(12) 让浏览器只在 12 个阶梯点上重新计算宽度，总时长 780ms，
   一次性结束，不需要任何句柄。

   诚实说明它的成本：动画的是 width，属于布局属性，不是合成器属性 ——
   所以它确实会在每个阶梯点触发一次这个内联块的布局。衡量后仍然选它，因为
   作用域被牢牢限制在一个 display:inline-block 的短文本上、总时长不到 1 秒、
   且只发生一次（不是循环）。相比之下，页面上的持续动画（走马灯、像素包、
   光标、扫光）全部只动 transform / opacity，那些才是每帧都在跑的东西。

   ⚠ 维护提醒：steps(12) 与下面的 12ch **必须**与 HTML 里
   `.hp-type` 内文本的字符数完全一致（当前是 `data: [DONE]`，12 个字符，
   含空格与方括号）。改文案就要同时改这两个数。 */
.hp-type {
  display: inline-block;
  overflow: hidden;
  vertical-align: bottom;
  white-space: pre;
}

/* ---- 方块光标 ----
   终端感的核心符号。1.05s 一周期、steps(1) 硬切换（不是渐隐）——
   真实的终端光标就是「啪」地亮灭，渐变会立刻显得像网页而不是终端。 */
.hp-cursor {
  display: inline-block;
  width: 8px;
  height: 1.05em;
  margin-left: 2px;
  background: var(--green);
  vertical-align: -.16em;
  animation: hp-blink 1.05s steps(1, end) infinite;
  /* 让光标在打字机快结束的时候才开始闪，避免「文字还没出来光标先闪」。
     820ms 与 .hp-type 的 780ms 时长是配好的：打字在 --hp-d + 780ms 结束，
     光标在 --hp-d + 820ms 起闪，中间留 40ms 的「打完、还没开始闪」的静止，
     这个极短的停顿正是真实终端的节奏。 */
  animation-delay: calc(var(--hp-d, 0ms) + 820ms);
}

/* 光标的闪烁同样受门控：区块离开视口就停。
   这条规则必须写在 .hp-cursor 之后，否则会被上面的 animation 简写覆盖
   （animation 简写会把 animation-play-state 重置为 running）。 */
.hp-gate .hp-cursor { animation-play-state: paused; }
.hp-gate.is-live .hp-cursor { animation-play-state: running; }

/* ---- 状态栏 ---- */
.hp-term-foot {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 20px;
  padding: 9px 14px;
  border-top: 2px solid var(--line);
  background: var(--surface-soft);
  color: var(--muted);
  font-family: 'DM Mono', ui-monospace, SFMono-Regular, Consolas, monospace;
  font-size: 10.5px;
  letter-spacing: .1em;
  text-transform: uppercase;
}
.hp-foot-cell { display: inline-flex; align-items: center; gap: 6px; }
/* 状态点：方形、绿色、微弱呼吸。用 opacity 呼吸而不是缩放，
   是因为它只有 5px，缩放看不出变化、只会让边缘抖。 */
.hp-foot-cell::before {
  content: '';
  width: 5px;
  height: 5px;
  background: var(--green);
  animation: hp-breathe 2.6s ease-in-out infinite;
}
.hp-gate .hp-foot-cell::before { animation-play-state: paused; }
.hp-gate.is-live .hp-foot-cell::before { animation-play-state: running; }

/* ==================================================================
   3. 像素管线（hp-pipe）
   ================================================================== */
/* 横向四段：CLIENT → LOCAL → POOL → UPSTREAM。
   每段是固定宽度的像素盒子，段与段之间是固定宽度的「链路」，
   链路里跑着像素包。

   为什么用固定 px 而不是弹性宽度？
   因为「包从链路左端跑到右端」这个动画需要知道跑多远。用百分比做不到
   （transform 的百分比是相对元素自身宽度），用 JS 逐帧量测又会引入
   持续开销。固定宽度 + 变量化，让位移距离可以在 keyframes 里直接写成
   calc(var(--hp-link-w) + 8px)，纯 CSS、纯合成器、零 JS。
   窄屏放不下时横向滚动（与站点既有的复刻块同一套方案），而不是把管线
   改成竖排 —— 竖排会破坏「请求从左往右流动」的方向隐喻。 */
.hp-pipe {
  --hp-node-w: 146px;
  --hp-link-w: 62px;
  display: flex;
  align-items: stretch;
  justify-content: center;
  /* 固定宽度之和 = 4 × 节点 + 3 × 链路，写死是为了让窄屏出现滚动条
     而不是把节点压扁。 */
  min-width: 770px;
  padding: 10px 4px;
}

.hp-pipe-scroll {
  overflow-x: auto;
  overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
  /* 让滚动容器的边缘不切断角标 */
  margin: 0 -2px;
  padding: 2px;
}

.hp-node {
  position: relative;
  display: flex;
  flex: 0 0 auto;
  flex-direction: column;
  gap: 8px;
  width: var(--hp-node-w);
  padding: 14px 12px 12px;
  border: 2px solid var(--line-strong);
  background-color: var(--surface-solid);
  /* 8px 方格网格：只有在这个尺寸上才读得出「像素阵列」而不是「灰尘」。 */
  background-image:
    repeating-linear-gradient(to right, var(--bg-grid-line) 0 1px, transparent 1px 8px),
    repeating-linear-gradient(to bottom, var(--bg-grid-line) 0 1px, transparent 1px 8px);
}

/* 节点序号：左上角的等宽小字，像引脚编号 */
.hp-node-idx {
  color: var(--subtle);
  font-family: 'DM Mono', ui-monospace, SFMono-Regular, Consolas, monospace;
  font-size: 9.5px;
  letter-spacing: .18em;
}

.hp-node-label {
  color: var(--ink);
  font-family: 'DM Mono', ui-monospace, SFMono-Regular, Consolas, monospace;
  font-size: 12.5px;
  font-weight: 500;
  letter-spacing: .1em;
}

.hp-node-value {
  color: var(--muted);
  font-family: 'DM Mono', ui-monospace, SFMono-Regular, Consolas, monospace;
  font-size: 10.5px;
  letter-spacing: .02em;
  word-break: break-all;
}

/* 节点右上角的「活动指示灯」：一个小方块，被数据流经过时闪一下。
   它的闪动周期与链路上的包是同一套时长（2.6s），并按 --i 递增延迟，
   于是视觉上读作「一波一波地穿过整条链路」，而不是四个各自乱闪的灯。 */
.hp-node-dot {
  position: absolute;
  top: 10px;
  right: 10px;
  width: 6px;
  height: 6px;
  background: var(--green);
  opacity: .3;
  animation: hp-ping 2.6s ease-in-out infinite;
  animation-delay: calc(var(--i, 0) * .62s);
}
.hp-gate .hp-node-dot { animation-play-state: paused; }
.hp-gate.is-live .hp-node-dot { animation-play-state: running; }

/* 链路：一条 2px 的横线 + 上下两条「车道」。
   上车道跑请求（绿、向右），下车道跑响应（蓝、向左）。
   双车道是这个组件最关键的一笔：单车道只能表达「有数据在动」，
   双车道才表达出「有去有回」，也就同时点出了「反代」这件事。 */
.hp-link {
  position: relative;
  flex: 0 0 auto;
  width: var(--hp-link-w);
  /* 上下留出车道空间，线本身垂直居中 */
  margin: 0;
  background: none;
}
.hp-link::before {
  content: '';
  position: absolute;
  top: 50%;
  left: 0;
  right: 0;
  height: 2px;
  margin-top: -1px;
  background: var(--line-strong);
}

/* 像素包：8px 实心方块。
   不用圆点、不用发光拖尾 —— 方块本身就是「数据包」的视觉等价物，
   而且 fillRect 式的硬边最省填充率。 */
.hp-pkt {
  position: absolute;
  top: 50%;
  left: 0;
  width: 8px;
  height: 8px;
  margin-top: -10px;
  background: var(--green);
  animation: hp-pkt-fwd 2.6s linear infinite;
  animation-delay: calc(var(--i, 0) * .62s);
}
/* 响应包：蓝、跑反方向、车道在线下方、相位错开半个周期，
   于是「请求刚走完，响应就回来了」的因果感成立。 */
.hp-pkt.is-back {
  margin-top: 2px;
  background: var(--blue);
  animation-name: hp-pkt-back;
  animation-delay: calc(var(--i, 0) * .62s + 1.3s);
}
.hp-gate .hp-pkt { animation-play-state: paused; }
.hp-gate.is-live .hp-pkt { animation-play-state: running; }

/* 管线图例：解释两种颜色的含义，顺便把「有去有回」讲清楚。 */
.hp-pipe-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 22px;
  margin-top: 6px;
  padding-left: 4px;
  color: var(--muted);
  font-family: 'DM Mono', ui-monospace, SFMono-Regular, Consolas, monospace;
  font-size: 10.5px;
  letter-spacing: .1em;
  text-transform: uppercase;
}
.hp-legend-item { display: inline-flex; align-items: center; gap: 7px; }
.hp-legend-item i { width: 8px; height: 8px; background: var(--green); }
.hp-legend-item.is-back i { background: var(--blue); }

/* ==================================================================
   4. 像素计数器（hp-metrics）
   ================================================================== */
.hp-metrics {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 2px;                    /* 方格阵列的缝，不用圆角卡片 */
  border: 2px solid var(--line-strong);
  background: var(--line);     /* 缝的颜色透出来 = 像素网格 */
}

.hp-metric {
  position: relative;
  padding: 22px 18px 18px;
  background-color: var(--surface-solid);
  /* 细扫描线，与终端面板同源，保证整页肌理一致 */
  background-image: repeating-linear-gradient(
    to bottom,
    var(--bg-grid-line) 0 1px,
    transparent 1px 4px
  );
  overflow: hidden;
}

/* 背景大号序号：4% 透明度的等宽数字，压在内容下面，制造「仪表盘刻度」
   的纵深。用 --bg-grid-line 这个现成 token，不引入新色值。 */
.hp-metric::before {
  content: attr(data-hp-no);
  position: absolute;
  right: 6px;
  bottom: -6px;
  color: var(--bg-grid-line);
  font-family: 'DM Mono', ui-monospace, SFMono-Regular, Consolas, monospace;
  font-size: 64px;
  font-weight: 500;
  line-height: 1;
  letter-spacing: -.05em;
  pointer-events: none;
}

.hp-metric-value {
  display: flex;
  align-items: baseline;
  gap: 2px;
  color: var(--ink);
  font-family: 'DM Mono', ui-monospace, SFMono-Regular, Consolas, monospace;
  /* 等宽 + 表格数字：计数滚动时字宽不能变，否则整行会左右抽动。
     tabular-nums 对 DM Mono 是冗余的，但对兜底字体是必需的。 */
  font-variant-numeric: tabular-nums;
  font-size: clamp(38px, 5.6vw, 62px);
  font-weight: 500;
  line-height: 1;
  letter-spacing: -.06em;
}
/* 「<5ms」里那个小于号：它是文字而不是数字，所以不能跟着计数节点一起
   变宽。单独给它一档小一号的字号，视觉上退到数字后面去。 */
.hp-metric-pre {
  color: var(--green);
  font-size: .72em;
  letter-spacing: 0;
}
.hp-metric-unit {
  color: var(--green);
  font-size: .42em;
  letter-spacing: 0;
}

.hp-metric-label {
  margin-top: 10px;
  color: var(--ink-soft);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: -.01em;
}
.hp-metric-sub {
  margin-top: 4px;
  color: var(--subtle);
  font-family: 'DM Mono', ui-monospace, SFMono-Regular, Consolas, monospace;
  font-size: 10px;
  letter-spacing: .16em;
  text-transform: uppercase;
}

/* 计数器区底部的免责小字。刻意用 --subtle 而不是 --muted：
   这行字的作用是「交代数据性质」，不该比读数更抢眼。 */
.hp-note {
  margin-top: 14px;
  color: var(--subtle);
  font-family: 'DM Mono', ui-monospace, SFMono-Regular, Consolas, monospace;
  font-size: 10.5px;
  letter-spacing: .06em;
  line-height: 1.6;
}

/* 填充条：数字跳完，条跟着推到对应比例。
   用 scaleX 而不是 width：width 会触发重排，scaleX 只走合成。
   这条过渡由共享的 data-reveal 系统驱动（见下），不额外写 JS。 */
.hp-metric-bar {
  position: relative;
  height: 3px;
  margin-top: 16px;
  background: var(--line);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 900ms var(--ease-out) var(--hp-bar-delay, 0ms);
}
.hp-metric-bar::after {
  content: '';
  position: absolute;
  inset: 0;
  background: var(--green);
  transform: scaleX(var(--hp-fill, .5));
  transform-origin: left center;
}
/* 复用 main.js 已经在跑的入场观察器：一旦 .is-revealed 落下来，条子就被
   推到目标比例。不需要 home-fx.js 再观察一次，省一个观察目标。 */
[data-reveal].is-revealed .hp-metric-bar { transform: scaleX(1); }

/* ==================================================================
   5. 收尾号召（hp-cta）
   ================================================================== */
.hp-cta-frame {
  padding: clamp(34px, 5.2vw, 62px) clamp(20px, 4vw, 54px);
  text-align: center;
  /* 一层极淡的绿色光晕托底，取的是背景系统现成的 --bg-glow-a，
     保证首页新组件的「光」和背景层是同一个色温。 */
  background-image:
    radial-gradient(120% 120% at 50% 0%, var(--bg-glow-a), transparent 62%),
    repeating-linear-gradient(to bottom, var(--bg-grid-line) 0 1px, transparent 1px 4px);
}

.hp-cta .hp-kicker { justify-content: center; }

.hp-cta-h {
  font-size: clamp(25px, 4vw, 42px);
  font-weight: 600;
  letter-spacing: -.04em;
  line-height: 1.2;
  color: var(--ink);
  text-wrap: balance;
}

.hp-cta-lead {
  margin: 14px auto 0;
  max-width: 560px;
  color: var(--muted);
  font-size: 14.5px;
  line-height: 1.7;
}

.hp-cta-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 12px;
  margin-top: 28px;
}

/* 复用站点既有的 .button 组件（颜色、焦点环、无障碍都现成），
   只把圆角压成直角 —— 这是本页唯一允许「改别人组件外观」的地方，
   而且作用域被 .hp-cta-actions 严格限制在首页的这一个块里。 */
.hp-cta-actions .button {
  border-radius: 0;
  min-height: 46px;
  padding: 0 24px;
  font-family: 'DM Mono', ui-monospace, SFMono-Regular, Consolas, monospace;
  font-size: 13px;
  letter-spacing: .02em;
}

/* 终端提示行：收尾处再敲一次「命令」，把整页语言收口。
   左右两条引线把视线引到中间的命令上。 */
.hp-cta-prompt {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-top: 30px;
  padding: 10px 16px;
  border: 1px solid var(--line);
  background: var(--app-bg);
  color: var(--ink-soft);
  font-family: 'DM Mono', ui-monospace, SFMono-Regular, Consolas, monospace;
  font-size: 12px;
  letter-spacing: .02em;
  text-align: left;
}
.hp-cta-prompt .hp-cursor {
  height: 1em;
  vertical-align: -.12em;
  animation-delay: 0ms;
}

/* ==================================================================
   6. 关键帧
   ================================================================== */

/* 光标硬切换。steps(1) 意味着「0% 到 49% 保持第一帧，50% 到 100% 跳到
   第二帧」—— 没有中间过渡，正是终端光标该有的干脆。 */
@keyframes hp-blink {
  0%, 49%   { opacity: 1; }
  50%, 100% { opacity: 0; }
}

/* 打字机：宽度从 0 推到 12ch。
   ⚠ 12 这个数字与 HTML 中 `.hp-type` 的文本字符数、以及 .hp-type 上
   steps(12) 严格绑定，改文案必须三处同改。 */
@keyframes hp-type {
  from { width: 0; }
  to   { width: 12ch; }
}

/* 标题栏扫光：从完全左侧之外滑到完全右侧之外。
   用 translate3d 强制走合成器；宽度 34% 让它在视觉上像一束窄光，
   而不是「半个标题栏都被点亮」。 */
@keyframes hp-sweep {
  from { transform: translate3d(-110%, 0, 0); }
  to   { transform: translate3d(330%, 0, 0); }
}

/* 请求包：向右穿过链路。
   0–10% 淡入（避免在左端点凭空出现的生硬），10–40% 完成位移，
   40–48% 淡出，剩下 52% 的周期留空 —— 这段「空」很重要：
   它让三个链路上的包按 --i 错峰后形成接力赛，而不是三颗同时飘。 */
@keyframes hp-pkt-fwd {
  0%   { transform: translate3d(0, 0, 0); opacity: 0; }
  10%  { transform: translate3d(0, 0, 0); opacity: 1; }
  40%  { transform: translate3d(var(--hp-link-w), 0, 0); opacity: 1; }
  48%  { transform: translate3d(var(--hp-link-w), 0, 0); opacity: 0; }
  100% { transform: translate3d(var(--hp-link-w), 0, 0); opacity: 0; }
}

/* 响应包：同样的曲线，方向相反。 */
@keyframes hp-pkt-back {
  0%   { transform: translate3d(var(--hp-link-w), 0, 0); opacity: 0; }
  10%  { transform: translate3d(var(--hp-link-w), 0, 0); opacity: 1; }
  40%  { transform: translate3d(0, 0, 0); opacity: 1; }
  48%  { transform: translate3d(0, 0, 0); opacity: 0; }
  100% { transform: translate3d(0, 0, 0); opacity: 0; }
}

/* 节点指示灯：一次「闪一下」。峰值放在 12%，与包抵达节点的时刻对齐。 */
@keyframes hp-ping {
  0%, 100% { opacity: .3; transform: scale(1); }
  12%      { opacity: 1;  transform: scale(2.2); }
  30%      { opacity: .3; transform: scale(1); }
}

/* 状态点呼吸 */
@keyframes hp-breathe {
  0%, 100% { opacity: .35; }
  50%      { opacity: 1; }
}

/* 走马灯：轨道含两份内容，所以位移 -50% 恰好等于一份的宽度。 */
@keyframes hp-marquee {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-50%, 0, 0); }
}

/* ==================================================================
   7. 响应式
   ================================================================== */
@media (min-width: 1100px) {
  /* 宽屏给管线和链路更多呼吸：节点变宽、链路变长，包跑得更「远」，
     整条链路的因果感更清楚。这两个值同时是 keyframes 里位移距离的
     来源，改一处即全局生效。 */
  .hp-pipe {
    --hp-node-w: 180px;
    --hp-link-w: 110px;
    min-width: 1050px;
  }
}

@media (max-width: 1000px) {
  /* 四列在中等屏幕上会挤成四条竖条，读数反而更慢；
     两行两列既保住了阵列感，又给了数字足够的宽度。 */
  .hp-metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 720px) {
  .hp-sec { padding: 58px 0; }
  .hp-term-code { font-size: 11.5px; }
  .hp-term-body { padding: 14px 12px 16px; }
  .hp-term-title { font-size: 10.5px; }
  .hp-metrics { grid-template-columns: 1fr; }
  .hp-metric { padding: 18px 16px 16px; }
  .hp-cta-actions .button { width: 100%; }
  .hp-cta-prompt { font-size: 11px; }
  /* 窄屏不给扫描线：一行 11.5px 的等宽字在 360px 宽下已经接近可读下限，
     再叠一层横线只会加重「糊」的观感。 */
  .hp-metric { background-image: none; }
}

@media (max-width: 420px) {
  .hp-tick { font-size: 10.5px; padding: 8px 14px; }
  .hp-metric-value { font-size: 40px; }
}

/* ==================================================================
   7.5 入场动画（逐行展开 + 打字机）
   ==================================================================
   这一段的隐藏态**同时**被两道条件包着，缺一不可：

   1. `html.hp-js` —— 由 home-fx.js 的第一行同步加上（它是 defer 脚本，
      在 HTML 解析完之后、DOMContentLoaded 之前跑）。如果脚本根本没加载、
      加载失败、或者解析时就抛错，这个类不会出现，于是下面的 opacity: 0
      从不生效，内容始终可见。这是「入场动画不能把人坑成白屏」的底线。
   2. `prefers-reduced-motion: no-preference` —— 偏好减少动效的用户同样
      不进入隐藏态。

   为什么隐藏态不放在 .hp-tl 的基础规则里（那样最省事）：基础规则是
   无条件生效的，一旦 JS 出问题，页面上就会出现一条永远不出现的终端 —— 
   而终端恰恰是本页的主视觉。宁可多写一个类，也不接受这个风险。

   触发方式：home-fx.js 的一次性 IntersectionObserver 在区块进入视口时
   给 `.hp-term` 加上 `.is-on`，动画随即起跑。`animation-fill-mode: both`
   保证起跑前停在 0%（隐藏）、跑完停在 100%（完全可见），中间不会闪回。 */
@media (prefers-reduced-motion: no-preference) {
  html.hp-js .hp-term .hp-tl {
    opacity: 0;
    /* 位移只有 8px：终端是「从上往下打出内容」，不是「从远处飞进来」。
       幅度再大就成了卡片入场，语言就错了。 */
    transform: translate3d(0, 8px, 0);
  }

  /* 逐行展开。delay 来自每行自己的 --hp-d（行内写死的递增毫秒数），
     所以整段代码像被一行一行「敲」出来。 */
  html.hp-js .hp-term.is-on .hp-tl {
    animation: hp-line-in 320ms var(--ease-out) both;
    animation-delay: var(--hp-d, 0ms);
  }

  /* 打字机与光标在整段行入场之后才开始，`.hp-type` 的 --hp-d 取的是
     比最后一行更晚的值（见 index.html 的行内变量）。 */
  html.hp-js .hp-term.is-on .hp-type {
    animation: hp-type 780ms steps(12, end) both;
    animation-delay: var(--hp-d, 0ms);
  }
}

/* 单行入场：只动 opacity + transform，两样都是合成器属性。 */
@keyframes hp-line-in {
  from { opacity: 0; transform: translate3d(0, 8px, 0); }
  to   { opacity: 1; transform: none; }
}

/* ==================================================================
   8. 动效降级（prefers-reduced-motion）
   ==================================================================
   这一段的思路和 style.css 保持一致：不使用「把时长压到 0.01ms」这种
   隐式兜底，而是**显式给出终态**。因为隐式兜底会让一次性动画「跳一帧
   然后定格」，观感反而像故障；显式终态则是稳的。 */
@media (prefers-reduced-motion: reduce) {
  /* 入场隐藏态一律不生效 —— 内容从一开始就完整可见。 */
  .hp-tl { opacity: 1 !important; transform: none !important; transition: none !important; }

  /* 打字机直接给到满宽，光标给个稳定的可见态（不闪）。 */
  .hp-type { width: auto !important; animation: none !important; }
  .hp-cursor { animation: none !important; opacity: 1 !important; }

  /* 所有持续动画整层关掉，而不是留个定格帧。 */
  .hp-ticker-track,
  .hp-term-top::after,
  .hp-node-dot,
  .hp-pkt,
  .hp-foot-cell::before { animation: none !important; }

  /* 关掉后像素包会停在 keyframes 的 0% 帧（opacity: 0），于是消失 ——
     这正是想要的：链路本身（那条 2px 的线）还在，信息没丢。 */

  /* 填充条直接到满，不保留「推出来」的过程。 */
  .hp-metric-bar { transform: scaleX(1) !important; transition: none !important; }
}

/* ==================================================================
   9. 打印
   ==================================================================
   首页的新组件里有大量「屏幕专属」的东西：走马灯、像素包、光标、
   扫描线肌理。它们在纸上没有意义，只会吃墨、并且在灰度打印时糊成脏灰。 */
@media print {
  .hp-ticker,
  .hp-pkt,
  .hp-cursor,
  .hp-node-dot,
  .hp-term-top::after,
  .hp-cta-actions,
  .hp-cta-prompt { display: none !important; }

  /* 面板退回白底黑字，边框保留（表格化的信息在纸上要靠框线读）。 */
  .hp-frame,
  .hp-term,
  .hp-node,
  .hp-metric,
  .hp-cta-frame {
    background: #fff !important;
    background-image: none !important;
    border-color: #000 !important;
  }
  .hp-frame::before,
  .hp-frame::after { display: none !important; }

  .hp-term-code,
  .hp-node-label,
  .hp-node-value,
  .hp-metric-value,
  .hp-metric-label,
  .hp-metric-sub,
  .hp-lead,
  .hp-cta-lead,
  .hp-cta-h,
  .hp-h2 { color: #000 !important; }

  /* 打字机在纸上必须是「已经打完」的状态。 */
  .hp-type { width: auto !important; animation: none !important; }

  /* 布局收紧：分页媒体里 clamp 的上限没有意义。 */
  .hp-sec { padding: 18px 0; break-inside: avoid; }
  .hp-metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* 首屏高度收口已交给 style.css：--hero-ticker-reserve 与 --header-h 并列放在
   同一个 :root 里，hero 的 min-height 与额外下内边距都引用它，调参只改那一处。
   早先这里用 .hero 覆盖来减 56px，实测在线上没有生效（style.css 的 base 规则
   仍然胜出），所以数值改为直接写在 base 规则里，不再依赖跨文件层叠顺序。 */
