@charset "UTF-8";
/* ============================================================================
   ALH Pro 官网 · 样式表
   目标:动效丰富、但**滚动期一帧都不掉**。成熟产品的观感 + 高性能的实现。
   纯静态、零外部依赖:没有 CDN、没有在线字体、没有图标字体、没有图片纹理。

   ─────────────────────────────────────────────────────────────────────────
   滚动性能铁律(改样式前必读;每条都有实测依据,见仓库外的性能报告)
     1. 滚动路径上**只允许动 `transform` 与 `opacity`**。
        不动 box-shadow / background-position / width / height / top / left(feature: 触发重排或重绘)。
     2. **禁止任何 `backdrop-filter`**。实测:大面积用它会让滚动期 UpdateLayer 涨到 16 倍、
        Paint 3.4 倍、RasterTask 3.3 倍、主线程任务时长 2.3 倍(见 §0 注释里的对比数字)。
        吸顶栏改用「近不透明实色」——在纯色底上肉眼几乎无差别。
     3. **禁止在滚动相关代码里调用任何 scroll API**(scrollTo / scrollIntoView / scrollTop = …)。
        实测:滚动处理器里的 `scrollIntoView({behavior:'smooth'})` 会让向下滚动时 scrollY
        回退 51~63 帧、"被拽着走";去掉后回退帧 = 0。这就是"牵引感"的唯一来源。
     4. 滚动联动优先用 **CSS 滚动驱动动画**(`animation-timeline: scroll()/view()`):
        它跑在合成器上、不占主线程、不需要 scroll 事件。不支持的引擎用 `@supports` 降级为静态。
     5. `will-change` 只给"真的每帧在动"的极少数元素,并且**离开视口就摘掉**(IO 控制)。
     6. 入场的错开 ≤60ms/级、时长 200~600ms、缓动走 `--ease`。
     7. 悬停位移 ≤2px;悬停只改颜色/边框/阴影。
     8. `prefers-reduced-motion: reduce` 下关掉全部位移与动画(见 §20)。
     9. 长列表用 `content-visibility: auto` 做分区隔离(见 §17)。

   组织方式:§0 性能基线 → §1 设计令牌 → §2 基础 → … → §19 响应式 → §20 减少动效
   ============================================================================ */

/* ===========================================================================
   §0 实测基线(同一台机器、同一段脚本滚轮输入、3 秒窗口、取 3 次中位数)
      指标                        d9de0c9(玻璃+大面积 backdrop-filter)   a21e681(无动效)
      UpdateLayoutTree(样式重算)  1198 次 / 142.6ms                       358 次 / 19.4ms
      Paint                       1950 次                               573 次
      RasterTask                  1032 次 / 30.1ms                       309 次 / 12.4ms
      UpdateLayer                 30828 次 / 14.7ms                      1900 次 / 1.4ms
      PrePaint                     784 次 / 49.9ms                       678 次 / 23.9ms
      主线程 TaskDuration          606ms                                 259ms
      向下滚动时的 scrollY 回退     63 次 / 525px                         51 次 / 425px
      禁用页面 scrollIntoView 后回退 0 次                                  0 次
      → 结论:回退(牵引感)100% 来自滚动处理器里的 scrollIntoView;
        卡顿来自大面积 backdrop-filter(UpdateLayer 16 倍);
        与强制同步布局无关(Layout 次数 31 vs 41,反而更少)。
   =========================================================================== */

/* ===========================================================================
   §1 设计令牌
   =========================================================================== */
:root {
  color-scheme: dark;

  /* 版式 */
  --wrap: 1120px;
  --pad: 24px;
  --header-h: 60px;
  --toc-h: 44px;

  /* 圆角 */
  --radius-sm: 8px;
  --radius: 12px;
  --pill: 999px;

  /* 时间与曲线 */
  --ease: cubic-bezier(.22, .61, .36, 1);
  --ease-out: cubic-bezier(.16, 1, .3, 1);
  --t-fast: 150ms;
  --t: 260ms;
  --t-slow: 420ms;

  /* 字体 */
  --font: system-ui, -apple-system, "Segoe UI", "Microsoft YaHei", "PingFang SC",
          "Hiragino Sans GB", "Source Han Sans SC", "Noto Sans CJK SC", sans-serif;
  --mono: ui-monospace, "Cascadia Mono", "SFMono-Regular", Consolas, "Courier New", monospace;
}

/* ---------- §1.1 深色(默认):近黑中性底 ---------- */
:root {
  --bg: #0a0a0b;
  --surface: #111113;
  --surface-2: #17171a;
  --line: rgba(255, 255, 255, .09);
  --line-strong: rgba(255, 255, 255, .17);

  --fg: #f4f4f5;
  --fg-soft: #a9a9b0;
  --muted: #8a8a93;

  --accent: #7da2ff;          /* 低饱和蓝,可当正文链接色 */
  --accent-top: #89adff;      /* 仅主按钮的极小渐变 */
  --accent-bottom: #6f97fa;
  --accent-ink: #0a0a0b;
  --accent-soft: rgba(125, 162, 255, .13);
  --accent-line: rgba(125, 162, 255, .32);
  --glow: rgba(125, 162, 255, .16);       /* 卡片跟随高光 */
  --aurora-1: rgba(125, 162, 255, .16);   /* 首屏光斑 */
  --aurora-2: rgba(162, 116, 255, .14);
  --demo-bg: #2b3140;                     /* 功能演示里"未抠图"的实色背景 */

  --ok: #5ecb9a;
  --ok-soft: rgba(94, 203, 154, .10);
  --ok-line: rgba(94, 203, 154, .28);
  --warn: #e0b062;
  --warn-soft: rgba(224, 176, 98, .10);
  --warn-line: rgba(224, 176, 98, .28);
  --danger: #e88a83;

  --shadow-1: 0 1px 2px rgba(0, 0, 0, .40);
  --shadow-2: 0 2px 8px rgba(0, 0, 0, .45);
  --shadow-3: 0 8px 24px rgba(0, 0, 0, .50);
  /* 吸顶栏:近不透明实色,刻意不用 backdrop-filter(滚动期最贵的一项) */
  --header-bg: rgba(10, 10, 11, .94);
  --plate: rgba(255, 255, 255, .92);
  --body-bg: #0a0a0b;
}

/* ---------- §1.2 浅色主题(已整块删除)----------
   原先这里有 `@media (prefers-color-scheme: light)` 的一整套浅色变量覆盖
   (--bg:#fafafa / --surface:#ffffff / --fg:#111827 / --body-bg:#fafafa 等 30 余项):
   系统处于浅色模式时它会接管全部颜色令牌,把整站刷成白底黑字 ——
   手机默认多为浅色模式,所以"手机上网页变白"就是它造成的。
   按用户要求「手机版网页也要黑色,像电脑端一样」:**深色成为唯一主题**,
   这里不再保留任何浅色分支,全站颜色只由上面 §1.1 那一套深色令牌决定,
   也不再新增硬编码颜色(要改色就改 §1.1 的令牌)。
   注:§21 打印样式里的白底黑字是给纸张用的、与屏幕主题无关,予以保留。 */

/* ===========================================================================
   §2 基础重置与排版
   =========================================================================== */
*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  /* 深色是唯一主题:固定 dark,不随系统的浅色模式改变(手机浏览器也跟着深色)。
     与 §1 的 :root 一致,这里再写一遍是为了让 html 元素本身的 UA 控件/滚动条也走深色。 */
  color-scheme: dark;
  /* 只影响锚点/程序化滚动,不影响滚轮手感;不做任何滚动劫持 */
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--header-h) + 20px);
  background: var(--body-bg);
}
html.has-toc { scroll-padding-top: calc(var(--header-h) + var(--toc-h) + 20px); }

body {
  margin: 0;
  min-height: 100%;
  background: var(--body-bg);
  color: var(--fg);
  font-family: var(--font);
  font-size: 16.5px;
  line-height: 1.75;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

img { max-width: 100%; height: auto; }

a { color: var(--accent); text-decoration: none; text-underline-offset: 3px; transition: color var(--t-fast) var(--ease); }
a:hover { text-decoration: underline; }

:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 6px; }
::selection { background: var(--accent-soft); color: var(--fg); }

/* ---------- §2.1 标题与正文 ---------- */
h1, h2, h3, h4 { color: var(--fg); line-height: 1.25; letter-spacing: -.011em; text-wrap: balance; }
h2 { font-size: clamp(23px, 2.6vw, 30px); font-weight: 700; margin: 60px 0 14px; }
h2:first-child { margin-top: 0; }
h3 { font-size: 18.5px; font-weight: 650; margin: 32px 0 10px; }
h4 { font-size: 16.5px; font-weight: 650; margin: 24px 0 8px; }

p { margin: 12px 0; }
ul, ol { margin: 12px 0; padding-left: 24px; }
li { margin: 6px 0; }
li::marker { color: var(--muted); }
strong { color: var(--fg); font-weight: 650; }
em { font-style: normal; color: var(--fg-soft); }

main p, main li { max-width: 74ch; }
main .grid p, main .grid li, main .card p, main .card li { max-width: none; }

hr { border: 0; border-top: 1px solid var(--line); margin: 44px 0; }

/* ---------- §2.2 行内代码 / 代码块 ---------- */
code, kbd, samp {
  font-family: var(--mono); font-size: .88em; font-variant-numeric: tabular-nums;
  background: var(--surface-2); border: 1px solid var(--line); border-radius: 6px;
  padding: .1em .38em; word-break: break-word;
}
pre { background: var(--surface-2); border: 1px solid var(--line); border-radius: var(--radius); padding: 16px 18px; overflow-x: auto; line-height: 1.65; }
pre code { background: none; border: 0; padding: 0; font-size: 13.5px; white-space: pre; }

/* ===========================================================================
   §3 布局容器
   =========================================================================== */
.wrap { width: 100%; max-width: var(--wrap); margin: 0 auto; padding-left: var(--pad); padding-right: var(--pad); }
.content { padding-top: 48px; padding-bottom: 88px; }

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

.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 60;
  background: var(--surface); color: var(--fg); padding: 11px 16px;
  border: 1px solid var(--line-strong); border-radius: var(--radius-sm);
}
.skip-link:focus { left: 12px; top: 12px; }

/* ===========================================================================
   §4 顶部滚动进度条
   由 CSS 滚动驱动动画 `animation-timeline: scroll(root)` 驱动 ——
   跑在合成器上,零 JS、零 scroll 事件、不占主线程。
   不支持的引擎:整条隐藏(@supports 降级),不留空壳。
   =========================================================================== */
.page-progress {
  position: fixed; left: 0; top: 0; right: 0; height: 2px;
  z-index: 70; pointer-events: none; display: none;
}
.page-progress > i {
  display: block; height: 100%; width: 100%;
  transform: scaleX(0); transform-origin: 0 50%;
  background: linear-gradient(90deg, var(--accent), var(--accent-top));
}
@supports (animation-timeline: scroll()) {
  .page-progress { display: block; }
  .page-progress > i {
    animation: progressGrow linear both;
    animation-timeline: scroll(root block);
  }
}
@keyframes progressGrow { from { transform: scaleX(0); } to { transform: scaleX(1); } }

/* ===========================================================================
   §5 吸顶导航(近不透明实色,刻意不用 backdrop-filter)
   =========================================================================== */
.site-header {
  position: sticky; top: 0; z-index: 50;
  background: var(--header-bg);
  border-bottom: 1px solid transparent;
  transition: border-color var(--t) var(--ease);
}
.site-header.is-stuck { border-bottom-color: var(--line); }

.header-inner { display: flex; align-items: center; gap: 14px; min-height: var(--header-h); flex-wrap: wrap; }

.brand { display: inline-flex; align-items: center; gap: 10px; color: var(--fg); font-weight: 650; font-size: 16px; white-space: nowrap; }
.brand:hover { text-decoration: none; }
.brand-logo { width: 28px; height: 28px; border-radius: 7px; border: 1px solid var(--line); background: var(--plate); padding: 2px; transition: transform var(--t) var(--ease-out); }
.brand:hover .brand-logo { transform: translateY(-1px); }
.brand-sub { font-size: 12px; font-weight: 400; color: var(--muted); }

.site-nav { display: flex; flex-wrap: wrap; gap: 2px; margin-left: auto; }
.site-nav a {
  display: inline-block; padding: 7px 12px; border-radius: var(--radius-sm);
  color: var(--fg-soft); font-size: 14.5px; white-space: nowrap;
  transition: color var(--t-fast) var(--ease), background-color var(--t-fast) var(--ease);
}
.site-nav a:hover { color: var(--fg); background: var(--surface-2); text-decoration: none; }
.site-nav a[aria-current="page"] { color: var(--fg); font-weight: 600; background: var(--surface-2); }

.nav-toggle {
  display: none; margin-left: auto; align-items: center; gap: 9px; padding: 8px 13px;
  font: inherit; font-size: 14px; color: var(--fg-soft); background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--radius-sm); cursor: pointer;
  transition: border-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}
.nav-toggle:hover { border-color: var(--line-strong); color: var(--fg); }
.nav-toggle-bars, .nav-toggle-bars::before, .nav-toggle-bars::after {
  display: block; width: 16px; height: 2px; border-radius: 2px; background: currentColor;
  transition: transform var(--t) var(--ease), opacity var(--t-fast) var(--ease);
}
.nav-toggle-bars { position: relative; }
.nav-toggle-bars::before, .nav-toggle-bars::after { content: ""; position: absolute; left: 0; }
.nav-toggle-bars::before { top: -5px; }
.nav-toggle-bars::after { top: 5px; }
.nav-toggle[aria-expanded="true"] .nav-toggle-bars { background: transparent; }
.nav-toggle[aria-expanded="true"] .nav-toggle-bars::before { transform: translateY(5px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] .nav-toggle-bars::after { transform: translateY(-5px) rotate(-45deg); }

@media (max-width: 900px) {
  .has-js .nav-toggle { display: inline-flex; }
  .has-js .site-nav {
    display: flex; width: 100%; margin: 0 0 12px; padding-top: 8px;
    border-top: 1px solid var(--line); flex-direction: column; align-items: stretch;
    max-height: 0; overflow: hidden; opacity: 0; visibility: hidden;
    transition: max-height 300ms var(--ease), opacity var(--t-fast) var(--ease), visibility 300ms;
  }
  .has-js .site-nav.is-open { max-height: min(72vh, 460px); overflow: auto; opacity: 1; visibility: visible; }
  .has-js .site-nav a { padding: 11px 14px; }
}

/* ===========================================================================
   §6 页内章节条(JS 依据 h2[id] 生成;当前章节高亮 + 横向平滑跟随)
   注意:跟随只作用于这条**内部横向滚动容器**,绝不调用页面级 scroll API。
   =========================================================================== */
.page-toc {
  position: sticky; top: var(--header-h); z-index: 45;
  background: var(--header-bg);
  border-bottom: 1px solid var(--line);
}
.page-toc[hidden] { display: none; }
.page-toc-track {
  display: flex; align-items: center; gap: 0; min-height: var(--toc-h);
  overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch;
}
.page-toc-track::-webkit-scrollbar { display: none; }
.page-toc a {
  position: relative; flex: 0 0 auto; padding: 5px 11px; border-radius: 6px;
  font-size: 13.5px; color: var(--muted); white-space: nowrap;
  transition: color var(--t) var(--ease);
}
.page-toc a:hover { color: var(--fg); background: var(--surface-2); text-decoration: none; }
.page-toc a[aria-current="true"] { color: var(--fg); font-weight: 600; }
/* 高亮下划短横:进入当前态时 scaleX 展开 */
.page-toc a::after {
  content: ""; position: absolute; left: 11px; right: 11px; bottom: 1px; height: 2px;
  border-radius: 2px; background: var(--accent);
  transform: scaleX(0); transform-origin: 50% 50%;
  transition: transform var(--t) var(--ease-out);
}
.page-toc a[aria-current="true"]::after { transform: scaleX(1); }

/* ===========================================================================
   §7 首屏
   光斑 = 预渲染的 CSS 径向渐变 + **只动 transform**;不用 `filter: blur`
   (大面积 filter 每帧重算,是典型的滚动杀手)。离开视口由 JS 暂停动画。
   =========================================================================== */
.hero {
  position: relative;
  padding: clamp(52px, 7vw, 88px) 0 clamp(40px, 5vw, 60px);
  border-bottom: 1px solid var(--line);
  overflow: hidden;                 /* 裁掉光斑溢出,避免横向滚动条 */
}
.hero--page { padding: clamp(38px, 5vw, 60px) 0 clamp(28px, 3.5vw, 40px); }
.hero-inner { position: relative; z-index: 2; }

.aurora { position: absolute; inset: -15% -5% -25%; z-index: 1; pointer-events: none; }
.aurora-blob { position: absolute; border-radius: 50%; display: block; }
.aurora-blob.b1 {
  width: 56vw; height: 56vw; min-width: 320px; min-height: 320px; left: -12%; top: -26%;
  background: radial-gradient(circle closest-side, var(--aurora-1), transparent 70%);
  animation: auroraA 26s var(--ease) infinite alternate;
}
.aurora-blob.b2 {
  width: 48vw; height: 48vw; min-width: 280px; min-height: 280px; right: -10%; top: -14%;
  background: radial-gradient(circle closest-side, var(--aurora-2), transparent 70%);
  animation: auroraB 32s var(--ease) infinite alternate;
}
.aurora-blob.b3 {
  width: 42vw; height: 26vw; min-width: 260px; min-height: 180px; left: 30%; bottom: -20%;
  background: radial-gradient(circle closest-side, var(--aurora-1), transparent 72%);
  opacity: .65;
  animation: auroraA 38s var(--ease) infinite alternate-reverse;
}
/* 只动 transform / opacity → 全部在合成器完成 */
@keyframes auroraA {
  from { transform: translate3d(0, 0, 0) scale(1); }
  50%  { transform: translate3d(5%, 4%, 0) scale(1.08); }
  to   { transform: translate3d(-3%, 7%, 0) scale(.97); }
}
@keyframes auroraB {
  from { transform: translate3d(0, 0, 0) scale(1.05); }
  50%  { transform: translate3d(-6%, 5%, 0) scale(.95); }
  to   { transform: translate3d(3%, 9%, 0) scale(1.07); }
}
/* 首屏离开视口 → 暂停光斑动画;同时摘掉 will-change,释放合成层与显存 */
.aurora-blob { will-change: auto; }
.hero.is-live .aurora-blob { will-change: transform; }
.hero:not(.is-live) .aurora-blob { animation-play-state: paused; }

/* 视差:CSS 滚动驱动动画(view()),同样零 JS、不占主线程。
   注意这里用 **2D translate**:`translate3d` 会强制元素生成独立合成层,
   对"一次入场/一次性滚过"的元素来说反而更贵(实测 UpdateLayer 涨到 16 倍)。 */
@supports (animation-timeline: view()) {
  .aurora {
    animation: auroraParallax linear both;
    animation-timeline: view();
    animation-range: exit 0% exit 100%;
  }
  .hero-inner {
    animation: heroExit linear both;
    animation-timeline: view();
    animation-range: exit 0% exit 60%;
  }
}
@keyframes auroraParallax { from { transform: translateY(0); } to { transform: translateY(70px); } }
@keyframes heroExit { from { transform: translateY(0); opacity: 1; } to { transform: translateY(-26px); opacity: .55; } }

/* ---------- §7.1 首屏入场(一次性,分行/逐个错开) ---------- */
@keyframes heroIn { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: none; } }
.has-js .hero-inner > * { opacity: 0; animation: heroIn 620ms var(--ease-out) both; }
.has-js .hero-inner > *:nth-child(1) { animation-delay: 40ms; }
.has-js .hero-inner > *:nth-child(2) { animation-delay: 140ms; }
.has-js .hero-inner > *:nth-child(3) { animation-delay: 240ms; }
.has-js .hero-inner > *:nth-child(4) { animation-delay: 330ms; }
.has-js .hero-inner > *:nth-child(5) { animation-delay: 400ms; }
.has-js .hero-inner > *:nth-child(6) { animation-delay: 470ms; }
.has-js .hero-inner > *:nth-child(7) { animation-delay: 540ms; }
.has-js .hero-inner > *:nth-child(8) { animation-delay: 610ms; }
/* 徽标容器不参与整块动画,让 li 逐个进 */
.has-js .hero-inner > .badges { opacity: 1; animation: none; }
.has-js .hero .badges li { opacity: 0; animation: heroIn 520ms var(--ease-out) both; }
.has-js .hero .badges li:nth-child(1) { animation-delay: 330ms; }
.has-js .hero .badges li:nth-child(2) { animation-delay: 390ms; }
.has-js .hero .badges li:nth-child(3) { animation-delay: 450ms; }
.has-js .hero .badges li:nth-child(4) { animation-delay: 510ms; }
.has-js .hero .badges li:nth-child(5) { animation-delay: 570ms; }

.hero-title {
  margin: 0 0 18px;
  font-size: clamp(34px, 5vw, 54px);
  font-weight: 700; line-height: 1.14; letter-spacing: -.022em;
}
.hero-title .line { display: block; }
.hero--page .hero-title { font-size: clamp(29px, 4vw, 42px); }

.hero .lede { font-size: clamp(16px, 1.7vw, 18px); line-height: 1.8; color: var(--fg-soft); margin: 0 0 26px; max-width: 68ch; }
.hero .lede strong { color: var(--fg); }

/* ---------- §7.2 (已删除)首屏数据条 ----------
   原来首屏那排"0.25 秒/帧 · 3 处 · 827 MB · 0 次"数字卡按用户要求删除
   (实测速度表也一并删掉了 —— 官网不再出现速度数字),这里不再保留样式。 */

/* ===========================================================================
   §8 卡片网格:平面 + 1px 细边框 + 极浅阴影;进入 viewport 时轻微放大 1.02→1
   =========================================================================== */
.grid { display: grid; gap: 16px; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); margin: 22px 0 8px; }
.grid-3 { grid-template-columns: repeat(auto-fit, minmax(288px, 1fr)); }

.card {
  position: relative; overflow: hidden; padding: 20px;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface); box-shadow: var(--shadow-1);
  transition: border-color var(--t) var(--ease), box-shadow var(--t) var(--ease), transform var(--t) var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .card:hover { border-color: var(--line-strong); box-shadow: var(--shadow-2); transform: translateY(-2px); }
}
/* 鼠标跟随高光:一个固定大小的圆,只改 transform 位移(不动渐变、不触发布局) */
.card::after {
  content: ""; position: absolute; left: 0; top: 0; width: 340px; height: 340px;
  margin: -170px 0 0 -170px; border-radius: 50%; pointer-events: none; opacity: 0;
  background: radial-gradient(circle closest-side, var(--glow), transparent 72%);
  transform: translate(var(--gx, 50%), var(--gy, 0%));
  transition: opacity var(--t) var(--ease);
}
@media (hover: hover) and (pointer: fine) {
  .card:hover::after { opacity: 1; }
}
.card > * { position: relative; z-index: 1; }
.card h3 { margin: 0 0 8px; font-size: 17.5px; }
.card p { margin: 8px 0 0; color: var(--fg-soft); font-size: 15px; }
.card ul { margin: 10px 0 0; padding-left: 20px; color: var(--fg-soft); font-size: 14.6px; }
.card .tag { font-size: 12.5px; color: var(--muted); display: block; margin-top: 10px; }

.card-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; margin: 0 0 14px;
  border: 1px solid var(--line); border-radius: var(--radius-sm); color: var(--fg-soft);
  transition: color var(--t) var(--ease), border-color var(--t) var(--ease), transform var(--t) var(--ease-out);
}
.card-icon svg { width: 19px; height: 19px; display: block; }
@media (hover: hover) and (pointer: fine) {
  .card:hover .card-icon { color: var(--accent); border-color: var(--accent-line); transform: translateY(-1px); }
}

/* ===========================================================================
   §9 提示块
   =========================================================================== */
.note {
  margin: 22px 0; padding: 16px 20px;
  border: 1px solid var(--line); border-left: 2px solid var(--line-strong);
  border-radius: var(--radius); background: var(--surface);
}
.note > :first-child { margin-top: 0; }
.note > :last-child { margin-bottom: 0; }
.note-ok { border-left-color: var(--ok); background: var(--ok-soft); }
.note-warn { border-left-color: var(--warn); background: var(--warn-soft); }
.note-title { font-weight: 650; color: var(--fg); }
.note-ok .note-title { color: var(--ok); }
.note-warn .note-title { color: var(--warn); }
p.note { padding: 14px 18px; }

/* ===========================================================================
   §10 表格
   =========================================================================== */
.table-scroll { overflow-x: auto; margin: 20px 0; border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); }
table { border-collapse: collapse; width: 100%; min-width: 520px; font-size: 14.8px; }
th, td { border-bottom: 1px solid var(--line); padding: 11px 14px; text-align: left; vertical-align: top; }
thead th { background: var(--surface-2); font-weight: 650; color: var(--fg); white-space: nowrap; }
tbody tr { transition: background-color var(--t-fast) var(--ease); }
tbody tr:hover { background: var(--surface-2); }
tbody tr:last-child td { border-bottom: 0; }
td code { font-size: .86em; }
.td-center { text-align: center; white-space: nowrap; }
/* 计数元素:JS 会给出与最终文本等宽的固定宽度,让"每帧改一次数字"不影响外部布局。
   注意**不能**用 `contain: size` —— 尺寸隔离会让元素失去内容基线,
   行内块的基线退化成底边,数字与单位的对齐会错位(实测过,截图可见)。
   固定宽度已经足够:元素尺寸不变 → 父级排版不重算。 */
.count { display: inline-block; font-variant-numeric: tabular-nums; }

/* ===========================================================================
   §11 页内目录卡(教程页)
   =========================================================================== */
.toc { margin: 24px 0; padding: 18px 20px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); }
.toc > strong { display: block; margin-bottom: 8px; font-size: 13px; font-weight: 650; color: var(--muted); }
.toc ol { columns: 2; column-gap: 32px; margin: 0; padding-left: 20px; font-size: 15px; }
.toc li { margin: 4px 0; max-width: none; break-inside: avoid; }
.toc a { color: var(--fg-soft); }
.toc a:hover { color: var(--accent); }
@media (max-width: 620px) { .toc ol { columns: 1; } }

/* ===========================================================================
   §12 折叠块(FAQ / 更新日志):高度过渡
   =========================================================================== */
details { margin: 10px 0; border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); transition: border-color var(--t) var(--ease); }
details[open] { border-color: var(--line-strong); }
@media (hover: hover) and (pointer: fine) { details:hover { border-color: var(--line-strong); } }
summary {
  display: flex; gap: 12px; align-items: baseline; padding: 15px 18px;
  cursor: pointer; font-weight: 650; font-size: 16px; line-height: 1.6;
  list-style: none; transition: color var(--t-fast) var(--ease);
}
summary::-webkit-details-marker { display: none; }
summary::before {
  content: ""; flex: 0 0 auto; width: 7px; height: 7px; margin-top: .45em;
  border-right: 1.5px solid var(--muted); border-bottom: 1.5px solid var(--muted);
  transform: rotate(-45deg); transition: transform var(--t) var(--ease);
}
details[open] > summary::before { transform: rotate(45deg); }
summary:hover { color: var(--accent); }
summary .when { color: var(--muted); font-weight: 400; font-size: 13.5px; margin-left: auto; white-space: nowrap; }

.details-body { padding: 0 18px 16px; }
.details-body > :first-child { margin-top: 0; }
.details-body > :last-child { margin-bottom: 0; }
.details-body.is-animating { overflow: hidden; }

/* ===========================================================================
   §13 按钮:悬停位移 ≤2px;主按钮是页面上唯一一处渐变,并带光泽扫过
   =========================================================================== */
.btn {
  position: relative; overflow: hidden;
  display: inline-flex; align-items: center; justify-content: center; gap: 9px;
  padding: 12px 20px; border-radius: var(--radius-sm);
  border: 1px solid var(--line-strong); background: var(--surface); color: var(--fg);
  font: inherit; font-size: 15px; font-weight: 600; line-height: 1.4; cursor: pointer;
  transition: background-color var(--t) var(--ease), border-color var(--t) var(--ease),
              color var(--t) var(--ease), transform var(--t) var(--ease-out), box-shadow var(--t) var(--ease);
}
.btn:hover { text-decoration: none; }
.btn svg { width: 18px; height: 18px; flex: 0 0 auto; }

.btn-primary { border-color: transparent; background-image: linear-gradient(180deg, var(--accent-top), var(--accent-bottom)); color: var(--accent-ink); }
.btn-ghost { background: var(--surface); color: var(--fg); }

/* 光泽扫过:只在 hover 时跑一次,且只动 transform */
.btn-primary::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(100deg, transparent 26%, rgba(255, 255, 255, .5) 50%, transparent 74%);
  transform: translateX(-130%);
}
@media (hover: hover) and (pointer: fine) {
  .btn:hover { transform: translateY(-2px); box-shadow: var(--shadow-2); }
  .btn-ghost:hover { background: var(--surface-2); border-color: var(--line-strong); }
  .btn-primary:hover::after { animation: btnShine 760ms var(--ease-out); }
}
@keyframes btnShine { from { transform: translateX(-130%); } to { transform: translateX(130%); } }

/* 未开放的按钮(例:手机版(Android)· 开发中,敬请期待):
   要求「一眼看出未开放」+「点了没有任何反应」。两处配合:
     ① HTML 侧用 <button type="button" disabled aria-disabled="true">:disabled 的按钮
        不会派发 click 事件、不可聚焦,所以点上去真的什么都不会发生(不跳转、不弹窗);
     ② 这里只写「不可用」的视觉差异,不覆盖 .btn / .btn-primary 的既有规则。
   刻意**不写 pointer-events: none** —— 否则鼠标不会变成 not-allowed,
   反而少掉一个「这里不能用」的信号;而点击本来就被 disabled 拦住了。
   hover 的两条是兜底:个别浏览器仍会对 disabled 元素匹配 :hover。 */
.btn.is-disabled,
.btn:disabled {
  cursor: not-allowed;
  opacity: .8;                  /* 半透明 = 明确的"未开放"(不压到看不清字) */
  background-image: none;       /* 去掉主按钮的渐变,变成平面灰 */
  background-color: var(--surface-2);
  border-color: var(--line);
  color: var(--fg-soft);        /* 文字仍保持可读,只是比正常按钮弱一档 */
  box-shadow: none;
}
.btn.is-disabled:hover,
.btn:disabled:hover { transform: none; box-shadow: none; background-color: var(--surface-2); }
.btn.is-disabled::after,
.btn:disabled::after { display: none; }   /* 关掉主按钮的光泽扫过 */

.btn-row, .cta-row { display: flex; flex-wrap: wrap; gap: 12px; margin: 24px 0 8px; }
.cta-row { margin: 22px 0 0; }
.btn-note { color: var(--muted); font-size: 13.6px; margin-top: 8px; max-width: 74ch; }

/* ===========================================================================
   §14 徽标 / 页内 meta
   =========================================================================== */
.badges { display: flex; flex-wrap: wrap; gap: 8px; margin: 20px 0 4px; padding: 0; list-style: none; }
.badges li { margin: 0; max-width: none; }
.badge {
  display: inline-flex; align-items: center; gap: 6px; padding: 5px 12px;
  border-radius: var(--pill); border: 1px solid var(--line); background: var(--surface);
  color: var(--fg-soft); font-size: 13.5px; font-weight: 500;
}
.badge svg { width: 14px; height: 14px; }
.badge-brand { color: var(--accent); border-color: var(--accent-line); background: var(--accent-soft); font-weight: 600; }
.badge-warn { color: var(--warn); border-color: var(--warn-line); }

.page-meta { color: var(--muted); font-size: 14.8px; margin: 0; padding: 0; max-width: 74ch; }
.page-meta code { font-size: .9em; }

/* ===========================================================================
   §15 复制行 / 许可条目 / 名单
   =========================================================================== */
.copy-row { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin: 12px 0; }
.copy-btn {
  font: inherit; font-size: 13.6px; font-weight: 500; padding: 6px 12px;
  border-radius: var(--radius-sm); border: 1px solid var(--line); background: var(--surface);
  color: var(--fg-soft); cursor: pointer;
  transition: border-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}
.copy-btn:hover { border-color: var(--line-strong); color: var(--fg); }
.copy-btn.is-done { border-color: var(--ok-line); color: var(--ok); background: var(--ok-soft); }

/* 长列表分区隔离:视口外的条目跳过渲染(licenses 页 15 条,页面很长) */
.lic {
  margin: 10px 0; padding: 18px 20px;
  border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface);
  box-shadow: var(--shadow-1);
  transition: border-color var(--t) var(--ease), box-shadow var(--t) var(--ease), transform var(--t) var(--ease-out);
  content-visibility: auto;
  contain-intrinsic-size: auto 150px;
}
@media (hover: hover) and (pointer: fine) {
  .lic:hover { border-color: var(--line-strong); box-shadow: var(--shadow-2); transform: translateY(-2px); }
}
.lic h3 { margin: 0 0 6px; font-size: 16.5px; }
.lic dl { margin: 8px 0 0; display: grid; grid-template-columns: 92px 1fr; gap: 4px 14px; font-size: 14.5px; }
.lic dt { color: var(--muted); }
.lic dd { margin: 0; color: var(--fg-soft); word-break: break-word; }

.names { display: flex; flex-wrap: wrap; gap: 10px; padding: 0; list-style: none; margin: 18px 0; }
.names li { margin: 0; max-width: none; padding: 6px 15px; border: 1px solid var(--line); border-radius: var(--pill); background: var(--surface); font-size: 15px; transition: transform var(--t) var(--ease-out), border-color var(--t) var(--ease); }
@media (hover: hover) and (pointer: fine) { .names li:hover { transform: translateY(-2px); border-color: var(--accent-line); } }

/* ===========================================================================
   §16 滚动进入动画
   位移 22px + 淡入,时长 380ms;同组 80ms 依次错开(最多 4 级)。
   只在进入视口时触发一次,随即 unobserve。只动 transform / opacity。
   "轻微缩放 0.98→1"**只给卡片**:实测把它套到全部入场元素(教程页 51 个)会让
   每个元素在过渡期反复重绘,主线程耗时 +67ms;只给卡片就基本零成本。
   =========================================================================== */
.has-js [data-reveal],
.has-js [data-reveal-group] > * {
  opacity: 0;
  transform: translateY(22px);
  transition: opacity 380ms var(--ease-out), transform 380ms var(--ease-out);
  transition-delay: calc(var(--d, 0) * 80ms);
}
.has-js [data-reveal].is-in,
.has-js [data-reveal-group] > *.is-in {
  opacity: 1;
  transform: translateY(0);
}
.has-js .card[data-reveal],
.has-js [data-reveal-group] > .card { transform: translateY(22px) scale(.98); }
.has-js .card[data-reveal].is-in,
.has-js [data-reveal-group] > .card.is-in { transform: translateY(0) scale(1); }

/* ===========================================================================
   §17 章节标题:遮罩上滑 + 逐词浮现 + 分隔线渐显
   外层 .t-mask 负责"遮罩"(overflow 隐藏),内层 .t-rise 整块上滑,
   再叠加每个词的小幅上移淡入 —— 全程只动 transform / opacity。
   用 block 级包裹而不是给每个字加 overflow:后者会让行内块的基线退化成底边,
   数字与单位会错位(实测过,截图可见)。
   =========================================================================== */
h2 { position: relative; padding-top: 20px; }
h2::before {
  content: ""; position: absolute; left: 0; top: 0; width: 44px; height: 3px; border-radius: 3px;
  background: linear-gradient(90deg, var(--accent), var(--accent-top));
  transform: scaleX(0); transform-origin: 0 50%;
  transition: transform 520ms var(--ease-out);
}
/* 分隔线:整行细线随标题入场从左侧展开,右端渐隐 */
h2::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: -1px; height: 1px;
  background: linear-gradient(90deg, var(--line-strong), transparent 78%);
  transform: scaleX(0); transform-origin: 0 50%;
  transition: transform 640ms var(--ease-out) 80ms;
}
h2.is-in::before { transform: scaleX(1); }
h2.is-in::after { transform: scaleX(1); }
h2:first-child { padding-top: 18px; }

.t-mask { display: block; overflow: hidden; padding-bottom: .08em; margin-bottom: -.08em; }
.t-rise { display: block; transform: translateY(105%); transition: transform 560ms var(--ease-out); }
h2.is-in .t-rise { transform: translateY(0); }

.wtok {
  display: inline-block; opacity: 0; transform: translateY(.5em);
  transition: opacity 420ms var(--ease-out), transform 420ms var(--ease-out);
  transition-delay: calc(var(--wi, 0) * 22ms);
}
h2.is-in .wtok { opacity: 1; transform: none; }

/* ===========================================================================
   §18 步骤流:竖线随滚动逐条点亮
   连接线走 scaleY(合成器);序号节点只改颜色/边框(小面积重绘)
   =========================================================================== */
.flow { list-style: none; counter-reset: flowstep; padding-left: 0; margin: 24px 0; }
.flow > li { position: relative; counter-increment: flowstep; margin: 0; padding: 0 0 26px 60px; max-width: 74ch; }
.flow > li:last-child { padding-bottom: 0; }
.flow > li::before {
  content: counter(flowstep);
  position: absolute; left: 0; top: 0; width: 34px; height: 34px;
  display: flex; align-items: center; justify-content: center; border-radius: 50%;
  border: 1px solid var(--line); background: var(--surface);
  font-family: var(--mono); font-size: 14px; font-weight: 600; color: var(--muted);
  transition: color 420ms var(--ease), border-color 420ms var(--ease),
              background-color 420ms var(--ease), box-shadow 420ms var(--ease);
}
.flow > li::after {
  content: ""; position: absolute; left: 16px; top: 38px; bottom: 2px; width: 2px; border-radius: 2px;
  background: linear-gradient(180deg, var(--accent), var(--accent-top));
  transform: scaleY(0); transform-origin: 50% 0;
  transition: transform 560ms var(--ease-out);
}
.flow > li:last-child::after { display: none; }
.flow > li.is-lit::before {
  color: var(--accent-ink); border-color: transparent;
  background-color: var(--accent);
  box-shadow: 0 0 0 4px var(--accent-soft);
}
.flow > li.is-lit::after { transform: scaleY(1); }
.flow > li > :first-child { margin-top: 6px; }

/* ===========================================================================
   §19 页脚 / 小工具类 / 响应式
   =========================================================================== */
.site-footer { padding: 40px 0 44px; font-size: 14.2px; color: var(--muted); border-top: 1px solid var(--line); background: var(--surface); }
.footer-links { display: flex; flex-wrap: wrap; gap: 8px 18px; margin: 0 0 16px; padding: 0; list-style: none; }
.footer-links li { margin: 0; max-width: none; }
.footer-links a { color: var(--fg-soft); }
.footer-links a:hover { color: var(--accent); }
.footer-legal { margin: 8px 0 0; }
.icp { margin: 6px 0 0; }
.icp a { color: var(--muted); }
.icp a:hover { color: var(--accent); }

.muted { color: var(--muted); }
.small { font-size: 13.8px; }
.nowrap { white-space: nowrap; }
.stack > * + * { margin-top: 12px; }
.center { text-align: center; }
.hash { font-family: var(--mono); font-size: 13px; word-break: break-all; white-space: normal; }
.timeline-ver { font-family: var(--mono); font-size: 13.5px; font-weight: 600; color: var(--accent); background: var(--accent-soft); border-radius: 6px; padding: 2px 8px; margin-right: 8px; }
.icon { width: 18px; height: 18px; flex: 0 0 auto; }
.inline-icon { width: 16px; height: 16px; vertical-align: -3px; margin-right: 5px; }

@media (max-width: 900px) { .header-inner { gap: 10px; } }

@media (max-width: 720px) {
  :root { --pad: 16px; }
  body { font-size: 16px; }
  .content { padding-top: 36px; padding-bottom: 64px; }
  h2 { margin-top: 46px; font-size: clamp(21px, 5.4vw, 27px); }
  .grid, .grid-3 { grid-template-columns: 1fr; gap: 14px; }
  .card { padding: 18px; }
  .lic dl { grid-template-columns: 1fr; gap: 0; }
  .lic dt { margin-top: 8px; font-size: 13px; }
  summary { padding: 14px 15px; font-size: 15.5px; }
  .details-body { padding: 0 15px 15px; }
  .btn { width: 100%; }
  table { font-size: 14px; }
  th, td { padding: 9px 11px; }
  .flow > li { padding-left: 50px; }
  .flow > li::before { width: 30px; height: 30px; font-size: 13px; }
  .flow > li::after { left: 14px; top: 34px; }
}

@media (min-width: 721px) and (max-width: 1080px) {
  .grid, .grid-3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ===========================================================================
   §20 减少动态效果:关掉全部位移与动画,只保留颜色变化
   =========================================================================== */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-property: color, background-color, border-color, box-shadow, opacity !important;
    transition-duration: 200ms !important;
    transition-delay: 0ms !important;
  }

  .hero-inner > *, .hero .badges li, .aurora-blob, .wtok, .t-title-plain, .t-rise,
  .page-progress > i, .aurora, .hero-inner,
  [data-reveal], [data-reveal-group] > *, .card, .card::after {
    opacity: 1 !important;
    transform: none !important;
    animation: none !important;
  }
  /* 滚动驱动动画也要关掉,否则会随滚动产生位移 */
  .page-progress > i { display: none !important; }
  h2::before { transform: scaleX(1) !important; transition: none !important; }
  .flow > li::after { transform: scaleY(1) !important; }
  .flow > li::before { transition: none !important; }
  .card::after, .btn-primary::after { display: none !important; }
  .card:hover, .lic:hover, .names li:hover, .btn:hover, .card:hover .card-icon { transform: none !important; }
}

/* ===========================================================================
   §21 打印
   =========================================================================== */
@media print {
  .site-header, .page-toc, .page-progress, .nav-toggle, .aurora,
  .btn, .copy-btn, .skip-link { display: none !important; }
  html, body { background: #fff !important; color: #000 !important; }
  .card, .note, .lic, details, .table-scroll, .demo {
    background: #fff !important;
    box-shadow: none !important;
    border-color: #ccc !important;
    content-visibility: visible !important;
  }
  [data-reveal], [data-reveal-group] > *, .wtok, .t-rise { opacity: 1 !important; transform: none !important; }
  h2 { page-break-after: avoid; }
  details { break-inside: avoid; }
  details > .details-body { display: block !important; height: auto !important; }
}

/* ===========================================================================
   §22 功能演示:6 个自动循环播放的小动画(首页「功能概览」)
   约束与做法
     · 纯 CSS + 内联 SVG,零外部资源、零图片文件;
     · 只动 `transform` / `opacity`(SVG 里同样是 transform / stroke-dashoffset),
       不逐帧改 DOM、不动画大半径 filter;
     · 每个演示 3.8~5.2s 一轮、`infinite`;**默认暂停,进入视口由 JS 加 `.is-live` 才播**,
       离开视口立刻回到暂停(省电、也不参与滚动期开销);
     · `prefers-reduced-motion: reduce` 下不播循环,而是**冻结在循环中间那一帧** ——
       正好是"前后对比"的两态(见文件末尾的 @media 块);
     · 配色、圆角、线宽全部复用 §1 的令牌,深浅两套自动跟随。
   =========================================================================== */
.demo {
  position: relative;
  height: 78px;
  margin: 0 0 14px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--surface-2);
  overflow: hidden;
}
/* 极轻的网格底衬,把演示画面"托"起来 */
.demo::before {
  content: "";
  position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background-image:
    linear-gradient(var(--line) 1px, transparent 1px),
    linear-gradient(90deg, var(--line) 1px, transparent 1px);
  background-size: 14px 14px;
  opacity: .6;
}
.demo-note { margin: 10px 0 0; font-size: 13.2px; line-height: 1.6; color: var(--muted); max-width: none; }
.demo svg { display: block; }
.demo-art { color: var(--fg-soft); }

/* 演示里的小标签 / 小说明 —— 每个动画都要能一眼看出"这一半是什么、前后差在哪"。
   没有文字的纯图形动画,用户看不懂(实测反馈),所以每个演示都必须带标签。 */
.demo-tag {
  display: inline-block; flex: 0 0 auto;
  padding: 1px 6px; border-radius: 4px;
  font-size: 9.5px; font-weight: 650; line-height: 1.6; letter-spacing: .02em;
  color: var(--muted); background: var(--surface);
  box-shadow: inset 0 0 0 1px var(--line);
  white-space: nowrap;
}
.demo-tag-ok { color: var(--accent); background: var(--accent-soft); box-shadow: inset 0 0 0 1px var(--accent-line); }
.demo-cap { font-size: 9.5px; font-weight: 650; letter-spacing: .02em; color: var(--muted); white-space: nowrap; }
.demo-cap-ok { color: var(--accent); }

/* 默认不播;进入视口(JS 加 .is-live)才播。
   必须用 !important:各演示自己的 `animation:` 简写会把 animation-play-state
   重置成 running,而它们的特异度(0,2,0)高于这里的 `.demo *`(0,1,0)。 */
.demo, .demo * { animation-play-state: paused !important; }
.demo.is-live, .demo.is-live * { animation-play-state: running !important; }

/* ---------- ① 超分:同一张图,左边 640×360 原片(只剩大色块),右边 4x 之后(顺滑) ----------
   原来那版是"两张几乎一样的线稿互相擦除",看不出变化 —— 改成左右并排对比:
   左边故意做小、做糊、叠一层像素格;右边是同图放大后的干净版本。两边都带文字标注。 */
.demo-up { display: flex; align-items: center; justify-content: center; gap: 12px; padding: 8px 10px; }
.up-side { display: flex; flex-direction: column; align-items: center; gap: 4px; }
.up-shot {
  position: relative; display: block; overflow: hidden;
  border-radius: 4px; background: var(--surface);
  box-shadow: inset 0 0 0 1px var(--line-strong);
}
.up-shot svg { width: 100%; height: 100%; }
/* 原片:小 + 糊 + 一层"像素格",让"细节丢了"看得见 */
.up-shot-s { width: 46px; height: 30px; filter: blur(1.1px) saturate(.5); opacity: .85; }
.up-shot-s::after {
  content: ""; position: absolute; inset: 0;
  background-image:
    repeating-linear-gradient(90deg, var(--bg) 0 1px, transparent 1px 4px),
    repeating-linear-gradient(0deg, var(--bg) 0 1px, transparent 1px 4px);
  opacity: .45;
}
/* 超分后:同一张图,尺寸放大、线条干净(轻微呼吸表示"刚处理完") */
.up-shot-l { width: 96px; height: 48px; animation: upPop 4.6s var(--ease) infinite; }
@keyframes upPop {
  0%, 10%   { transform: scale(1); }
  48%, 86%  { transform: scale(1.035); }
  100%      { transform: scale(1); }
}
/* 中间那颗走动的点是"正在处理"的提示 */
.up-arrow {
  position: relative; display: inline-flex; align-items: center;
  padding-left: 20px; font-size: 10px; font-weight: 700; letter-spacing: .02em;
  color: var(--accent);
}
.up-arrow::before {
  content: ""; position: absolute; left: 0; top: 50%; width: 16px; height: 1px;
  background: var(--accent-line);
}
.up-arrow i {
  position: absolute; left: 0; top: 50%; width: 5px; height: 5px; margin-top: -2.5px;
  border-radius: 50%; background: var(--accent);
  animation: upDot 2.2s var(--ease) infinite;
}
@keyframes upDot {
  0%        { transform: translateX(0); opacity: 0; }
  18%, 78%  { opacity: 1; }
  100%      { transform: translateX(16px); opacity: 0; }
}

/* ---------- ② 补帧:同一个球,上行 24fps 一跳一跳,下行 60fps 连续顺滑 ----------
   球在同一根轨道上走,靠"跳 vs 走"的对比说明补帧;下行多出来的刻度 = 新生成的中间帧。 */
.demo-frame { display: flex; flex-direction: column; justify-content: center; gap: 6px; padding: 8px 10px; }
.fr-row { display: flex; align-items: center; gap: 8px; height: 26px; }
.fr-row .demo-tag { flex: 0 0 68px; text-align: center; }
.fr-track { position: relative; flex: 1 1 auto; height: 24px; }
.fr-line { position: absolute; left: 0; right: 0; top: 8px; height: 1px; background: var(--line-strong); }
.fr-run { position: absolute; left: 0; top: 2px; width: 100%; height: 12px; }
.fr-ball { position: absolute; left: 0; top: 0; width: 12px; height: 12px; border-radius: 50%; }
/* 注意:位移必须加在 `.fr-run`(宽度 = 整条轨道)上,不能加在 12px 的球上 ——
   translateX 的百分比是相对"元素自己"的宽度算的,加在球上 = 100% - 12px = 0,球根本不会动
   (实测踩到过:采样 800ms 位移恒为 0)。 */
.fr-raw .fr-run { animation: frStep 3.6s infinite; }
.fr-after .fr-run { animation: frGlide 3.6s var(--ease) infinite; }
.fr-raw .fr-ball { background: var(--muted); }
.fr-after .fr-ball { background: var(--accent); }
/* 关键:原片用 steps() 走 —— 位置一格一格跳;补帧后是 linear/cubic 连续走 */
@keyframes frStep {
  0%   { transform: translateX(0); animation-timing-function: steps(6, end); }
  45%  { transform: translateX(calc(100% - 12px)); animation-timing-function: steps(6, end); }
  55%  { transform: translateX(calc(100% - 12px)); animation-timing-function: steps(6, end); }
  100% { transform: translateX(0); }
}
@keyframes frGlide {
  0%   { transform: translateX(0); }
  45%  { transform: translateX(calc(100% - 12px)); }
  55%  { transform: translateX(calc(100% - 12px)); }
  100% { transform: translateX(0); }
}
.fr-ticks { position: absolute; left: 0; right: 0; bottom: 0; display: flex; justify-content: space-between; }
.fr-ticks i { width: 3px; height: 7px; border-radius: 1.5px; background: var(--line-strong); }
/* 偶数刻度是"生成"出来的中间帧:循环亮起。
   只动 opacity / scaleY —— 不能动 `background`,它是重绘属性,每帧都要重绘
   (静态审计会把它标成违规,也是滚动期 Paint 的来源之一)。 */
.fr-after .fr-ticks i:nth-child(even) { background: var(--accent); animation: frInsert 3.6s var(--ease) infinite; }
@keyframes frInsert {
  0%, 10%  { opacity: .28; transform: scaleY(.6); }
  40%, 88% { opacity: 1; transform: scaleY(1.3); }
  100%     { opacity: .28; transform: scaleY(.6); }
}

/* ---------- ③ 去重:6 帧里有一半跟前一帧一模一样,被划掉删除,帧数 6 → 3 ---------- */
.demo-dup { display: flex; align-items: center; justify-content: center; padding: 8px 10px; }
.dp-strip { display: flex; gap: 5px; margin-top: 14px; }
.dp-cell {
  position: relative; display: block; width: 26px; height: 26px;
  border-radius: 4px; background: var(--surface); overflow: hidden;
  box-shadow: inset 0 0 0 1px var(--line-strong);
}
.dp-cell svg { width: 100%; height: 100%; }
/* 偶数是重复帧:画面淡下去 + 划一道斜线。
   注意淡出必须加在**里面的图**上,不能加在整格上 —— 整格 opacity 会把划线的 ::after
   一起变淡(实测截图里几乎看不见红线),而"帧框留着、画面划掉"也更贴近真实语义。 */
.dp-cell:nth-child(even) svg { animation: dpDrop 4.4s var(--ease) infinite; }
.dp-cell:nth-child(even)::after {
  content: ""; position: absolute; left: -12%; right: -12%; top: 50%; height: 1.5px;
  background: var(--danger); transform-origin: 50% 50%;
  animation: dpStrike 4.4s var(--ease) infinite;
}
@keyframes dpDrop {
  0%, 16%   { opacity: 1; }
  40%, 84%  { opacity: .14; }
  100%      { opacity: 1; }
}
@keyframes dpStrike {
  0%, 16%   { opacity: 0; transform: rotate(-20deg) scaleX(0); }
  40%, 84%  { opacity: 1; transform: rotate(-20deg) scaleX(1); }
  100%      { opacity: 0; transform: rotate(-20deg) scaleX(0); }
}
/* 左上角这句跟着一起换:原片 6 帧 → 去重后 3 帧 */
.dp-tag-a, .dp-tag-b { position: absolute; left: 8px; top: 7px; }
.dp-tag-a { animation: dpTagA 4.4s var(--ease) infinite; }
.dp-tag-b { opacity: 0; animation: dpTagB 4.4s var(--ease) infinite; }
@keyframes dpTagA {
  0%, 16%   { opacity: 1; }
  40%, 84%  { opacity: 0; }
  100%      { opacity: 1; }
}
@keyframes dpTagB {
  0%, 16%   { opacity: 0; }
  40%, 84%  { opacity: 1; }
  100%      { opacity: 0; }
}

/* ---------- ④ 抠图:实色背景淡出,露出棋盘格(透明) ---------- */
.demo-cut .ct-check {
  position: absolute; inset: 0; z-index: 1;
  background-image:
    repeating-conic-gradient(var(--line-strong) 0% 25%, transparent 0% 50%);
  background-size: 12px 12px;
}
.demo-cut .ct-solid {
  position: absolute; inset: 0; z-index: 2; background: var(--demo-bg);
  animation: ctClear 4.8s var(--ease) infinite;
}
@keyframes ctClear {
  0%, 18%  { opacity: 1; }
  46%, 80% { opacity: 0; }
  100%     { opacity: 1; }
}
/* 【2026-09-14 修「主体被裁」】原来只有 `position:absolute; inset:0`,没给尺寸 ——
   而 SVG 是**替换元素**:width/height 为 auto 时按自身 viewBox 的宽高比取尺寸,inset 的拉伸对它无效。
   实测:面板 299×76,SVG 被撑成 **297×152**(高 2 倍),圆头落在 33~84px(刚出下沿)、
   身体落在 92~137px(完全在面板外被 overflow 裁掉)—— 用户看到的就是"只剩半个头、没有身子"。
   显式给 100%/100% 后 SVG 铺满面板,再由默认 preserveAspectRatio=xMidYMid meet 把 152×78 等比居中放入。 */
.demo-cut .ct-subject { position: absolute; inset: 0; width: 100%; height: 100%; z-index: 3; color: var(--accent); }
.demo-cut .ct-marquee {
  position: absolute; left: 50%; top: 50%; z-index: 4;
  width: 66px; height: 54px; margin: -27px 0 0 -33px;
  border: 1px dashed var(--accent); border-radius: 6px;
  opacity: 0; animation: ctMark 4.8s var(--ease) infinite;
}
@keyframes ctMark {
  0%, 24%  { opacity: 0; transform: scale(1.06); }
  48%, 80% { opacity: .9; transform: scale(1); }
  100%     { opacity: 0; transform: scale(1.06); }
}
/* 左上角说明跟着背景一起换:原图(有背景) → 背景已抠掉 */
.demo-cut .ct-tag-a, .demo-cut .ct-tag-b { position: absolute; left: 8px; top: 7px; }
.demo-cut .ct-tag-a { animation: ctTagA 4.8s var(--ease) infinite; }
.demo-cut .ct-tag-b { opacity: 0; animation: ctTagB 4.8s var(--ease) infinite; }
@keyframes ctTagA {
  0%, 18%  { opacity: 1; }
  46%, 80% { opacity: 0; }
  100%     { opacity: 1; }
}
@keyframes ctTagB {
  0%, 18%  { opacity: 0; }
  46%, 80% { opacity: 1; }
  100%     { opacity: 0; }
}

/* ---------- ⑤ 音频增强:上行"噪、抖",下行"干净、平滑",两行都写明是哪一段 ---------- */
.demo-audio { display: flex; flex-direction: column; justify-content: center; gap: 4px; padding: 8px 10px; }
.demo-audio .au-row {
  display: flex; align-items: center; justify-content: flex-start; gap: 6px;
  height: 26px; position: relative; z-index: 2;
}
.demo-audio .au-row .demo-tag { flex: 0 0 48px; text-align: center; }
.demo-audio .au-row i {
  display: block; width: 5px; height: 20px; border-radius: 2px;
  background: var(--fg-soft); transform-origin: 50% 50%;
}
.demo-audio .au-noisy i { background: var(--muted); }
.demo-audio .au-clean i { background: var(--accent); }
/* 噪声:每根柱子各自的周期与幅度都不一样,看起来"乱抖"。
   周期刻意放慢到 420~880ms —— 150~410ms 的抖动画在滚动期会明显增加帧尖峰
   (实测 index 有 3 帧 >16.7ms、最长 25ms),放慢后既不影响"乱"的观感又便宜。 */
.demo-audio .au-noisy i:nth-child(1) { animation: auJit 620ms linear infinite alternate; }
.demo-audio .au-noisy i:nth-child(2) { animation: auJit 780ms linear infinite alternate-reverse; }
.demo-audio .au-noisy i:nth-child(3) { animation: auJit 460ms linear infinite alternate; }
.demo-audio .au-noisy i:nth-child(4) { animation: auJit 880ms linear infinite alternate-reverse; }
.demo-audio .au-noisy i:nth-child(5) { animation: auJit 420ms linear infinite alternate; }
.demo-audio .au-noisy i:nth-child(6) { animation: auJit 820ms linear infinite alternate-reverse; }
.demo-audio .au-noisy i:nth-child(7) { animation: auJit 540ms linear infinite alternate; }
.demo-audio .au-noisy i:nth-child(8) { animation: auJit 700ms linear infinite alternate-reverse; }
.demo-audio .au-noisy i:nth-child(9) { animation: auJit 440ms linear infinite alternate; }
.demo-audio .au-noisy i:nth-child(10) { animation: auJit 860ms linear infinite alternate-reverse; }
.demo-audio .au-noisy i:nth-child(11) { animation: auJit 600ms linear infinite alternate; }
@keyframes auJit { from { transform: scaleY(.18); } to { transform: scaleY(1); } }
/* 增强后:统一周期 + 依次相位,形成一道平滑推进的波形 */
.demo-audio .au-clean i { animation: auWave 1.1s var(--ease) infinite alternate; }
.demo-audio .au-clean i:nth-child(1)  { animation-delay: -.00s; }
.demo-audio .au-clean i:nth-child(2)  { animation-delay: -.10s; }
.demo-audio .au-clean i:nth-child(3)  { animation-delay: -.20s; }
.demo-audio .au-clean i:nth-child(4)  { animation-delay: -.30s; }
.demo-audio .au-clean i:nth-child(5)  { animation-delay: -.40s; }
.demo-audio .au-clean i:nth-child(6)  { animation-delay: -.50s; }
.demo-audio .au-clean i:nth-child(7)  { animation-delay: -.60s; }
.demo-audio .au-clean i:nth-child(8)  { animation-delay: -.70s; }
.demo-audio .au-clean i:nth-child(9)  { animation-delay: -.80s; }
.demo-audio .au-clean i:nth-child(10) { animation-delay: -.90s; }
.demo-audio .au-clean i:nth-child(11) { animation-delay: -1.00s; }
@keyframes auWave { from { transform: scaleY(.28); } to { transform: scaleY(.94); } }

/* ---------- ⑥ 长视频:进度条推进 + 分批格子逐个填满 ---------- */
.lg-tag { position: absolute; left: 8px; top: 7px; z-index: 3; }
.demo-long .lg-bar {
  position: absolute; left: 14px; right: 14px; top: 32px; z-index: 2; height: 6px;
  border-radius: 3px; background: var(--line); overflow: hidden;
}
.demo-long .lg-bar b {
  display: block; height: 100%; width: 100%; border-radius: 3px;
  background: var(--accent); transform-origin: 0 50%;
  animation: lgFill 5.2s linear infinite;
}
@keyframes lgFill {
  0%   { transform: scaleX(0); }
  88%  { transform: scaleX(1); }
  100% { transform: scaleX(0); }
}
.demo-long .lg-cells {
  position: absolute; left: 14px; right: 14px; bottom: 11px; z-index: 2;
  display: flex; justify-content: space-between;
}
.demo-long .lg-cells i {
  width: 12px; height: 12px; border-radius: 3px;
  background: var(--accent); opacity: .35;
  animation: lgCell 5.2s linear infinite;
}
.demo-long .lg-cells i:nth-child(1) { animation-delay: 0s; }
.demo-long .lg-cells i:nth-child(2) { animation-delay: .42s; }
.demo-long .lg-cells i:nth-child(3) { animation-delay: .84s; }
.demo-long .lg-cells i:nth-child(4) { animation-delay: 1.26s; }
.demo-long .lg-cells i:nth-child(5) { animation-delay: 1.68s; }
.demo-long .lg-cells i:nth-child(6) { animation-delay: 2.10s; }
.demo-long .lg-cells i:nth-child(7) { animation-delay: 2.52s; }
.demo-long .lg-cells i:nth-child(8) { animation-delay: 2.94s; }
.demo-long .lg-cells i:nth-child(9) { animation-delay: 3.36s; }
.demo-long .lg-cells i:nth-child(10) { animation-delay: 3.78s; }
@keyframes lgCell {
  0%        { opacity: .35; transform: scale(.8); }
  10%, 80%  { opacity: 1; transform: scale(1); }
  100%      { opacity: .35; transform: scale(.8); }
}

/* ---------- 减少动态效果:不播循环,冻结在"前后对比"那一帧 ----------
   做法:把动画时长还原成真实周期,用负的 animation-delay 把它停在循环中点,
   再 paused 住 —— 每个演示都同时呈现"改前 / 改后"两态,而不是一片空白。 */
@media (prefers-reduced-motion: reduce) {
  /* 选择器把 .is-live 两种形态都写上,确保压过上面的"播放中"规则 */
  .demo, .demo *, .demo.is-live, .demo.is-live * {
    /* 只改时长/延迟/播放状态,保留各自的 animation-name 与缓动 */
    animation-duration: var(--ffz-dur, 4s) !important;
    animation-delay: var(--ffz-at, -2s) !important;
    animation-iteration-count: 1 !important;
    animation-play-state: paused !important;
    animation-fill-mode: both !important;
    transition: none !important;
  }
  /* 每个演示挑一个"前后都在"的时刻 */
  .demo-up .up-shot-l { --ffz-dur: 4.6s; --ffz-at: -2.4s; }                 /* 放大到位 */
  .demo-up .up-arrow i { --ffz-dur: 2.2s; --ffz-at: -1.1s; }                /* 走到一半 */
  .demo-frame .fr-raw .fr-run, .demo-frame .fr-after .fr-run { --ffz-dur: 3.6s; --ffz-at: -.9s; }
  .demo-frame .fr-after .fr-ticks i:nth-child(even) { --ffz-dur: 3.6s; --ffz-at: -1.7s; }   /* 中间帧亮着 */
  .demo-dup .dp-cell:nth-child(even) svg { --ffz-dur: 4.4s; --ffz-at: -2s; }    /* 重复帧已淡掉 */
  /* ::after 不受上面 `.demo *` 那条选择器管辖,得单独写一遍 */
  .demo-dup .dp-cell:nth-child(even)::after {
    animation-duration: 4.4s !important;
    animation-delay: -2s !important;
    animation-iteration-count: 1 !important;
    animation-play-state: paused !important;
    animation-fill-mode: both !important;
  }
  .demo-dup .dp-tag-a, .demo-dup .dp-tag-b { --ffz-dur: 4.4s; --ffz-at: -2s; }   /* 停在"去重后:3 帧" */
  .demo-cut .ct-solid { --ffz-dur: 4.8s; --ffz-at: -1.35s; }                /* 背景淡到一半 */
  .demo-cut .ct-marquee { --ffz-dur: 4.8s; --ffz-at: -1.9s; }
  .demo-cut .ct-tag-a, .demo-cut .ct-tag-b { --ffz-dur: 4.8s; --ffz-at: -2.2s; }
  .demo-audio .au-noisy i { --ffz-dur: 300ms; --ffz-at: -150ms; }
  .demo-audio .au-clean i { --ffz-dur: 1.1s; --ffz-at: -.55s; }
  .demo-long .lg-bar b { --ffz-dur: 5.2s; --ffz-at: -2.3s; }
  .demo-long .lg-cells i { --ffz-dur: 5.2s; --ffz-at: -1.2s; }
  /* 波形/抖动用 alternate,减动态下也停在中间高度 */
  .demo-audio .au-noisy i, .demo-audio .au-clean i { animation-direction: alternate !important; }
}

/* ===========================================================================
   §22 首页「设备适配」卡的档位行(纯静态:grid + 文字颜色,零动画、零滤镜)
   为什么不用 <table>:表格带着 `min-width: 520px`,在 ≤390px 上只能靠容器横向滚动
   (要么横向溢出、要么文字被裁)。这里改成「标签 + 说明」两列 grid,窄屏再折成单列,
   天生不会横向溢出。开销只有布局:没有 backdrop-filter、没有 filter、没有叠层阴影,
   也没有新增任何 transition/animation(入场渐入沿用页面既有的 data-reveal)。
   =========================================================================== */
.fit-list { margin: 12px 0 0; padding-left: 0; list-style: none; display: grid; gap: 7px; }
/* 单独再写一条压过 `.card ul` 的 padding-left:20px / margin(它比 .fit-list 优先级高) */
.card .fit-list { margin: 12px 0 0; padding-left: 0; }
.card .fit-list > li {
  margin: 0; display: grid; gap: 1px 12px;
  grid-template-columns: 88px minmax(0, 1fr);   /* minmax(0,1fr):长串字符也撑不破右侧列 */
  font-size: 14.5px; line-height: 1.65; color: var(--fg-soft);
}
.card .fit-list .fit-k { color: var(--muted); }
.card .fit-list .fit-v { overflow-wrap: anywhere; }   /* 极窄屏兜底:长串不溢出 */
@media (max-width: 420px) {
  /* 窄屏:两列改单列纵向罗列,免得 88px 标签列把说明挤成窄条 */
  .card .fit-list > li { grid-template-columns: 1fr; gap: 0; }
  .card .fit-list .fit-k { font-size: 13.4px; }
}

