/* ==========================================================================
   课记官网
   设计令牌取自应用本体 DesignSystem.swift：
   暖米纸底 / 墨绿 accent / 星标金 / 七种学习点类型色
   ========================================================================== */

:root {
  --paper: #f8f6f1;      /* appBackground */
  --white: #ffffff;      /* panelBackground */
  --card: #fffefa;       /* cardBackground */
  --ink: #1a1f21;        /* ink */
  /* 比 App 本体的 #707873 深一档：官网正文更小、底色更暖，
     原值在米纸底上 4.2:1，差 WCAG AA 的 4.5 一步 */
  --muted: #666e69;
  --line: #dbd9cf;       /* line */
  --accent: #146957;     /* accent 墨绿 */
  --accent-deep: #0f5244;
  --star: #eda81f;       /* star 星标金 */

  /* 学习点类型色（pointTint 浅色值） */
  --t-question: #3d4fb8;
  --t-explain: #057a80;
  --t-key: #2161c2;
  --t-method: #0f734d;
  --t-trap: #c72e24;
  --t-example: #6640b3;
  --t-memory: #b8610d;

  --video-a: #141a1c;
  --video-b: #262e2b;
  --dark: #0e1213;         /* 暗色章节底 */
  --dark-ink: #f2f4f2;
  --dark-muted: #9aa5a0;
  --mint: #7fd8c2;

  --r-s: 8px;
  --r-m: 12px;
  --r-l: 18px;
  --r-xl: 26px;

  --font: -apple-system, BlinkMacSystemFont, "SF Pro Display", "SF Pro Text",
          "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", sans-serif;

  /* ---- 动效令牌 ----
     此前缓动与时长是散着写的：0.18s / 0.2s / 0.25s / 0.28s / 0.3s / 0.34s / 0.5s /
     0.9s / 0.95s，曲线在 ease、ease-in-out、cubic-bezier(0.22,1,0.36,1) 之间混用。
     单看每一条都合理，合起来就是「每个组件各有各的手感」——这正是与苹果官网那种
     「整站像一个东西」最直接的差距：他们的动效只有很少几档，且几乎只用一条出场曲线。
     收敛成两条曲线、四档时长，后续所有动效都从这里取值。 */

  /* 出场/位移主曲线。起步快、尾巴长，运动结束前很久就几乎停住 —— 这是「稳重」的来源。
     注意这里必须写字面值：曾被全局替换误写成 var(--ease-out) 自引用，
     令牌因循环成为无效值，全站所有引用它的 transition/animation 整条失效、
     一律瞬移——静态截图看不出瞬移与缓动的差别，这个坑在无头验证里潜伏了三轮 */
  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
  /* 状态互换（颜色、底色、边框）用对称曲线，不需要那条长尾巴 */
  --ease-soft: cubic-bezier(0.4, 0, 0.2, 1);

  --dur-1: 0.2s;    /* 微反馈：颜色、底色 */
  --dur-2: 0.34s;   /* 悬停位移、小元件变形 */
  --dur-3: 0.62s;   /* 栅格卡片入场 */
  --dur-4: 0.95s;   /* 大标题、大块内容入场 */

  /* ---- 章节垂直节奏 ----
     改前是五个互不相干的定值：常规 128、三步 96、暗色 138/132、翻滚 118、下载 120/128，
     并且【在窄屏完全不缩】—— 八个章节各上下 128px，手机上光是章节留白就有两千多像素，
     读起来永远在滚空白。
     收成两档，并且跟着视口走：窄屏自动收到 72/84，宽屏才给到 128/138。
     两档的区别是「气口」：暗色章节与下载页是要让人停一下的地方，给多一点。 */
  --sec-y: clamp(72px, 9vw, 128px);
  --sec-y-lg: clamp(84px, 10vw, 138px);

  /* 章节接缝：深色区块与其后的浅色区块互相「盖」上去的重叠量与圆角半径。
     改前每一次底色切换都是一条生硬的直边——白、米、白、纯黑，
     像四张不同的纸拼在一起。 */
  --seam: clamp(22px, 3vw, 40px);
}

* { box-sizing: border-box; }

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

body {
  margin: 0;
  font-family: var(--font);
  color: var(--ink);
  background: var(--paper);
  line-height: 1.6;
  font-synthesis: none;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  /* clip 不创建滚动容器，不会破坏 position: sticky */
  overflow-x: hidden;
  overflow-x: clip;
}

img, video { display: block; }
a { color: inherit; text-decoration: none; }
button { font-family: inherit; }
time { font-variant-numeric: tabular-nums; }

::selection { background: rgba(20, 105, 87, 0.18); }

/* 键盘焦点环。此前全站没有任何 :focus 规则，靠浏览器默认环——它在墨绿实心按钮
   与暗色章节上几乎看不见，用 Tab 走一遍不知道自己停在哪。
   用 :focus-visible 而非 :focus：鼠标点击不出现焦点环，只有键盘导航才出现。
   焦点环画在元素外侧（outline-offset），不参与布局，不会把相邻元素挤动。 */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: var(--r-s);
}
.btn-primary:focus-visible,
.btn-small:focus-visible {
  outline-color: var(--ink);
}
.section-dark :focus-visible,
.nav-dark .nav :focus-visible {
  outline-color: var(--mint);
}
a[aria-disabled="true"] { cursor: default; }

.container {
  max-width: 1040px;
  margin: 0 auto;
  padding: 0 24px;
}
.container-narrow { max-width: 720px; }
/* 小屏上 24px 的双边留白会把本就窄的正文再挤掉一截（375px 宽的屏少 13% 可读宽度） */
@media (max-width: 400px) {
  .container { padding: 0 18px; }
}

/* 导航是 52px 的固定条，原来只留 56px —— 点导航跳过去，章节标题正好贴在导航下沿，
   差 4px，像被压住。留出一整个气口，落点才有「停稳了」的感觉。 */
section[id], header[id] { scroll-margin-top: 80px; }

.br-d { display: inline; }
@media (max-width: 640px) { .br-d { display: none; } }

/* --------------------------------------------------------------------------
   入场动效（仅 JS 可用时才隐藏初始状态）
   -------------------------------------------------------------------------- */
body.js .reveal {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity var(--dur-4) var(--ease-out),
              transform var(--dur-4) var(--ease-out),
              filter var(--dur-4) var(--ease-out);
}
body.js .reveal.in { opacity: 1; transform: none; }
/* 未被 JS 改成遮罩上移时（无 JS / 极旧浏览器）保留原来的去模糊淡入作兜底 */
body.js .sec-title.reveal:not(.masked) { filter: blur(10px); }
body.js .sec-title.reveal:not(.masked).in { filter: blur(0); }

/* ---- 章节标题：遮罩上移 ----
   原本章节标题走的是「上移 18px + 从 blur(10px) 化开」，而首屏标题走的是
   遮罩上移（.hl / .hl-in）—— 同一个页面里两种出场语言。
   模糊淡入是很典型的「网页动效」；遮罩上移是影视与苹果官网在用的那一种：
   字被一条看不见的边裁着，从下方推上来，边界始终锐利。
   全站统一成后者，每一节的进入从此是同一个动作。
   包裹由 main.js 生成（8 处标题的 HTML 不必各改一遍），CSS 只负责这一层的运动。 */
body.js .sec-title.reveal.masked {
  opacity: 1;
  transform: none;
  filter: none;
}
body.js .sec-title.masked .hl {
  display: block;
  overflow: hidden;
  /* 与首屏同一处理：给下缘留出行外空间，遮罩不会切掉「，」「Q」这类下伸笔画 */
  padding-bottom: 0.08em;
  margin-bottom: -0.08em;
}
body.js .sec-title.masked .hl-in {
  display: inline-block;
  transform: translateY(112%);
  transition: transform var(--dur-4) var(--ease-out);
}
body.js .sec-title.masked.in .hl-in { transform: none; }

/* 副标题比标题慢半拍跟上，两行之间有先后而不是齐步走 */
body.js .sec-sub.reveal { transition-delay: 0.12s; }
.reveal.d1 { transition-delay: 0.08s; }
.reveal.d2 { transition-delay: 0.16s; }
.reveal.d3 { transition-delay: 0.24s; }
.reveal.d4 { transition-delay: 0.32s; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  body.js .reveal { opacity: 1; transform: none; filter: none; transition: none; }
  body.js .sec-title.masked .hl-in { transform: none; transition: none; }
  body.js .sec-sub.reveal { transition-delay: 0s; }
}

/* --------------------------------------------------------------------------
   导航
   -------------------------------------------------------------------------- */
.nav {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 100;
  background: rgba(248, 246, 241, 0.78);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  backdrop-filter: saturate(180%) blur(20px);
  border-bottom: 1px solid transparent;
  /* 换成 background-color 而非 background 简写：只有颜色在变，
     用简写会让浏览器把整份背景当作可插值属性来处理 */
  transition: border-color var(--dur-2) var(--ease-soft),
              background-color var(--dur-3) var(--ease-soft);
}
.nav.scrolled { border-bottom-color: rgba(219, 217, 207, 0.7); }

/* 滚进暗色章节时，导航自动换成深色毛玻璃 */
body.nav-dark .nav { background: rgba(14, 18, 19, 0.8); }
body.nav-dark .nav.scrolled { border-bottom-color: rgba(255, 255, 255, 0.08); }
body.nav-dark .nav-brand,
body.nav-dark .nav-links { color: rgba(242, 244, 242, 0.9); }
body.nav-dark .nav-links a:hover,
body.nav-dark .nav-links a.active { color: var(--mint); }
body.nav-dark .nav-links a::after { background: var(--mint); }
body.nav-dark .nav-burger span { background: var(--dark-ink); }

.nav-inner {
  max-width: 1040px;
  margin: 0 auto;
  padding: 0 24px;
  height: 52px;
  display: flex;
  align-items: center;
  gap: 28px;
}

.nav-brand {
  display: flex;
  align-items: center;
  gap: 9px;
  font-size: 16.5px;
  font-weight: 650;
}
.nav-brand img { border-radius: 6.5px; }

.nav-links {
  display: flex;
  gap: 24px;
  margin: 0 auto;
  font-size: 13.5px;
  color: rgba(26, 31, 33, 0.8);
}
.nav-links a { transition: color var(--dur-1) var(--ease-soft); }
.nav-links a:hover { color: var(--accent); }

@media (min-width: 781px) {
  .nav-links a { position: relative; }
  .nav-links a::after {
    content: "";
    position: absolute;
    left: 0; right: 0; bottom: -5px;
    height: 1.5px;
    border-radius: 2px;
    background: var(--accent);
    transform: scaleX(0);
    transition: transform var(--dur-2) var(--ease-out);
  }
  .nav-links a:hover::after,
  .nav-links a.active::after { transform: scaleX(1); }
  .nav-links a.active { color: var(--accent); }
}

.nav-cta { display: flex; align-items: center; gap: 14px; margin-left: auto; }

.nav-burger {
  display: none;
  width: 36px; height: 36px;
  border: 0; background: none;
  cursor: pointer;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 5px;
}
.nav-burger span {
  display: block;
  width: 18px; height: 1.6px;
  background: var(--ink);
  transition: transform var(--dur-2) var(--ease-out);
}
.nav-burger[aria-expanded="true"] span:first-child { transform: translateY(3.3px) rotate(45deg); }
.nav-burger[aria-expanded="true"] span:last-child { transform: translateY(-3.3px) rotate(-45deg); }

@media (max-width: 780px) {
  .nav-links {
    position: fixed;
    top: 52px; left: 0; right: 0;
    flex-direction: column;
    gap: 0;
    background: rgba(248, 246, 241, 0.97);
    -webkit-backdrop-filter: blur(20px);
    backdrop-filter: blur(20px);
    border-bottom: 1px solid var(--line);
    padding: 8px 0 16px;
    display: none;
  }
  body.nav-dark .nav-links {
    background: rgba(14, 18, 19, 0.97);
    border-bottom-color: rgba(255, 255, 255, 0.08);
  }
  .nav-links.open { display: flex; }
  .nav-links a {
    padding: 13px 28px;
    font-size: 16px;
    border-bottom: 1px solid rgba(0, 0, 0, 0.04);
  }
  .nav-burger { display: flex; }
}

/* --------------------------------------------------------------------------
   按钮（连续圆角，同应用按钮语言）
   -------------------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--r-m);
  font-weight: 550;
  transition: transform var(--dur-1) var(--ease-soft),
              box-shadow var(--dur-1) var(--ease-soft),
              background-color var(--dur-1) var(--ease-soft),
              color var(--dur-1) var(--ease-soft);
  cursor: pointer;
}
/* 按下才位移。悬停不动是有意为之：苹果的行动点在 hover 时只加深材质、不离开纸面，
   位移留给「真的被按下」那一刻——反馈因此有了层次，而不是碰一下就跳。 */
.btn:active { transform: scale(0.98); }

.btn-small {
  padding: 6px 15px;
  font-size: 13px;
  background: var(--accent);
  color: #fff;
}
.btn-small:hover { background-color: var(--accent-deep); }

.btn-primary {
  padding: 13px 28px;
  font-size: 16.5px;
  /* 高光层与底色拆开写：hover 只换 background-color，那层顶部高光原样留着，
     不会因为整条 background 被替换而在悬停瞬间闪一下 */
  background-image: linear-gradient(180deg, rgba(255, 255, 255, 0.14), rgba(255, 255, 255, 0) 55%);
  background-color: var(--accent);
  color: #fff;
  box-shadow: 0 3px 10px rgba(20, 105, 87, 0.24);
}
.btn-primary:hover {
  background-color: var(--accent-deep);
  box-shadow: 0 5px 14px rgba(20, 105, 87, 0.28);
}

.btn-large { padding: 15px 36px; font-size: 18px; }

.btn-ghost {
  padding: 13px 22px;
  font-size: 16.5px;
  color: var(--accent);
}
.btn-ghost:hover { color: var(--accent-deep); }
/* 次要行动点（「看看它怎么工作」）此前只是一段变色的文字，和正文里的链接看不出区别。
   补一个角标：静止时贴着文字，悬停时往前挪 3px —— 苹果式的「这里可以去」暗示，
   不加边框、不抢主按钮的分量。角标是纯装饰，对读屏隐藏。 */
.btn-ghost::after {
  content: "›";
  margin-left: 6px;
  font-size: 1.15em;
  line-height: 0;
  transform: translateX(0);
  transition: transform var(--dur-2) var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .btn-ghost:hover::after { transform: translateX(3px); }
}
@media (prefers-reduced-motion: reduce) {
  .btn-ghost::after { transition: none; }
  .btn-ghost:hover::after { transform: none; }
}

/* --------------------------------------------------------------------------
   Hero（宣传片式 3D 舞台：磨砂玻璃文案板 + 真机 + 漂浮几何体）
   -------------------------------------------------------------------------- */
.hero {
  /* 顶部要让开 52px 的固定导航再留气口。原来是定值 118px，手机上等于开屏就先滚掉
     一屏的六分之一空白才见到品牌行；跟着视口收到 84px（= 52 + 32，仍不压导航）。 */
  padding: clamp(84px, 11vw, 118px) 24px 0;
  text-align: center;
}

.hero-sticky { position: relative; }

/* 舞台网格底纹（宣传片里的浅色棚拍地面） */
.hero-sticky::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image:
    radial-gradient(circle, rgba(26, 31, 33, 0.16) 1.2px, transparent 1.8px),
    linear-gradient(rgba(26, 31, 33, 0.05) 1px, transparent 1px),
    linear-gradient(90deg, rgba(26, 31, 33, 0.05) 1px, transparent 1px);
  background-size: 66px 66px;
  background-position: -0.6px -0.6px, 0 0, 0 0;
  -webkit-mask-image: radial-gradient(130% 105% at 50% 42%, #000 30%, transparent 78%);
  mask-image: radial-gradient(130% 105% at 50% 42%, #000 30%, transparent 78%);
}

.hero-grid { position: relative; z-index: 1; }

/* 桌面 + JS：Hero 变成一段可回放的滚动镜头（sticky 钉住） */
@media (min-width: 861px) {
  body.mjs .hero { height: 178vh; padding-top: 0; }
  body.mjs .hero-sticky {
    position: sticky;
    top: 0;
    height: 100vh;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    overflow: clip;
  }
  body.mjs .hero-grid {
    display: grid;
    grid-template-columns: 0.94fr 1.06fr;
    align-items: center;
    gap: 46px;
    width: min(1180px, 94vw);
    margin-top: 26px;
  }

  /* 磨砂玻璃文案板（悬浮在舞台上的一块玻璃） */
  body.mjs .hero-copy {
    text-align: left;
    padding: 46px 44px 40px;
    border-radius: 30px;
    background: linear-gradient(135deg, rgba(255, 255, 255, 0.66), rgba(255, 255, 255, 0.38));
    -webkit-backdrop-filter: blur(26px) saturate(170%);
    backdrop-filter: blur(26px) saturate(170%);
    border: 1px solid rgba(255, 255, 255, 0.78);
    box-shadow:
      0 34px 80px rgba(26, 31, 33, 0.13),
      inset 0 1px 0 rgba(255, 255, 255, 0.9);
    will-change: transform, opacity, filter;
  }
  body.mjs .hero-title { max-width: none; font-size: clamp(32px, 3.7vw, 52px); }
  body.mjs .hero-sub { margin-left: 0; margin-right: 0; font-size: 17px; }
  /* 只限首屏玻璃板内的 CTA：下载区复用了 .hero-cta，不限定的话
     「即将上架」在桌面滚动引擎下会被一起拽成左对齐 */
  body.mjs .hero-copy .hero-cta { justify-content: flex-start; }
}

/* 首屏品牌行：图标 + 名称，用户进来先知道这是什么 app */
.hero-brand {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 13px;
  margin-bottom: 24px;
}
.hero-brand img {
  border-radius: 13px;
  box-shadow: 0 8px 22px rgba(26, 31, 33, 0.18), inset 0 0 0 0.5px rgba(26, 31, 33, 0.05);
}
.hb-txt { display: flex; flex-direction: column; text-align: left; }
.hb-txt b { font-size: 19px; font-weight: 700; line-height: 1.2; }
.hb-txt i { font-style: normal; font-size: 12.5px; color: var(--muted); margin-top: 2px; }
@media (min-width: 861px) {
  body.mjs .hero-brand { justify-content: flex-start; }
}

.hero-title {
  margin: 0 auto;
  max-width: 900px;
  font-size: clamp(40px, 7vw, 84px);
  line-height: 1.08;
  font-weight: 700;
  letter-spacing: -0.022em;
}

/* 渐变字（苹果式，只用在一两处）。
   第十五轮：渐变之上再铺一层白色高光带（第一层背景），平时停在字外
   （background-position 115%，配合 no-repeat 完全不可见），入场落定后
   由 gradSheen 扫过一次——金属字被灯掠过的那一下，只在登场时发生，不循环。
   两层都被 background-clip: text 裁进笔画里，页面底色不会跟着亮。 */
.grad {
  /* 高光带用星标金而不是白：白带叠在亮绿渐变上会与纸底同化成「字缺了一块」，
     金带与绿字色相拉开，是「灯光掠过」而不是「褪色」 */
  background-image:
    linear-gradient(105deg,
      rgba(240, 185, 80, 0) 44%,
      rgba(240, 185, 80, 0.9) 50%,
      rgba(240, 185, 80, 0) 56%),
    linear-gradient(96deg, #146957 8%, #1f8a68 45%, #3aa76d 88%);
  background-size: 220% 100%, 100% 100%;
  background-position: 115% 0, 0 0;
  background-repeat: no-repeat;
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
}
.grad-mint {
  background-image:
    linear-gradient(105deg,
      rgba(255, 255, 255, 0) 44%,
      rgba(255, 255, 255, 0.75) 50%,
      rgba(255, 255, 255, 0) 56%),
    linear-gradient(96deg, #8fe3c9 5%, #5cc9b0 50%, #b7e9a8 95%);
  background-size: 220% 100%, 100% 100%;
  background-position: 115% 0, 0 0;
  background-repeat: no-repeat;
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
}
/* 光扫时机：首屏跟在两行标题升定之后；自测标题跟在遮罩上移完成之后。
   不用 fill 模式：播完回到 115% 的静止位，与没播过时是同一个状态。 */
body.hero-in .hero-title .grad { animation: gradSheen 1.5s var(--ease-soft) 1.4s 1; }
body.js .sec-title.masked.in .grad-mint { animation: gradSheen 1.5s var(--ease-soft) 1.05s 1; }
@keyframes gradSheen {
  from { background-position: 115% 0, 0 0; }
  to { background-position: -15% 0, 0 0; }
}
@media (prefers-reduced-motion: reduce) {
  body.hero-in .hero-title .grad,
  body.js .sec-title.masked.in .grad-mint { animation: none; }
}
.hl { display: block; overflow: hidden; padding-bottom: 0.06em; margin-bottom: -0.06em; }
.hl-in { display: inline-block; }
body.js .hl-in {
  transform: translateY(112%);
  transition: transform var(--dur-4) var(--ease-out);
}
body.js .hl-in.d1 { transition-delay: 0.1s; }
body.hero-in .hl-in { transform: none; }
@media (prefers-reduced-motion: reduce) {
  body.js .hl-in { transform: none; transition: none; }
}

.hero-sub {
  margin: 24px auto 0;
  max-width: 620px;
  font-size: clamp(16.5px, 2.1vw, 20px);
  line-height: 1.7;
  color: var(--muted);
}

.hero-cta {
  margin-top: 32px;
  display: flex;
  gap: 12px;
  justify-content: center;
  align-items: center;
  flex-wrap: wrap;
}

.hero-note {
  margin: 14px 0 0;
  font-size: 12.5px;
  color: var(--muted);
}

/* ---- 设备舞台 ---- */
.device {
  position: relative;
  max-width: 980px;
  margin: 64px auto 0;
  padding-bottom: 84px;
}

@media (min-width: 861px) {
  body.mjs .device {
    margin: 0;
    padding-bottom: 0;
    width: 100%;
    max-width: none;
    transform: translate(var(--devx, 0px), var(--devy, 0px));
    will-change: transform;
  }
}

/* 3D 舞台：宣传片式悬浮 */
.stage-3d {
  position: relative;
  z-index: 1;
  perspective: 1600px;
}
.device-tilt {
  transform-style: preserve-3d;
  will-change: transform;
}

/* 窄屏：静态小倾角，露出机身侧轨（无滚动引擎时的真机感） */
@media (max-width: 860px) {
  .device-tilt {
    transform: rotateY(-13deg) rotateX(3.5deg) rotateZ(1deg);
  }
}

/* 地面投影（真机悬浮在舞台上方的影子） */
.stage-3d::after {
  content: "";
  position: absolute;
  left: 7%; right: 7%;
  bottom: -42px;
  height: 50px;
  z-index: -1;
  border-radius: 50%;
  background: radial-gradient(closest-side, rgba(26, 31, 33, 0.28), rgba(26, 31, 33, 0) 88%);
  filter: blur(9px);
}

/* 漂浮几何体（品牌色 3D 道具，JS 统一驱动视差 + 浮动） */
.shp {
  position: absolute;
  height: auto;
  pointer-events: none;
  user-select: none;
  will-change: transform, opacity;
}
.shp-a {
  width: clamp(200px, 22vw, 320px);
  top: -5%; left: -6.5%;
  z-index: 0;
  transform: rotate(-14deg);
  filter: blur(1.4px) drop-shadow(0 34px 44px rgba(20, 105, 87, 0.2));
}
.shp-b {
  width: clamp(64px, 6.6vw, 96px);
  top: 10%; left: 45%;
  z-index: 0;
  filter: drop-shadow(0 20px 28px rgba(26, 31, 33, 0.16));
}
.shp-c {
  width: clamp(170px, 17vw, 250px);
  top: 22%; right: -6%;
  z-index: 0;
  opacity: 0.85;
  filter: blur(2.4px) drop-shadow(0 30px 40px rgba(26, 31, 33, 0.14));
}
.shp-d {
  width: clamp(120px, 13vw, 186px);
  bottom: 9%; left: -2.5%;
  z-index: 3;
  transform: rotate(26deg) scaleX(-1);
  filter: drop-shadow(0 30px 44px rgba(20, 105, 87, 0.26));
}
.shp-e {
  width: clamp(44px, 4.4vw, 64px);
  bottom: 21%; right: 13.5%;
  z-index: 3;
  filter: blur(0.5px) drop-shadow(0 16px 22px rgba(26, 31, 33, 0.18));
}

/* 几何体只在桌面滚动引擎下出场（静态布局里会挡内容） */
body:not(.mjs) .hero .shp { display: none; }
@media (max-width: 860px) {
  .hero .shp { display: none; }
}

/* ---- 真机（正面黑玻璃 + 切片堆叠出的铝合金机身，真 3D 实体） ---- */
.device-frame {
  position: relative;
  border-radius: 34px;
  padding: 3.5px;
  background: linear-gradient(150deg,
    #fdfefe 0%, #c9ced4 20%, #939ba4 46%, #e4e7ea 74%, #a9b0b8 100%);
  box-shadow:
    0 46px 90px rgba(26, 31, 33, 0.24),
    0 14px 30px rgba(26, 31, 33, 0.15),
    inset 0 1px 1px rgba(255, 255, 255, 0.9),
    inset 0 -1px 1px rgba(0, 0, 0, 0.3);
  transform: translateY(var(--dy, 0px)) scale(var(--ds, 1));
  transform-style: preserve-3d;
}
body.js.mjs #deviceFrame { will-change: transform; }

/* 机身切片（JS 为 [data-3d] 生成）：一叠圆角薄片向后堆出真实厚度，
   任何角度旋转都会露出连续的铝合金侧轨和圆角 */
.dev3d-slice {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  background: linear-gradient(150deg,
    #eef1f3 0%, #b6bdc5 26%, #848c95 50%, #ccd1d7 76%, #969ea7 100%);
}
.device-frame-dark .dev3d-slice {
  background: linear-gradient(150deg,
    #585e66 0%, #2c3036 42%, #16181b 58%, #3e434a 84%, #26292d 100%);
}

/* 背面：铝壳 + 相机模组 + 微弱的品牌压印 */
.dev3d-back {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
  background:
    radial-gradient(130% 100% at 28% 10%, rgba(255, 255, 255, 0.24), rgba(255, 255, 255, 0) 46%),
    linear-gradient(150deg, #e2e6ea 0%, #b2b9c1 34%, #949ca5 62%, #c9ced4 100%);
  box-shadow:
    inset 0 0 0 1px rgba(255, 255, 255, 0.4),
    inset 0 0 30px rgba(0, 0, 0, 0.1);
}
.device-frame-dark .dev3d-back {
  background:
    radial-gradient(130% 100% at 28% 10%, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0) 46%),
    linear-gradient(150deg, #4e545b 0%, #2e3237 38%, #1d2024 64%, #383d43 100%);
}
.dev3d-back::after {
  content: "";
  position: absolute;
  left: 50%; top: 50%;
  width: 12%; aspect-ratio: 1;
  transform: translate(-50%, -50%);
  background: url("icon-192.webp") center / cover;
  border-radius: 22%;
  filter: grayscale(1);
  opacity: 0.14;
}
.dev3d-camera {
  position: absolute;
  top: 4.5%; left: 3%;
  width: 10%;
  aspect-ratio: 1;
  min-width: 30px;
  max-width: 62px;
  border-radius: 26%;
  background: linear-gradient(145deg, #d6dbe0, #99a1a9);
  box-shadow:
    inset 0 1px 2px rgba(255, 255, 255, 0.75),
    inset 0 -2px 5px rgba(0, 0, 0, 0.2),
    0 2px 7px rgba(0, 0, 0, 0.2);
}
.device-frame-dark .dev3d-camera {
  background: linear-gradient(145deg, #4c5158, #24272b);
}
.dev3d-camera i {
  position: absolute;
  display: block;
  border-radius: 50%;
  background: radial-gradient(circle at 36% 32%, #46525f 0%, #0a0e13 58%, #000 100%);
  box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.15), 0 1px 2px rgba(0, 0, 0, 0.4);
}
.dev3d-camera i:first-child { top: 10%; left: 10%; width: 44%; aspect-ratio: 1; }
.dev3d-camera i:last-child { bottom: 12%; right: 12%; width: 30%; aspect-ratio: 1; }

.device-bezel {
  position: relative;
  border-radius: 30.5px;
  background: #060809;
  padding: 17px;
  /* 机身转过 90° 后隐藏正面组件，否则屏幕内容会镜像透出背面 */
  transform: translateZ(0.4px);
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
  box-shadow:
    inset 0 0 0 1px rgba(255, 255, 255, 0.06),
    inset 0 0 16px rgba(0, 0, 0, 0.55);
}

/* 前置摄像头（横置 iPad：长边居中） */
.device-cam {
  position: absolute;
  top: 6px; left: 50%;
  width: 6px; height: 6px;
  margin-left: -3px;
  border-radius: 50%;
  background: radial-gradient(circle at 35% 32%, #41505e 0%, #0a0e12 62%);
  box-shadow: 0 0 0 1.5px rgba(255, 255, 255, 0.045);
}

/* 侧键（音量 / 电源，只出现在 Hero 真机上） */
.dev-btn {
  position: absolute;
  top: -4px;
  height: 4px;
  border-radius: 3px 3px 1px 1px;
  background: linear-gradient(180deg, #f5f7f9, #b0b7bf 62%, #8d959e);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.85);
}
.dev-btn-pw { right: 56px; width: 46px; }
.dev-btn-v1 { right: 136px; width: 30px; }
.dev-btn-v2 { right: 176px; width: 30px; }

.device-screen {
  position: relative;
  border-radius: 12px;
  overflow: hidden;
  background: var(--paper);
}
/* 屏内视差：内容相对机身反向微移，玻璃下面因此有了厚度。
   真设备转动时，你看到的画面并不贴在玻璃表面，而是在玻璃下方几毫米——
   转角一变，画面相对边框就会偏移一点点。这一点点正是「这是块真屏幕」的来源。
   --sx 由各章节的旋转角驱动（见 main.js）；预放大 3% 留出位移余量，
   否则一偏就会在边缘露出底色。 */
.device-screen video,
.device-screen img {
  width: 100%;
  height: auto;
  transform: translateX(var(--sx, 0px)) scale(1.03);
  transform-origin: center;
  will-change: transform;
}

/* 进入视野时点亮：屏幕先压着一层暗，随后化开。
   改前所有真机一进画面就是全亮的静态图，六个章节六台，看久了像贴图；
   点亮之后每一台都有一个「醒过来」的瞬间。
   仅在 body.js 下生效：无 JS 时 .lit 永远加不上，屏幕不能停在暗着的状态。 */
body.js .device-screen::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background: #05070a;
  opacity: 0.62;
  transition: opacity var(--dur-4) var(--ease-out);
}
body.js .device-frame.lit .device-screen::before { opacity: 0; }

@media (prefers-reduced-motion: reduce) {
  body.js .device-screen::before { display: none; }
  .device-screen video, .device-screen img { transform: none; }
}

/* 屏幕玻璃反光：固定角高光 + 随旋转扫过的反光带（--gx 由 JS 驱动） */
.device-screen::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;   /* 反光永远在最上：它是玻璃表面，压过点亮遮罩与屏内画面 */
  pointer-events: none;
  background:
    linear-gradient(105deg,
      rgba(255, 255, 255, 0) 34%,
      rgba(255, 255, 255, 0.12) 47%,
      rgba(255, 255, 255, 0.035) 55%,
      rgba(255, 255, 255, 0) 66%),
    linear-gradient(115deg,
      rgba(255, 255, 255, 0.09) 0%,
      rgba(255, 255, 255, 0.028) 26%,
      rgba(255, 255, 255, 0) 44%);
  background-size: 260% 100%, 100% 100%;
  background-position: var(--gx, 30%) 0, 0 0;
}

/* 小尺寸真机（竖屏截图用） */
.frame-sm { border-radius: 30px; }
.frame-sm .device-bezel { border-radius: 27px; padding: 12px; }
.frame-sm .device-screen { border-radius: 13px; }

/* Hero 第二台设备：竖屏真机，极端倾角立在主机旁（宣传片式）
   外层管透明度/位移（避免 opacity<1 拍扁 3D），内层管旋转 */
.dev-side-wrap {
  position: absolute;
  z-index: 2;
  left: 42.5%;
  bottom: 4%;
  width: clamp(150px, 15vw, 216px);
  will-change: transform, opacity;
}
.dev-side {
  transform-style: preserve-3d;
  will-change: transform;
}
.dev-side .device-frame {
  border-radius: 24px;
  padding: 3px;
  box-shadow:
    26px 44px 60px rgba(26, 31, 33, 0.3),
    10px 16px 26px rgba(26, 31, 33, 0.18),
    inset 0 1px 1px rgba(255, 255, 255, 0.9),
    inset 0 -1px 1px rgba(0, 0, 0, 0.3);
}
.dev-side .device-bezel { border-radius: 21px; padding: 9px; }
.dev-side .device-screen { border-radius: 13px; }
/* 竖屏截图内容偏小，轻微放大以便在小机身上可读 */
.dev-side .device-screen img {
  transform: scale(1.14);
  transform-origin: 50% 10%;
}
body:not(.mjs) .dev-side-wrap { display: none; }
@media (max-width: 860px) { .dev-side-wrap { display: none; } }

.device-hint {
  margin: 18px 0 0;
  font-size: 12.5px;
  color: var(--muted);
}

/* --------------------------------------------------------------------------
   章节通用
   -------------------------------------------------------------------------- */
.section { padding: var(--sec-y) 0; }
.section-white { background: var(--white); }

.kicker {
  margin: 0 0 14px;
  text-align: center;
  font-size: 14.5px;
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--accent);
}
.kicker-mint { color: var(--mint); }

.sec-title {
  margin: 0;
  text-align: center;
  font-size: clamp(32px, 5.4vw, 60px);
  line-height: 1.12;
  font-weight: 700;
  letter-spacing: -0.02em;
}

.sec-sub {
  margin: 20px auto 0;
  max-width: 640px;
  text-align: center;
  font-size: clamp(16px, 2vw, 19.5px);
  line-height: 1.7;
  color: var(--muted);
}

.shot-cap {
  margin: 14px 0 0;
  text-align: center;
  font-size: 12.5px;
  color: var(--muted);
}

/* --------------------------------------------------------------------------
   三步（逐屏展示：设备居中，界面随滚动切换，宣传片式）
   -------------------------------------------------------------------------- */
.section-showcase { padding: var(--sec-y) 0; }

.sc-beat { text-align: center; }
/* 窄屏三步纵向堆叠时的段间距。跟着章节节奏一起缩，
   否则章节留白在手机上收到了 72px，段与段之间却还撑着 84px，比章节之间还宽。 */
.sc-beat + .sc-beat { margin-top: clamp(56px, 8vw, 84px); }
.sc-beat .kicker { margin-bottom: 10px; }
.sc-title {
  margin: 0;
  font-size: clamp(28px, 4.4vw, 48px);
  line-height: 1.14;
  font-weight: 700;
  letter-spacing: -0.02em;
}
.sc-sub {
  margin: 14px auto 0;
  max-width: 560px;
  font-size: clamp(15px, 1.8vw, 17.5px);
  line-height: 1.7;
  color: var(--muted);
}
.sc-frame {
  margin: 34px auto 0;
  width: min(720px, 92vw);
}
.sc-frame .device-screen img { width: 100%; height: auto; }

.sc-dots { display: none; }

/* 窄屏：三步是纵向堆叠的三段，由各自的 IO 触发显现（见 main.js 的非桌面分支）。
   此前这里没有任何入场，三张真机截图一次全在，滚下去像翻一份静态说明书。 */
body.mjs .sc-beat.beat-anim {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity var(--dur-3) var(--ease-out),
              transform var(--dur-3) var(--ease-out);
}
body.mjs .sc-beat.beat-anim.beat-in { opacity: 1; transform: none; }

/* 桌面 + JS：钉住逐屏切换 */
@media (min-width: 861px) {
  body.mjs .section-showcase { padding: 0; }
  /* 行程账（第十一轮）：330vh 时代首拍前 55vh、末拍后 56vh 滚动零响应（实测），
     驻留与交接失衡；270vh + 均匀节拍后三段驻留各 ~33vh、两次交接各 ~36vh，
     没有任何一段比一次交接更久的「滚了没反应」。 */
  body.mjs .showcase { height: 270vh; }
  body.mjs .sc-pin {
    position: sticky;
    top: 0;
    height: 100vh;
    display: flex;
    align-items: center;
    overflow: clip;
  }
  body.mjs .sc-pin > .container { width: 100%; }
  /* 舞台级灭点（第十三轮）：perspective 从每台真机的 inline transform 上移到
     舞台——此前两台各带各的 perspective()，灭点跟着 translateX 走，看起来是
     一张卡片原地打转；共享灭点后离轴透视随位置变化，才是「空间里的物体」。 */
  body.mjs .sc-stage { position: relative; height: min(78vh, 760px); perspective: 1300px; }
  body.mjs .sc-beat {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    margin: 0 !important;
    /* preserve-3d 让 .sc-frame 的 translateZ 参与舞台透视。
       原来的 will-change: opacity/filter 必须去掉——它们是分组属性，
       会把 preserve-3d 拍扁（beat 本身也从不吃这三样内联值，只切 visibility）。 */
    transform-style: preserve-3d;
  }
  body.mjs .sc-frame {
    margin-top: 30px;
    width: min(640px, 52vh * 4 / 3);
    will-change: transform;
  }
  body.mjs .sc-dots {
    display: flex;
    justify-content: center;
    gap: 9px;
    margin-top: 26px;
  }
  body.mjs .sc-dots i {
    width: 8px; height: 8px;
    border-radius: 999px;
    background: rgba(26, 31, 33, 0.18);
    transition: width 0.35s var(--ease-out), background 0.35s ease;
  }
  body.mjs .sc-dots i.on { width: 26px; background: var(--accent); }
}

/* --------------------------------------------------------------------------
   滚动叙事（讲义时间戳 → 悬浮播放器回跳）
   三幕舞台：讲义卡为主角 → 镜头推近时间戳 → 播放器滑上台前回卷
   -------------------------------------------------------------------------- */
/* 320→280vh（第十一轮）：开场 29vh / 收尾 35vh 的零响应段（实测）收敛为
   入场落定 21vh + 金光后 21vh 的收势——前者从死区变成戏，后者是刻意的定格。 */
.story { position: relative; height: 280vh; margin-top: 10px; }
.story-pin {
  position: sticky;
  top: 0;
  height: 100vh;
  display: flex;
  align-items: center;
  overflow: clip;
}
.story-grid {
  display: grid;
  grid-template-columns: 0.72fr 1.28fr;
  gap: 60px;
  align-items: center;
  width: 100%;
}

/* 左侧：一次只讲一句（桌面钉住时步骤叠放同一槽位，由 JS 逐帧驱动） */
.story-steps { position: relative; }
.story-step { padding: 20px 0; }
@media (min-width: 861px) {
  body.mjs .story:not(.no-pin) .story-steps {
    min-height: 236px;
    display: flex;
    align-items: center;
  }
  body.mjs .story:not(.no-pin) .story-step {
    position: absolute;
    left: 0; right: 0;
    padding: 0;
    will-change: transform, opacity;
  }
}
.story-i {
  display: block;
  margin-bottom: 10px;
  font-size: 12px;
  font-weight: 650;
  letter-spacing: 0.16em;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}
.story-step h3 {
  margin: 0 0 10px;
  font-size: clamp(23px, 2.9vw, 33px);
  font-weight: 700;
  letter-spacing: -0.015em;
  line-height: 1.22;
}
.story-step.on h3 { color: var(--ink); }
.story-step p {
  margin: 0;
  font-size: clamp(15px, 1.7vw, 16.5px);
  line-height: 1.75;
  color: var(--muted);
  max-width: 340px;
}

/* 步骤进度点（沿用逐屏展示的视觉语言） */
.story-dots { display: none; }
@media (min-width: 861px) {
  body.mjs .story:not(.no-pin) .story-dots {
    display: flex;
    gap: 8px;
    margin-top: 34px;
  }
  body.mjs .story-dots i {
    width: 8px; height: 8px;
    border-radius: 999px;
    background: rgba(26, 31, 33, 0.16);
    transition: width 0.35s var(--ease-out), background 0.35s ease;
  }
  body.mjs .story-dots i.on { width: 26px; background: var(--accent); }
}

/* 窄屏：这三个点从装饰变成真控件。
   窄屏的这一场戏是定时自动播放的，此前点是 display:none —— 内容自己在变，
   用户既不知道有三幕，也没有任何办法停下或回看。现在它们显形、可点、可聚焦，
   点一下就接管（自动播放永久停止，见 main.js）。 */
@media (max-width: 860px) {
  body.mjs .story.dots-live .story-dots {
    display: flex;
    justify-content: center;
    /* 16px 而不是 11px：点心距要 ≥24px，相邻触摸热区才互不重叠 */
    gap: 16px;
    margin-top: 20px;
  }
  body.mjs .story-dots i {
    position: relative;
    width: 8px; height: 8px;
    border-radius: 999px;
    background: rgba(26, 31, 33, 0.18);
    cursor: pointer;
    transition: width var(--dur-2) var(--ease-out),
                background-color var(--dur-2) var(--ease-soft);
  }
  /* 视觉上仍是 8px 的小点，可点范围撑到 24×44 —— 触摸目标不能按视觉尺寸给 */
  body.mjs .story-dots i::after {
    content: "";
    position: absolute;
    inset: -18px -8px;
  }
  body.mjs .story-dots i.on { width: 26px; background: var(--accent); }
}

/* 右侧舞台：透视场景 + 品牌色底光 */
.story-stage { position: relative; perspective: 1500px; }
.story-glow {
  position: absolute;
  inset: -16% -20%;
  pointer-events: none;
  background:
    radial-gradient(46% 42% at 60% 44%, rgba(20, 105, 87, 0.10), transparent 72%),
    radial-gradient(30% 26% at 26% 76%, rgba(237, 168, 31, 0.08), transparent 70%);
}
.story-scene {
  position: relative;
  height: clamp(400px, 52vh, 500px);
  transform-style: preserve-3d;
}
body.js.mjs .story-scene { will-change: transform; }

/* 讲义段落（复刻工作台文档样式） */
.doc {
  position: absolute;
  top: 0; left: 0;
  width: 92%;
  transform-origin: 26% 24%;
  background: var(--card);
  border: 0.5px solid rgba(219, 217, 207, 0.8);
  border-radius: var(--r-l);
  padding: 26px 28px 22px;
  box-shadow: 0 18px 44px rgba(26, 31, 33, 0.1);
  text-align: left;
}
.doc-row {
  position: relative;
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 12px;
}
.doc-chip {
  flex: none;
  padding: 3px 10px;
  border-radius: 999px;
  background: var(--star);
  /* 金底配深墨而不是白：白字在星标金上只有 2:1，看不清的恰恰是时间戳本身 */
  color: rgba(26, 31, 33, 0.88);
  font-size: 12.5px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  transition: transform 0.3s var(--ease-out), box-shadow 0.3s ease;
}
.doc-row h3 { margin: 0; font-size: clamp(15px, 1.9vw, 18px); font-weight: 700; }
.doc p {
  margin: 0 0 10px;
  font-size: 13.5px;
  line-height: 1.75;
  color: var(--ink);
}
.doc .doc-dim { margin-bottom: 0; color: var(--muted); }
.doc sup { font-size: 0.68em; }

.story.is-1 .doc-chip {
  transform: scale(0.92);
  box-shadow: 0 0 0 5px rgba(237, 168, 31, 0.25);
}
.story.is-2 .doc-chip { transform: scale(1); }

.sv-tap {
  position: absolute;
  left: 27px; top: 50%;
  width: 34px; height: 34px;
  margin: -17px 0 0 -17px;
  border-radius: 50%;
  border: 2.5px solid var(--accent);
  opacity: 0;
  pointer-events: none;
}
.story.is-1 .sv-tap { animation: tapRipple 1.25s ease-out infinite; }
@keyframes tapRipple {
  0% { opacity: 0; transform: scale(0.35); }
  30% { opacity: 0.9; }
  100% { opacity: 0; transform: scale(1.5); }
}

/* 悬浮播放器（复刻应用内 LinkedVideoFloatingPlayer）
   停靠在舞台右下角，第三幕由 JS 驱动滑上台前 */
.sv-player {
  position: absolute;
  right: 0; bottom: 0;
  width: 76%;
  display: flex;
  flex-direction: column;
  gap: 9px;
  border-radius: var(--r-l);
  background: var(--video-a);
  padding: 10px;
  box-shadow: 0 22px 54px rgba(26, 31, 33, 0.26);
  transform-origin: 84% 92%;
  transition: box-shadow 0.45s ease;
}
body.js.mjs .sv-player { will-change: transform; }
.story.is-2 .sv-player { box-shadow: 0 34px 84px rgba(20, 105, 87, 0.36); }

.sv-canvas {
  position: relative;
  aspect-ratio: 920 / 517;
  border-radius: 10px;
  overflow: hidden;
  background: url("screens/board.webp") center / cover no-repeat, var(--video-b);
}
.sv-time {
  position: absolute;
  left: 2%; bottom: 4%;
  padding: 3px 11px;
  border-radius: 7px;
  background: rgba(16, 19, 20, 0.88);
  color: #fff;
  font-size: clamp(11px, 1.4vw, 14px);
  font-weight: 650;
  font-variant-numeric: tabular-nums;
}
.sv-flash { position: absolute; inset: 0; pointer-events: none; }
/* 桌面：回卷走完的瞬间闪金光；窄屏/静态：进入第三步时闪 */
.story.is-done .sv-flash,
.story.no-pin.is-2 .sv-flash { animation: svFlash 0.9s ease-out 1; }
@keyframes svFlash {
  0% { background: rgba(237, 168, 31, 0.16); }
  100% { background: rgba(237, 168, 31, 0); }
}

.player-bar {
  position: relative;
  height: 5px;
  margin: 0 3px 3px;
  border-radius: 999px;
  background: rgba(238, 243, 240, 0.18);
}
.player-fill {
  position: absolute;
  inset: 0 auto 0 0;
  width: 64.5%;
  border-radius: 999px;
  background: var(--accent);
  filter: brightness(1.55);
}
.player-knob {
  position: absolute;
  top: 50%; left: 64.5%;
  width: 12px; height: 12px;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1.5px 6px rgba(0, 0, 0, 0.4);
  transition: box-shadow 0.35s ease, transform 0.35s cubic-bezier(0.34, 1.56, 0.64, 1);
}
/* 回卷落点：旋钮弹一下并点亮金圈 */
.story.is-done .player-knob,
.story.no-pin.is-2 .player-knob {
  transform: translate(-50%, -50%) scale(1.3);
  box-shadow: 0 0 0 4px rgba(237, 168, 31, 0.35), 0 1.5px 6px rgba(0, 0, 0, 0.4);
}
/* 非 JS 逐帧驱动时（窄屏定时演示 / 静态回退），进度用 CSS 过渡 */
.no-pin .player-fill { transition: width 0.65s cubic-bezier(0.3, 1, 0.4, 1); }
.no-pin .player-knob { transition: left 0.65s cubic-bezier(0.3, 1, 0.4, 1); }
.story.is-2 .player-fill { width: 47.3%; }
.story.is-2 .player-knob { left: 47.3%; }

/* 非固定态（无 JS、减少动效或窄屏） */
.story.no-pin { height: auto; }
.no-pin .story-pin { position: static; height: auto; padding: 30px 0 42px; }
/* 非钉住时步骤按当前幕点亮。
   点亮方式从「整块压到 opacity 0.34」改为换色：透明度合成后的未激活文字
   只剩 1.5:1 的对比度，低视力用户读不到「还有另外两幕」这件事本身。
   现在未激活幕全灰（muted，4.8:1 起）、当前幕序号回绿、标题回墨——
   状态区分还在，每一幕任何时刻都可读。 */
.no-pin .story-step h3,
.no-pin .story-step .story-i {
  color: var(--muted);
  transition: color 0.5s var(--ease-soft);
}
.no-pin .story-step.on h3 { color: var(--ink); }
.no-pin .story-step.on .story-i { color: var(--accent); }
body:not(.js) .story-step h3 { color: var(--ink); }
body:not(.js) .story-step .story-i { color: var(--accent); }

@media (max-width: 860px) {
  .story { height: auto !important; }
  .story-pin { position: static; height: auto; padding: 16px 0 34px; }
  .story-grid { grid-template-columns: 1fr; gap: 30px; }
  .story-stage { order: -1; }
  .story-scene { height: auto; }
  .story-step { padding: 12px 0; }
  .story-i { margin-bottom: 6px; }
  /* 窄屏改为文档流：讲义卡在上，播放器错位叠在右下 */
  .doc { position: relative; width: auto; padding-bottom: 32px; }
  .sv-player {
    position: relative;
    width: 88%;
    margin: -22px 0 0 auto;
    transition: transform 0.7s var(--ease-out),
                opacity 0.7s ease, box-shadow 0.7s ease;
  }
  .doc { transition: transform 0.7s var(--ease-out), opacity 0.7s ease; }
  /* 定时演示的三幕：第三幕播放器升起、讲义卡后退 */
  .story.is-2 .doc { transform: scale(0.975); opacity: 0.62; }
  .story.is-2 .sv-player { transform: translateY(-10px) scale(1.03); }
}

@media (prefers-reduced-motion: reduce) {
  .sv-tap { display: none; }
  .story.is-done .sv-flash,
  .story.no-pin.is-2 .sv-flash { animation: none; }
  .doc, .sv-player { transition: none; }
}

/* --------------------------------------------------------------------------
   讲义预览切换：真机屏幕里可点击切换的真实讲义（截图 / 生成原文）
   -------------------------------------------------------------------------- */
.ws-tabs {
  display: flex;
  justify-content: center;
  gap: 4px;
  width: max-content;
  max-width: 100%;
  margin: 34px auto 26px;
  padding: 4px;
  border-radius: 16px;
  background: rgba(26, 31, 33, 0.055);
}
body:not(.js) .ws-tabs { display: none; }
.ws-tab {
  appearance: none;
  border: 0;
  cursor: pointer;
  padding: 8px 20px 9px;
  border-radius: 12.5px;
  background: transparent;
  text-align: center;
  color: var(--muted);
  transition: background 0.3s ease, color 0.3s ease, box-shadow 0.3s ease;
}
.ws-tab b {
  display: block;
  font-size: 14px;
  font-weight: 650;
  color: inherit;
}
.ws-tab span {
  display: block;
  margin-top: 1px;
  font-size: 11.5px;
  opacity: 0.75;
  font-variant-numeric: tabular-nums;
}
/* 这三个标签是本节里唯一真能按的东西，此前悬停只换一个字色——
   比旁边根本不可点的积分包（抬 3px + 加影 + 跟指针轻转）、计费行（整行加底色）
   反馈都弱，「能按的」看上去反而最死，可点性被压在了装饰底下。
   补一层淡药丸底：悬停即预演选中态，是分段控件通行的说法。
   已选中的那个排除在外，否则会在自己的白药丸上再糊一层灰。 */
.ws-tab:hover { color: var(--ink); }
@media (hover: hover) and (pointer: fine) {
  .ws-tab:not(.on):hover { background: rgba(26, 31, 33, 0.06); }
}
.ws-tab.on {
  background: var(--card);
  color: var(--ink);
  box-shadow: 0 4px 14px rgba(26, 31, 33, 0.1), 0 1px 3px rgba(26, 31, 33, 0.08);
}
.ws-tab.on b { color: var(--accent); }

/* 屏幕内面板：与实录截图同为 4:3，切换时交叉淡入 */
.ws-panes {
  position: relative;
  aspect-ratio: 4 / 3;
  background: var(--paper);
}
/* 未选中的面板此前只是 opacity:0 + pointer-events:none —— 鼠标点不到，
   但它仍在无障碍树和 Tab 序列里，而两个 .ws-note 带 tabindex="0"（为了能键盘滚动），
   于是用 Tab 走到这里会连着掉进两个看不见的滚动区。
   改用 visibility 收起：淡出结束的那一刻才隐藏，交叉淡入的观感不变。 */
.ws-pane {
  position: absolute;
  inset: 0;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  /* 切换加一层纵深：来的面板从 7px 下方微放大升入，走的沉回去——
     纯透明度交叉在真机屏幕里像换贴图，加位移后像换了一页 */
  transform: scale(0.986) translateY(7px);
  transition: opacity 0.5s ease, transform var(--dur-3) var(--ease-out), visibility 0s linear 0.5s;
}
.ws-pane.on {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transform: none;
  transition: opacity 0.5s ease, transform var(--dur-3) var(--ease-out), visibility 0s linear 0s;
}
@media (prefers-reduced-motion: reduce) {
  .ws-pane { transform: none; transition: opacity 0.5s ease, visibility 0s linear 0.5s; }
  .ws-pane.on { transition: opacity 0.5s ease, visibility 0s linear 0s; }
}
.ws-pane img { width: 100%; height: 100%; object-fit: cover; }
/* 这两个面板是真讲义节选、内容比屏幕长，此前只在下方的图注里用文字写着「可滚动」——
   面板本身没有任何视觉暗示，看上去就是一张截到一半的图。
   补经典的滚动阴影：两层 local 背景负责在到顶/到底时把阴影盖住，
   两层 scroll 背景是阴影本身，于是「上面还有内容」「下面还有内容」自己会显形。
   纯 CSS、不加元素、不需要监听滚动。 */
.ws-note {
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  background:
    linear-gradient(var(--paper) 34%, rgba(248, 246, 241, 0)) top / 100% 30px no-repeat local,
    linear-gradient(rgba(248, 246, 241, 0), var(--paper) 66%) bottom / 100% 30px no-repeat local,
    radial-gradient(farthest-side at 50% 0, rgba(26, 31, 33, 0.13), rgba(26, 31, 33, 0)) top / 100% 13px no-repeat scroll,
    radial-gradient(farthest-side at 50% 100%, rgba(26, 31, 33, 0.13), rgba(26, 31, 33, 0)) bottom / 100% 13px no-repeat scroll,
    var(--paper);
}
.ws-note::-webkit-scrollbar { width: 8px; }
.ws-note::-webkit-scrollbar-thumb { background: rgba(26, 31, 33, 0.16); border-radius: 8px; }
.ws-note::-webkit-scrollbar-track { background: transparent; }
/* Firefox 走标准属性，别让它顶着一根系统灰的粗滚动条 */
.ws-note { scrollbar-width: thin; scrollbar-color: rgba(26, 31, 33, 0.22) transparent; }
/* 底部渐隐，提示还能往下滚 */
.ws-note::after {
  content: "";
  position: sticky;
  display: block;
  bottom: 0;
  height: 36px;
  margin-top: -36px;
  background: linear-gradient(rgba(248, 246, 241, 0), var(--paper));
  pointer-events: none;
}
.ws-doc {
  max-width: 880px;
  margin: 0 auto;
  padding: 30px 44px 42px;
  text-align: left;
}

.sm-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 6px 14px;
  padding-bottom: 16px;
  border-bottom: 1px solid rgba(219, 217, 207, 0.6);
}
.sm-head b { font-size: clamp(15.5px, 1.9vw, 18px); font-weight: 700; }
.sm-head span { font-size: 12.5px; color: var(--muted); }

.sm-sec { padding: 20px 0 4px; }
.sm-sec + .sm-sec { border-top: 1px dashed rgba(219, 217, 207, 0.7); }
.sm-row {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 10px;
}
.sm-row .doc-chip { font-size: 12px; }
.sm-row h3 { margin: 0; font-size: clamp(14.5px, 1.8vw, 16.5px); font-weight: 700; flex: 1; }
.sm-row .chip { flex: none; font-size: 11px; padding: 2.5px 10px; }
.sm-sec > p {
  margin: 0 0 10px;
  font-size: 13.5px;
  line-height: 1.8;
  color: var(--ink);
}
.sm-sec > p b { font-weight: 650; }

.sm-options {
  list-style: none;
  margin: 0 0 12px;
  padding: 10px 14px;
  border-radius: var(--r-m);
  background: rgba(26, 31, 33, 0.028);
  font-size: 12.8px;
  line-height: 1.9;
  color: var(--muted);
}
.sm-options .sm-right {
  color: var(--accent);
  font-weight: 650;
}
.sm-explain {
  position: relative;
  padding-left: 14px;
}
.sm-explain::before {
  content: "";
  position: absolute;
  left: 0; top: 5px; bottom: 5px;
  width: 3px;
  border-radius: 3px;
  background: rgba(20, 105, 87, 0.4);
}
.sm-ans {
  display: inline-block;
  margin-left: 8px;
  padding: 1px 10px;
  border-radius: 999px;
  background: var(--accent);
  color: #fff;
  font-size: 11.5px;
  font-weight: 650;
}
.sm-trap { display: flex; align-items: baseline; gap: 8px; color: var(--muted) !important; font-size: 12.8px !important; }
.sm-trap .chip { flex: none; }

.sm-more {
  margin-top: 18px;
  padding-top: 14px;
  border-top: 1px solid rgba(219, 217, 207, 0.6);
  text-align: center;
  font-size: 12.5px;
  color: var(--muted);
}

@media (max-width: 640px) {
  .ws-tabs {
    width: auto;
    max-width: 100%;
    justify-content: flex-start;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    /* 三个标签在窄屏放不下，第三个被切在视野外。滚动条又被完全隐藏，
       于是「右边还有」这件事没有任何东西在说。改用按滚动位置生效的边缘渐隐
       （类名由 main.js 的 syncTabEdges 维护），滚到头自动收掉，不会白啃掉最后一个标签。 */
    scroll-snap-type: x proximity;
  }
  .ws-tabs::-webkit-scrollbar { display: none; }
  .ws-tab { scroll-snap-align: center; }
  .ws-tabs.edge-r {
    -webkit-mask-image: linear-gradient(90deg, #000 calc(100% - 32px), transparent);
    mask-image: linear-gradient(90deg, #000 calc(100% - 32px), transparent);
  }
  .ws-tabs.edge-l {
    -webkit-mask-image: linear-gradient(90deg, transparent, #000 32px);
    mask-image: linear-gradient(90deg, transparent, #000 32px);
  }
  .ws-tabs.edge-l.edge-r {
    -webkit-mask-image: linear-gradient(90deg, transparent, #000 32px, #000 calc(100% - 32px), transparent);
    mask-image: linear-gradient(90deg, transparent, #000 32px, #000 calc(100% - 32px), transparent);
  }
  .ws-tab { flex: none; white-space: nowrap; padding: 8px 14px 9px; }
  .ws-tab b { font-size: 12.5px; }
  .ws-tab span { font-size: 10px; }
  .ws-doc { padding: 16px 14px 24px; }
  .ws-doc .sm-sec > p, .ws-doc .sm-options, .ws-doc .sm-trap { font-size: 12px !important; }
  .sm-row { flex-wrap: wrap; }
}

/* --------------------------------------------------------------------------
   讲义（真实截图：真机从平躺随滚动立起）
   -------------------------------------------------------------------------- */
.shot-wide { margin-top: 52px; }
@media (min-width: 861px) {
  body.mjs .shot-wide {
    transform-origin: 50% 88%;
    will-change: transform;
  }
}

.type-row {
  margin-top: 46px;
  display: flex;
  justify-content: center;
  gap: 8px;
  flex-wrap: wrap;
}
.type-row .chip { font-size: 12px; padding: 3.5px 12px; }

.chip {
  display: inline-block;
  padding: 1.5px 7px;
  border-radius: 999px;
  font-style: normal;
  font-size: 10.5px;
  font-weight: 650;
  color: #fff;
  white-space: nowrap;
}
.c-question { background: var(--t-question); }
.c-explain { background: var(--t-explain); }
.c-key { background: var(--t-key); }
.c-method { background: var(--t-method); }
.c-trap { background: var(--t-trap); }
.c-example { background: var(--t-example); }
.c-memory { background: var(--t-memory); }

.note-points {
  margin: 44px 0 0;
  padding: 0;
  list-style: none;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 18px;
}
.note-points li { padding: 18px 4px 0; border-top: 1px solid var(--line); }
.note-points b { display: block; font-size: 15.5px; margin-bottom: 5px; }
.note-points span { font-size: 13px; color: var(--muted); line-height: 1.6; }

@media (max-width: 860px) {
  .note-points { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 480px) {
  .note-points { grid-template-columns: 1fr; }
}

/* --------------------------------------------------------------------------
   今日自测（暗色章节）
   -------------------------------------------------------------------------- */
.section-dark {
  position: relative;
  overflow: clip;
  background: var(--dark);
  color: var(--dark-ink);
  padding: var(--sec-y-lg) 0;
  /* 深色区块以一块圆角面板的姿态【盖】在上一节之上，而不是与它对接一条直边。
     负 margin 让它真的压上去，圆角处露出的就是上一节的白 —— 层次由此成立。
     上缘半径 --qr 由滚动驱动（见 main.js 的 quizSec 项）：刚露头时是圆的，
     完全占满视口时收成平的，像一张卡片推上来贴合。 */
  margin-top: calc(-1 * var(--seam));
  border-radius: var(--qr, var(--seam)) var(--qr, var(--seam)) 0 0;
  box-shadow: 0 -22px 44px rgba(26, 31, 33, 0.13);
}
/* 深色区块之后那一节同样盖回来，深色因此夹在两层浅色之间，进出各有一次交接 */
.section-dark + .section {
  position: relative;
  margin-top: calc(-1 * var(--seam));
  border-radius: var(--seam) var(--seam) 0 0;
  background: var(--white);
}
.section-dark .sec-sub,
.section-dark .shot-cap { color: var(--dark-muted); }
.section-dark .container { position: relative; z-index: 1; }

/* 暗色章节的漂浮道具（纯 CSS 缓慢漂移） */
.shp-dk1 {
  width: clamp(70px, 8vw, 110px);
  top: 170px; right: 9%;
  opacity: 0.5;
  filter: blur(1.5px);
  animation: dkDrift1 11s ease-in-out infinite alternate;
}
.shp-dk2 {
  width: clamp(120px, 15vw, 210px);
  bottom: 90px; left: -60px;
  opacity: 0.28;
  filter: blur(3px);
  animation: dkDrift2 14s ease-in-out infinite alternate;
}
@keyframes dkDrift1 {
  from { transform: translateY(-12px) rotate(-4deg); }
  to { transform: translateY(14px) rotate(5deg); }
}
@keyframes dkDrift2 {
  from { transform: translateY(10px) rotate(3deg); }
  to { transform: translateY(-16px) rotate(-6deg); }
}
@media (prefers-reduced-motion: reduce) {
  .shp-dk1, .shp-dk2 { animation: none; }
}

.quiz-device {
  max-width: 880px;
  margin: 60px auto 0;
}
body.js.mjs .quiz-device {
  transform: translateY(var(--qy, 0px)) scale(var(--qs, 1));
  will-change: transform;
}

/* 深空灰真机（暗色章节用） */
.device-frame-dark {
  background: linear-gradient(150deg,
    #838991 0%, #464b51 22%, #26292d 50%, #5d636a 78%, #35393e 100%);
  box-shadow:
    0 60px 120px rgba(0, 0, 0, 0.5),
    0 18px 40px rgba(0, 0, 0, 0.4),
    inset 0 1px 1px rgba(255, 255, 255, 0.32),
    inset 0 -1px 1px rgba(0, 0, 0, 0.55);
}
.device-frame-dark .device-bezel { background: #000; }

.quiz-points {
  max-width: 880px;
  margin: 68px auto 0;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 30px 28px;
  text-align: left;
}
.quiz-point b {
  display: block;
  font-size: 16.5px;
  font-weight: 700;
  margin-bottom: 7px;
}
.quiz-point b::before {
  content: "";
  display: block;
  width: 22px; height: 3px;
  border-radius: 2px;
  background: var(--mint);
  margin-bottom: 14px;
  opacity: 0.9;
}
.quiz-point span { font-size: 14px; color: var(--dark-muted); line-height: 1.7; }

@media (max-width: 700px) {
  .quiz-points { grid-template-columns: 1fr; gap: 24px; }
}

/* --------------------------------------------------------------------------
   更多功能
   -------------------------------------------------------------------------- */
.feature-grid {
  margin-top: 56px;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 40px 36px;
}
.feature h3 { margin: 0 0 6px; font-size: 17px; font-weight: 700; }
/* 强调小横条已让位给线性图标（第十八轮）；暗区要点保留横条语言 */
.feature p { margin: 0; font-size: 14px; color: var(--muted); line-height: 1.7; }

@media (max-width: 760px) {
  .feature-grid { grid-template-columns: 1fr 1fr; gap: 32px 24px; }
}
@media (max-width: 480px) {
  .feature-grid { grid-template-columns: 1fr; }
}

/* --------------------------------------------------------------------------
   积分
   -------------------------------------------------------------------------- */
.pricing-grid {
  margin-top: 56px;
  display: grid;
  grid-template-columns: 1.15fr 0.85fr;
  gap: 56px;
  align-items: start;
}

.packs {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 14px;
}
.pack {
  position: relative;
  padding: 26px 14px 20px;
  border-radius: var(--r-l);
  border: 0.5px solid rgba(219, 217, 207, 0.9);
  background: var(--card);
  text-align: center;
}
.pack-mid { border-color: rgba(20, 105, 87, 0.45); box-shadow: 0 10px 28px rgba(20, 105, 87, 0.1); }
.pack-badge {
  position: absolute;
  top: -11px; left: 50%;
  transform: translateX(-50%);
  padding: 3px 11px;
  border-radius: 999px;
  background: var(--accent);
  color: #fff;
  font-size: 11px;
  font-weight: 650;
  white-space: nowrap;
}
.pack-badge-gold { background: var(--star); color: rgba(26, 31, 33, 0.88); }
.pack-credits {
  font-size: 28px;
  font-weight: 750;
  letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums;
}
.pack-credits .unit { font-size: 13px; font-weight: 600; color: var(--muted); margin-left: 3px; }
.pack-price { margin-top: 2px; font-size: 15.5px; font-weight: 650; color: var(--accent); }

.rules {
  margin-top: 22px;
  background: var(--white);
  border: 0.5px solid rgba(219, 217, 207, 0.9);
  border-radius: var(--r-l);
  overflow: hidden;
}
.rule {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 18px;
  padding: 13px 20px;
  font-size: 14px;
}
.rule + .rule { border-top: 1px solid rgba(219, 217, 207, 0.5); }
.rule b { font-weight: 600; }
.rule span { color: var(--muted); text-align: right; white-space: nowrap; }
.rule .free { color: var(--accent); font-weight: 650; }

.pricing-shot { text-align: center; }
.shot-frame {
  box-shadow:
    0 30px 60px rgba(26, 31, 33, 0.2),
    inset 0 1px 1px rgba(255, 255, 255, 0.9),
    inset 0 -1px 1px rgba(0, 0, 0, 0.3);
}

@media (max-width: 860px) {
  .pricing-grid { grid-template-columns: 1fr; gap: 44px; }
  .pricing-shot { max-width: 380px; margin: 0 auto; }
  .rule { flex-wrap: wrap; }
  .rule span { white-space: normal; text-align: left; }
}
@media (max-width: 560px) {
  .packs { grid-template-columns: 1fr; max-width: 300px; margin-left: auto; margin-right: auto; }
}

/* --------------------------------------------------------------------------
   隐私
   -------------------------------------------------------------------------- */
.privacy-list {
  margin-top: 48px;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 32px 28px;
}
.privacy-item h3 { margin: 0 0 6px; font-size: 16.5px; font-weight: 700; }
.privacy-item p { margin: 0; font-size: 13.5px; line-height: 1.7; color: var(--muted); }
.privacy-note {
  margin: 40px 0 0;
  text-align: center;
  font-size: 13px;
  color: var(--muted);
}
/* 全站唯一没有悬停态的链接，而它恰好还是唯一一个嵌在句子里的行内链接
   （其余都是页脚中的独立条目）：只靠一点墨绿色差与正文区分，鼠标扫过毫无回应。
   行内链接给下划线而不是照搬页脚的纯变色——句子中间需要更明确的边界。 */
.privacy-note a {
  color: var(--accent);
  transition: color var(--dur-1) var(--ease-soft);
}
.privacy-note a:hover {
  color: var(--accent-deep);
  text-decoration: underline;
  text-underline-offset: 3px;
}

@media (max-width: 700px) {
  .privacy-list { grid-template-columns: 1fr; gap: 24px; }
}

/* --------------------------------------------------------------------------
   真机翻转展示（滚动驱动整机 360° 翻滚，掠过侧面一线薄边和背面相机模组）
   -------------------------------------------------------------------------- */
.section-spin {
  background: var(--white);
  padding: var(--sec-y) 0;
  text-align: center;
}
.spin-stage {
  position: relative;
  width: min(720px, 88vw);
  margin: 52px auto 0;
  perspective: 2000px;
}
.spin-tilt {
  transform-style: preserve-3d;
  will-change: transform;
}
/* 地面影子：设备翻滚时始终落在舞台上。
   --spsh-s / --spsh-o 由 JS 按翻转角驱动（正对最宽最实、侧面收窄变淡）——
   影子跟着姿态呼吸，悬空翻滚才有着地的参照；无 JS / 减少动效时停在默认值 */
.spin-stage::after {
  content: "";
  position: absolute;
  left: 10%; right: 10%;
  bottom: -52px;
  height: 46px;
  border-radius: 50%;
  transform: scaleX(var(--spsh-s, 1));
  opacity: var(--spsh-o, 0.24);
  background: radial-gradient(closest-side, rgba(26, 31, 33, 1), rgba(26, 31, 33, 0) 88%);
  filter: blur(9px);
}
.spin-stage .device-frame { box-shadow: none; }

@media (min-width: 861px) {
  body.mjs .section-spin { padding: 0; }
  /* 250→225vh（第十一轮）：翻滚全程连续（20/20 采样步都在转，无死区），
     只收行程——360° 从 2.4°/vh 提到 2.9°/vh，一次滚轮拨动约转 25°，更跟手。 */
  body.mjs .spin-wrap { height: 225vh; }
  body.mjs .spin-pin {
    position: sticky;
    top: 0;
    height: 100vh;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    overflow: clip;
  }
  body.mjs .spin-stage { width: min(680px, 62vh * 4 / 3); }
}
@media (max-width: 860px) {
  /* 窄屏：静态小倾角展示 */
  .spin-tilt { transform: rotateY(14deg) rotateX(4deg); }
  .spin-stage { perspective: 1300px; }
}

/* --------------------------------------------------------------------------
   下载 / 页脚
   -------------------------------------------------------------------------- */
.section-download {
  background: var(--white);
  text-align: center;
  padding: var(--sec-y-lg) 0;
}
.dl-icon {
  margin: 0 auto 26px;
  border-radius: 20px;
  box-shadow: 0 16px 40px rgba(26, 31, 33, 0.18);
}

.footer {
  border-top: 1px solid var(--line);
  padding: 38px 0 46px;
  text-align: center;
  font-size: 12.5px;
  color: var(--muted);
}
.footer-links {
  display: flex;
  justify-content: center;
  gap: 26px;
  margin-bottom: 14px;
  font-size: 13px;
}
.footer-links a { color: var(--ink); transition: color var(--dur-1) var(--ease-soft); }
.footer-links a:hover { color: var(--accent); }
.footer p { margin: 4px 0; }
.footer p a { color: var(--muted); }
.footer p a:hover { color: var(--accent); }

/* --------------------------------------------------------------------------
   正文内容区的微交互

   首屏与三处钉住场景是电影感的，而中段的六个内容栅格
   （更多功能 / 自测要点 / 隐私 / 积分包 / 计费规则 / 讲义要点）
   此前一个 hover 状态都没有——鼠标扫过去毫无回应，读起来像静态图片，
   和上下文的动效落差很大。

   这里不新造视觉语言，只放大页面已有的母题：
   `.feature h3::before` / `.quiz-point b::before` 那根 22×3px 的强调色小横条。
   悬停时让它长出去，卡片整体轻抬 2px，缓动统一用全站在用的
   var(--ease-out)。横条用 scaleX 而非 width，走合成器、不触发布局。

   全部包在 (hover: hover) and (pointer: fine) 里：触摸屏上 :hover 会在点击后
   「粘住」不放，栅格会留一片抬起的卡片下不来。
   -------------------------------------------------------------------------- */
.feature,
.quiz-point,
.privacy-item {
  transition: transform var(--dur-2) var(--ease-out);
}
.quiz-point b::before {
  transform-origin: left center;
  transition: transform var(--dur-2) var(--ease-out),
              opacity var(--dur-2) var(--ease-soft);
}
.feature h3,
.privacy-item h3 { transition: color var(--dur-1) var(--ease-soft); }

.pack {
  transition: transform var(--dur-2) var(--ease-out),
              box-shadow var(--dur-2) var(--ease-soft),
              border-color var(--dur-1) var(--ease-soft);
}
.rule { transition: background-color var(--dur-1) var(--ease-soft); }
.note-points li { transition: border-color var(--dur-2) var(--ease-soft); }
.type-row .chip {
  transition: transform var(--dur-2) var(--ease-out),
              box-shadow var(--dur-2) var(--ease-soft);
}

@media (hover: hover) and (pointer: fine) {
  .feature:hover,
  .quiz-point:hover,
  .privacy-item:hover { transform: translateY(-2px); }

  .quiz-point:hover b::before {
    transform: scaleX(1.8);
    opacity: 1;
  }
  .feature:hover h3 { color: var(--accent); }
  .privacy-item:hover h3 { color: var(--accent); }

  /* 积分包最像「可以点」的东西，抬得比别处明显一点 */
  .pack:hover {
    transform: translateY(-3px);
    border-color: rgba(20, 105, 87, 0.4);
    box-shadow: 0 14px 30px rgba(26, 31, 33, 0.1);
  }
  .pack-mid:hover { box-shadow: 0 16px 34px rgba(20, 105, 87, 0.18); }

  /* 六行计费规则此前是一整块死表格，扫过时给当前行一点底色 */
  .rule:hover { background: rgba(20, 105, 87, 0.045); }

  .note-points li:hover { border-top-color: var(--accent); }

  .type-row .chip:hover {
    transform: translateY(-1px) scale(1.05);
    box-shadow: 0 4px 12px rgba(26, 31, 33, 0.14);
  }
}

/* 「即将上架」是 aria-disabled 的占位按钮，不该有可按的反馈 */
a[aria-disabled="true"]:hover,
a[aria-disabled="true"]:active {
  transform: none;
  box-shadow: 0 3px 10px rgba(20, 105, 87, 0.24);
}

/* 入场节奏分层：大标题慢慢化开，栅格里的小卡片走得更短更轻快。
   此前全站共用同一条 translateY(18px)/0.9s，标题和卡片同速，读起来没有层次。 */
body.js .feature-grid .reveal,
body.js .quiz-points .reveal,
body.js .privacy-list .reveal,
body.js .note-points .reveal {
  transform: translateY(11px);
  transition-duration: var(--dur-3);
}

@media (prefers-reduced-motion: reduce) {
  .feature, .quiz-point, .privacy-item, .pack,
  .quiz-point b::before,
  .type-row .chip { transition: none; }
  .feature:hover, .quiz-point:hover, .privacy-item:hover,
  .pack:hover, .type-row .chip:hover { transform: none; }
  .quiz-point:hover b::before { transform: none; }
}

/* --------------------------------------------------------------------------
   第十轮：3D 与登场节奏补齐
   -------------------------------------------------------------------------- */

/* 下载章节：图标以 3D 仰角落定——收尾一幕的仪式感。
   复用 .reveal 的 --dur-4 过渡；后写的初始 transform 需要自带 .in 复位 */
.section-download .container { perspective: 900px; }
body.js .dl-icon.reveal { transform: translateY(24px) rotateX(26deg) scale(0.93); }
body.js .dl-icon.reveal.in { transform: none; }

/* 七个类型胶囊：瀑布式登场（行整体上移的同时逐个冒出）。
   用 animation 而不是 transition-delay——delay 挂在 transition 上会
   永久污染之后的悬停回弹，动画播完即走不留延迟 */
body.js .type-row.reveal.in .chip { animation: chipIn var(--dur-3) var(--ease-out) backwards; }
body.js .type-row.reveal.in .chip:nth-child(2) { animation-delay: 0.05s; }
body.js .type-row.reveal.in .chip:nth-child(3) { animation-delay: 0.1s; }
body.js .type-row.reveal.in .chip:nth-child(4) { animation-delay: 0.15s; }
body.js .type-row.reveal.in .chip:nth-child(5) { animation-delay: 0.2s; }
body.js .type-row.reveal.in .chip:nth-child(6) { animation-delay: 0.25s; }
body.js .type-row.reveal.in .chip:nth-child(7) { animation-delay: 0.3s; }
@keyframes chipIn {
  from { opacity: 0; transform: translateY(9px) scale(0.9); }
}

/* 三个积分包：错峰登场，从左到右依次立稳 */
body.js .packs.reveal.in .pack { animation: packIn var(--dur-3) var(--ease-out) backwards; }
body.js .packs.reveal.in .pack:nth-child(2) { animation-delay: 0.07s; }
body.js .packs.reveal.in .pack:nth-child(3) { animation-delay: 0.14s; }
@keyframes packIn {
  from { opacity: 0; transform: translateY(14px) scale(0.97); }
}

/* --------------------------------------------------------------------------
   第十二轮：hero 开场编排 + 次级章节登场补齐
   -------------------------------------------------------------------------- */

/* 首屏主机：淡入之外补上动作——低位沉降落定（与全站「升起就位」同一语言）。
   dev-enter 由 main.js 只在桌面滚动引擎分支加一次；.stage-3d 从不被 JS 写
   transform，动画与 scrub 驱动的父子层互不冲突。0.26s 延迟与既有淡入同拍。 */
.device.dev-enter .stage-3d {
  animation: stageIn 1.15s var(--ease-out) 0.26s backwards;
}
@keyframes stageIn {
  from { transform: translateY(26px) scale(0.976); }
}

/* 计费规则六行：随块整体上移的同时逐行接续显形。第十轮给了积分包和类型胶囊
   瀑布，唯独这张六行表还是一整块淡入。与瀑布同用 animation+backwards，
   播完即走，不污染悬停行高亮。 */
body.js .rules.reveal.in .rule { animation: rowIn var(--dur-3) var(--ease-out) backwards; }
body.js .rules.reveal.in .rule:nth-child(2) { animation-delay: 0.05s; }
body.js .rules.reveal.in .rule:nth-child(3) { animation-delay: 0.1s; }
body.js .rules.reveal.in .rule:nth-child(4) { animation-delay: 0.15s; }
body.js .rules.reveal.in .rule:nth-child(5) { animation-delay: 0.2s; }
body.js .rules.reveal.in .rule:nth-child(6) { animation-delay: 0.25s; }
body.js .rules.reveal.in .rule:nth-child(7) { animation-delay: 0.3s; }
@keyframes rowIn {
  from { opacity: 0; transform: translateY(8px); }
}

/* 讲义三个预览标签：同一套接续显形——此前与大真机同拍整块淡入，
   三个「可以点」的控件在一坨里没有存在感 */
body.js .ws-tabs.reveal.in .ws-tab { animation: rowIn var(--dur-3) var(--ease-out) backwards; }
body.js .ws-tabs.reveal.in .ws-tab:nth-child(2) { animation-delay: 0.06s; }
body.js .ws-tabs.reveal.in .ws-tab:nth-child(3) { animation-delay: 0.12s; }

@media (prefers-reduced-motion: reduce) {
  body.js .dl-icon.reveal { transform: none; }
  body.js .type-row.reveal.in .chip,
  body.js .packs.reveal.in .pack,
  body.js .rules.reveal.in .rule,
  body.js .ws-tabs.reveal.in .ws-tab { animation: none; }
  .device.dev-enter .stage-3d { animation: none; }
}

/* --------------------------------------------------------------------------
   第十五轮：材质与光

   动效体系到第十四轮已经收拢（两条曲线、四档时长、单级滚动平滑），
   但「高级感」还差一层东西：光。全站的材质都是静态的——按钮的顶部高光、
   屏幕的反光带（那个跟滚动）之外，没有任何一处光会对人的动作与登场作出反应。
   这一轮只补光与质感，不新增运动语言：
   · 可动的光：主按钮悬停光泽扫过（材质反应，机身不位移，守住「hover 不离开纸面」）；
   · 跟指针的光：积分包描边辉光，与既有的 3D 倾斜同一驱动源；
   · 氛围光：暗色章节的薄荷底光、下载终幕的品牌色聚光——大块底色不再是死平面；
   · 纸的质感：整站细颗粒 + 页脚品牌水印，「暖米纸」从一个色值变成一种材质。
   -------------------------------------------------------------------------- */

/* ---- 主按钮：光泽扫过 ----
   一条斜置的窄光带，悬停时从左到右掠过按钮表面。按钮本体不动、不变形，
   是「玻璃面上过了一道灯」而不是「按钮跳了一下」——与既有悬停哲学一致。
   用 animation 而非 transition：移开再悬停会重新播，且不留任何驻留状态。 */
.btn-primary,
.btn-small {
  position: relative;
  overflow: hidden;
}
.btn-primary::before,
.btn-small::before {
  content: "";
  position: absolute;
  top: -20%; bottom: -20%;
  left: -34%;
  width: 26%;
  background: linear-gradient(100deg,
    rgba(255, 255, 255, 0) 8%,
    rgba(255, 255, 255, 0.34) 50%,
    rgba(255, 255, 255, 0) 92%);
  transform: skewX(-16deg);
  opacity: 0;
  pointer-events: none;
}
@media (hover: hover) and (pointer: fine) {
  .btn-primary:hover::before,
  .btn-small:hover::before { animation: btnSheen 0.85s var(--ease-soft); }
  /* 「即将上架」不可用，不该有任何可按的暗示 */
  a[aria-disabled="true"]:hover::before { animation: none; }
}
@keyframes btnSheen {
  0% { opacity: 1; transform: skewX(-16deg) translateX(0); }
  100% { opacity: 1; transform: skewX(-16deg) translateX(560%); }
}

/* ---- 积分包：指针描边辉光 + 主推包渐变描边 ----
   卡片已经会朝指针倾斜（像捏着一块玻璃牌），但玻璃对光毫无反应。
   卡面是近白色，白色高光叠上去看不见——把光放到「边」上：
   一圈只在描边处显形的辉光（mask 挖空内部），圆心跟着指针走，
   与倾斜同一份 pointermove 数据（--mx/--my 由 main.js 顺手写入）。 */
.pack::before {
  content: "";
  position: absolute;
  inset: -1px;
  border-radius: inherit;
  padding: 1.5px;
  background: radial-gradient(150px circle at var(--mx, 50%) var(--my, 30%),
    rgba(20, 105, 87, 0.55), rgba(20, 105, 87, 0.08) 55%, transparent 72%);
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  mask-composite: exclude;
  opacity: 0;
  transition: opacity var(--dur-2) var(--ease-soft);
  pointer-events: none;
}
@media (hover: hover) and (pointer: fine) {
  .pack:hover::before { opacity: 1; }
}

/* 主推包（900 积分）：静态的墨绿→星标金渐变描边取代单色描边。
   双层背景法：padding-box 铺卡面底色，border-box 露出渐变的 1px 边。 */
.pack-mid {
  border: 1px solid transparent;
  background:
    linear-gradient(var(--card), var(--card)) padding-box,
    linear-gradient(155deg,
      rgba(20, 105, 87, 0.5),
      rgba(58, 167, 109, 0.16) 46%,
      rgba(237, 168, 31, 0.42)) border-box;
}
/* 通用 hover 会把 border-color 涂成实色、盖掉渐变描边，这里按住 */
@media (hover: hover) and (pointer: fine) {
  .pack-mid:hover { border-color: transparent; }
}
.pack-badge { box-shadow: 0 4px 12px rgba(20, 105, 87, 0.28); }
.pack-badge-gold { box-shadow: 0 4px 12px rgba(201, 138, 18, 0.32); }

/* 积分区舞台底纹：主推商品站的台子。复用首屏棚拍地面的点阵母题
   （尺寸减半、透明度更低），椭圆遮罩罩在三个积分包所在的左半区，
   积分区与首屏因此是同一个空间里的两幕。 */
@media (min-width: 861px) {
  #pricing { position: relative; }
  #pricing::before {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    background-image: radial-gradient(circle, rgba(26, 31, 33, 0.13) 1.2px, transparent 1.8px);
    background-size: 33px 33px;
    background-position: -0.6px -0.6px;
    -webkit-mask-image: radial-gradient(64% 56% at 32% 42%, #000 18%, transparent 74%);
    mask-image: radial-gradient(64% 56% at 32% 42%, #000 18%, transparent 74%);
  }
}

/* ---- 计费规则：悬停时数值显形 ----
   行底色已有，但人扫这张表是为了看右列的数字——悬停把当前行的数值
   从 muted 提到正文墨色，视线落点跟着亮。 */
.rule span { transition: color var(--dur-1) var(--ease-soft); }
@media (hover: hover) and (pointer: fine) {
  .rule:hover span { color: var(--ink); }
  .rule:hover .free { color: var(--accent-deep); }
}

/* ---- 讲义预览标签：按下反馈 ----
   全站按钮都有 :active 缩放，唯独这三个真按钮没有——按下去毫无着落。 */
.ws-tab {
  transition: background 0.3s ease, color 0.3s ease, box-shadow 0.3s ease,
              transform var(--dur-1) var(--ease-soft);
}
.ws-tab:active { transform: scale(0.96); }

/* ---- 暗色章节：薄荷氛围底光 ----
   近黑的大面此前是死平的，真机像贴在黑纸上。垫一层缓慢呼吸的薄荷底光，
   暗面有了纵深，真机是「悬在光里」。容器 z-index:1 已有，光永远在内容之下。 */
.section-dark::before {
  content: "";
  position: absolute;
  left: 50%; top: 24%;
  width: min(920px, 86vw);
  aspect-ratio: 1.7;
  transform: translateX(-50%);
  background: radial-gradient(closest-side,
    rgba(127, 216, 194, 0.12), rgba(127, 216, 194, 0.035) 55%, transparent 78%);
  opacity: 0.8;
  animation: ambientBreathe 9s ease-in-out infinite alternate;
  pointer-events: none;
}
@keyframes ambientBreathe {
  from { opacity: 0.55; transform: translateX(-50%) scale(0.97); }
  to { opacity: 1; transform: translateX(-50%) scale(1.05); }
}

/* ---- 下载终幕：品牌色聚光 + 图标悬浮 ----
   收尾一幕此前是纯白平面。头顶给一束极淡的墨绿→星标金聚光（像产品站的顶灯），
   图标落定后开始极缓的悬浮呼吸——终幕有一件「活着的」东西。 */
.section-download {
  position: relative;
  overflow: clip;
}
.section-download::before {
  content: "";
  position: absolute;
  left: 50%; top: -6%;
  width: min(780px, 92vw);
  aspect-ratio: 2.1;
  transform: translateX(-50%);
  background: radial-gradient(closest-side,
    rgba(20, 105, 87, 0.085), rgba(237, 168, 31, 0.035) 58%, transparent 78%);
  pointer-events: none;
}
body.js .dl-icon.reveal.in { animation: iconFloat 5.4s ease-in-out 1.1s infinite alternate; }
@keyframes iconFloat {
  from { transform: none; }
  to { transform: translateY(-7px); }
}

/* ---- 页脚：品牌水印收笔 ----
   页面以巨大的、只露出上半的「课记」二字沉在纸底收尾——品牌的落款。
   水印在所有文字之后、透明度 4.5%，裁掉的下半截交给页面边缘。 */
.footer {
  position: relative;
  overflow: clip;
  padding-bottom: clamp(120px, 16vw, 190px);
}
.footer::after {
  content: "课记";
  position: absolute;
  left: 50%;
  bottom: -0.36em;
  transform: translateX(-50%);
  font-size: clamp(120px, 22vw, 240px);
  line-height: 1;
  font-weight: 800;
  letter-spacing: 0.06em;
  white-space: nowrap;
  color: rgba(26, 31, 33, 0.045);
  pointer-events: none;
  user-select: none;
  -webkit-user-select: none;
}

/* 页脚链接补齐导航同款的下划线滑出——同为「可以去」的地方，同一种说法 */
.footer-links a { position: relative; }
.footer-links a::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: -4px;
  height: 1.5px;
  border-radius: 2px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform var(--dur-2) var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .footer-links a:hover::after { transform: scaleX(1); }
}

/* ---- 整站细颗粒 ----
   「暖米纸」此前只是一个色值。铺一层 5% 的静态细颗粒（SVG 噪声平铺，
   合成层一次到位，滚动零开销），大面积浅底从「屏幕上的米色」变成「纸」。
   只在桌面：窄屏读文字为主，颗粒收益低、还要多合成一层。 */
@media (min-width: 861px) {
  body::after {
    content: "";
    position: fixed;
    inset: 0;
    z-index: 200;
    pointer-events: none;
    opacity: 0.05;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
  }
}

/* ---- 窄屏菜单：条目接续显形 ----
   汉堡菜单此前是整块 display 切换，「啪」地全在。六个条目借用计费行的
   rowIn 接续显形，播完即走。 */
@media (max-width: 780px) {
  body.js .nav-links.open a { animation: rowIn 0.38s var(--ease-out) backwards; }
  body.js .nav-links.open a:nth-child(2) { animation-delay: 0.04s; }
  body.js .nav-links.open a:nth-child(3) { animation-delay: 0.08s; }
  body.js .nav-links.open a:nth-child(4) { animation-delay: 0.12s; }
  body.js .nav-links.open a:nth-child(5) { animation-delay: 0.16s; }
  body.js .nav-links.open a:nth-child(6) { animation-delay: 0.2s; }
}

@media (prefers-reduced-motion: reduce) {
  .btn-primary::before,
  .btn-small::before { display: none; }
  .section-dark::before,
  body.js .dl-icon.reveal.in,
  body.js .nav-links.open a { animation: none; }
  .ws-tab:active { transform: none; }
  .pack::before { transition: none; }
  .footer-links a::after { transition: none; }
}

/* --------------------------------------------------------------------------
   第十六轮：转化舞台（视频 → 讲义）+ 首屏滚动暗示

   产品的核心动作「一节视频课变成一份讲义」此前从没被演出来过：三步展示
   给的是三张结果截图，滚动叙事讲的是讲义生成之后怎么用。中间缺的正是
   过程本身。这一节是一台自动演出的工作台：左边视频在播（字幕逐句换、
   波形在听、进度在走），右边讲义在长（骨架微光先行、文字随后落定），
   中间一条数据流把两边接起来；讲到板书时金框圈住黑板、咔一下闪光，
   关键帧插进讲义。一轮约十秒，由 main.js 的时间轴驱动，可见才演，
   离开视口即停。讲义文字与 #demo 同一节课（幂函数求导），前一幕看它
   怎么写成，后一幕看时间戳怎么用，两节是连续剧。
   -------------------------------------------------------------------------- */
.tf-stage {
  margin-top: 56px;
  display: grid;
  grid-template-columns: 1fr 64px 1.06fr;
  align-items: center;
}

/* ---- 视频卡（复刻应用内播放器语言：video-a 底、圆角、进度条） ---- */
.tf-video {
  border-radius: var(--r-l);
  background: var(--video-a);
  padding: 10px;
  box-shadow: 0 22px 54px rgba(26, 31, 33, 0.22);
}
.tf-canvas {
  position: relative;
  aspect-ratio: 16 / 10;
  border-radius: 10px;
  overflow: hidden;
  background: url("screens/board.webp") center / cover no-repeat, var(--video-b);
}
/* 板书识别的「咔」：一层白闪，拍下关键帧的那一瞬 */
.tf-canvas::after {
  content: "";
  position: absolute;
  inset: 0;
  background: #fff;
  opacity: 0;
  pointer-events: none;
}
.tf-stage.flash .tf-canvas::after { animation: tfFlash 0.55s ease-out both; }
@keyframes tfFlash {
  0% { opacity: 0.7; }
  100% { opacity: 0; }
}

.tf-time {
  position: absolute;
  top: 5%; right: 3%;
  padding: 3px 11px;
  border-radius: 7px;
  background: rgba(16, 19, 20, 0.88);
  color: #fff;
  font-size: clamp(11px, 1.3vw, 13.5px);
  font-weight: 650;
  font-variant-numeric: tabular-nums;
}

/* 字幕条：波形在左（它在听），句子在右（它听到的） */
.tf-sub {
  position: absolute;
  left: 2.5%; right: 2.5%; bottom: 4%;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 12px;
  border-radius: 9px;
  background: rgba(16, 19, 20, 0.84);
  color: rgba(255, 255, 255, 0.94);
  font-size: clamp(11px, 1.35vw, 13px);
  line-height: 1.5;
}
.tf-wave {
  display: flex;
  align-items: center;
  gap: 2.5px;
  height: 15px;
  flex: none;
}
.tf-wave i {
  width: 2.5px;
  height: 100%;
  border-radius: 2px;
  background: var(--mint);
  transform: scaleY(0.24);
  transform-origin: center;
}
.tf-stage.live .tf-wave i { animation: tfWave 0.85s ease-in-out infinite alternate; }
.tf-wave i:nth-child(2) { animation-delay: 0.12s; }
.tf-wave i:nth-child(3) { animation-delay: 0.28s; }
.tf-wave i:nth-child(4) { animation-delay: 0.05s; }
.tf-wave i:nth-child(5) { animation-delay: 0.2s; }
.tf-wave i:nth-child(6) { animation-delay: 0.34s; }
@keyframes tfWave {
  from { transform: scaleY(0.2); }
  to { transform: scaleY(1); }
}
.tf-sub-txt { display: block; min-width: 0; }
/* 转写光标：字幕由 main.js 逐字打出，句尾一枚细光标在闪——
   「它此刻正在听、正在写」比整句整句地换更像转写本身 */
.tf-stage.live .tf-sub-txt::after {
  content: "";
  display: inline-block;
  width: 2px;
  height: 1em;
  margin-left: 3px;
  vertical-align: -0.14em;
  background: var(--mint);
  animation: tfCaret 1s steps(2, start) infinite;
}
@keyframes tfCaret {
  50% { opacity: 0; }
}

/* 识别框：星标金描边圈住板书，左上挂小标签 */
.tf-scan {
  position: absolute;
  left: 9%; top: 13%;
  width: 46%; height: 54%;
  border: 1.5px solid rgba(237, 168, 31, 0.95);
  border-radius: 8px;
  box-shadow:
    0 0 0 4px rgba(237, 168, 31, 0.14),
    0 0 30px rgba(237, 168, 31, 0.32),
    inset 0 0 22px rgba(237, 168, 31, 0.12);
  opacity: 0;
  transition: opacity 0.4s var(--ease-soft);
}
.tf-scan b {
  position: absolute;
  top: -10px; left: 10px;
  padding: 2px 9px;
  border-radius: 999px;
  background: var(--star);
  color: #fff;
  font-size: 10.5px;
  font-weight: 650;
}
.tf-scan.on { animation: tfScanIn 0.85s var(--ease-out) both; }
@keyframes tfScanIn {
  0% { opacity: 0; transform: scale(1.12); }
  40% { opacity: 1; }
  100% { opacity: 1; transform: scale(1); }
}

/* 进度条：复用播放器语言，宽度由时间轴一轮推到底 */
.tf-video .player-bar { margin: 12px 4px 4px; }
.tf-video .player-fill { width: 22%; }

/* ---- 数据流：三个发光点循环滑过一条虚线 ---- */
.tf-flow { position: relative; height: 100%; min-height: 150px; }
.tf-flow::before {
  content: "";
  position: absolute;
  left: 6px; right: 6px; top: 50%;
  height: 2px;
  margin-top: -1px;
  background: repeating-linear-gradient(90deg,
    rgba(20, 105, 87, 0.34) 0 6px, transparent 6px 13px);
}
.tf-flow i {
  position: absolute;
  top: 50%; left: 0;
  width: 7px; height: 7px;
  margin-top: -3.5px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 10px rgba(20, 105, 87, 0.55);
  opacity: 0;
}
.tf-stage.live .tf-flow i { animation: tfFlowDot 2.1s linear infinite; }
.tf-flow i:nth-child(2) { animation-delay: 0.7s; }
.tf-flow i:nth-child(3) { animation-delay: 1.4s; }
@keyframes tfFlowDot {
  0% { left: -2%; opacity: 0; }
  14% { opacity: 1; }
  86% { opacity: 1; }
  100% { left: 98%; opacity: 0; }
}

/* ---- 讲义卡：随讲解逐段生长 ---- */
.tf-note {
  background: var(--card);
  border: 0.5px solid rgba(219, 217, 207, 0.8);
  border-radius: var(--r-l);
  padding: 24px 26px 18px;
  box-shadow: 0 18px 44px rgba(26, 31, 33, 0.1);
  text-align: left;
}
.tf-head {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 12px;
  opacity: 0;
  transition: opacity 0.45s var(--ease-soft);
}
.tf-head h3 { margin: 0; font-size: clamp(14.5px, 1.8vw, 16.5px); font-weight: 700; flex: 1; }
.tf-head .chip { flex: none; font-size: 11px; padding: 2.5px 10px; }
.tf-head.on { animation: tfPop var(--dur-3) var(--ease-out) both; }
@keyframes tfPop {
  from { opacity: 0; transform: translateY(8px) scale(0.985); }
  to { opacity: 1; transform: none; }
}

/* 正文行的生成分两拍：骨架微光先占位（AI 正在写这段），
   文字随后浮现、骨架让位——比文字直接蹦出来多一层「正在发生」。 */
.tf-line {
  position: relative;
  margin: 0 0 10px;
  font-size: 13.5px;
  line-height: 1.75;
  color: var(--ink);
  opacity: 0;
  transition: opacity 0.45s var(--ease-soft);
}
.tf-line sup, .tf-head sup { font-size: 0.68em; }
.tf-line .txt { opacity: 0; display: block; }
.tf-line::before,
.tf-line::after {
  content: "";
  position: absolute;
  left: 0; top: 4px;
  height: 15px;
  width: 94%;
  border-radius: 7px;
  background: linear-gradient(90deg,
    rgba(20, 105, 87, 0.09) 25%, rgba(20, 105, 87, 0.2) 50%, rgba(20, 105, 87, 0.09) 75%);
  background-size: 220% 100%;
  opacity: 0;
  pointer-events: none;
}
.tf-line::after { top: 28px; width: 56%; }
.tf-line.on { opacity: 1; }
.tf-line.on::before,
.tf-line.on::after { animation: tfSkel 1.15s ease both; }
.tf-line.on::after { animation-delay: 0.08s; }
@keyframes tfSkel {
  0% { opacity: 0; background-position: 60% 0; }
  16% { opacity: 1; }
  62% { opacity: 1; background-position: -40% 0; }
  100% { opacity: 0; background-position: -80% 0; }
}
.tf-line.on .txt { animation: tfTxt 1.2s var(--ease-out) both; }
@keyframes tfTxt {
  0%, 52% { opacity: 0; transform: translateY(4px); }
  100% { opacity: 1; transform: none; }
}

/* 板书关键帧：识别闪光之后插进讲义，行高从 0 展开把后文推开 */
.tf-shot {
  display: grid;
  grid-template-rows: 0fr;
  opacity: 0;
  transform: translateY(6px);
  /* margin 也要在过渡清单里：它随 .on 从 0 长到 12px，漏掉的话
     展开的瞬间后文会先跳一下再被推开 */
  transition:
    grid-template-rows 0.6s var(--ease-out),
    opacity 0.5s var(--ease-soft),
    transform 0.6s var(--ease-out),
    margin-bottom 0.6s var(--ease-out);
}
.tf-shot > span {
  position: relative;
  display: block;
  min-height: 0;
  overflow: hidden;
  border-radius: 10px;
}
.tf-shot img {
  width: 100%;
  height: auto;
  border-radius: 10px;
  border: 1px solid rgba(237, 168, 31, 0.5);
}
.tf-shot em {
  position: absolute;
  left: 8px; bottom: 8px;
  padding: 2.5px 10px;
  border-radius: 999px;
  background: rgba(16, 19, 20, 0.82);
  color: #fff;
  font-style: normal;
  font-size: 10.5px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.tf-shot.on {
  grid-template-rows: 1fr;
  opacity: 1;
  transform: none;
  margin-bottom: 12px;
}

/* 完成落款：讲义写完的那一拍，同时把话头递给下一节（时间戳） */
.tf-done {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 4px;
  padding-top: 12px;
  border-top: 1px solid rgba(219, 217, 207, 0.6);
  font-size: 12.5px;
  font-weight: 650;
  color: var(--accent);
  opacity: 0;
  transition: opacity 0.45s var(--ease-soft);
}
.tf-done b {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 17px; height: 17px;
  border-radius: 50%;
  background: var(--accent);
  color: #fff;
  font-size: 10px;
  flex: none;
}
.tf-done.on { animation: tfPop var(--dur-3) var(--ease-out) both; }

/* 一轮演完的谢幕：reset 把 animation 摘掉，元素滑回各自的基态透明度，
   基态上的 opacity 过渡负责这 0.45s 的整体淡出，随后时间轴从头再来 */
.tf-stage.reset .tf-head,
.tf-stage.reset .tf-line,
.tf-stage.reset .tf-line .txt,
.tf-stage.reset .tf-line::before,
.tf-stage.reset .tf-line::after,
.tf-stage.reset .tf-scan,
.tf-stage.reset .tf-done { animation: none; }
.tf-stage.reset .tf-line .txt { opacity: 0; transition: opacity 0.45s var(--ease-soft); }

/* ---- 静态回退：无 JS / 减少动效 / QA 直接呈现完成态 ---- */
.tf-static .tf-head, body:not(.js) .tf-head,
.tf-static .tf-line, body:not(.js) .tf-line,
.tf-static .tf-done, body:not(.js) .tf-done { opacity: 1; animation: none; }
.tf-static .tf-line .txt, body:not(.js) .tf-line .txt { opacity: 1; animation: none; }
.tf-static .tf-scan, body:not(.js) .tf-scan { opacity: 1; animation: none; transform: none; }
.tf-static .tf-shot, body:not(.js) .tf-shot {
  grid-template-rows: 1fr;
  opacity: 1;
  transform: none;
  margin-bottom: 12px;
}
.tf-static .tf-video .player-fill, body:not(.js) .tf-video .player-fill { width: 64%; }

@media (prefers-reduced-motion: reduce) {
  .tf-stage.live .tf-wave i,
  .tf-stage.live .tf-flow i,
  .tf-stage.flash .tf-canvas::after { animation: none; }
  .tf-stage.live .tf-sub-txt::after { display: none; }
}

/* ---- 响应式：窄屏纵向堆叠，数据流转为竖直 ---- */
@media (max-width: 860px) {
  .tf-stage { grid-template-columns: 1fr; margin-top: 40px; }
  .tf-flow { min-height: 0; height: 48px; }
  .tf-flow::before {
    left: 50%; right: auto; top: 6px; bottom: 6px;
    width: 2px; height: auto;
    margin: 0 0 0 -1px;
    background: repeating-linear-gradient(180deg,
      rgba(20, 105, 87, 0.34) 0 6px, transparent 6px 13px);
  }
  .tf-flow i { left: 50%; top: 0; margin: 0 0 0 -3.5px; }
  .tf-stage.live .tf-flow i { animation-name: tfFlowDotV; }
  @keyframes tfFlowDotV {
    0% { top: -4%; opacity: 0; }
    14% { opacity: 1; }
    86% { opacity: 1; }
    100% { top: 96%; opacity: 0; }
  }
  .tf-note { padding: 20px 18px 14px; }
  .tf-line { font-size: 12.5px; }
  .tf-line::before { height: 13px; }
  .tf-line::after { top: 25px; }
}

/* ---- 首屏滚动暗示 ----
   首屏是一段 178vh 的钉住镜头，玻璃板与真机都在等滚动，却没有任何东西
   说「往下滚」。一枚鼠标形的小暗示：内点循环下滑，开始滚动即淡出
   （由 hero 的 scrub 写内联透明度）。挂在玻璃文案板正下方——舞台底部
   中央站着第二台竖屏真机，放那儿会压在它屏幕上。只在桌面滚动引擎下出现。 */
.hero-copy { position: relative; }
.hero-cue {
  position: absolute;
  left: 50%;
  bottom: -58px;
  width: 22px; height: 34px;
  margin-left: -11px;
  border: 1.5px solid rgba(26, 31, 33, 0.32);
  border-radius: 12px;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.7s var(--ease-soft);
  z-index: 2;
}
.hero-cue i {
  position: absolute;
  left: 50%; top: 6px;
  width: 3px; height: 7px;
  margin-left: -1.5px;
  border-radius: 2px;
  background: var(--accent);
  animation: cueDrop 1.9s var(--ease-soft) infinite;
}
body.mjs.hero-in .hero-cue { opacity: 1; }
@keyframes cueDrop {
  0% { transform: translateY(0); opacity: 0; }
  22% { opacity: 1; }
  72% { opacity: 1; }
  100% { transform: translateY(13px); opacity: 0; }
}
@media (max-width: 860px) {
  .hero-cue { display: none; }
}

/* --------------------------------------------------------------------------
   第十七轮：控件手感

   两件事。其一，讲义预览的三个标签是全页最主要的真控件，选中的白药丸
   却是瞬间从一个格子消失、在另一个格子出现——iOS 分段控件的白块是滑过去的，
   滑动本身就是「这三个是一组」的说法。补一枚由 main.js 定位的滑块，
   药丸底与投影都交给它，标签自身只管文字变色。
   其二，转化舞台的两张大卡是页面中部最大的可视对象，旁边的积分包都会
   朝指针倾斜，它们却纹丝不动——把倾斜泛化成 [data-tilt]，两张卡用小角度
   （±2.4°，比积分包收着，它们是「戏台」不是「商品」）。
   -------------------------------------------------------------------------- */
.ws-tabs { position: relative; }
.ws-thumb {
  position: absolute;
  top: 4px;
  bottom: 4px;
  left: 0;
  width: 0;
  border-radius: 12.5px;
  background: var(--card);
  box-shadow: 0 4px 14px rgba(26, 31, 33, 0.1), 0 1px 3px rgba(26, 31, 33, 0.08);
  transition: left var(--dur-2) var(--ease-out), width var(--dur-2) var(--ease-out);
  pointer-events: none;
}
.ws-tab { position: relative; z-index: 1; }
/* 药丸底交给滑块之后，选中标签自身回到透明 */
body.js .ws-tab.on { background: transparent; box-shadow: none; }

.tf-video,
.tf-note { transition: transform var(--dur-2) var(--ease-out); }

@media (prefers-reduced-motion: reduce) {
  .ws-thumb { transition: none; }
  .tf-video, .tf-note { transition: none; }
}

/* --------------------------------------------------------------------------
   第十八轮：功能图标

   「更多功能」是全页最后一块纯文字区——六个功能名配六段说明，扁得像目录。
   配一套同一笔性的线性图标（24 视框 / 1.6 描边 / 圆头），每条 path 带
   pathLength="1"，长度归一之后 draw-in 只需要一条通用规则：进入视口时
   线条被一笔一笔画出来（子笔画接续），是「功能被点亮」而不是「图片加载完了」。
   小横条的强调语言让位给图标；暗区要点仍用横条。
   -------------------------------------------------------------------------- */
.f-ico {
  display: block;
  width: 26px;
  height: 26px;
  margin-bottom: 13px;
  overflow: visible;
  transition: transform var(--dur-2) var(--ease-out);
}
.f-ico * {
  fill: none;
  stroke: var(--accent);
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: stroke var(--dur-1) var(--ease-soft);
}
/* 长度归一（pathLength=1）后所有笔画共用同一档 dash；1.02 微超一点，
   隐藏 round cap 在 offset 恰好等于全长时可能露出的针尖。
   规则挂在 .reveal 上而不是某个区块上——第十九轮隐私区也配了同一套图标 */
body.js .reveal .f-ico * {
  stroke-dasharray: 1.02;
  stroke-dashoffset: 1.02;
}
body.js .reveal.in .f-ico * {
  stroke-dashoffset: 0;
  transition: stroke-dashoffset 0.8s var(--ease-soft) 0.18s,
              stroke var(--dur-1) var(--ease-soft);
}
body.js .reveal.in .f-ico *:nth-child(2) { transition-delay: 0.34s, 0s; }
body.js .reveal.in .f-ico *:nth-child(3) { transition-delay: 0.46s, 0s; }
body.js .reveal.in .f-ico *:nth-child(4) { transition-delay: 0.58s, 0s; }
body.js .reveal.in .f-ico *:nth-child(5) { transition-delay: 0.7s, 0s; }
body.js .reveal.in .f-ico *:nth-child(n+6) { transition-delay: 0.8s, 0s; }

@media (hover: hover) and (pointer: fine) {
  .feature:hover .f-ico,
  .privacy-item:hover .f-ico { transform: translateY(-1px) scale(1.06); }
  .feature:hover .f-ico *,
  .privacy-item:hover .f-ico * { stroke: var(--accent-deep); }
}

@media (prefers-reduced-motion: reduce) {
  body.js .reveal .f-ico * { stroke-dasharray: none; stroke-dashoffset: 0; transition: none; }
  .f-ico { transition: none; }
  .feature:hover .f-ico,
  .privacy-item:hover .f-ico { transform: none; }
}

/* 暗色章节的文字选区：全站的墨绿浅底在近黑底色上几乎不可见 */
.section-dark ::selection { background: rgba(127, 216, 194, 0.28); }

/* --------------------------------------------------------------------------
   第二十轮：常见问题（原生 details 手风琴）

   付费前的最后几个犹豫（积分过不过期、能导入什么、数据安不安全），
   此前页面上没有答案。用原生 details/summary：语义、键盘、无 JS 全都白拿；
   name 同组原生互斥，一次只开一条。展开动画是渐进增强——
   支持 ::details-content 的浏览器平滑滑开，其余瞬开，内容不受影响。
   -------------------------------------------------------------------------- */
html { interpolate-size: allow-keywords; }

.faq-list {
  margin-top: 44px;
  border-top: 1px solid var(--line);
}
.faq-item { border-bottom: 1px solid var(--line); }
.faq-item summary {
  list-style: none;
  cursor: pointer;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 20px;
  padding: 19px 2px;
  font-size: 16px;
  font-weight: 650;
  transition: color var(--dur-1) var(--ease-soft);
}
.faq-item summary::-webkit-details-marker { display: none; }
@media (hover: hover) and (pointer: fine) {
  .faq-item summary:hover { color: var(--accent); }
}

/* 加号：两条短线，展开时整体旋 45° 变成关闭的 × */
.faq-x {
  position: relative;
  width: 18px; height: 18px;
  flex: none;
  transition: transform var(--dur-2) var(--ease-out);
}
.faq-x::before,
.faq-x::after {
  content: "";
  position: absolute;
  left: 2px; right: 2px; top: 50%;
  height: 1.8px;
  margin-top: -0.9px;
  border-radius: 2px;
  background: var(--accent);
}
.faq-x::after { transform: rotate(90deg); }
.faq-item[open] .faq-x { transform: rotate(45deg); }
.faq-item[open] summary { color: var(--accent); }

.faq-a {
  padding: 2px 38px 20px 2px;
  font-size: 14.5px;
  line-height: 1.85;
  color: var(--muted);
}
.faq-a p { margin: 0; }
.faq-a a {
  color: var(--accent);
  transition: color var(--dur-1) var(--ease-soft);
}
.faq-a a:hover {
  color: var(--accent-deep);
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* 滑开动画（Chrome 129+；其余浏览器瞬开）。选择器独立成行：
   不认识 ::details-content 的引擎会丢弃整条规则，不能连累别的声明 */
.faq-item::details-content {
  height: 0;
  overflow: clip;
  transition: height var(--dur-2) var(--ease-out),
              content-visibility var(--dur-2) allow-discrete;
}
.faq-item[open]::details-content { height: auto; }

/* 答案本体展开时轻轻浮现。这条不依赖 ::details-content，全浏览器可用——
   没有滑开动画的引擎里，瞬开也因为这 0.45s 的淡入而不再生硬 */
.faq-item[open] .faq-a { animation: faqIn 0.45s var(--ease-out); }
@keyframes faqIn {
  from { opacity: 0; transform: translateY(-4px); }
}

@media (prefers-reduced-motion: reduce) {
  .faq-x { transition: none; }
  .faq-item::details-content { transition: none; }
  .faq-item[open] .faq-a { animation: none; }
}

/* --------------------------------------------------------------------------
   第二十一轮：无障碍收尾

   一、开了「提高对比度」的用户：辅助文字与分隔线各加深一档。
   二、Windows 强制配色（高对比主题）：渐变字靠 background-clip: text +
   透明填充实现，强制配色会把背景整层拿掉——文字直接消失。这是必须兜住的：
   还原成系统文字色，渐变让位给可读性。
   -------------------------------------------------------------------------- */
@media (prefers-contrast: more) {
  :root {
    --muted: #4c554f;
    --line: #b3b1a6;
  }
}

@media (forced-colors: active) {
  .grad,
  .grad-mint {
    background: none;
    -webkit-text-fill-color: currentColor;
    color: CanvasText;
  }
}
