/* ==========================================================================
   CodeRelay — 官网样式表
   零构建：纯 CSS，无预处理器。设计 token 直接取自产品 src/styles.css，
   保证「官网」与「产品界面」共用同一套黑白灰 + 单一绿色强调的视觉语言。
   顺序：字体 / token / 基础 / 布局 / 站点框架 / 按钮 / Hero /
        界面复刻 / 各内容区块 / 下载页 / 404 / 动效 / 响应式 / 无障碍
   ========================================================================== */

/* ---------------------------------------------------------------- 基础重置 */
*,
*::before,
*::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }

body {
  margin: 0;
  font-family: Inter, 'Segoe UI Variable', 'Segoe UI', system-ui, -apple-system, sans-serif;
  font-size: 16px;
  line-height: 1.65;
  color: var(--ink-soft);
  background: var(--app-bg);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img, svg { display: block; max-width: 100%; }
h1, h2, h3, h4 { margin: 0; color: var(--ink); letter-spacing: -.03em; line-height: 1.18; font-weight: 600; }
p { margin: 0; }
a { color: inherit; text-decoration: none; }
ul, ol { margin: 0; padding: 0; list-style: none; }
button { font: inherit; color: inherit; }
code, kbd, samp, pre { font-family: 'DM Mono', ui-monospace, SFMono-Regular, Consolas, monospace; }

/* ------------------------------------------------------------------ token */
:root {
  /* 固定暗色（站点只有一套主题）：色值原样取自产品 src/styles.css 的 data-theme='dark' 段 */
  --app-bg: #14171a;
  --surface: rgba(25, 29, 33, .92);
  --surface-solid: #191d21;
  --surface-soft: #20242a;
  --surface-hover: #272c33;
  --line: #2c3239;
  --line-strong: #373e46;
  --ink: #eef1f4;
  --ink-strong: #ced3d9;
  --ink-soft: #b2b8bf;
  --muted: #9aa1a9;
  --subtle: #858d96;
  --accent: #eef1f4;
  --green: #52b17b;
  --green-soft: rgba(82, 177, 123, .16);
  --yellow: #c3994e;
  --yellow-soft: rgba(195, 153, 78, .16);
  --red: #ea8279;
  --red-soft: rgba(234, 130, 121, .16);
  --blue: #86a0ce;
  --blue-soft: rgba(134, 160, 206, .16);
  --purple: #b094ca;
  --orange: #ce935a;
  /* 补定义：此前 :root 缺失 --purple-soft / --orange-soft，指标卡背景会静默失效 */
  --purple-soft: rgba(176, 148, 202, .16);
  --orange-soft: rgba(206, 147, 90, .16);
  --radius: 11px;
  --shadow: 0 14px 34px rgba(0, 0, 0, .42), 0 2px 5px rgba(0, 0, 0, .3);

  /* 站点层补充 */
  --site-muted: #9aa1a9;
  --site-header: rgba(20, 23, 26, .84);
  --site-card: #191d21;
  --site-card-dashed: #373e46;
  --site-shadow-lift: 0 18px 40px rgba(0, 0, 0, .45), 0 2px 6px rgba(0, 0, 0, .3);
  --site-ring-track: rgba(82, 177, 123, .18);
  --site-chart-grid: #2c3239;
  --site-chrome: rgba(21, 24, 28, .74);
  --site-overlay: rgba(30, 34, 39, .95);
  --site-focus: #8fa0b3;
  --container: 1180px;

  /* 动效 token：产品内微交互是快而短的（实测 120–190ms），
     官网层区块入场可更舒缓（400–520ms）。所有动画统一走这套变量。 */
  --anim-fast: 120ms;
  --anim-base: 200ms;
  --anim-slow: 340ms;
  --anim-slower: 520ms;
  --ease-out: cubic-bezier(.16, 1, .3, 1);
  --ease-in-out: cubic-bezier(.4, 0, .2, 1);
  --ease-spring: cubic-bezier(.34, 1.56, .64, 1);

  color-scheme: dark;
}

/* -------------------------------------------------------------- 背景图案系统 */
/* 设计意图：底色 --app-bg 是品牌色，一个字都不能改；所以在它「之上」铺一层
   有氛围但不抢戏的图案 —— 远看是明暗与光感，近看才认出是 48px 网格。

   性能红线：
   1. 全部用渐变绘制，零模糊滤镜。radial-gradient 自带柔和边缘，
      而模糊滤镜每帧都要重新栅格化，是卡顿元凶。
   2. 零 animation、零 JS：图案完全静态，主线程与合成器都没有持续开销。
   3. 挂在 body::before 上，position: fixed + z-index: -1：
      · 不参与文档流，不影响任何布局；
      · fixed 使它在滚动时无需重新绘制/计算，只是既有图层的一次贴图；
      · body 没有 position、也没有 isolation，不自建层叠上下文，因此
        z-index: -1 的伪元素落进根堆叠上下文的负层：绘制在 canvas 底色
        （由 body 的 background 传播而来）之上、正常流内容之下 —— 正是所需。
   4. pointer-events: none，绝不拦截鼠标事件。
   5. 固定暗色下图案只绘制一次，无任何过渡声明：background-image 本就
      不可过渡，加上去只会产生无意义的样式计算。 */

:root {
  /* 顶栏高度，单一事实来源。sticky 顶栏依然占据常规文档流里的 64px，
     所以凡是「撑满一屏要减去顶栏」的计算都必须引用这个变量，而不是各处
     重复字面量 64px —— 否则日后调整栏高时只要漏改一处，就会出现内容被
     顶栏遮住、或页脚提前露出折线以上的漂移。 */
  --header-h: 64px;

  /* 首屏为首页走马灯预留的垂直空间。hero 的 min-height 与「抬高内容」那段
     额外下内边距同时减去它：盒子短一截、走马灯被让进第一屏，而内容的绝对
     位置一格不动（推导见下方 .hero 规则）。与 --header-h 同属「消费于首屏
     高度计算」的布局常量，故放在一起，日后只改这一处即可。 */
  --hero-ticker-reserve: 56px;

  /* 固定暗色：线条反相为冷白；光晕取暗色品牌绿（--green: #52b17b）
     + 冷蓝（--blue: #86a0ce）。亮度整体比亮色高，
     否则在 #14171a 上会完全看不见。 */
  --bg-grid-line: rgba(255, 255, 255, .035);
  --bg-dot: rgba(255, 255, 255, .025);
  --bg-glow-a: rgba(82, 177, 123, .06);
  --bg-glow-b: rgba(134, 160, 206, .04);

  /* 3D 流动代码层（.bgcode）用色。铁律：代码是「肌理」不是「内容」，
     所以亮度压在 .085 量级 —— 远看是流动的文字质感，凑近才认出是代码。
     暗底上刻意用偏冷的白 (190,210,230) 而不是纯白：冷白在 #14171a 上
     读作「屏幕在发光」，纯白只会显灰显脏。透明度比亮色更高，因为深底上
     低透明度的衰减更厉害，必须补齐这一档才和亮色时期的存在感持平。
     强调色复用暗色品牌绿 #52b17b，透明度比正文高一点点。 */
  --bg-code-ink: rgba(190, 210, 230, .085);
  --bg-code-accent: rgba(82, 177, 123, .14);
}

body::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  /* 五层叠加，书写顺序即绘制顺序（先写的在上）：点阵 → 网格双轴 → 两团光晕。
     光晕压在最底层，等于「光从网格背后透出来」，网格线始终保持清晰。 */
  background-image:
    /* 第三层：24px 点阵，1px 圆点。透明度极低，只在近距离可见，负责细节层次 */
    radial-gradient(var(--bg-dot) 1px, transparent 1px),
    /* 第一层：48px 双轴网格线，设计感的主体来源 */
    repeating-linear-gradient(to right, var(--bg-grid-line) 0 1px, transparent 1px 48px),
    repeating-linear-gradient(to bottom, var(--bg-grid-line) 0 1px, transparent 1px 48px),
    /* 第二层：两团大半径光晕，打破网格的机械感。
       刻意不用模糊滤镜 —— 渐变本身就有柔和过渡，成本为零 */
    radial-gradient(60% 50% at 15% 0%, var(--bg-glow-a), transparent 70%),
    radial-gradient(50% 45% at 85% 10%, var(--bg-glow-b), transparent 70%);
  background-size: 24px 24px, auto, auto, auto, auto;
  /* 点阵用默认 0 0 定位：瓦片 24px、圆心落在瓦片中心，即 x/y = 12 + 24k；
     而网格线在 48m。两者恰好错开 12px，圆点永远不会压到线上 ——
     否则「点 + 两根线」会在交点处三重叠加，出现肉眼可见的硬亮点。
     （实测：若改成 -12px，圆心变成 0,48,96… 正好落在网格线上，必须避免。） */
  background-position: 0 0;
  background-repeat: repeat, no-repeat, no-repeat, no-repeat, no-repeat;
  /* 椭圆遮罩：把整层图案向视口四边淡出。满屏等强度网格会像制图软件的底纹，
     有了这个遮罩，网格只在页面中上部「有存在感」，边缘自然收干净。 */
  -webkit-mask-image: radial-gradient(ellipse 100% 76% at 50% 8%, #000 0 34%, transparent 100%);
  mask-image: radial-gradient(ellipse 100% 76% at 50% 8%, #000 0 34%, transparent 100%);
}

/* ============================================ 3D 透视流动代码层（.bgcode） */
/* 在已有静态网格「之上」再叠一层有纵深感的代码肌理。叠放顺序靠 DOM 保证：
   .bgcode 是 body 的第一个子元素、且同为 z-index: -1；body::before 属于
   伪元素，天然先于真实子元素绘制，所以代码层正好压住网格 —— 无需抬 z-index。

   3D 是怎么做出来的（三层叠加，缺一不可）：
   1. .bgcode 上 perspective: 900px —— 把「相机」推到 900px 外，
      perspective-origin: 50% 0% 把消失点钉在视口顶部中央，
      于是代码列越往上越窄、越往下越宽，视线自然向页面上方退去。
   2. .bgcode-plane 整体 rotateX(18deg) —— 平面向后仰，
      近端（视口底部）离相机近、远端（顶部）离相机远，形成地面般的纵深。
   3. 每列自己的 translateZ(--depth) + rotateY(--rot) —— 把列分别推到
      远近不同的 Z 层、并各转一点点角度，避免六列像贴在同一个平面上。
      注意：这三个值全是「静态行内自定义属性」，绝不进动画。

   为什么用第 3 条而不是旋转整个 plane 来制造层次：rotateX 只能给出一条
   统一的纵深曲线，六列会像同一张纸；translateZ 让每列拥有独立的缩放倍率
   （近大远小由 perspective 自动完成），纵深才有「参差」感。

   性能红线（与上面网格同源）：动画只动 transform，绝不碰
   top / left / margin / width / height / filter / opacity ——
   后四者是布局或重绘属性，每帧重新排版，滚动必掉帧。 */
.bgcode {
  position: fixed;
  inset: 0;
  /* 与 body::before 同为 -1；靠在 DOM 中后绘制而覆盖网格（见上方叠放说明） */
  z-index: -1;
  pointer-events: none;
  overflow: hidden;
  perspective: 900px;
  perspective-origin: 50% 0%;
  /* 纵向遮罩：顶部 12% 才完全显影 —— 让 sticky header 底下是干净的；
     45% 之后开始淡出，80% 归零，代码只在首屏中上部有存在感。
     用 mask 而不是降低整层 opacity：mask 不产生额外合成图层，
     且能在「同一个元素内」做出渐进的、方向性的淡出。 */
  -webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 12%, #000 45%, transparent 80%);
  mask-image: linear-gradient(to bottom, transparent 0%, #000 12%, #000 45%, transparent 80%);
}

/* 平面外扩 20%/10%，因为 rotateX 之后四个角会向里收，
   不外扩就会在屏幕边缘露出被裁掉的地平线断口。 */
.bgcode-plane {
  position: absolute;
  inset: -20% -10%;
  transform-style: preserve-3d;
  transform: rotateX(18deg);
}

/* 单列：静态 3D 定位 + 裁切窗口。height: 100% 撑满 plane，
   overflow: hidden 让内部的无限滚动永远只在这一条竖带里发生。 */
.bgcode-col {
  position: absolute;
  top: 0;
  left: var(--x, 0%);
  width: 220px;
  height: 100%;
  overflow: hidden;
  transform: translateZ(var(--depth, 0)) rotateY(var(--rot, 0));
  /* 只对本层做合成层提示：不向子元素通配，否则 168 行文字各占一层，
     显存与合成开销会翻几十倍。 */
  will-change: transform;
  /* contain 把重排/重绘范围锁在列内。这里用 layout style paint 三件套：
     列的尺寸由父级给定、内容绝不外溢，裁剪与隔离都不会改变绘制结果。 */
  contain: layout style paint;
  /* 上下两段淡出：这是「代码列」而不是「矩形色块」的关键 ——
     没有它，220px 宽的裁切窗口会在顶部与底部留下生硬的直切口。 */
  -webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 6%, #000 82%, transparent 100%);
  mask-image: linear-gradient(to bottom, transparent 0%, #000 6%, #000 82%, transparent 100%);
}

/* 远端更淡：按各列实际的 --depth 分档调透明度。
   记住 translateZ 的符号语义：正值 = 离相机更近（更大更清楚），
   负值 = 更远（更小更淡）。所以 opacity 必须随 --depth 单调递增。
   当前 HTML 六列的 --depth 依次为 -160 / -80 / +40 / -110 / -190 / +70，
   与之对应的档位即下面六个值 —— 若日后 HTML 调整了 --depth，
   这张表必须同步，否则会出现「最近的列反而最淡」的反直觉纵深。
   用 opacity 做「一次性静态」分档：它不参与动画，因此不触发逐帧重绘。 */
.bgcode-col:nth-child(1) { opacity: .55; }  /* --depth: -160px 很远 */
.bgcode-col:nth-child(2) { opacity: .72; }  /* --depth:  -80px 中远 */
.bgcode-col:nth-child(3) { opacity: .92; }  /* --depth:  +40px 偏近 */
.bgcode-col:nth-child(4) { opacity: .65; }  /* --depth: -110px 远 */
.bgcode-col:nth-child(5) { opacity: .48; }  /* --depth: -190px 最远 */
.bgcode-col:nth-child(6) { opacity: 1;   }  /* --depth:  +70px 最近 */

/* 滚动体：只有这一层在动，且只动 transform。
   translate3d 而非 translateY —— 强制走合成器路径，避免主线程重绘。
   -50% 恰好等于一个 chunk 的高度（两个 chunk 完全相同），所以循环无缝。 */
.bgcode-scroll {
  animation: bgcode-flow var(--dur, 44s) linear infinite;
  animation-delay: var(--delay, 0s);
  will-change: transform;
}

@keyframes bgcode-flow {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(0, -50%, 0); }
}

/* chunk 的下内边距就是两个 chunk 之间的缝；它同时被计入 50% 的位移，
   所以缝宽在循环点前后完全一致，不会出现「跳一下」。 */
.bgcode-chunk { display: block; padding-bottom: 24px; }

.bgcode-line {
  display: block;
  white-space: nowrap;
  font-family: 'DM Mono', ui-monospace, SFMono-Regular, Consolas, monospace;
  font-size: 12px;
  line-height: 1.9;
  /* 极低透明度。目标：远看是肌理，近看才认出代码。 */
  color: var(--bg-code-ink);
}

/* 高亮行（sk-*、200 OK 之类）只是「略微」提亮，幅度小到不会形成阅读焦点。
   复用品牌绿，让背景与全站单一强调色的语言保持一致。 */
.bgcode-line.is-accent { color: var(--bg-code-accent); }

/* ====================================== 绿色像素流星（.bgpixels，新增层） */
/* 明确是「叠加」而不是「替换」：上面那层冷白的 3D 透视代码、更下面的静态网格
   与两团光晕，全部原样保留、一个字不动。这一层是本次唯一新增的东西 ——
   一堆随机生成、先由点渐显成绿色小方块、再带拖尾坠落的像素流星。

   为什么把上一版那九条 CSS 渐变条纹整层换成 canvas（这是本次改动的核心决定）：
   旧实现用 repeating-linear-gradient 画出三条 8px 周期的横向断线竖带（读作
   「码行」），再叠六条细的「亮头 + 淡尾」光条。它能做出「在下雨」的观感，
   但有三件事是纯 CSS 表达不了的：
   1. 真随机落点。CSS 背景没有随机函数，旧版只能让九层各自用不同的相位与周期
      去「装作」随机，代价是每条竖带的横坐标永远固定 —— 观感上就成了
      「左右两边各一条竖着的短横线」，这正是用户要移除的东西。
   2. 每颗粒子自己的速度。CSS 动画挂在图层上，整层共用一个时长；而用户要求
      「快的拖尾是一条连续渐隐绿光、慢的拖尾是一串方块」，拖尾形态必须由
      那一颗粒子自身的速度决定 —— 这需要逐粒子的运行时状态。
   3. 由点渐显。像素块要先在某个随机点从「一个点」缩放到满尺寸、同时淡入，
      之后才转为下落；这是两段不同节奏的生命周期，CSS 的单一 keyframes
      没法给每颗粒子独立的出发时间与相位。

   所以这一层改由 canvas 逐帧绘制（见 assets/js/bg-pixels.js），
   随机性、速度、生命周期全部是运行时状态。CSS 这边只负责定位与遮罩。

   性能红线（与其它背景层同源，务必保留）：
   1. 只画实心方块（fillRect），不用滤镜、不用 shadowBlur、不画抗锯齿圆形 ——
      逐帧模糊/阴影是卡顿元凶。
   2. 每帧先 clearRect 再按需重绘，同屏粒子上限由 assets/js/bg-pixels.js 里的 MAX_METEORS 常量决定（当前为 24 颗；这里刻意不写死数字，免得调参后这句又变成过期的假话），池子定长、数组不会无界增长。
   3. 全场只有一个 requestAnimationFrame 循环，按 delta-time 积分，
      与刷新率解耦；页面切到后台立刻停，回到前台再续。
   4. canvas 按 devicePixelRatio 放大（比例上限 2 倍）：高分屏不糊，
      又不会在 3 倍屏上白烧三倍像素填充率。
   5. pointer-events: none，绝不拦截鼠标事件；aria-hidden 由 JS 写在元素上。 */

.bgpixels {
  position: fixed;
  inset: 0;
  /* canvas 是「替换元素」，只给 inset: 0 并不会把它拉满视口 ——
     宽度会落回位图自身的固有尺寸（默认 300×150）。必须显式写 100%，
     位图分辨率则由 JS 按 devicePixelRatio 另行设置，两者互不干扰。 */
  width: 100%;
  height: 100%;
  display: block;
  /* 与 body::before / .bgcode 同为 -1。它由 JS 追加到 body 末尾，
     DOM 中位于 .bgcode 之后；同为负层时，后绘制者覆盖先绘制者，
     于是像素流星压住 3D 代码层，同时仍落在所有正常流内容之下。
     （body 没有 position 也没有 isolation，不会自建层叠上下文，
      所以 -1 确实进入根堆叠上下文的负层 —— 与上面两层的规则完全一致。） */
  z-index: -1;
  pointer-events: none;
  /* 纵向遮罩：取值与旧绿色雨幕完全一致（0% → 12% 淡入、45% 起淡出、80% 归零），
     让流星只在视口上部有存在感：顶部不顶到页头，底部自然收干净、不留硬边。
     这里用 mask 而不是降低整层 opacity：mask 不产生额外合成图层，
     且能在同一个元素内做出方向性的淡出。
     与旧实现的关键差别：canvas 自身 fixed 且不移动，mask 的坐标空间
     就等于视口坐标，不会像旧版那样随整层下移而被反复扫过。 */
  -webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 12%, #000 45%, transparent 80%);
  mask-image: linear-gradient(to bottom, transparent 0%, #000 12%, #000 45%, transparent 80%);
}

/* ------------------------------------------------------------------ 布局 */
.container {
  width: 100%;
  max-width: var(--container);
  margin: 0 auto;
  padding: 0 24px;
}

.container-wide { max-width: 1240px; }

.section { padding: clamp(56px, 7vw, 96px) 0; }
.section-tight { padding: clamp(40px, 5vw, 64px) 0; }
.section + .section { padding-top: 0; }

.eyebrow {
  display: inline-block;
  margin-bottom: 14px;
  color: var(--site-muted);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .13em;
  text-transform: uppercase;
}

.section-head { max-width: 720px; margin-bottom: clamp(28px, 4vw, 44px); }
.section-head h2 { font-size: clamp(24px, 3.4vw, 34px); }
.section-head p { margin-top: 14px; color: var(--site-muted); font-size: 15.5px; line-height: 1.7; }
.section-head-center { margin-left: auto; margin-right: auto; text-align: center; }

.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: absolute;
  left: 16px;
  top: -60px;
  z-index: 200;
  padding: 10px 16px;
  border-radius: 8px;
  background: var(--ink);
  color: var(--app-bg);
  font-size: 13px;
  font-weight: 600;
  transition: top .18s ease;
}
.skip-link:focus-visible { top: 14px; }

/* -------------------------------------------------------------- 站点页头 */
/* 像素 / 终端语言（与 home.css、download.css 同一套，不另起炉灶）
   ---------------------------------------------------------------------
   首页与下载页已经把这两个页面的语言写成了成文约束（home.css 开头第 3、4 条，
   download.css 开头第 1、2 条）：
     · 像素感 = 硬边：2px 实线边框、border-radius: 0、方形色块；
     · 代码感 = 等宽：微标签一律 DM Mono + 大写 + 字距，唯一强调色仍是 --green；
     · 肌理用 repeating-linear-gradient 画 4px 周期的扫描线，静态、不参与动画。
   顶栏是全站共享组件（四张页面都加载 style.css），所以这里只搬用它们已经
   验证过的手法：不引入新色值、不引入新字体，也绝不引入依赖某一页的类名 ——
   顶栏不能反过来依赖 features.html 的样式。

   为什么先拿掉 backdrop-filter
   ---------------------------------------------------------------------
   顶栏原来靠 blur(18px) 把背后的内容糊成一层磨砂。它是整套语言里最刺眼的
   一个例外：模糊把所有边界都抹软，而像素风的全部识别度都建在边界上；更实际
   的是，模糊滤镜在滚动时要连续对整条顶栏的背面重新采样，是这一层最贵的开销。
   换成「半透明纯色 + 静态扫描线 + 2px 硬底边」之后，滚动时顶栏只剩一张既有
   的贴图，没有任何逐帧滤镜。 */
.site-header {
  position: sticky;
  top: 0;
  z-index: 100;
  /* 底边 1px → 2px。1px 在 1.5x / 2x 缩放下会被渲染成半透明的抗锯齿灰线，
     正好与「像素硬边」相反；2px 才是一格稳定、可辨认、放大也不糊的边。 */
  border-bottom: 2px solid var(--line-strong);
  /* 底色分两行写：先用既有的半透明 token 打底（不支持 color-mix 的引擎停在
     这一行），再用 color-mix 把同一枚 --app-bg 调到 94%。去掉模糊以后，84%
     的底会透出下面滚过去的正文，看着像蒙了一层脏雾；94% 仍是半透明（保留
     「浮在内容之上」的层级感），但足以把背后的文字压到看不见。混色只引用
     现有 token，没有新增任何色值。 */
  background-color: var(--site-header);
  background-color: color-mix(in srgb, var(--app-bg) 94%, transparent);
}

/* 顶栏不再使用 backdrop-filter，原来那条能力守卫也就失去了意义。现在真正
   需要兜底的是上面那行 color-mix：不支持它的引擎会退回 84% 半透明底，于是
   这里补一道完全不透明的最终兜底，保证任何情况下导航文字都有足够对比。 */
@supports not (color: color-mix(in srgb, #000 50%, transparent)) {
  .site-header { background-color: var(--app-bg); }
}

/* ---- 顶栏肌理层：两个伪元素，零额外 DOM ---- */
/* ::before = 静态扫描线；::after = 页面载入时从左到右扫过一次的亮带。
   两者都只铺在 64px 的导航带内（top: 0 + height: var(--header-h)），而不是
   inset: 0 —— 因为 .mobile-menu 也是 .site-header 的子元素，展开时会把 header
   撑高；若铺满整个 header，扫描线就会跟着爬到菜单面板上，两种纹理叠一起会脏。
   ::before 先于子元素绘制，所以纹理在文字下面；::after 最后绘制，亮带在文字
   上面掠过 —— 2px 高的一条线，扫过去不会挡住任何字。 */
.site-header::before {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  height: var(--header-h);
  pointer-events: none;
  /* 两层静态肌理：
     1) 底沿一排刻度：4px 亮 / 8px 空、高 2px，紧贴在那条 2px 实线底边之上，
        读起来像终端窗口的「铁轨」细边，是这一层最有辨识度的一笔。
        刻度用 --line-strong 而不是 --green：绿色在这套语言里是「选中 / 交互」
        的专属信号（导航项的 hover 框、当前页下沿、品牌角标、光标都用它），
        再拿绿色去铺一条横贯全宽的刻度，绿色就不再指代任何东西了。
     2) 4px 周期、1px 线宽的扫描线，与 .hp-frame / .dx-panel 完全同款，
        复用背景系统里本来就为「极低透明度肌理」准备的 --bg-grid-line。
     刻度层要单独收成 2px 高，所以用逐层的 background-size/position/repeat；
     不这么做，那条 to right 的渐变会铺满整个 64px 带，变成竖条纹。 */
  background-image:
    repeating-linear-gradient(
      to right,
      var(--line-strong) 0 4px,
      transparent 4px 12px
    ),
    repeating-linear-gradient(
      to bottom,
      var(--bg-grid-line) 0 1px,
      transparent 1px 4px
    );
  background-repeat: no-repeat, repeat;
  background-size: 100% 2px, auto;
  background-position: left bottom, left top;
}

.site-header::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  /* 正好压在那条 2px 底边上方（绝对定位的包含块是顶栏的 padding box，
     所以 top: 64px 落点正是边框所在的那 2px）—— 扫光走的是「底边自己被点亮
     一下」，而不是另外浮一条线。 */
  top: var(--header-h);
  height: 2px;
  pointer-events: none;
  /* 一段两端渐隐的亮带：两端透明，中段是品牌绿夹一小截近白的高光。 */
  background-image: linear-gradient(
    to right,
    transparent 0%,
    var(--green) 38%,
    var(--ink) 50%,
    var(--green) 62%,
    transparent 100%
  );
  background-repeat: no-repeat;
  background-size: 42% 100%;
  /* 起始位置算好的：background-size 42% 时，position-x 的 0%–100% 区间只对应
     元素宽度的 58%（offset = (100% - 42%) × X）。取 -75% 时 offset = -43.5%，
     亮带整段落在左边界之外；取 175% 时 offset = 101.5%，整段落到右边界之外。
     这样「从完全看不见 → 完全看不见」，两端都干净，也不会像 translateX 那样
     在动画结束后给页面留下一条溢出的横向滚动区域（绝对定位元素是会计入可滚动
     溢出量的）。 */
  background-position-x: -75%;
  animation: hdr-sweep 900ms var(--ease-out) forwards;
}

.header-inner {
  display: flex;
  align-items: center;
  gap: 18px;
  height: var(--header-h);
  /* 绘制顺序说明：上面两层装饰都是 .site-header 的绝对定位伪元素，z-index
     为 auto，因此它们落在「定位元素」这一绘制层里 —— 这一层整体高于普通
     文档流内容。若 header-inner 保持 static，扫描线就会盖在品牌与导航文字
     之上（虽然只有 3.5% 的白，仍是不该有的叠色）。这里把它提升为定位元素，
     与两个伪元素同处一层，再靠 DOM 顺序（::before 在最前、header-inner 在
     其后）自然胜出，从而得到「纹理在下、内容在上」的顺序 —— 不用负 z-index，
     也就不用担心它跑到顶栏背景之下而整层消失。 */
  position: relative;
}

/* 锚点跳转避让 sticky 顶栏。
   数值推导：导航带高仍是 --header-h(64px)，但底边从 1px 加粗到 2px，
   顶栏实际占位 = 64 + 2 = 66px；74 = 66 + 8，与原来「栏高 + 8px 呼吸」的
   契约逐字一致。若这里仍写 72px，呼吸就只剩 6px —— 视觉上没错，但破坏了
   「改动栏高时必须同步这里」这条唯一事实来源的语义。 */
#overview, #features, #architecture, #start, #faq, #main, #pain { scroll-margin-top: 74px; }

.brand {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-size: 15px;
  font-weight: 700;
  letter-spacing: -.02em;
  color: var(--ink);
}
/* 基线：两处（顶栏与页脚）共用的 logo 尺寸与圆角，保持原样不动。 */
.brand img { width: 26px; height: 26px; border-radius: 7px; }

/* 像素化改写只作用在顶栏作用域内。
   这里刻意用 `.site-header .brand img` 而不是直接改上面那条共享基线：
   .brand 同时被页脚复用（index / features / download / 404 的页脚都是它），
   而页脚不在本次范围内。顶栏改方角、页脚保持原样，唯一稳妥的写法就是在
   顶栏作用域里覆盖，而不是改基线再给页脚反向写回来（那样一旦漏写一处，
   页脚 logo 就会跟着走形）。
   border-radius: 7px → 0 是这套语言的第一条硬规则（圆角会把观感立刻拉回
   普通网页）。image-rendering: pixelated 只对「栅格图被放大」起作用，本站
   的 logo 是 SVG、且按 26×26 的声明尺寸原样绘制，所以它今天视觉上不改变
   任何东西 —— 但它是「这个图标理应被当作像素图对待」的成文声明，哪天换成
   位图或 2x 资源时会直接生效，而不会因为漏写而糊掉。 */
.site-header .brand img { border-radius: 0; image-rendering: pixelated; }

/* ---- 顶栏品牌：终端窗口标题的写法 ---- */
/* 用 DM Mono 而不是继续用 Inter：顶栏这一排的核心是「终端标题栏」，而
   download.css 里同位置的东西（.dx-term-title）就是 DM Mono + 小字距。
   但保留 "CodeRelay" 的原始大小写，不做大写化 —— 终端标题栏显示的是真实的
   程序名，不是喊出来的标签；大写留给导航项和微标签，层级才不会糊成一片。 */
.site-header .brand {
  position: relative;
  font-family: 'DM Mono', ui-monospace, SFMono-Regular, Consolas, monospace;
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .06em;
}
/* 像素角标：只在左上角画一枚 L 形括号（照抄 .hp-frame / .dx-panel 的 2px
   角标做法）。只画一枚是刻意的：两枚会把 26px 的 logo 夹成取景框，反而
   显得拥挤；一枚在这里刚好起到「这个图标是终端里的一个格」的点题作用。 */
.site-header .brand::before {
  content: '';
  position: absolute;
  top: -4px;
  left: -5px;
  width: 7px;
  height: 7px;
  border-top: 2px solid var(--green);
  border-left: 2px solid var(--green);
  pointer-events: none;
}
/* 品牌后的方块光标：终端里「等待输入」的那一格。
   用 flex 伪元素而不是绝对定位 —— .brand 是 inline-flex，::after 会自然排在
   文字之后并吃到 gap，字号变化时也不会错位。
   steps(1, end) 保证是「啪」地亮灭而不是淡入淡出（淡入淡出立刻变得像网页）。 */
.site-header .brand::after {
  content: '';
  /* 不让它参与 flex 收缩：6px 宽的格子一旦被压缩，光标就不再是「一格里的一格」了。 */
  flex: 0 0 auto;
  width: 6px;
  height: 13px;
  margin-left: -4px;   /* 抵消 gap，让光标贴住最后一字，像紧跟其后的输入点 */
  background: var(--green);
  animation: hdr-blink 1.15s steps(1, end) infinite;
}

.header-nav { display: flex; align-items: center; gap: 2px; margin-left: 12px; }

/* ---- 导航项：2px 硬框 + 等宽大写 ---- */
/* 尺寸判断：12.5px + .105em 字距。DM Mono 在大写状态下的字面宽度比 Inter
   大，13px 配 .14em 会让 6 个中文/英文混排的项挤成一片；12.5/.105 既保住
   「等宽 + 大字距」这个最强的系统信号，又留出了点击区。
   边框平时为 transparent 而不是 none：所有项恒占 2px，hover 变色时才不会
   让相邻项跳一格（这正是「硬边」最忌讳的抖动）。 */
.nav-link {
  padding: 7px 10px;
  border: 2px solid transparent;
  border-radius: 0;
  color: var(--site-muted);
  font-family: 'DM Mono', ui-monospace, SFMono-Regular, Consolas, monospace;
  font-size: 12.5px;
  font-weight: 500;
  letter-spacing: .105em;
  text-transform: uppercase;
  /* 等宽大写比原来的 Inter 13.5px 更宽（英文 "GET STARTED" 一项就要 ~100px），
     901–1080px 这一档最紧。绝不能让标签折行：顶栏高度是写死的 64px，一旦
     折成两行，项会从栏里顶出去。禁止换行后若仍然放不下，由下面
     max-width: 1080px 那条媒体查询统一缩字号来兜底。 */
  white-space: nowrap;
  position: relative;
  /* 颜色与边框用 linear 120ms：线性插值在中段也不会「先快后慢」，观感更接近
     一个硬开关；位移才用 --ease-out 收一下尾。整套交互不碰 background，
     所以不存在大面积的柔光淡入。 */
  transition: color var(--anim-fast) linear,
              border-color var(--anim-fast) linear,
              transform var(--anim-fast) var(--ease-out);
}
.nav-link:hover {
  color: var(--ink);
  border-color: var(--green);
  transform: translateY(-1px);
}
/* 当前页：绿色下沿色块（与「导航项本身的 2px 框」同一套几何，不做左侧竖条，
   否则一横一竖两种强调方式并存，反而不成体系）。
   注意这里**不写 border-color**：框是「指针正指着它」的瞬时反馈，下沿是
   「你就在这里」的持久状态，两者同属一套几何但不该同时出现 —— 当前页在
   未悬停时只有一条绿下沿，悬停时下沿之外再亮起绿框。 */
.nav-link[aria-current='page'] {
  color: var(--ink);
  /* DM Mono 只加载了 400/500 两档，600 会触发合成加粗、在等宽字上糊成一片；
     当前页靠颜色 + 绿色下沿区分，不靠字重。 */
  font-weight: 500;
}
.nav-link::after {
  content: '';
  position: absolute;
  left: -2px;
  right: -2px;
  bottom: -2px;
  height: 2px;
  background: var(--green);
  /* 平时 scaleX(0) 而不是 opacity: 0 —— 缩放在合成器上跑，且能从 0 宽
     「长」出来，比淡入更像一条线被画上去。 */
  transform: scaleX(0);
  transform-origin: left center;
  pointer-events: none;
}
.nav-link[aria-current='page']::after {
  transform: scaleX(1);
  animation: hdr-active-in var(--anim-slow) var(--ease-out) both;
}

.header-actions { display: flex; align-items: center; gap: 8px; margin-left: auto; }

/* ---- 图标按钮 / 语言开关：方形、2px 框、等宽 ---- */
.icon-button {
  display: inline-grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border: 2px solid transparent;
  border-radius: 0;
  background: transparent;
  color: var(--site-muted);
  cursor: pointer;
  transition: color var(--anim-fast) linear, border-color var(--anim-fast) linear;
}
.icon-button:hover { color: var(--ink); background: transparent; border-color: var(--green); }
.icon-button[aria-pressed='true'] { color: var(--ink); border-color: var(--green); }

.lang-toggle {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 6px 10px;
  border: 2px solid var(--line-strong);
  border-radius: 0;
  background: transparent;
  color: var(--ink-soft);
  font-family: 'DM Mono', ui-monospace, SFMono-Regular, Consolas, monospace;
  font-size: 11.5px;
  font-weight: 500;
  letter-spacing: .08em;
  cursor: pointer;
  transition: border-color var(--anim-fast) linear, color var(--anim-fast) linear;
}
/* hover 只把边框点亮成品牌绿：底色保持在透明，避免出现一块会「呼吸」的灰面。 */
.lang-toggle:hover { color: var(--ink); border-color: var(--green); background: transparent; }
.lang-toggle .lang-sep { color: var(--line-strong); font-weight: 400; }
.lang-toggle .lang-active { color: var(--ink); }
.lang-toggle .lang-idle { color: var(--subtle); }

.menu-button { display: none; }

.mobile-menu {
  display: none;
  /* 1px → 2px，与顶栏底边同宽：菜单面板是从顶栏「延长」出来的一格，
     它和顶栏之间的分界也必须是同一条硬边。 */
  border-top: 2px solid var(--line-strong);
  background-color: var(--site-overlay);
  /* 与 .hp-frame / .dx-panel 同款的 4px 周期扫描线，静态铺一次。 */
  background-image: repeating-linear-gradient(
    to bottom,
    var(--bg-grid-line) 0 1px,
    transparent 1px 4px
  );
  padding: 10px 24px 18px;
}
/* 原 `.mobile-menu.is-open { display: block }` 已移除：
   它的特异性 (0,2,0) 高于窄屏媒体查询里的 `.mobile-menu` (0,1,0)，
   会造成「窄屏打开后拉宽窗口，菜单残留」。改由动效区的 max-height +
   visibility 方案控制显隐，桌面宽度下恒为 display:none。 */
.mobile-menu a {
  display: block;
  padding: 11px 6px;
  border-bottom: 0;
  color: var(--ink-soft);
  font-family: 'DM Mono', ui-monospace, SFMono-Regular, Consolas, monospace;
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .1em;
  text-transform: uppercase;
  /* 像素分隔线：不用 1px 实线（细到像浏览器默认分隔符），改用 4px 亮 / 4px 空的
     虚点带 —— 与 .dx-kicker-line 完全同一个节奏，是这套语言里「虚线」的标准写法。
     用背景图而不是 border-bottom，是为了让分隔线严格 2px、且不参与盒子尺寸计算。 */
  background-image: repeating-linear-gradient(
    to right,
    var(--line-strong) 0 4px,
    transparent 4px 8px
  );
  background-repeat: no-repeat;
  background-size: 100% 2px;
  background-position: left bottom;
  /* 补过渡：原来 hover 变绿是硬跳变。只过渡颜色，位移/底色不动。 */
  transition: color var(--anim-fast) linear, box-shadow var(--anim-fast) linear;
}
.mobile-menu a:last-of-type { background-image: none; }
/* hover 用 inset 硬色条而不是换底色：不产生任何位移，也没有柔和面。 */
.mobile-menu a:hover {
  color: var(--green);
  box-shadow: inset 3px 0 0 var(--green);
}
/* 移动菜单里的当前页：同样用左侧绿条，和桌面导航的绿色强调保持一致。 */
.mobile-menu a[aria-current='page'] {
  color: var(--ink);
  box-shadow: inset 3px 0 0 var(--green);
}

/* ------------------------------------------------------------------ 按钮 */
.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 44px;
  padding: 0 20px;
  border: 1px solid transparent;
  border-radius: 9px;
  font-size: 14px;
  font-weight: 600;
  letter-spacing: -.005em;
  cursor: pointer;
  transition: background .16s ease, border-color .16s ease, color .16s ease, transform .12s ease;
}
.button:active { transform: translateY(1px); }

.button-primary { background: var(--accent); border-color: var(--accent); color: var(--app-bg); }
.button-primary:hover { background: var(--ink-strong); border-color: var(--ink-strong); }

.button-secondary {
  background: transparent;
  border-color: var(--line-strong);
  color: var(--ink-soft);
}
.button-secondary:hover { background: var(--surface-hover); border-color: var(--muted); color: var(--ink); }

.button-small { min-height: 36px; padding: 0 14px; font-size: 13px; border-radius: 8px; }

.button svg { flex: 0 0 auto; }

/* ------------------------------------------------------------------ Hero */
/* 首屏只放 Hero：高度 = 一屏减去 sticky 顶栏，于是 hero 与顶栏相加正好等于
   一个视口高度，页脚（.site-footer）的顶边落在 100vh 处，首屏看不到，必须
   往下滚才出现。这是纯 CSS 的尺寸安排，没有隐藏、没有 fixed 定位、也没有
   JS —— 页脚始终可被键盘和屏幕阅读器正常访问，只是位置在折线以下。
   1. 两个 min-height 声明是渐进增强：先给旧浏览器 vh 兜底，再给支持 svh
      的浏览器覆盖。svh 取「地址栏展开时」的最小视口高度，移动端浏览器
      滚动时地址栏收展不会让首屏高度跳动、也不会把页脚挤进视口。
   2. 用 min-height 而不是 height：视口很矮（或用户放大字号）时内容比一屏
      更高，此时区块要能自然长高，而不是被固定高度裁掉、溢出到页脚身上。
   3. flex 纵向 + justify-content: center 把内容在剩余空间里垂直居中；
      因为方向是 column，交叉轴是水平方向，子元素默认 stretch，.hero-inner
      仍是 width: 100% 的容器并保留自己的 max-width: 860px; margin: 0 auto，
      居中方式与原来完全一致。这里刻意不写 align-items: center，那会让
      容器收缩成内容宽度，破坏横向布局。
   4. 原有 padding 保留不动：矮视口下它不是多余的，而是给上下留出呼吸，
      避免内容贴住顶栏或吃满整屏。
   5. 减数用 var(--header-h) 而不是字面量 64px，顶栏高度与这里同源，不会
      各改各的。
   6. 视觉重心明确上移到约 40% 处：justify-content: center 把内容压在剩余
      空间的正中，上下 padding 相等时几何中心就是视觉中心 —— 但首屏实测
      顶栏到徽标之间那块空白太大，内容压在中线上显得「吊在半空」。这里把
      下 padding 加厚 min(23vh, 240px)：flex 的内容盒因此整体收缩，内容盒
      的中心随之上移，内容也跟着上浮 —— 上浮量恰好是「新增下 padding 的
      一半」，即 (23vh - 4vh) / 2 ≈ 9.5vh。以 1044px 高的视口为例，内容中心
      从剩余空间的 50% 升到约 40%，上方留白变窄、下方留白约为上方的两倍。
      不改 min-height，所以「hero + 顶栏 = 一屏」的关系和页脚留在折线以下
      的结论都不变。
      为什么不用 transform: translateY()：transform 只改绘制位置、不改布局盒，
      内容上移了而盒子还留在原处，与下方区块的间距、滚动高度都不跟随，还会
      被祖先的 overflow 裁掉；加厚下 padding 改的是真实布局盒，后面所有区块
      一起跟着走。
      为什么不用 justify-content: flex-start + 固定像素 padding-top：那等于把
      偏移量写死，矮视口或用户放大字号时内容会直接贴住顶栏，也丧失了「内容
      高于一屏时上下都能自然让位」的能力。23vh 只在高度足够的视口里成立，
      矮视口由下方的 max-height 查询回落到 8vh。 */
.hero {
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: calc(100vh - var(--header-h) - var(--hero-ticker-reserve));
  min-height: calc(100svh - var(--header-h) - var(--hero-ticker-reserve));
  padding: clamp(48px, 7vw, 84px) 0 clamp(28px, 4vw, 40px);
  /* 单独追加下内边距，把 flex 的居中轴整体抬高（机制见上方第 6 条）。
     上浮量 = 该额外值的一半，23vh ≈ 内容上移 11.5vh 减去原有的 2vh。
     封顶 240px：视口更高时不再按比例加厚，避免在超高窗口里把内容推得
     离顶栏过近。
     这里同样扣掉 --hero-ticker-reserve（首屏为走马灯让出的那段）：min-height
     与下内边距同时减同一个 d，内容盒可用高度 A = (min-height − d) − pt −
     (pb − d) = A 分毫不变，内容的绝对位置一格不动，只有 hero 的盒子与下方
     那块空白一起短掉 d。max(0px, …) 不是装饰：矮视口里 min(23vh, 240px)
     会小于预留值，直接相减得负数，而 padding 的合法范围是 [0, ∞)，越界
     结果在不同引擎上处理并不一致，显式夹到 0 才与引擎无关。 */
  padding-bottom: calc(clamp(28px, 4vw, 40px) + max(0px, min(23vh, 240px) - var(--hero-ticker-reserve)));
  text-align: center;
}
.hero-inner { max-width: 860px; margin: 0 auto; }

.hero-badges { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 8px; margin-bottom: 22px; }

.badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 11px;
  border: 1px solid var(--line-strong);
  border-radius: 99px;
  background: var(--surface-soft);
  color: var(--site-muted);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: .01em;
}
.badge-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--green); flex: 0 0 auto; }

.hero h1 {
  font-size: clamp(30px, 5.2vw, 54px);
  font-weight: 700;
  letter-spacing: -.045em;
  line-height: 1.14;
  text-wrap: balance;
}
/* 标题里的两个 span 各自占一行：断行是设计的一部分，不是视口太窄的副作用。
   为什么不用 <br>：两个 span 分别挂着 hero.title.a / hero.title.b 两个 i18n 键，
   切换语言时 setI18nText 只替换文本节点，元素结构必须原样保留；<br> 若夹在
   两者之间，既不属于任何一个键，也会在语言切换后与新的断句位置打架。
   为什么不用 flex/column：那会把两行变成两个独立盒，行高与 letter-spacing 的
   视觉密排会被拉开；display:block 只改行的排布，字号、行高、字距全部沿用 h1
   自身的设置，两行读起来仍是同一个标题。 */
.hero h1 > span { display: block; }

.hero h1 .accent { color: var(--green); }

.hero-lead {
  max-width: 660px;
  margin: 20px auto 0;
  color: var(--site-muted);
  font-size: clamp(15px, 1.7vw, 17.5px);
  line-height: 1.72;
  text-wrap: pretty;
}

.hero-actions { display: flex; flex-wrap: wrap; justify-content: center; gap: 12px; margin-top: 30px; }

.hero-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 8px 18px;
  margin-top: 22px;
  color: var(--site-muted);
  font-size: 12.5px;
}
.hero-meta span { display: inline-flex; align-items: center; gap: 6px; }
.hero-meta svg { color: var(--green); flex: 0 0 auto; }

/* ==================================================================
   界面复刻（核心区块）
   结构逐项对照产品 src/App.tsx 的 sidebar / titlebar / OverviewPage /
   statusbar，尺寸与字号照抄 src/styles.css 的对应值，仅做等比与主题化。
   ================================================================== */
.replica-wrap { padding: 0 0 clamp(20px, 3vw, 34px); }

/* 本区块内的容器单独收窄。窗口整体缩小后，若两侧仍撑到 1240px 满幅，
   窗口会显得被孤零零扔在一大片留白里；收到 1040px 后它与上下各区块的
   文字列宽更接近，读起来像一个被刻意安排的展示台。
   只绑定 .replica-wrap，绝不改动全站共用的 .container-wide。 */
.replica-wrap .container-wide { max-width: 1040px; }

.replica-scroll {
  overflow-x: auto;
  overflow-y: hidden;
  border-radius: 16px;
  -webkit-overflow-scrolling: touch;
}

/* 窗口框：细描边 + 微阴影，让复刻块看起来像真的应用在展示 */
.ui-window {
  min-width: 940px;
  overflow: hidden;
  border: 1px solid var(--line-strong);
  border-radius: 16px;
  background: var(--app-bg);
  box-shadow: 0 30px 70px rgba(0, 0, 0, .5), 0 3px 10px rgba(0, 0, 0, .34);

  /* ---- 整体等比缩小到 88%（一个数就是全部旋钮）----
     为什么用 zoom 而不是把内部那几十条写死的 px 逐条改小：这个窗口是照着
     产品 src/styles.css 逐值复刻的，侧栏 62px、面板内边距、9–11px 的微标签、
     条宽 8px……全是彼此咬合的固定值。逐条按比例改，等于把「等比例」这件
     事手工重复几十遍，错一个数字比例就断，而且以后想再调大小要整轮重来。
     zoom 在布局阶段把所有长度（字号、内边距、边框、阴影、min-width）统一
     乘同一个系数，比例天然自洽，调大小只动下面这一个数。

     为什么不用 transform: scale()：transform 不参与布局，元素原先占的盒子
     仍按原尺寸留在原处，缩完右侧和下方各空出一截，滚动容器也感知不到宽度
     变化，那块死白只能用负外边距或写死高度去补，反而更脆。zoom 影响布局，
     缩完占位就是缩完的大小，不留空隙。

     两个尺寸各管一头，合起来才是「整体小一圈」：
       · 高度、字号、内边距、描边、阴影、以及 min-width —— 全部长度被 zoom
         乘 0.88，所以窗口矮了约 12%，内部细节同步等比变小；
       · 外框宽度不归 zoom 管（zoom 不作用于 auto / 百分比宽度，块级宽度
         仍是铺满容器），所以由上面那条收窄后的 .replica-wrap .container-wide
         来收：容器宽 1240 → 1040，窗口随之从约 1192px 变成约 992px。
     横向溢出逻辑不受影响：min-width: 940px 缩放后的用值约 827px，窄屏上
     窗口依旧比容器宽，.replica-scroll 照常可以左右滑动，不会被裁掉；
     @media (max-width: 420px) 里的 min-width: 860px 同理自动按 88% 生效，
     无需另改一条规则。浏览器不支持 zoom 时只是退回原始尺寸（只剩容器收窄），
     版式不会坏。 */
  zoom: .88;

  /* 兜底居中对齐：不同引擎对「带 zoom 的块级 auto 宽度」处理不一致，
     若窗口最终比容器窄，这条 auto 外边距让它居中而不是贴着左边留一大块空白；
     若窗口正好铺满容器，自由空间为 0，auto 外边距解析为 0，等于没有这条。
     窄屏上窗口比容器宽时同样解析为 0，不会顶出负外边距、也不会干扰横向滚动。 */
  margin-inline: auto;
}

/* 新增可见标题后必须补的上内边距：.replica-wrap 原本 padding-top 为 0，
   而它又是 <main> 里的第一块，标题会直接怼在 sticky 顶栏的下边框上。
   之前这里是个带描边的窗口，贴着顶栏还算凑合；现在第一行是 11px 的小字眉标，
   零间距就只剩「被截断」的观感。只在本区块内补，不动 .section 的通用节奏。 */
.replica-wrap .section-head { padding-top: clamp(30px, 4.5vw, 56px); }

.ui-shell {
  display: flex;
  align-items: stretch;
  min-height: 0;
  background:
    radial-gradient(circle at 18% -10%, rgba(255, 255, 255, .035), transparent 31%),
    var(--app-bg);
}

/* —— 左侧窄栏导航（App.tsx .sidebar / .nav-rail）—— */
.ui-sidebar {
  display: flex;
  flex: 0 0 62px;
  flex-direction: column;
  align-items: center;
  gap: 0;
  padding: 14px 10px 14px;
  border-right: 1px solid var(--line);
  background: var(--site-chrome);
}

.ui-brandmark {
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border-radius: 10px;
  background: var(--ink);
  color: var(--app-bg);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: -.06em;
  line-height: 1;
  box-shadow: inset 0 1px rgba(255, 255, 255, .18), 0 5px 12px rgba(0, 0, 0, .12);
}

.ui-nav { display: flex; flex: 1; flex-direction: column; align-items: center; gap: 6px; padding-top: 24px; width: 100%; }

.ui-nav-icon {
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--subtle);
  cursor: default;
  transition: background 150ms ease, color 150ms ease, transform 100ms ease;
}
.ui-nav-icon.is-active {
  color: var(--ink);
  background: var(--surface-hover);
  box-shadow: none;
}

.ui-sidebar-bottom { display: flex; flex-direction: column; align-items: center; gap: 10px; padding-bottom: 2px; }
.ui-avatar {
  display: grid;
  place-items: center;
  width: 27px;
  height: 27px;
  border: 1px solid var(--line-strong);
  border-radius: 50%;
  background: var(--surface-hover);
  color: var(--ink-soft);
  font-size: 11px;
  font-weight: 600;
}

/* —— 分组式导航 + 弹出层（对齐真实项目 App.tsx 的 navGroups / nav-popover）——
   真实项目是「5 个分组图标 → 悬停弹出该组的叶子项」。复刻块沿用同一层级模型，
   但作为展示件：按钮 tabindex="-1" 不参与 Tab 序列（避免干扰真实导航），
   弹出靠 hover / focus-within 触发。 */
.ui-nav-group {
  position: relative;
  display: flex;
  justify-content: center;
  width: 100%;
}

/* 复刻块里的图标是 button，需抹掉 UA 默认样式；
   cursor 用 pointer —— 它现在真的可交互，不再是纯装饰。 */
button.ui-nav-icon {
  padding: 0;
  font: inherit;
  appearance: none;
  -webkit-appearance: none;
  cursor: pointer;
}
button.ui-nav-icon:hover,
button.ui-nav-icon:focus-visible {
  color: var(--ink);
  background: var(--surface-hover);
}
button.ui-nav-icon:active {
  transform: scale(.94);
}

/* 弹出层：默认隐藏，分组 hover 或内部聚焦时出现。
   定位到图标右侧：left 用 100% + 小间距，垂直居中。
   nowrap + 固定 max-content 宽度，避免文字换行。 */
.ui-nav-popover {
  position: absolute;
  top: 50%;
  left: calc(100% + 8px);
  z-index: 30;
  display: flex;
  min-width: 132px;
  flex-direction: column;
  gap: 2px;
  padding: 8px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--surface-solid);
  box-shadow: 0 10px 28px rgba(0, 0, 0, .18), 0 2px 6px rgba(0, 0, 0, .08);
  white-space: nowrap;

  /* 隐藏态：不可见 + 不接收指针 + 略微内缩 */
  visibility: hidden;
  opacity: 0;
  transform: translate(-4px, -50%) scale(.96);
  transform-origin: left center;
  transition:
    opacity 140ms var(--ease-out),
    transform 140ms var(--ease-out),
    visibility 0s linear 140ms;
}

.ui-nav-group:hover > .ui-nav-popover,
.ui-nav-group:focus-within > .ui-nav-popover {
  visibility: visible;
  opacity: 1;
  transform: translate(0, -50%) scale(1);
  transition-delay: 0s, 0s, 0s;
}

.ui-popover-label {
  padding: 3px 8px 5px;
  color: var(--muted);
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
}

.ui-popover-item {
  padding: 5px 8px;
  border-radius: 6px;
  color: var(--ink-soft);
  font-size: 12px;
  line-height: 1.4;
  cursor: default;
}
.ui-popover-item:hover { background: var(--surface-hover); color: var(--ink); }
.ui-popover-item.is-active { background: var(--surface-hover); color: var(--ink); font-weight: 600; }

/* 减少动效：popover 直接出现/消失，不做位移与淡入 */
@media (prefers-reduced-motion: reduce) {
  .ui-nav-popover { transition: none; }
  button.ui-nav-icon:active { transform: none; }
}

/* —— 主列 —— */
.ui-main { display: flex; flex: 1; min-width: 0; flex-direction: column; }

/* 自定义无边框标题栏（.titlebar）：左侧品牌面包屑 + 右侧窗口控件位 */
.ui-titlebar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 56px;
  padding: 0 18px 0 26px;
  border-bottom: 1px solid var(--line);
  background: var(--site-chrome);
  user-select: none;
}
.ui-titlebar-left { display: flex; align-items: center; gap: 9px; color: var(--muted); font-size: 12px; }
.ui-title-brand { color: var(--ink-strong); font-weight: 700; letter-spacing: -.02em; }
.ui-title-sep { color: var(--subtle); }
.ui-title-current { color: var(--muted); }

.ui-window-controls { display: flex; align-items: center; gap: 2px; }
.ui-wc {
  display: grid;
  place-items: center;
  width: 31px;
  height: 29px;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--subtle);
  cursor: default;
}
.ui-wc.is-close:hover { color: #b33c3c; background: #fceeed; }

/* —— 内容区 —— */
.ui-content { flex: 1; min-height: 0; padding: 24px 30px 26px; }

.ui-page-intro {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 26px;
  margin-bottom: 20px;
}
.ui-eyebrow {
  color: var(--subtle);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: .11em;
  line-height: 1.3;
  text-transform: uppercase;
}
.ui-page-intro h1,
.ui-h1 {
  margin: 8px 0 7px;
  color: var(--ink);
  font-size: 27px;
  font-weight: 600;
  letter-spacing: -.045em;
  line-height: 1.12;
}
.ui-page-intro p { margin: 0; color: var(--muted); font-size: 12px; line-height: 1.55; }

.ui-intro-actions { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; }
.ui-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  min-height: 32px;
  padding: 0 12px;
  border: 1px solid var(--line-strong);
  border-radius: 7px;
  background: var(--surface-soft);
  color: var(--ink-soft);
  font-size: 12px;
  font-weight: 600;
  cursor: default;
  white-space: nowrap;
}
.ui-btn.icon-only { width: 32px; padding: 0; }

/* 统计视图切换栏（.stats-view-bar / .view-toggle） */
.ui-stats-bar { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin: 0 0 14px; }
.ui-view-toggle {
  display: inline-flex;
  padding: 3px;
  gap: 3px;
  border: 1px solid var(--line);
  border-radius: 9px;
  background: var(--surface-soft);
}
.ui-view-toggle span {
  padding: 5px 15px;
  border-radius: 7px;
  color: var(--muted);
  font-size: 12px;
  font-weight: 600;
}
.ui-view-toggle span.is-active {
  background: var(--surface-hover);
  color: var(--ink);
  box-shadow: none;
}

.ui-date-trigger {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  min-height: 32px;
  padding: 0 12px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface);
  color: var(--ink-soft);
  font-size: 12px;
  font-weight: 600;
}

/* 面板基类（.panel） */
.ui-panel {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow);
}

/* 状态卡 + 4 张指标卡（.overview-grid） */
.ui-overview {
  display: grid;
  grid-template-columns: minmax(250px, 1.6fr) repeat(4, minmax(112px, 1fr));
  gap: 10px;
  margin-bottom: 10px;
}

.ui-hero-status {
  display: flex;
  min-height: 168px;
  flex-direction: column;
  padding: 15px 17px 13px;
  border-color: rgba(82, 177, 123, .32);
  background: linear-gradient(145deg, var(--green-soft), var(--surface-solid));
}
.ui-topline { display: flex; align-items: center; justify-content: space-between; }
.ui-kicker {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--subtle);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: .11em;
  text-transform: uppercase;
}
.ui-hero-value { margin-top: 24px; color: var(--ink); font-size: 21px; font-weight: 600; letter-spacing: -.04em; }
.ui-hero-status p { margin: 6px 0 0; color: var(--muted); font-size: 11px; line-height: 1.5; }
.ui-hero-foot {
  display: flex;
  align-items: center;
  gap: 18px;
  margin-top: auto;
  padding-top: 13px;
  border-top: 1px solid var(--line);
}
.ui-hero-foot > div { display: flex; flex-direction: column; gap: 3px; }
.ui-hero-foot span { color: var(--subtle); font-size: 10px; }
.ui-hero-foot strong { color: var(--ink-strong); font-size: 11px; font-weight: 600; }
.ui-inline-link {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-left: auto;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--ink-soft);
  font-size: 11px;
  font-weight: 600;
  white-space: nowrap;
}

/* 指标卡（.metric-card） */
.ui-metric { display: flex; min-height: 168px; flex-direction: column; padding: 14px 13px; }
.ui-metric-icon {
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  margin-bottom: 16px;
  border-radius: 9px;
}
.ui-metric-icon.blue { color: var(--blue); background: var(--blue-soft); }
.ui-metric-icon.purple { color: var(--purple); background: var(--purple-soft); }
.ui-metric-icon.green { color: var(--green); background: var(--green-soft); }
.ui-metric-icon.orange { color: var(--orange); background: var(--orange-soft); }
.ui-metric-label { color: var(--muted); font-size: 11px; }
.ui-metric strong { margin-top: 5px; color: var(--ink); font-size: 22px; font-weight: 600; letter-spacing: -.045em; }
.ui-metric small { margin-top: auto; color: var(--subtle); font-size: 10px; }

/* 双栏（.two-column-grid）：请求统计图表 + 账号池健康度 */
.ui-two-col { display: grid; grid-template-columns: 1.4fr 1fr; gap: 10px; margin-bottom: 10px; }
.ui-chart-panel, .ui-health-panel { padding: 16px 17px; }
.ui-panel-heading { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.ui-panel-heading h3 { margin: 6px 0 0; color: var(--ink-strong); font-size: 14px; font-weight: 600; letter-spacing: -.02em; }
.ui-heading-side { color: var(--subtle); font-size: 10px; white-space: nowrap; }

/* 图例（.chart-legend） */
.ui-legend {
  display: flex;
  align-items: center;
  gap: 13px;
  margin-top: 14px;
  color: var(--subtle);
  font-size: 10px;
}
.ui-legend > span { display: inline-flex; align-items: center; gap: 5px; }
.ui-legend .legend-dot { width: 6px; height: 6px; border-radius: 2px; flex: 0 0 auto; }
.ui-legend .legend-dot.hit { background: var(--green); }
.ui-legend .legend-dot.miss { background: var(--orange); }
.ui-legend .ui-legend-avg { margin-left: auto; }
.ui-legend .ui-legend-avg strong { margin-left: 3px; color: var(--ink-soft); font-weight: 600; }

/* 柱状图（.bar-chart）：24 根柱 = 8 个 3 小时桶 × 命中/未命中 两段 */
.ui-bars {
  display: flex;
  align-items: flex-end;
  justify-content: space-around;
  height: 118px;
  margin-top: 10px;
  padding: 8px 2px 0;
  border-bottom: 1px solid var(--line);
  background: repeating-linear-gradient(to bottom, transparent 0, transparent 27px, var(--site-chart-grid) 28px);
}
.ui-bar-col {
  display: flex;
  flex: 1;
  height: 100%;
  flex-direction: column;
  align-items: center;
  justify-content: flex-end;
  gap: 5px;
  color: var(--subtle);
  font-family: 'DM Mono', monospace;
  font-size: 9px;
}
.ui-bar-stack { display: flex; align-items: flex-end; height: 82px; gap: 2px; }
.ui-bar {
  display: block;
  width: 8px;
  border-radius: 3px 3px 0 0;
  transform-origin: bottom center;
  transform: scaleY(0);
  transition: transform .5s cubic-bezier(.16, 1, .3, 1);
  transition-delay: calc(var(--i, 0) * 30ms);
}
.ui-bar.hit { background: var(--green); }
.ui-bar.miss { background: var(--orange); }

/* 环形进度（.score-ring 用等宽 border 画，这里改用 conic-gradient 以便精确表达百分比） */
.ui-health-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.ui-health-score { display: flex; align-items: center; gap: 14px; margin: 18px 0 16px; }
.ui-ring {
  position: relative;
  display: grid;
  place-items: center;
  width: 68px;
  height: 68px;
  flex: 0 0 auto;
  border-radius: 50%;
  background: conic-gradient(var(--green) 0 calc(var(--pct, 0) * 1%), var(--site-ring-track) calc(var(--pct, 0) * 1%) 100%);
}
.ui-ring::after {
  content: '';
  position: absolute;
  inset: 7px;
  border-radius: 50%;
  background: var(--surface-solid);
}
.ui-ring-label {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: baseline;
  color: var(--ink);
  font-size: 16px;
  font-weight: 600;
  letter-spacing: -.05em;
}
.ui-ring-label span { margin-left: 1px; font-size: 9px; font-weight: 600; }
.ui-health-copy { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.ui-health-copy strong { color: var(--ink-strong); font-size: 12px; }
.ui-health-copy p { color: var(--muted); font-size: 10px; }
.ui-health-copy b { color: var(--ink-soft); font-weight: 600; }

.ui-health-list {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 11px 0;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}
.ui-health-list > div { display: flex; flex-direction: column; gap: 5px; }
.ui-health-list strong { color: var(--ink-strong); font-size: 15px; font-weight: 600; }
.ui-health-label { display: flex; align-items: center; gap: 5px; color: var(--subtle); font-size: 10px; }
.tiny-dot { width: 6px; height: 6px; border-radius: 50%; flex: 0 0 auto; }
.tiny-dot.green { background: var(--green); }
.tiny-dot.yellow { background: var(--yellow); }
.tiny-dot.red { background: var(--red); }

.ui-accounts { margin-top: 11px; }
.ui-account-row {
  display: flex;
  align-items: center;
  gap: 9px;
  min-height: 26px;
  color: var(--ink-soft);
  font-size: 10.5px;
}
.ui-account-row .ui-acc-mail { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ui-account-row code {
  color: var(--muted);
  font-size: 9.5px;
}

/* 最近活动 / 请求日志摘要（.activity-panel / .mini-log） */
.ui-activity-panel { padding: 15px 17px 10px; }
.ui-mini-logs { margin-top: 12px; }
.ui-mlog {
  display: flex;
  align-items: center;
  gap: 11px;
  min-height: 44px;
  border-top: 1px solid var(--line);
}
.ui-log-icon {
  display: grid;
  place-items: center;
  width: 18px;
  height: 18px;
  flex: 0 0 auto;
  border-radius: 50%;
}
.ui-log-icon.ok { color: var(--green); background: var(--green-soft); }
.ui-log-icon.fail { color: var(--red); background: var(--red-soft); }
.ui-mlog-main { display: flex; flex: 1; flex-direction: column; gap: 2px; min-width: 0; }
.ui-mlog-main strong { overflow: hidden; color: var(--ink-strong); font-size: 11px; font-weight: 600; text-overflow: ellipsis; white-space: nowrap; }
.ui-mlog-main span {
  overflow: hidden;
  color: var(--subtle);
  font-size: 10px;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ui-mlog-time { color: var(--subtle); font-family: 'DM Mono', monospace; font-size: 10px; flex: 0 0 auto; }

/* 状态 Pill（.status-pill） */
.ui-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  width: fit-content;
  padding: 4px 8px;
  border: 1px solid transparent;
  border-radius: 99px;
  font-size: 10px;
  font-weight: 600;
  white-space: nowrap;
  flex: 0 0 auto;
}
.ui-pill .ui-pill-dot { width: 5px; height: 5px; border-radius: 50%; background: currentColor; flex: 0 0 auto; }
.ui-pill.success { color: var(--green); background: var(--green-soft); border-color: rgba(82, 177, 123, .35); }
.ui-pill.muted { color: var(--muted); background: var(--surface-hover); border-color: var(--line-strong); }
.ui-code-status { font-family: 'DM Mono', monospace; font-size: 10px; font-weight: 500; flex: 0 0 auto; }
.ui-code-status.ok { color: var(--green); }
.ui-code-status.fail { color: var(--red); }

/* 底部持久状态栏（.statusbar） */
.ui-statusbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  min-height: 42px;
  padding: 0 15px 0 22px;
  border-top: 1px solid var(--line);
  background: var(--site-chrome);
}
.ui-statusbar-left, .ui-statusbar-right {
  display: flex;
  align-items: center;
  gap: 9px;
  color: var(--subtle);
  font-size: 10px;
}
.ui-secure-note { display: inline-flex; align-items: center; gap: 5px; color: var(--muted); }
.ui-divider { width: 1px; height: 12px; background: var(--line-strong); }
.ui-statusbar-right .ui-btn { min-height: 26px; padding: 0 9px; font-size: 10.5px; border-radius: 6px; }
.ui-statusbar-right .ui-btn.primary {
  border-color: var(--ink);
  background: var(--ink);
  color: var(--app-bg);
}

.replica-caption {
  margin: 16px auto 0;
  max-width: 760px;
  color: var(--site-muted);
  font-size: 12.5px;
  text-align: center;
  line-height: 1.6;
}

/* ------------------------------------------------------------ 痛点对照 */
.pain-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
  align-items: start;
}
.pain-col-head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 14px;
  color: var(--site-muted);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
}
.pain-col-head .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--red); }
.pain-grid > .pain-col:last-child .pain-col-head .dot { background: var(--green); }

.pain-list { display: flex; flex-direction: column; gap: 10px; }

.pain-item {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 16px 18px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--site-card);
}
.pain-item .pain-icon {
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  flex: 0 0 auto;
  border-radius: 8px;
  color: var(--red);
  background: var(--red-soft);
}
.pain-item.is-solved .pain-icon { color: var(--green); background: var(--green-soft); }
.pain-item h3 { font-size: 14.5px; letter-spacing: -.02em; }
.pain-item p { margin-top: 4px; color: var(--site-muted); font-size: 13.5px; line-height: 1.6; }

.pain-merge {
  display: grid;
  place-items: center;
  gap: 6px;
  margin: 18px 0;
  color: var(--site-muted);
  font-size: 12.5px;
  text-align: center;
}
.pain-merge svg { color: var(--green); }

/* -------------------------------------------------------------- 功能卡片 */
.feature-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 12px;
}
.feature-card {
  display: flex;
  flex-direction: column;
  padding: 20px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--site-card);
  transition: border-color .18s ease, box-shadow .18s ease, transform .18s ease;
}
.feature-card:hover { border-color: var(--line-strong); box-shadow: var(--site-shadow-lift); transform: translateY(-2px); }
.feature-icon {
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  margin-bottom: 14px;
  border-radius: 10px;
  color: var(--green);
  background: var(--green-soft);
}
.feature-card h3 { font-size: 15px; letter-spacing: -.02em; }
.feature-card p { margin-top: 8px; color: var(--site-muted); font-size: 13.5px; line-height: 1.62; }
.feature-card code {
  padding: 1px 5px;
  border-radius: 4px;
  background: var(--surface-soft);
  border: 1px solid var(--line);
  color: var(--ink-soft);
  font-size: .88em;
}

/* ------------------------------------------------------------ 架构与可信度 */
.arch-layout {
  display: grid;
  grid-template-columns: 1.05fr .95fr;
  gap: clamp(24px, 4vw, 48px);
  align-items: start;
}

.arch-stack { display: flex; flex-direction: column; gap: 0; }
.arch-layer {
  position: relative;
  padding: 16px 18px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  background: var(--site-card);
}
.arch-layer-top { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.arch-layer h3 { font-size: 14.5px; letter-spacing: -.02em; }
.arch-layer .arch-tag {
  color: var(--site-muted);
  font-family: 'DM Mono', monospace;
  font-size: 10.5px;
  white-space: nowrap;
}
.arch-layer p { margin-top: 5px; color: var(--site-muted); font-size: 13px; line-height: 1.6; }
.arch-layer.is-engine { border-color: var(--green); background: var(--green-soft); }
.arch-layer.is-engine h3 { color: var(--green); }
.arch-layer.is-engine p { color: var(--muted); }

.arch-arrow {
  display: grid;
  place-items: center;
  gap: 2px;
  padding: 7px 0;
  color: var(--site-muted);        /* 原 --subtle 仅 2.5:1；此处承载真实文案，改用 AA 达标的 --site-muted */
  font-family: 'DM Mono', monospace;
  font-size: 11.5px;              /* 原 10px 太小，与 --site-muted 一起保证可读 */
}
.arch-arrow svg { color: var(--line-strong); }

.trust-list { display: flex; flex-direction: column; gap: 14px; }
.trust-item {
  display: flex;
  align-items: flex-start;
  gap: 13px;
  padding: 17px 18px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--site-card);
}
.trust-item .trust-icon {
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  flex: 0 0 auto;
  border-radius: 9px;
  color: var(--green);
  background: var(--green-soft);
}
.trust-item h3 { font-size: 14.5px; letter-spacing: -.02em; }
.trust-item p { margin-top: 5px; color: var(--site-muted); font-size: 13.5px; line-height: 1.62; }
.trust-item code {
  padding: 1px 5px;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: var(--surface-soft);
  color: var(--ink-soft);
  font-size: .88em;
}

/* -------------------------------------------------------------- 三步上手 */
.steps-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 12px;
  margin-bottom: 22px;
}
.step-card {
  position: relative;
  padding: 20px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--site-card);
}
.step-num {
  display: inline-grid;
  place-items: center;
  width: 26px;
  height: 26px;
  margin-bottom: 12px;
  border: 1px solid var(--line-strong);
  border-radius: 8px;
  color: var(--ink-soft);
  font-family: 'DM Mono', monospace;
  font-size: 12px;
  font-weight: 500;
}
.step-card h3 { font-size: 15px; letter-spacing: -.02em; }
.step-card p { margin-top: 7px; color: var(--site-muted); font-size: 13.5px; line-height: 1.62; }
.step-card code {
  padding: 1px 5px;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: var(--surface-soft);
  color: var(--ink-soft);
  font-size: .88em;
}

.code-card {
  overflow: hidden;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  background: var(--site-card);
}
.code-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 11px 14px;
  border-bottom: 1px solid var(--line);
  background: var(--surface-soft);
}
.code-title {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: var(--site-muted);
  font-family: 'DM Mono', monospace;
  font-size: 11.5px;
}
.code-body { margin: 0; padding: 16px; overflow-x: auto; color: var(--ink-soft); font-size: 12.5px; line-height: 1.75; }
.code-body code { font-size: inherit; }
.code-endpoint { color: var(--green); }

.copy-button {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 30px;
  padding: 0 11px;
  border: 1px solid var(--line-strong);
  border-radius: 7px;
  background: var(--site-card);
  color: var(--ink-soft);
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  transition: color .15s ease, border-color .15s ease, background .15s ease;
  flex: 0 0 auto;
}
.copy-button:hover { color: var(--ink); border-color: var(--muted); }
.copy-button.is-copied { color: var(--green); border-color: var(--green); }
.copy-button svg { flex: 0 0 auto; }

.endpoint-hint {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-top: 14px;
  color: var(--site-muted);
  font-size: 13px;
}
.endpoint-hint code {
  padding: 3px 8px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--surface-soft);
  color: var(--ink-soft);
  font-size: 12.5px;
}

/* -------------------------------------------------------------------- FAQ */
.faq-list { display: flex; flex-direction: column; gap: 10px; max-width: 880px; }

.faq-item {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--site-card);
  overflow: hidden;
}
.faq-item summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 17px 20px;
  color: var(--ink);
  font-size: 14.5px;
  font-weight: 600;
  letter-spacing: -.015em;
  cursor: pointer;
  list-style: none;
  /* 补过渡：原来 hover 的底色变化是硬跳变 */
  transition: color var(--anim-fast) ease, background var(--anim-fast) ease;
}
.faq-item summary::-webkit-details-marker { display: none; }
.faq-item summary::after {
  content: '';
  flex: 0 0 auto;
  width: 9px;
  height: 9px;
  margin-right: 3px;
  border-right: 1.6px solid var(--site-muted);
  border-bottom: 1.6px solid var(--site-muted);
  transform: rotate(45deg) translateY(-2px);
  transition: transform .2s ease;
}
.faq-item[open] summary::after { transform: rotate(-135deg) translateY(-2px); }
.faq-item summary:hover { background: var(--surface-hover); }
.faq-item p { padding: 0 20px 18px; color: var(--site-muted); font-size: 13.8px; line-height: 1.7; }
.faq-item p code {
  padding: 1px 5px;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: var(--surface-soft);
  color: var(--ink-soft);
  font-size: .88em;
}

/* ------------------------------------------------------------------ 页脚 */
.site-footer {
  border-top: 1px solid var(--line);
  padding: clamp(36px, 5vw, 56px) 0 32px;
  background: var(--surface-soft);
}
.footer-top {
  display: grid;
  grid-template-columns: 1.5fr 1fr 1fr;
  gap: 32px;
  padding-bottom: 28px;
  border-bottom: 1px solid var(--line);
}
.footer-brand { display: flex; flex-direction: column; gap: 12px; max-width: 420px; }
.footer-brand .brand { font-size: 16px; }
.footer-brand p { color: var(--site-muted); font-size: 13px; line-height: 1.68; }
.footer-col h4 {
  margin-bottom: 12px;
  color: var(--site-muted);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .11em;
  text-transform: uppercase;
}
.footer-col li + li { margin-top: 9px; }
.footer-col a { color: var(--ink-soft); font-size: 13.5px; transition: color var(--anim-fast) ease, background var(--anim-fast) ease; }
.footer-col a:hover { color: var(--green); }

.footer-bottom {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding-top: 22px;
  color: var(--site-muted);
  font-size: 12.5px;
}
.footer-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 16px; }
.footer-meta code { font-size: 12px; }

.disclaimer {
  margin-top: 18px;
  padding-top: 18px;
  border-top: 1px solid var(--line);
  color: var(--site-muted);
  font-size: 12.5px;
  line-height: 1.7;
}

/* --------------------------------------------------------------- 下载页 */
.download-hero { padding: clamp(40px, 6vw, 68px) 0 0; }
.download-hero h1 { font-size: clamp(28px, 4.4vw, 44px); }
.download-hero .hero-lead { margin-left: 0; text-align: left; max-width: 640px; }

.version-card {
  display: grid;
  grid-template-columns: 1.1fr .9fr;
  gap: 24px;
  margin-top: 28px;
  padding: clamp(20px, 3vw, 28px);
  border: 1px solid var(--line-strong);
  border-radius: 14px;
  background: var(--site-card);
}
.version-main { display: flex; flex-direction: column; gap: 10px; }
.version-label {
  color: var(--site-muted);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .11em;
  text-transform: uppercase;
}
.version-value {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  color: var(--ink);
  font-family: 'DM Mono', monospace;
  font-size: clamp(24px, 3.4vw, 32px);
  font-weight: 500;
  letter-spacing: -.02em;
  line-height: 1.2;
}
.version-sub { color: var(--site-muted); font-size: 13px; line-height: 1.65; }
.version-meta {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding-left: 24px;
  border-left: 1px solid var(--line);
}
.version-meta div { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; font-size: 13px; }
.version-meta span { color: var(--site-muted); }
.version-meta strong { color: var(--ink-strong); font-weight: 600; font-family: 'DM Mono', monospace; font-size: 12.5px; }

.asset-list { display: flex; flex-direction: column; gap: 10px; }
.asset-row {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 16px;
  padding: 16px 18px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--site-card);
}
.asset-icon {
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  border-radius: 10px;
  color: var(--green);
  background: var(--green-soft);
}
.asset-info { min-width: 0; }
.asset-name {
  display: block;
  overflow: hidden;
  color: var(--ink);
  font-family: 'DM Mono', monospace;
  font-size: 13px;
  font-weight: 500;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.asset-meta { margin-top: 3px; color: var(--site-muted); font-size: 12.5px; }
.asset-actions { display: flex; align-items: center; gap: 8px; }

.download-note {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 16px 18px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface-soft);
  color: var(--site-muted);
  font-size: 13.5px;
  line-height: 1.68;
}
.download-note svg { flex: 0 0 auto; margin-top: 2px; color: var(--yellow); }
.download-note strong { color: var(--ink-strong); }

.requirements-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 12px;
}
.requirement {
  padding: 16px 18px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--site-card);
}
.requirement dt { color: var(--site-muted); font-size: 11px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase; }
.requirement dd { margin: 7px 0 0; color: var(--ink-strong); font-size: 13.5px; }
.requirement dd code { font-size: 12.5px; }

.skeleton {
  display: inline-block;
  min-width: 90px;
  height: 1em;
  border-radius: 4px;
  background: var(--surface-hover);
  animation: skeleton-pulse 1.4s ease-in-out infinite;
}
@keyframes skeleton-pulse { 0%, 100% { opacity: .55; } 50% { opacity: 1; } }

/* ------------------------------------------------------------------- 404 */
.error-page {
  display: grid;
  place-items: center;
  min-height: calc(100vh - 64px);
  min-height: calc(100svh - 64px);   /* 移动端地址栏收展时用 svh 避免溢出 */
  padding: 60px 24px;
  text-align: center;
}
.error-code {
  font-family: 'DM Mono', monospace;
  font-size: clamp(64px, 12vw, 112px);
  font-weight: 500;
  letter-spacing: -.05em;
  line-height: 1;
  color: var(--ink);
}
.error-code .accent { color: var(--green); }
.error-page h1 { margin-top: 16px; font-size: clamp(22px, 3vw, 30px); }
.error-page p { max-width: 480px; margin: 14px auto 0; color: var(--site-muted); font-size: 15px; }
.error-actions { display: flex; flex-wrap: wrap; justify-content: center; gap: 12px; margin-top: 28px; }

/* ------------------------------------------------------------------ 动效 */
.js-reveal {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity .5s cubic-bezier(.16, 1, .3, 1), transform .5s cubic-bezier(.16, 1, .3, 1);
  will-change: opacity, transform;
}
.js-reveal.is-visible { opacity: 1; transform: none; will-change: auto; }

/* ------------------------------------------------------- 语言切换动画 */
/* 切换瞬间 JS 给 <body> 挂 .lang-fading 约 130ms，让旧文案先淡出再换新。
   选择器用 html body.lang-fading [data-i18n]（0,3,0）而非 body.lang-fading [data-i18n]（0,2,0），
   以保证它压过 [data-reveal].is-revealed，避免已入场元素漏掉淡出。
   [data-i18n-html] 是含 <code> 等内联标签、需按 DOM 渲染的文案，同样参与淡出。

   刻意把 transition 一并写进这里、只在 .lang-fading 存在的 130ms 内生效：
   原先另有一条常驻的 [data-i18n] transition，让约 180 个元素永久挂着
   过渡声明，空闲时也要占样式计算资源，属于纯粹浪费。 */
html body.lang-fading [data-i18n],
html body.lang-fading [data-i18n-html] {
  opacity: .18;
  transform: translateY(-3px);
  transition: opacity 130ms var(--ease-out), transform 130ms var(--ease-out);
}

/* 中/英切换时当前语言放大、非当前语言缩小，配合弹簧曲线制造「切换到位」的手感 */
#lang-toggle .lang-zh,
#lang-toggle .lang-en {
  display: inline-block;
  transition: color 240ms var(--ease-out), transform 340ms var(--ease-spring), opacity 240ms ease;
}
#lang-toggle .lang-active { transform: scale(1.08); }
#lang-toggle .lang-idle   { transform: scale(.92); opacity: .62; }

/* --------------------------------------------------------- 入场动画系统 */
/* @property 注册 --pct 使其成为可插值的 <number>，健康度环数值才能爬升。 */
@property --pct {
  syntax: '<number>';
  inherits: false;
  initial-value: 0;
}

/* 关键：把 opacity:0 包进 prefers-reduced-motion: no-preference。
   偏好减少动效的用户从一开始就能看到内容；缺 JS 时也有 noscript 兜底，
   绝不会出现整页空白。 */
@media (prefers-reduced-motion: no-preference) {
  [data-reveal] {
    opacity: 0;
    transform: translateY(18px);
    transition: opacity var(--anim-slower) var(--ease-out),
                transform var(--anim-slower) var(--ease-out);
    transition-delay: var(--reveal-delay, 0ms);
    will-change: opacity, transform;
  }
  [data-reveal].is-revealed {
    opacity: 1;
    transform: none;
    will-change: auto;      /* 入场结束即释放合成层 */
  }
}

.ui-ring { transition: --pct 1100ms var(--ease-out); }

/* ===================================================== 关键帧 (keyframes) */
/*! Animate.css 4.1.1 — Hippocratic License 2.1 — Copyright 2021 Daniel Eden
    https://animate.style/  以下 5 组 keyframes 复制自 source/ 单动画文件 */
/* 以下 5 组复制自 animate.css source/ 下的单动画文件，已按站点约定统一加 `cr-` 前缀
   （animate.css 的 `animate__` 前缀是构建期注入的，源码里的类名无前缀，
   直接内联会与站点既有类名冲突，故类选择器与 keyframes 名同步改写）。 */
@keyframes cr-fade-in {
  from {
    opacity: 0;
  }

  to {
    opacity: 1;
  }
}

.cr-fade-in {
  animation-name: cr-fade-in;
}

@keyframes cr-fade-out {
  from {
    opacity: 1;
  }

  to {
    opacity: 0;
  }
}

.cr-fade-out {
  animation-name: cr-fade-out;
}

@keyframes cr-fade-up {
  from {
    opacity: 0;
    transform: translate3d(0, 100%, 0);
  }

  to {
    opacity: 1;
    transform: translate3d(0, 0, 0);
  }
}

.cr-fade-up {
  animation-name: cr-fade-up;
}

@keyframes cr-zoom-in {
  from {
    opacity: 0;
    transform: scale3d(0.3, 0.3, 0.3);
  }

  50% {
    opacity: 1;
  }
}

.cr-zoom-in {
  animation-name: cr-zoom-in;
}

@keyframes cr-rotate-in {
  from {
    transform: rotate3d(0, 0, 1, -200deg);
    opacity: 0;
  }

  to {
    transform: translate3d(0, 0, 0);
    opacity: 1;
  }
}

.cr-rotate-in {
  animation-name: cr-rotate-in;
  transform-origin: center;
}
/* —— animate.css 复制段结束 —— */

/* 站点自写：animate.css 没有合适的一次性「按下反馈」脉冲，自写 cr-pop。
   用于复制按钮成功态（.copy-button.is-copied）。 */
@keyframes cr-pop {
  0%   { transform: scale(1); }
  45%  { transform: scale(1.18); }
  100% { transform: scale(1); }
}

/* 站点自写：移动菜单自上而下的轻微滑入（-8px 极小位移，保持克制）。 */
@keyframes cr-slide-down {
  from { opacity: 0; transform: translateY(-8px); }
  to   { opacity: 1; transform: none; }
}

/* 站点自写：配合 @property --pct 使用，让健康度环从 0 爬到目标值。 */
@keyframes cr-ring-fill {
  from { --pct: 0; }
}

/* ================================================ 顶栏像素动效关键帧 */
/* 命名统一加 hdr- 前缀。为什么必须自建、不能复用 home.css 的 hp-* 或
   download.css 的 dx-*：那两个文件分别只被 index.html / download.html 加载，
   而顶栏样式在 style.css 里、四张页面都要用。若这里直接写 `animation: hp-blink`
   之类，在 features.html 与 404.html 上关键帧并不存在 —— 浏览器会静默忽略整条
   声明，顶栏在那两页就成了静态死物，而且不会有任何报错提示。
   所以这里是「镜像定义」：形状照抄那两页的做法，名字换成自己的。 */

/* 扫光：页面载入时沿顶栏底边从左到右扫过一次。
   动的是 background-position-x 而不是 transform：亮带被 background-size 收成
   42% 宽、background-repeat: no-repeat，所以它在元素盒外根本不会被绘制，两端
   都能真正「进出来」，不会像位移那样在结束后给页面留下横向溢出的滚动区域。
   代价是要重绘这条 2px × 视口宽的窄带 —— 一次性、900ms，可以忽略。
   70% 之前保持不透明、最后 30% 淡出，避免扫到尽头时「啪」地被切断。 */
@keyframes hdr-sweep {
  from {
    background-position-x: -75%;
    opacity: 1;
  }

  70% {
    opacity: 1;
  }

  to {
    background-position-x: 175%;
    opacity: 0;
  }
}

/* 方块光标：0–49% 亮、50–100% 灭。配 steps(1, end) 就是「啪」地硬切换，
   这是终端光标唯一正确的观感；一旦用缓动淡入淡出，立刻变回普通网页。 */
@keyframes hdr-blink {
  0%, 49%   { opacity: 1; }
  50%, 100% { opacity: 0; }
}

/* 当前页下沿色块：从 0 宽「长」到满宽。
   scaleX 只走合成器，不触发重排；起点在 left center，所以是向右长出，
   和「这一项被选中」的阅读方向一致。 */
@keyframes hdr-active-in {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}

/* ================================================== FAQ 展开/收起动画 */
/* 现代浏览器：interpolate-size 让 height/block-size 可以过渡到 auto，
   再配合 ::details-content 的 allow-discrete 让 content-visibility 延迟切换。 */
html { interpolate-size: allow-keywords; }
.faq-item::details-content {
  block-size: 0;
  overflow: hidden;
  transition: block-size var(--anim-slow) var(--ease-out),
              content-visibility var(--anim-slow) allow-discrete;
}
.faq-item[open]::details-content { block-size: auto; }
/* 全浏览器兜底：仅在浏览器不支持 ::details-content 时才用「内容淡入上移」，
   用 @supports 收口，避免与现代浏览器的 block-size 过渡叠加成双重动画。 */
@supports not selector(::details-content) {
  .faq-item[open] > p {
    animation: cr-fade-up var(--anim-slow) var(--ease-out) both;
  }
}

/* ================================================ 移动菜单滑入/收起 */
/* 原实现是 display 硬切换。改为 max-height + opacity + visibility 三件套：
   visibility 用 0s + delay 保证收起动画跑完才真正不可点击。 */
.mobile-menu {
  max-height: 0;
  /* 纵向可滚动：菜单展开后内容超出视口时仍能下滑查看。
     横向保持 hidden，避免页面被意外横向撑破。
     overscroll-behavior: contain 阻止滚动穿透到背后的页面
     （否则在菜单里滑到底会连带滚动 body，移动端观感很差）。 */
  overflow-x: hidden;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  opacity: 0;
  visibility: hidden;
  transition: max-height var(--anim-slow) var(--ease-out),
              opacity var(--anim-base) var(--ease-out),
              visibility 0s linear var(--anim-base);
}
/* 菜单内滚动条收窄，避免在深色面板上过于突兀。
   拇指也改成方角（原 3px 圆角）：滚动条是菜单面板的一部分，圆角会在这块
   纯粹由硬边构成的深色面里露出一小段「软」的观感，很跳。 */
.mobile-menu::-webkit-scrollbar { width: 6px; }
.mobile-menu::-webkit-scrollbar-thumb {
  border-radius: 0;
  background: var(--line-strong);
}
.mobile-menu::-webkit-scrollbar-track { background: transparent; }
.mobile-menu.is-open {
  /* 上限 = 视口高度减去 sticky 顶栏的占位。占位是 66px（--header-h 的 64px
     + 2px 底边），74 与之相差 8px，正好是「栏高 + 8px 呼吸」这套口径；
     改动前写 72 对应的是 1px 底边时代。 */
  max-height: calc(100vh - 74px);
  max-height: calc(100dvh - 74px);   /* 支持动态视口单位的浏览器优先用这个，避开移动端地址栏跳动 */
  opacity: 1;
  visibility: visible;
  transition: max-height var(--anim-slow) var(--ease-out),
              opacity var(--anim-base) var(--ease-out),
              visibility 0s;
}
/* 展开时内容自上而下滑入，与盒子撑开同步 —— 让「滑入」不只是高度变化 */
@media (prefers-reduced-motion: no-preference) {
  .mobile-menu.is-open { animation: cr-slide-down var(--anim-slow) var(--ease-out) both; }
}
/* 桌面宽度强制收口，杜绝「窄屏打开 → 拉宽窗口」后的菜单残留。 */
@media (min-width: 901px) {
  .mobile-menu { display: none !important; }
}

/* ===================================================== 卡片 hover 质感 */
/* 位移只从 -2px 提到 -3px，图标随之微缩放+微旋转 —— 幅度都很小，
   符合 PRD「克制」基调：只为让指针下有了「托起」的因果感。 */
@media (prefers-reduced-motion: no-preference) {
  .feature-card {
    transition: border-color var(--anim-base) ease,
                box-shadow var(--anim-slow) var(--ease-out),
                transform var(--anim-slow) var(--ease-out);
  }
  .feature-card:hover { transform: translateY(-3px); }
  .feature-icon { transition: transform var(--anim-slow) var(--ease-spring); }
  .feature-card:hover .feature-icon { transform: scale(1.08) rotate(-3deg); }
}

/* 复制按钮成功态的一次性脉冲反馈（cr-pop 的消费方，避免关键帧空置） */
@media (prefers-reduced-motion: no-preference) {
  .copy-button.is-copied { animation: cr-pop 320ms var(--ease-out); }
}

/* 复刻块进入视口后，柱子按 --i 依次升起 */
.is-visible .ui-bar { transform: scaleY(1); }

/* ---------------------------------------------------------------- 响应式 */
/* 矮视口单独收口（高度轴，与下面按宽度递降的一串查询互不干涉）。
   上面把下 padding 加厚到 23vh 是为了在高窗口里把内容推到约 40% 处，
   但这个量和视口高度成正比：内容本身约 339px，加上上内边距 84px 与
   基础下内边距 40px 已占 463px，再叠 23vh 之后，视口矮于约 684px 时
   hero 就撑过一屏，页脚会从折线下面探出来 —— 正好破坏首屏的契约。
   取 760px 而不是 684px，是提前 76px 收手，给系统字体放大与浏览器
   缩放留出余量；回落到 8vh 后上浮量仍有一点点（约 2vh），不会出现
   「矮窗口里又退回完全居中」的割裂感。 */
@media (max-height: 760px) {
  .hero { padding-bottom: calc(clamp(28px, 4vw, 40px) + max(0px, min(8vh, 90px) - var(--hero-ticker-reserve))); }
}

@media (max-width: 1080px) {
  /* 顶栏在 901–1080px 这一档最挤：英文导航变成大写等宽后，6 项合计比原来的
     Inter 版宽出约 90px，会和品牌 + 右侧按钮抢空间。这里只收顶栏自身的
     字号、字距与左右内边距，不动任何布局结构 —— 目的是让它在这个区间
     仍然稳稳地排成一行，而不是折行把 64px 的栏高顶破。 */
  .header-nav { gap: 0; margin-left: 8px; }
  .nav-link { padding: 7px; font-size: 11.5px; letter-spacing: .06em; }
  .feature-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .arch-layout { grid-template-columns: 1fr; }
  .footer-top { grid-template-columns: 1fr 1fr; }
  .footer-brand { grid-column: 1 / -1; max-width: none; }
}

@media (max-width: 900px) {
  .header-nav { display: none; }
  .menu-button { display: inline-grid; }
  /* 窄屏时品牌与光标已经够醒目，缩小一点给右侧两个按钮让位；
     同时把 34px 的按钮放大到 38px，满足触摸目标的最小尺寸。
     （.icon-button 的宽度定义在上面，这条媒体查询特异度相同、位置更靠后，
     因此直接生效。） */
  .site-header .brand { font-size: 12px; letter-spacing: .04em; }
  .icon-button { width: 38px; height: 38px; }
  /* 窄屏才把菜单变成可参与 max-height 过渡的盒子；显隐交给动效区的
     max-height + opacity + visibility 三件套，桌面宽度下仍由基础规则的
     display:none 兜底，绝不可见。 */
  .mobile-menu { display: block; }
  .pain-grid { grid-template-columns: 1fr; }
  .steps-grid { grid-template-columns: 1fr; }
  .version-card { grid-template-columns: 1fr; }
  .version-meta { padding-left: 0; padding-top: 18px; border-left: 0; border-top: 1px solid var(--line); }
  /* 小屏只保留前 3 列：手机 GPU 与电池都紧张，6 条持续滚动的合成层
     是纯粹的浪费；3 列已足够在小视口里读出「流动的代码」这件事。 */
  .bgcode-col:nth-child(n+4) { display: none; }
}

@media (max-width: 720px) {
  body { font-size: 15.5px; }
  .container { padding: 0 18px; }
  .feature-grid { grid-template-columns: 1fr; }
  .asset-row { grid-template-columns: auto 1fr; }
  .asset-actions { grid-column: 1 / -1; }
  .asset-actions .button { width: 100%; }
  .footer-top { grid-template-columns: 1fr; gap: 24px; }
  .footer-bottom { flex-direction: column; align-items: flex-start; }
  .hero-actions .button { width: 100%; }
  .replica-scroll { margin: 0 -18px; padding: 0 18px; border-radius: 0; }
  .ui-window { border-radius: 12px; }
}

@media (max-width: 420px) {
  .hero h1 { font-size: 28px; }
  .badge { font-size: 11.5px; }
  .code-body { font-size: 11.5px; padding: 13px; }
  .ui-window { min-width: 860px; }
}

/* -------------------------------------------------------------- 无障碍 */
:focus-visible {
  outline: 2px solid var(--site-focus);
  outline-offset: 2px;
}
.button:focus-visible, .icon-button:focus-visible, .lang-toggle:focus-visible,
.copy-button:focus-visible, .nav-link:focus-visible { outline-width: 2px; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
  .js-reveal { opacity: 1 !important; transform: none !important; }
  .ui-bar { transform: scaleY(1) !important; transition: none !important; }
  .feature-card:hover { transform: none; }
  /* 通配 * 只清零时长，并不清零 transform 本身，故下面几项需显式归零 */
  [data-reveal] { opacity: 1 !important; transform: none !important; }
  .ui-ring { transition: none !important; }
  .button:active { transform: none !important; }
  .feature-card:hover .feature-icon { transform: none !important; }
  /* 减少动效：保留 3D 静态代码纹理（它本身是有信息量的设计元素），
     但绝不流动。这里必须显式 animation: none —— 上面通配的
     animation-duration: .01ms 只是把时长压到无穷短，配合
     iteration-count: 1 会让代码层「跳一帧然后停住」，观感反而像故障；
     直接关掉动画才是稳定的静态纵深。 */
  .bgcode-scroll { animation: none; }
  /* 像素流星同理：上面的通配会把动画时长压到 .01ms 并只跑一次，
     结果就是「跳一帧然后定格」，观感反而像故障。
     这一层是 canvas 逐帧绘制，没有 CSS 动画可关 —— 真正的开关在 JS：
     prefers-reduced-motion 命中时连 requestAnimationFrame 都不启动，画布保持空白。
     这里再兜一道 display: none，确保任何情况下都不会留下静止的残影。 */
  .bgpixels { display: none; }

  /* ---- 顶栏像素动效的终态兜底 ----
     上面那条通配只把时长压到 .01ms、把次数压到 1，并不能让动画「消失」：
     · hdr-blink 是 infinite 的方块光标，被这样压过之后会「跳一帧然后定住」，
       观感像故障；而且停在 49% 还是 100% 取决于时序，可能留下一个常亮的绿块。
     · hdr-sweep 是一条一次性亮带，压到 .01ms 等于根本没看见，却仍会在首帧
       闪一下全宽的绿线。
     所以这几条一律显式 animation: none —— 顶栏的静态像素样式（2px 硬边、扫描
     线、方角、等宽标签）全部保留，只是不再有任何会自己动的东西。
     光标那一格改成常亮：它在语义上是「这里可以输入/跳转」的提示，静态保留比
     整个删掉更清楚。 */
  .site-header::after { animation: none !important; opacity: 0; }
  .site-header .brand::after { animation: none !important; opacity: 1; }
  .nav-link[aria-current='page']::after {
    animation: none !important;
    transform: scaleX(1);
  }
  /* 通配条已经把 transition-duration 压到 .01ms，但 hover 的 -1px 位移仍然
     存在；减少动效时不再让导航项位移，只留颜色/边框的变化作为反馈。 */
  .nav-link:hover { transform: none !important; }
}

@media (prefers-reduced-transparency: reduce) {
  /* 顶栏已经不用 backdrop-filter 了，这里只需保证底色完全不透明。 */
  .site-header { background: var(--app-bg); }
  /* 顶栏那层 4px 周期扫描线同样是「低透明度的半透明效果」，与 body::before
     的网格光晕属于同一范畴，这个查询里一并移除，口径与其它背景层保持一致。 */
  .site-header::before { display: none; }
  .mobile-menu {
    background-image: none;
    background-color: var(--surface-solid);
  }
  .ui-sidebar, .ui-titlebar, .ui-statusbar { background: var(--surface-soft); }
  .ui-panel { background: var(--surface-solid); }
  /* 用户明确要求「减少透明/半透明效果」时，半透明光晕与低透明网格同样属于该范畴：
     整层图案直接移除，页面回到纯净的品牌底色（--app-bg 保持不变）。 */
  body::before { display: none; }
  /* 流动代码层全部由极低透明度文字构成，正是该关掉的「半透明效果」；
     连同它的合成层一起移除，连运行动画的开销也不留。 */
  .bgcode { display: none; }
  /* 像素流星同样是一层低透明度的绿色微光，属于要关掉的「半透明效果」；
     这里整层移除，连 canvas 的逐帧绘制开销也不留（JS 侧会同时停掉循环）。 */
  .bgpixels { display: none; }
}

@media (prefers-contrast: more) {
  .feature-card, .pain-item, .step-card, .trust-item, .arch-layer,
  .faq-item, .asset-row, .version-card, .ui-panel { border-color: var(--muted); }
}

@media print {
  /* 顶栏整块不进纸。本次新增的两层装饰（.site-header::before 的扫描线、
     ::after 的扫光亮带）都是 .site-header 的伪元素，父级 display: none 时
     伪元素根本不会被生成，因此不需要再单独关一次 —— 这里没有留下任何
     会漏到纸面上的像素纹理。 */
  .site-header, .mobile-menu, .replica-scroll, .hero-actions { display: none; }
  /* 上面隐藏了 .replica-scroll，而新加的可见标题在它外面，纸面上会变成
     一个「只有标题、下面空无一物」的孤儿块。打印时一并隐去，让分页输出
     和改动前保持一致（窗口 + 其说明都不进纸）。 */
  .replica-wrap .section-head { display: none; }
  /* 首屏撑满视口的规则只属于屏幕：分页媒体里视口单位以页面盒为参照，
     一条 min-height 会把 hero 撑成整页、在正文前留下一大片空白。打印时
     退回普通流，让内容紧接排下去。 */
  /* 上浮用的那段额外下内边距同样是视口单位，分页媒体里没有意义，
     一并退回来，免得 hero 后面多出一块空白。 */
  .hero { display: block; min-height: 0; padding-bottom: clamp(28px, 4vw, 40px); }
  body { background: #fff; color: #000; }
  /* 打印不需要屏幕上的氛围图案：既省墨，也避免灰度打印时网格变成脏底纹 */
  body::before { display: none; }
  /* 代码层同理：灰度打印时 12px 的淡字会糊成一片脏灰；
     而且 fixed + 3D 变换在分页媒体里没有意义。 */
  .bgcode { display: none; }
  /* 像素流星同为氛围层，打印时一并移除（它由 JS 注入，不在 HTML 流里，
     所以必须显式关掉，不能指望打印时的 DOM 差异）。 */
  .bgpixels { display: none; }
}
