/* ==========================================================================
   3e学习助手 · 宣传页
   --------------------------------------------------------------------------
   色板与基本版式**照抄官网 site/public/style.css**（同一套 --primary/--surface/
   --border，同一条 900px 断点），免得"官网和宣传页看起来不像一家人"。
   下半部分是本页自己的东西：真机画布、滚动入场、光晕。

   图标/文案里的产品事实全部来自仓库（ui/Pages.kt、各插件清单、
   site/public/index.html），没有一条是这里编的。
   ========================================================================== */

:root {
  --bg: #fafaf8;
  --surface: #ffffff;
  --surface-2: #f4f4f1;
  --text: #1f2023;
  --muted: #6b6c70;
  --border: #e2e2de;
  --primary: #185fa5;
  --primary-fg: #ffffff;
  --primary-soft: #e6f1fb;
  --teal: #0f6e56;
  --radius: 8px;
  --radius-lg: 14px;
  --wrap: 1040px;
  /* 出错时的提示色（例如「页面」那一节的截图加载失败） */
  --danger: #b3261e;
  /* 悬停虚拟手机时，屏幕其余部分压暗到这一档 */
  --veil: rgba(14, 18, 26, .62);
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #16171a;
    --surface: #1e1f23;
    --surface-2: #26272c;
    --text: #ececee;
    --muted: #a0a1a6;
    --border: #33343a;
    --primary: #85b7eb;
    --primary-fg: #042c53;
    --primary-soft: #0c447c;
    --teal: #5dcaa5;
    --danger: #ff8a80;
    /* 暗色底子上再压暗，需要更重一档才看得出"聚焦" */
    --veil: rgba(0, 0, 0, .72);
  }
}

* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-padding-top: 66px; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: "Microsoft YaHei UI", "Microsoft YaHei", system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 16px;
  line-height: 1.7;
  overflow-x: hidden;
}

.wrap { width: 100%; max-width: var(--wrap); margin: 0 auto; padding: 0 20px; }
a { color: var(--primary); text-decoration: none; }
a:hover { text-decoration: underline; }
h1, h2, h3 { line-height: 1.3; margin: 0; }

/* ---------- 顶栏 ---------- */

.scroll-bar {
  position: fixed; top: 0; left: 0; right: 0; height: 2px; z-index: 40;
  background: var(--primary); transform: scaleX(0); transform-origin: 0 50%;
  will-change: transform;
}

.nav {
  position: sticky; top: 0; z-index: 10;
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: saturate(1.5) blur(10px);
  border-bottom: 1px solid transparent;
  transition: border-color .2s, background .2s;
}
.nav.stuck { border-bottom-color: var(--border); }
.nav-inner {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; min-height: 58px;
}
.brand { display: inline-flex; align-items: center; gap: 9px; color: var(--text); }
.brand:hover { text-decoration: none; }
.brand-mark { color: var(--primary); display: inline-flex; }
.brand-name { font-weight: 600; letter-spacing: .2px; }
.nav-links { display: flex; gap: 20px; font-size: 14px; }
.nav-links a { color: var(--muted); }
.nav-links a:hover { color: var(--text); text-decoration: none; }

/* ---------- 按钮 ---------- */

.btn {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 44px; padding: 0 22px; border-radius: 10px;
  font-size: 15px; font-weight: 500; border: 1px solid transparent;
  cursor: pointer; transition: opacity .15s, background .15s, transform .15s;
}
.btn:hover { text-decoration: none; opacity: .92; }
.btn:active { transform: translateY(1px); }
.btn-primary { background: var(--primary); color: var(--primary-fg); }
.btn-ghost { background: transparent; color: var(--text); border-color: var(--border); }
.btn-sm { min-height: 38px; padding: 0 16px; font-size: 14px; border-radius: 8px; }

/* ---------- Hero ---------- */

.hero { position: relative; padding: 64px 0 56px; overflow: hidden; }

/* 背景那两团光。只在支持动画的浏览器上跑，且动得很慢 —— 它是氛围，
   不该抢画面；降低动效偏好下直接停在原地。 */
.hero-glow { position: absolute; inset: -20% -10% auto -10%; height: 620px; pointer-events: none; z-index: 0; }
.hero-glow i {
  position: absolute; display: block; border-radius: 50%;
  filter: blur(58px); opacity: .5;
}
.hero-glow i:nth-child(1) {
  width: 420px; height: 420px; left: 4%; top: 30px;
  background: radial-gradient(circle at 35% 35%, color-mix(in srgb, var(--primary) 55%, transparent), transparent 70%);
  animation: drift-a 26s ease-in-out infinite;
}
.hero-glow i:nth-child(2) {
  width: 360px; height: 360px; right: 12%; top: 0;
  background: radial-gradient(circle at 60% 40%, color-mix(in srgb, var(--teal) 35%, transparent), transparent 70%);
  animation: drift-b 31s ease-in-out infinite;
}
@keyframes drift-a {
  0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
  50% { transform: translate3d(38px, 26px, 0) scale(1.08); }
}
@keyframes drift-b {
  0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
  50% { transform: translate3d(-44px, 18px, 0) scale(.94); }
}

.hero-grid {
  position: relative; z-index: 1;
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  gap: 40px; align-items: center;
}
.hero-copy { max-width: 560px; }
.hero h1 { font-size: 44px; letter-spacing: -.6px; }
.hero .tagline { margin: 10px 0 0; font-size: 19px; color: var(--primary); font-weight: 500; }
.hero .lede { max-width: 560px; margin: 18px 0 0; color: var(--muted); font-size: 16px; }
.hero .lede strong { color: var(--text); }
.hero-actions { margin-top: 28px; display: flex; gap: 12px; flex-wrap: wrap; }
.hero-note { margin: 14px 0 0; font-size: 13px; color: var(--muted); }

.hero-points { margin: 22px 0 0; padding: 0; list-style: none; font-size: 14px; color: var(--muted); }
.hero-points li { position: relative; padding-left: 18px; margin: 5px 0; }
.hero-points li::before {
  content: ""; position: absolute; left: 0; top: .68em;
  width: 6px; height: 6px; border-radius: 50%; background: var(--primary); opacity: .7;
}

/* ---------- 真机画布（iframe = Compose→WasmJS） ---------- */

.dev-wrap {
  display: flex; flex-direction: column; align-items: center; gap: 14px;
}

/* 尺寸全部由 --s 推出来。
   ⚠️ 不用父容器的宽度算 --s：hero 那一列是 `auto`，父宽又取决于设备宽 —— 会绕成环。
   这里 --s 只由 JS 按**视口**算（见 promo.js 的 fitDevice）。 */
.dev-frame {
  --s: .78;
  position: relative;
  flex: none;
  border-radius: 26px;
  padding: 8px;
  background: linear-gradient(160deg, color-mix(in srgb, var(--text) 14%, transparent), color-mix(in srgb, var(--text) 6%, transparent));
  box-shadow:
    0 1px 0 color-mix(in srgb, #fff 60%, transparent) inset,
    0 26px 60px -18px color-mix(in srgb, #000 34%, transparent),
    0 0 0 1px color-mix(in srgb, var(--border) 80%, transparent);
}
.dev-screen {
  position: relative;
  width: calc(393px * var(--s));
  height: calc(852px * var(--s));
  border-radius: 19px;
  overflow: hidden;
  background: var(--bg);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--border) 70%, transparent) inset;
}
.dev-frame[data-land="1"] .dev-screen {
  width: calc(852px * var(--s));
  height: calc(393px * var(--s));
}
/* iframe 按**真实像素**给（393×852），再用 transform 缩到 --s ——
   界面是按 dp 排的，少给一个像素，布局就不是手机上那个了。 */
.dev-screen iframe {
  display: block; border: 0;
  width: 393px; height: 852px;
  transform: scale(var(--s));
  transform-origin: top left;
  background: transparent;
}
.dev-frame[data-land="1"] .dev-screen iframe { width: 852px; height: 393px; }

/* 画布没起来之前的底色。不是"占位界面"，就是一块底色 + 一句状态。
   「页面」那一节的截图没出来时用的也是它，出来之后加 .off 收起来。 */
.dev-loading {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  font-size: 12px; color: var(--muted); letter-spacing: .5px;
}
.dev-loading.off { display: none; }
/* ★ 截图加载失败要看得见。这一块原来是画布，画布挂了也是白的；
   截图挂掉更容易被当成"还在加载"而忽略，所以给个显眼的红框 + 一句原因。 */
.dev-screen.shot-fail { box-shadow: inset 0 0 0 2px var(--danger); }
.dev-screen.shot-fail .dev-loading {
  color: var(--danger); font-weight: 600;
  padding: 24px; text-align: center; line-height: 1.7;
}
/* 「页面」那一节的截图。像素比例就是 393×852，直接铺满 .dev-screen，
   **不加 transform** —— transform 缩放是给 iframe 用的（iframe 要按真实 dp 像素拿尺寸）。 */
.dev-shot {
  display: block; width: 100%; height: 100%;
  object-fit: cover; background: var(--bg);
}
.dev-skip {
  position: absolute; inset: 0; margin: 0; display: flex; align-items: center;
  justify-content: center; text-align: left; padding: 28px;
  font-size: 13px; line-height: 1.8; color: var(--muted);
}

/* 设备下的一行说明（"这是源码直出"那种话）。 */
.dev-caption { margin: 0; font-size: 12.5px; color: var(--muted); text-align: center; }

/* 怎么玩这颗手机。放在设备下方，不压在界面上（压上去就挡住了要点的东西）。 */
.dev-hint {
  display: inline-flex; align-items: center; gap: 7px;
  margin: 0; padding: 7px 13px; border-radius: 999px;
  background: var(--primary-soft); color: var(--primary);
  font-size: 12.5px; text-align: center;
}
.dev-hint i { font-style: normal; animation: hint-bob 2.4s ease-in-out infinite; display: inline-block; }
@keyframes hint-bob {
  0%, 100% { transform: translateY(-2px); }
  50% { transform: translateY(3px); }
}

/* ---------- 聚焦态：鼠标停在虚拟手机上 ----------
 *
 * 鼠标进到某台虚拟手机上面时，屏幕其余部分压暗，只留这台手机是亮的；
 * 同时滚轮、翻页键全部拦在父页面上 —— 也就是"这时只操作手机，不动网页"。
 * 鼠标一离开（哪怕只是移到手机上方的网页区域）就自动恢复。
 *
 * 遮罩只有一份（`.dev-spot`），跟着当前聚焦的那台设备挪，几何由 JS 写进 CSS 变量。
 *
 * ⚠️ 两条不能改的：
 *   ① `pointer-events: none` —— 遮罩压在 iframe 上面，一旦收事件，
 *      鼠标就落不到画布上，"只操作手机"会变成"手机也操作不了"。
 *   ② 压暗靠 `box-shadow` 向外扩 9999px，不靠 clip-path ——
 *      扩边是一整块矩形，便宜；而洞（.dev-spot-hole）自己不画底色，
 *      所以手机是"透出来"的，不是"被盖住又抠开"。
 */
.dev-spot {
  position: fixed; inset: 0; z-index: 55;
  pointer-events: none;
  opacity: 0;
  transition: opacity .2s ease;
}
.dev-spot.on { opacity: 1; }
.dev-spot-hole,
.dev-spot-ring {
  position: absolute;
  left: var(--x); top: var(--y);
  width: var(--w); height: var(--h);
  border-radius: var(--r);
}
.dev-spot-hole { box-shadow: 0 0 0 9999px var(--veil); }
/* 光圈：一圈主色辉光，两个半径叠出"从手机边缘散出去"的渐变感 */
.dev-spot-ring {
  box-shadow:
    0 0 0 1px color-mix(in srgb, var(--primary) 50%, transparent),
    0 0 30px 2px color-mix(in srgb, var(--primary) 30%, transparent),
    0 0 96px 20px color-mix(in srgb, var(--primary) 14%, transparent);
  animation: spot-breathe 2.8s ease-in-out infinite;
}
@keyframes spot-breathe {
  0%, 100% { opacity: .78; }
  50% { opacity: 1; }
}
/* 聚焦的那台浮起来一点，别和旁边几台看着一样平 */
.dev-frame.focus { transform: translateY(-2px); }
.dev-frame { transition: transform .2s ease; }

/* ---------- 分区 ---------- */

.section { padding: 64px 0; }
.section-alt { background: var(--surface-2); }
.section h2 { font-size: 26px; }
.section-lede { margin: 8px 0 26px; color: var(--muted); max-width: 660px; }
.section-lede strong { color: var(--text); }
.fine { margin: 18px 0 0; font-size: 13px; color: var(--muted); }
.center { text-align: center; }

/* ---------- 「源码直出」三张证明卡 ---------- */

.proof-grid {
  display: grid; gap: 18px;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
}
.proof {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-lg); padding: 20px;
}
.proof .proof-k {
  display: inline-flex; align-items: center; gap: 8px;
  font-size: 12px; color: var(--primary); font-weight: 600;
  background: var(--primary-soft); border-radius: 999px; padding: 3px 10px;
}
.proof h3 { font-size: 16px; margin: 12px 0 8px; }
.proof p { margin: 0; font-size: 14px; color: var(--muted); }
.proof code {
  font-family: ui-monospace, Consolas, "Courier New", monospace;
  font-size: 12.5px; background: var(--surface-2); border-radius: 4px; padding: 1px 5px;
  color: var(--text);
}

/* ---------- 功能卡 ---------- */

.feat-grid { display: grid; gap: 18px; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }
.feat {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-lg); padding: 20px;
  transition: transform .2s, border-color .2s;
}
.feat:hover { transform: translateY(-3px); border-color: color-mix(in srgb, var(--primary) 45%, var(--border)); }
.feat h3 { font-size: 16px; margin-bottom: 8px; color: var(--primary); }
.feat p { margin: 0 0 8px; font-size: 14px; color: var(--muted); }
.feat p:last-child { margin-bottom: 0; }
.feat b { color: var(--text); font-weight: 600; }

/* ---------- 页面（点页签，上面的手机换成那一页的截图） ---------- */

/* ★ 2026-10-15 改版：手机上方一排横滑页签（页面 + 子视图两排），手机在下面居中。
   原来这里是「左两列页签 + 右一台 0.62 的手机」—— 手机小到看不清、也点不准，
   用户报"第三块屏幕没法正常用"，并且要"左边那两排整成一排、可以滑动切换"。
   现在手机尺寸与首屏那台**完全一致**（两边 data-cap 同为 0.78，--s 只由视口算，
   所以量出来一样大）。 */
.page-grid {
  display: flex; flex-direction: column; align-items: center; gap: 14px;
}
.page-picker {
  display: flex; flex-wrap: nowrap; gap: 10px;
  width: 100%;
  overflow-x: auto; overflow-y: hidden;
  scroll-snap-type: x proximity;
  scroll-padding-inline: 4px;
  padding: 2px 2px 12px;
  overscroll-behavior-x: contain;   /* 横滑到头别把整页带着走 */
  scrollbar-width: thin;
}
.page-pick {
  flex: 0 0 auto;
  min-width: 176px;
  white-space: nowrap;              /* 内容不换行：放不下就横向滑，别挤成一团 */
  scroll-snap-align: center;
}
.page-pick {
  display: flex; align-items: center; gap: 9px;
  text-align: left; cursor: pointer; font: inherit; color: inherit;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 8px 12px;
  transition: transform .18s, border-color .18s, background .18s;
}
.page-pick:hover { transform: translateY(-2px); border-color: color-mix(in srgb, var(--primary) 45%, var(--border)); }
.page-pick.on {
  border-color: var(--primary);
  background: color-mix(in srgb, var(--primary) 8%, var(--surface));
  box-shadow: inset 0 0 0 1px var(--primary);
}
.pp-icon { font-size: 18px; line-height: 1; flex: none; }
.pp-body { min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.pp-body strong { font-size: 14px; font-weight: 600; }
.pp-body span {
  font-size: 12px; color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* 子视图那一排（#viewPicker）：只有当前页不止一个视图时才出现。 */
.view-picker {
  display: flex; flex-wrap: nowrap; gap: 8px;
  width: 100%;
  overflow-x: auto; overflow-y: hidden;
  scroll-snap-type: x proximity;
  scroll-padding-inline: 4px;
  padding: 0 2px 8px;
  overscroll-behavior-x: contain;
  scrollbar-width: thin;
}
.view-picker[hidden] { display: none; }
.view-pick {
  flex: 0 0 auto;
  white-space: nowrap;
  cursor: pointer; font: inherit; font-size: 13px;
  color: var(--muted);
  background: transparent; border: 1px solid var(--border);
  border-radius: 999px; padding: 5px 14px;
  transition: color .15s, border-color .15s, background .15s;
}
.view-pick:hover { color: var(--text); border-color: color-mix(in srgb, var(--primary) 40%, var(--border)); }
.view-pick.on {
  color: var(--primary-fg); background: var(--primary); border-color: var(--primary);
}

/* ---------- 外观 ---------- */

.look-grid { display: grid; gap: 16px; grid-template-columns: repeat(auto-fit, minmax(296px, 1fr)); }
.look {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-lg); padding: 20px;
}
.look h3 { font-size: 16px; margin-bottom: 8px; }
.look p { margin: 0 0 8px; font-size: 14px; color: var(--muted); }
.look p:last-child { margin-bottom: 0; }
.look b { color: var(--text); font-weight: 600; }

code {
  font-family: ui-monospace, "Cascadia Mono", Consolas, monospace;
  font-size: .88em; background: var(--surface-2);
  border: 1px solid var(--border); border-radius: 5px; padding: 1px 5px;
}

/* ---------- 插件（卡片版：名称/版本/说明/页面全部来自清单） ---------- */

.plug-grid { display: grid; gap: 14px; grid-template-columns: repeat(auto-fill, minmax(286px, 1fr)); }
.plug-card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-lg); padding: 16px 18px;
  transition: transform .18s, border-color .18s;
}
.plug-card:hover { transform: translateY(-2px); border-color: color-mix(in srgb, var(--primary) 40%, var(--border)); }
.plug-head { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
.plug-i { font-size: 20px; line-height: 1; flex: none; }
.plug-t { min-width: 0; }
.plug-t b { display: block; font-size: 15px; font-weight: 600; }
.plug-t span { font-size: 12px; color: var(--muted); }
.plug-desc { margin: 0 0 10px; font-size: 13.5px; line-height: 1.65; color: var(--muted); }
.plug-tags { display: flex; flex-wrap: wrap; gap: 6px; }
.tag {
  font-size: 11.5px; color: var(--primary); background: var(--primary-soft);
  border-radius: 999px; padding: 2px 9px;
}
.plug-facts { margin: 10px 0 0; font-size: 12px; color: var(--muted); }

/* ---------- 折叠的技术说明 ---------- */

.note-fold {
  margin-top: 16px; padding: 12px 16px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}
.note-fold summary { cursor: pointer; font-size: 14px; color: var(--primary); }
.note-fold summary::marker { color: var(--muted); }
.note-fold p { margin: 10px 0 0; font-size: 13.5px; line-height: 1.75; color: var(--muted); }
.note-fold b { color: var(--text); }

/* ---------- 六端 ---------- */

.end-grid { display: grid; gap: 14px; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); }
.end {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-lg); padding: 16px 18px;
}
.end b { display: block; font-size: 15px; margin-bottom: 4px; }
.end span { font-size: 13px; color: var(--muted); }
.end .dot {
  display: inline-block; width: 7px; height: 7px; border-radius: 50%;
  background: var(--teal); margin-right: 7px; vertical-align: 1px;
}
.end.is-wip .dot { background: var(--muted); opacity: .6; }

/* ---------- 开源 ---------- */

.repo-card {
  display: flex; align-items: center; gap: 14px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-lg); padding: 18px 20px; color: var(--text);
}
.repo-card:hover { text-decoration: none; border-color: var(--primary); }
.repo-icon { color: var(--primary); display: inline-flex; flex: none; }
.repo-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.repo-text b { font-weight: 600; word-break: break-all; }
.repo-text span { font-size: 13px; color: var(--muted); }
.repo-arrow { margin-left: auto; color: var(--muted); font-size: 18px; flex: none; }

/* ---------- 页脚 ---------- */

.footer {
  border-top: 1px solid var(--border);
  padding: 28px 0 calc(28px + env(safe-area-inset-bottom));
}
.footer-inner {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 18px; flex-wrap: wrap;
}
.footer-name { margin: 0; font-weight: 600; }
.footer-dim { color: var(--muted); font-size: 13px; margin: 4px 0 0; }
.footer-links { display: flex; gap: 18px; font-size: 13px; flex-wrap: wrap; }
.footer-links a { color: var(--muted); }

.footer-legal {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin-top: 8px; color: var(--muted); font-size: 13px;
}
.footer-legal a { color: var(--muted); }
.footer-legal .beian-link { text-decoration: none; }
.footer-legal .beian-link:hover { text-decoration: underline; }
.footer-sep { color: var(--border); }

/* ---------- 滚动入场 ---------- */

.reveal {
  opacity: 0; transform: translateY(18px);
  transition: opacity .6s cubic-bezier(.22, .61, .36, 1), transform .6s cubic-bezier(.22, .61, .36, 1);
  will-change: opacity, transform;
}
.reveal.in { opacity: 1; transform: none; }

/* ---------- 窄屏（断点与官网一致：900px） ---------- */

@media (max-width: 900px) {
  .hero { padding: 40px 0 32px; }
  .hero h1 { font-size: 32px; }
  .hero .tagline { font-size: 17px; }
  .hero-grid { grid-template-columns: 1fr; gap: 28px; }
  .hero-copy { max-width: none; }
  .section { padding: 44px 0; }
  .section h2 { font-size: 22px; }
  /* 8 个分区链接在窄屏上放不下 —— 让它们能横滑，而不是挤成两行或溢出。 */
  .nav-links { gap: 14px; font-size: 13px; overflow-x: auto; -webkit-overflow-scrolling: touch; }
  .brand-name { font-size: 15px; }
  .btn { min-height: 46px; }
  .btn-sm { min-height: 42px; }
  .footer-links { gap: 14px; }
  .page-picker { padding-bottom: 10px; }
  .page-pick { min-width: 152px; }
  .feat:hover, .plug-card:hover, .page-pick:hover { transform: none; }
}

/* ==========================================================================
   动效层（2026-10-15 · 四组：滚动推进 / 指针交互 / 画面花活 / 真机联动）
   --------------------------------------------------------------------------
   全部**零依赖** —— 只用原生 CSS + JS，不引任何库。

   三条自我约束，加东西之前先看这三条：
     ① **只动 transform / opacity**，不动 width/height/top/left 那类会触发重排的属性
        （`.dev-spot` 那种由 JS 直接量出 px 的例外，它本来就不参与滚动）。
     ② 每一个动效都要在文件末尾那块 `prefers-reduced-motion` 里**能关掉**。
        关掉之后页面必须和没做这些动效时一样完整 —— 动效一律是加法。
     ③ 指针类效果在触屏和窄屏上不开（那两种情况没有"悬停"这回事）。

   ⚠️ 下面这几条**不能碰**（有闸门盯着，见 cache/promo-check.cjs）：
     · `.dev-frame { transition: transform ... }` —— 聚焦时上浮靠它；
     · `function revealChip` 与 `box.scrollTo(` —— 点后面的页签时靠它滑进视野。
   ========================================================================== */

/* ---------- ① 滚动推进 ---------- */

/* 分区标题下面那条主色短线，横着长出来。
   挂在 `.reveal` 的 `.in` 上 —— 标题浮到位的同时线展开，两件事是一件。 */
.section h2 { position: relative; }
.section h2::after {
  content: ""; display: block;
  width: 68px; height: 3px; border-radius: 2px;
  margin-top: 12px;
  background: linear-gradient(90deg, var(--primary), color-mix(in srgb, var(--teal) 65%, var(--primary)));
  transform: scaleX(0); transform-origin: 0 50%;
  transition: transform .72s cubic-bezier(.22, .61, .36, 1) .1s;
}
.section h2.in::after { transform: scaleX(1); }

/* 首屏视差：文案层往上慢移，背景光斑往下更慢地移。
   ⚠️ 这两层都**不是** `.reveal`（`.hero-copy` 本身不是，它的子元素才是），
      所以这里的 transform 不会和入场动画打架。 */
.hero-copy { transform: translate3d(0, var(--hero-copy-dy, 0px), 0); will-change: transform; }
.hero-glow {
  transform: translate3d(
    calc(var(--glow-scroll-x, 0px) + var(--glow-px, 0px)),
    calc(var(--glow-scroll-y, 0px) + var(--glow-py, 0px)),
    0);
  will-change: transform;
}

/* 顶栏的当前分区。 */
.nav-links a { position: relative; }
.nav-links a::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: -6px; height: 2px;
  border-radius: 2px; background: var(--primary);
  transform: scaleX(0); transform-origin: 50% 50%;
  transition: transform .28s cubic-bezier(.22, .61, .36, 1);
}
.nav-links a.on { color: var(--text); }
.nav-links a.on::after { transform: scaleX(1); }

/* 数字滚动：滚动的时候宽度会变，留着这点最小宽度免得标题跟着抖。 */
#pageList, #plugCount { display: inline-block; min-width: 1.4em; text-align: center; }

/* ---------- ② 指针交互 ---------- */

/* 卡片：跟随指针的 3D 倾斜 + 一块跟着走的高光。
   倾斜角与高光位置全部由 JS 写进 CSS 变量（见 promo.js 的 initTilt）。
   ⚠️ 选择器写成 `.feat.tilt` 这种两段式，是为了**压过**卡片自己那条
      `:hover { transform: translateY(-3px) }`（同特异度、谁在后面谁赢）。
      抬起的那几像素由 JS 一起写进 `--ty`，不然两边会互相覆盖。 */
.proof.tilt, .feat.tilt, .look.tilt, .plug-card.tilt, .end.tilt, .repo-card.tilt {
  position: relative;
  transform: perspective(820px)
             rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg))
             translate3d(0, var(--ty, 0px), 0);
  transition: transform .16s ease-out, border-color .2s;
  will-change: transform;
}
.tilt::after {
  content: ""; position: absolute; inset: 0; border-radius: inherit;
  pointer-events: none;
  background: radial-gradient(220px 180px at var(--mx, 50%) var(--my, 50%),
              color-mix(in srgb, var(--primary) 16%, transparent), transparent 72%);
  opacity: 0; transition: opacity .24s ease;
}
.tilt:hover::after { opacity: 1; }

/* 按钮磁吸：靠近时被轻轻吸过去，移开回弹。位移同样只有几个像素。 */
.btn {
  transform: translate3d(var(--bx, 0px), var(--by, 0px), 0);
  position: relative; overflow: hidden;
}
.btn:active { transform: translate3d(var(--bx, 0px), calc(var(--by, 0px) + 1px), 0); }
/* 点下去的涟漪：JS 在点击处插一个 span，动画跑完自己删掉。 */
.ripple {
  position: absolute; border-radius: 50%; pointer-events: none;
  background: currentColor; opacity: .28;
  transform: translate(-50%, -50%) scale(0);
  animation: ripple-out .58s ease-out forwards;
}
@keyframes ripple-out {
  to { transform: translate(-50%, -50%) scale(1); opacity: 0; }
}

/* ---------- ③ 画面花活 ---------- */

/* 首屏那层光点。canvas 由 JS 画，`pointer-events: none` 是硬要求 ——
   它铺在 hero 上，一旦收指针事件，"鼠标停在手机上"那套判据就被挡了。 */
.hero-dust {
  position: absolute; inset: 0; width: 100%; height: 100%;
  pointer-events: none; z-index: 0;
}

/* 标题逐字入场。JS 把 h1 拆成一个个 `.ch`，每个带自己的 `--d` 延迟。
   没拆之前（JS 没跑、或者用户禁用了脚本）就是一整行文字，什么都不缺。 */
.hero h1 .ch {
  display: inline-block;
  opacity: 0; transform: translateY(.34em) rotate(2.5deg);
  transition: opacity .46s ease, transform .62s cubic-bezier(.22, .61, .36, 1);
  transition-delay: var(--d, 0ms);
}
.hero h1.chars-in .ch { opacity: 1; transform: none; }
/* 空格在 inline-block 里会被吃掉，单独撑一个宽度。 */
.hero h1 .ch.is-space { width: .32em; }

/* ---------- ④ 真机联动 ---------- */

/* 手机进场：比普通 `.reveal` 抬得更高、缩得更小，落位更有分量。
   ⚠️ 这两条必须成对，且特异度要压过 `.reveal` / `.reveal.in` —— 靠的是"在后面"。 */
.reveal-rise { transform: translateY(46px) scale(.94); transition-duration: .82s; }
.reveal-rise.in { transform: none; }

/* 换页时截图那一下。缩放**从大往小落**（1.03 → 1），
   这样任何时候都不会露出边上的底色；反过来缩就会露一条缝。 */
.dev-shot.swap { animation: shot-swap .42s cubic-bezier(.22, .61, .36, 1); }
@keyframes shot-swap {
  from { opacity: .2; transform: scale(1.03); }
  to { opacity: 1; transform: scale(1); }
}

/* 页签槽里那块跟着指针走的高光。
   ⚠️ 两个槽都要 `position: relative`：一是给这块高光定位，
      二是**顺手修掉** `revealChip` 的一个老毛病 —— 它用 `chip.offsetLeft` 算要滚多少，
      而槽没定位时 `offsetParent` 是 body，算出来的是"从页面左边量"的距离，
      比该滚的多出一个槽自己的左边距，越靠后的页签偏得越多。 */
.page-picker, .view-picker { position: relative; }
.chip-hl {
  position: absolute; left: 0; top: 0; z-index: 0;
  width: var(--iw, 0px); height: var(--ih, 0px);
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--primary) 9%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--primary) 32%, transparent);
  pointer-events: none;
  transform: translate3d(var(--ix, 0px), var(--iy, 0px), 0);
  transition: transform .28s cubic-bezier(.22, .61, .36, 1),
              width .28s cubic-bezier(.22, .61, .36, 1),
              height .28s cubic-bezier(.22, .61, .36, 1),
              opacity .18s ease;
  opacity: 0;
}
.chip-hl.on { opacity: 1; }
.page-pick, .view-pick { position: relative; z-index: 1; }
.view-pick.on { border-radius: 999px; }

/* ---------- 窄屏：指针类效果全关，进场距离缩短 ---------- */

@media (max-width: 900px) {
  .proof.tilt, .feat.tilt, .look.tilt, .plug-card.tilt, .end.tilt, .repo-card.tilt { transform: none; }
  .tilt::after { display: none; }
  .btn { transform: none; }
  .reveal-rise { transform: translateY(26px); }
  .hero-copy { transform: none; }
  .chip-hl { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .hero-glow i { animation: none; }
  .dev-hint i { animation: none; }
  .dev-spot-ring { animation: none; }
  .dev-frame { transition: none; }
  .dev-frame.focus { transform: none; }
  .reveal { opacity: 1; transform: none; transition: none; }
  /* ---- 动效层 ---- */
  .section h2::after { transform: scaleX(1); transition: none; }
  .nav-links a::after { transition: none; }
  .hero-copy, .hero-glow, .hero-dust { transform: none; }
  .hero-dust { display: none; }
  .hero h1 .ch { opacity: 1; transform: none; transition: none; }
  .proof.tilt, .feat.tilt, .look.tilt, .plug-card.tilt, .end.tilt, .repo-card.tilt { transform: none; transition: none; }
  .tilt::after { display: none; }
  .btn { transform: none; }
  .ripple { display: none; }
  .dev-shot.swap { animation: none; }
  .chip-hl { display: none; }
  .reveal-rise { opacity: 1; transform: none; transition: none; }
}
