/* =====================================================================
 * WordVerse v2u 首页动效 · motion.css
 * 2026-10-29 17:03 墨川抽离移交（wv-progress §9.9「动效已抽离，可移交」）→ 流光｜动效接手重写，2026-10-29 CST。
 * 最终依据：pm/V2U-VISUAL.md 第 4 版 §5 / §8.2 M01–M19（17:15）+ 项目总监裁定（17:14）+ 衡舟细化（17:14）
 *           + ops-audit/aesthetic-review-v2t.md §8.D + dev/WV-AESTHETIC-CHECKLIST.md 第 6 条 + V2U-PLAN 动效 8 条。
 * 规则：一条曲线 cubic-bezier(.2,.8,.2,1)；时长只有 150 / 240 / 480（480 只给连线描绘，18:48 由 600 压缩）；入场总长 ≤700ms；
 *       只动 transform / opacity。唯一例外：连线描绘 stroke-dashoffset（不加 will-change）；
 *       生成后生词逐个点亮用 background-color（§8.D 生词底色例外，绿色只出现在生词上）。
 * 循环只有两个：① 连线光点 .wvm-dot（--wv-dot 中性灰，1.5px 半径，≤3 个，6.4s，峰值 .35，离屏 / 页面隐藏暂停）
 *               ② 输入框光标 .wv-input-caret（仅输入框聚焦且为空时闪，steps(1) 1000ms；输入或失焦即停并隐藏）。
 * 开关（写在 <html> 上，值为 "off" 即关；名称按 V2U-VISUAL §8.2，兼容墨川早先的别名）：
 *   data-motion-enter（= data-motion-intro）  入场 M02 / M03 / M04
 *   data-motion-hover                          悬浮 M05 / M13 / M14 与主按钮 hover 叠层
 *   data-motion-loop （= data-motion-ambient） 循环 M06 光点 / M07 光标（off 时光点隐藏、光标常亮不闪）
 * prefers-reduced-motion: reduce → 全部直达终态（M19，文件末尾）。
 * 加载顺序：style.css、v2-theme.css、v2-smart-input.css 之后。颜色只引用 --wv-* / --v2-* 变量，本文件不定色值。
 * ===================================================================== */

/* —— 0. 变量（墨川抽离的 --v2-* 时长变量按新档位改值，保持兼容） —— */
:root {
  --wvm-ease: var(--wv-ease, cubic-bezier(.2, .8, .2, 1));
  --wvm-fast: var(--wv-dur-fast, 150ms);
  --wvm-base: var(--wv-dur-base, 240ms);
  --wvm-draw: var(--wv-dur-draw, 540ms);   /* s15b-g5：600→540，首屏 t1 晚起 ≤~60ms 时入场总长仍 ≤650 */
  --wvm-step: 60ms;
  --wvm-rise: 8px;
  --wvm-dot-period: 6.4s;
  --wvm-dot-peak: var(--wv-dot-opacity, .35);
  --wvm-caret-period: 1000ms;
  --wvm-caret-blinks: infinite;       /* 衡舟：「闪 3 次即停」保留为配置项，默认关闭；<html data-motion-caret="3"> 打开 */
  /* M01：旧 token 统一到三档 + 一条曲线（style.css 旧有过渡都引用这几个变量） */
  --ease: var(--wvm-ease);
  --dur-fast: 150ms; --dur-base: 240ms; --dur-slow: 240ms;
  --v2-ease: var(--wvm-ease);
  --v2-dur-xs: 150ms; --v2-dur-s: 240ms; --v2-dur-m: 240ms; --v2-dur-l: 600ms;
  --v2-stagger: 60ms; --v2-rise: 8px; --v2-hover-lift: -2px; --v2-btn-press-scale: .98;
  --v2-caret-period: 1000ms; --v2-flow-period: 6.4s;
}
html[data-motion-caret="3"] { --wvm-caret-blinks: 3; }

/* —— M01 旧动效清理（在本文件内覆盖禁用；源头删除见 HANDOFF §4） —— */
.v2-pv-threads path.v2u-flow, .v2-pv-threads .v2-pv-flow { display: none; animation: none; }   /* 旧 dash 光点 → M06 .wvm-dot */
.wv-dots span { animation: none; opacity: .6; }                                                   /* wv-dot busy 圆点：静态省略号 */
#casting-bar .wv-progress-bar::after, .modal:not(.visible) .wv-progress-bar::after,
body:not([data-page="home"]) #casting-bar .wv-progress-bar::after { animation: none; opacity: 0; }  /* wv-indeterminate：空闲也在跑 */
#export-modal .wv-progress-bar::after { animation: none; opacity: 0; }                                   /* 导出弹窗不做进度条（衡舟 17:36：无真实进度）；轨道本身由墨川隐藏，见 HANDOFF §13 */
#page-home #generate-btn.loading::after { animation: none; content: none; }                        /* v2u-progress 9s 进度条 */
.more-menu, #more-menu { animation-duration: var(--wvm-fast); }                                     /* wv-pop-in → 150 */
.v2-pv-caret { animation: none; opacity: 1; }                                                       /* 故事末尾光标：常亮不闪（V47） */

/* ===================== M02 首屏入场（enter）：H1 → 副标题 → 输入卡 → 随机来一组；各 240，延迟 0/60/120/180，总 420 ===================== */
@keyframes wvm-rise { from { opacity: 0; translate: 0 var(--wvm-rise); } }   /* 独立 translate 属性，不与 hover / 按压的 transform 冲突 */
@keyframes wvm-draw { from { stroke-dashoffset: 100; } to { stroke-dashoffset: 0; } }
html:not(.wvm-entered):not([data-motion-enter="off"]):not([data-motion-intro="off"]) #page-home .home-title        { animation: wvm-rise var(--wvm-base) var(--wvm-ease) 0ms both; }
html:not(.wvm-entered):not([data-motion-enter="off"]):not([data-motion-intro="off"]) #page-home .home-sub          { animation: wvm-rise var(--wvm-base) var(--wvm-ease) 60ms both; }
html:not(.wvm-entered):not([data-motion-enter="off"]):not([data-motion-intro="off"]) #page-home .v2-input-card     { animation: wvm-rise var(--wvm-base) var(--wvm-ease) 120ms both; }
html:not(.wvm-entered):not([data-motion-enter="off"]):not([data-motion-intro="off"]) #page-home .v2u-input-foot    { animation: wvm-rise var(--wvm-base) var(--wvm-ease) 180ms both; }

/* ===================== M03 故事区入场（enter）：首次进入视口（IO .2，只一次；motion.js 给 .v2-preview 加 .wvm-play）=====================
 * 18:48 压缩（守望：入场总长 ≤700，从首个入场开始算）：标签 240 延迟 0/40/80；卡片 240 延迟 60；连线 480 延迟 60，与卡片并行（stroke-dashoffset，唯一例外，不加 will-change）；本区总 540。
 * 390 紧凑「生词汇入线」同样适用：[data-motion-rise]（style="--motion-i:0..3"）与 [data-motion-draw]（pathLength="100"），见 HANDOFF §2。 */
html.wvm-js:not([data-motion-enter="off"]):not([data-motion-intro="off"]) .v2-preview:not(.wvm-play) .v2-pv-word, html.wvm-js:not([data-motion-enter="off"]):not([data-motion-intro="off"]) .v2-preview:not(.wvm-play) .v2-pv-card,
html.wvm-js:not([data-motion-enter="off"]):not([data-motion-intro="off"]) .v2-preview:not(.wvm-play) [data-motion-rise] { opacity: 0; }
html.wvm-js:not([data-motion-enter="off"]):not([data-motion-intro="off"]) .v2-preview:not(.wvm-play) .v2-pv-threads path.v2-thr,
html.wvm-js:not([data-motion-enter="off"]):not([data-motion-intro="off"]) .v2-preview:not(.wvm-play) [data-motion-draw] { stroke-dasharray: 100 100; stroke-dashoffset: 100; }
html:not([data-motion-enter="off"]):not([data-motion-intro="off"]) .v2-preview.wvm-play .v2-pv-word    { animation: wvm-rise var(--wvm-base) var(--wvm-ease) 0ms both; }
html:not([data-motion-enter="off"]):not([data-motion-intro="off"]) .v2-preview.wvm-play .v2-pv-word.w3 { animation-delay: 40ms; }
html:not([data-motion-enter="off"]):not([data-motion-intro="off"]) .v2-preview.wvm-play .v2-pv-word.w4 { animation-delay: 80ms; }
html:not([data-motion-enter="off"]):not([data-motion-intro="off"]) .v2-preview.wvm-play .v2-pv-card    { animation: wvm-rise var(--wvm-base) var(--wvm-ease) 60ms both; }
html:not([data-motion-enter="off"]):not([data-motion-intro="off"]) .v2-preview.wvm-play .v2-pv-threads path.v2-thr, html:not([data-motion-enter="off"]):not([data-motion-intro="off"]) .v2-preview.wvm-play [data-motion-draw] { stroke-dasharray: 100 100; animation: wvm-draw var(--wvm-draw) var(--wvm-ease) 0ms both;   /* s15b-g5：540ms（s13 为 600ms），与文字入场第一段（.v2-pv-word 0ms）同起 */ }
html:not([data-motion-enter="off"]):not([data-motion-intro="off"]) .v2-preview.wvm-play [data-motion-rise] { animation: wvm-rise var(--wvm-base) var(--wvm-ease) calc(min(var(--motion-i, 0), 3) * 40ms) both; }

/* ===================== M04 生词点亮（enter，原创落点）：M03 中 200ms 起，三词错开 40（200/240/280），各 240，结束 520 =====================
 * 下划线层 mark::before scaleX 0→1（origin left）；当前词的浅底层 mark::after opacity 0→1。之后每次联动走 M05。 */
@keyframes wvm-underline { from { transform: scaleX(0); } to { transform: scaleX(1); } }
@keyframes wvm-tint { from { opacity: 0; } }
.v2-pv-en mark::before { transform-origin: 0 50%; }
html.wvm-js:not([data-motion-enter="off"]):not([data-motion-intro="off"]) .v2-preview:not(.wvm-play) .v2-pv-en mark::before { transform: scaleX(0); }
html:not([data-motion-enter="off"]):not([data-motion-intro="off"]) .v2-preview.wvm-play .v2-pv-en mark::before { animation: wvm-underline var(--wvm-base) var(--wvm-ease) calc(200ms + var(--wvm-k, 0) * 40ms) both; }
html:not([data-motion-enter="off"]):not([data-motion-intro="off"]) .v2-preview.wvm-play .v2-pv-en mark.is-on::after { animation: wvm-tint var(--wvm-base) var(--wvm-ease) calc(200ms + var(--wvm-k, 0) * 40ms) both; }
.v2-pv-en mark[data-w="lantern"] { --wvm-k: 0; } .v2-pv-en mark[data-w="harbor"] { --wvm-k: 1; } .v2-pv-en mark[data-w="whisper"] { --wvm-k: 2; }
/* 播放一次后不再重播（motion.js 在 620ms 后加 .wvm-done） */
.v2-preview.wvm-done *, .v2-preview.wvm-done *::before, .v2-preview.wvm-done *::after,
html.wvm-entered #page-home .home-title, html.wvm-entered #page-home .home-sub, html.wvm-entered #page-home .v2-input-card, html.wvm-entered #page-home .v2u-input-foot { animation-name: none; }
.v2-preview.wvm-done .wvm-dot { animation-name: var(--wvm-kf, none); }
html[data-motion-enter="off"] #page-home .home-title, html[data-motion-enter="off"] #page-home .home-sub, html[data-motion-enter="off"] #page-home .v2-input-card, html[data-motion-enter="off"] #page-home .v2u-input-foot, html[data-motion-enter="off"] .v2-preview .v2-pv-word, html[data-motion-enter="off"] .v2-preview .v2-pv-card, html[data-motion-enter="off"] .v2-preview .v2-pv-threads path.v2-thr, html[data-motion-enter="off"] .v2-preview .v2-pv-en mark::before, html[data-motion-enter="off"] .v2-preview .v2-pv-en mark::after, html[data-motion-enter="off"] #page-home [data-motion-rise], html[data-motion-enter="off"] #page-home [data-motion-draw], html[data-motion-intro="off"] #page-home .home-title, html[data-motion-intro="off"] #page-home .home-sub, html[data-motion-intro="off"] #page-home .v2-input-card, html[data-motion-intro="off"] #page-home .v2u-input-foot, html[data-motion-intro="off"] .v2-preview .v2-pv-word, html[data-motion-intro="off"] .v2-preview .v2-pv-card, html[data-motion-intro="off"] .v2-preview .v2-pv-threads path.v2-thr, html[data-motion-intro="off"] .v2-preview .v2-pv-en mark::before, html[data-motion-intro="off"] .v2-preview .v2-pv-en mark::after, html[data-motion-intro="off"] #page-home [data-motion-rise], html[data-motion-intro="off"] #page-home [data-motion-draw] { animation: none; opacity: 1; stroke-dashoffset: 0; }
html[data-motion-enter="off"] .v2-pv-en mark::before, html[data-motion-intro="off"] .v2-pv-en mark::before { transform: none; }
html[data-motion-enter="off"] .v2-pv-en mark:not(.is-on)::after, html[data-motion-intro="off"] .v2-pv-en mark:not(.is-on)::after { opacity: 0; }

/* ===================== M05 标签 ↔ 生词 ↔ 连线联动（hover，150）：浅底层 / 亮线层 opacity；释义条内容交叉淡入 =====================
 * 绿色只在生词与生词标签上（--wv-green-tint 浅底）；连线亮线层是 --wv-text-3 中性灰（motion.js 复制 .v2-thr 为 .wvm-thr-hi）。 */
.v2-pv-en mark::after { transform: none; opacity: 0; background: var(--wv-green-tint, var(--v2-word-tint)); }
.v2-pv-en mark.is-on::after, .v2-pv-en mark:focus-visible::after { opacity: 1; }
@media (hover: hover) { .v2-pv-en mark:hover::after { opacity: 1; } }
.v2-pv-threads path.wvm-thr-hi { stroke: var(--wv-text-3, var(--v2-text-3)); fill: none; opacity: 0; pointer-events: none; }
.v2-pv-threads path.wvm-thr-hi.is-on { opacity: 1; }
.v2-pv-threads path.v2-thr.is-on { stroke: var(--v2-thread, var(--wv-line)); }   /* 原线不换色，提亮只走亮线层 opacity */
.v2-preview:not(.wvm-done) .v2-pv-threads path.wvm-thr-hi { opacity: 0; }
html:not([data-motion-hover="off"]) .v2-pv-en mark::after, html:not([data-motion-hover="off"]) .v2-pv-threads path.wvm-thr-hi, html:not([data-motion-hover="off"]) .v2-pv-word::after, html:not([data-motion-hover="off"]) .v2-pv-threads path { transition: opacity var(--wvm-fast) var(--wvm-ease); }
html[data-motion-hover="off"] .v2-pv-en mark::after, html[data-motion-hover="off"] .v2-pv-threads path, html[data-motion-hover="off"] .v2-pv-word::after { transition: none; }
.v2-pv-gloss-main { transition: opacity var(--wvm-fast) var(--wvm-ease); }
.v2-pv-gloss.v2u-swap .v2-pv-gloss-main, .v2-pv-gloss.wvm-swap .v2-pv-gloss-main { opacity: 0; }

/* ===================== M06 连线光点（loop ①）：只 1 个 .wvm-dot、1 个无限动画，依次沿 3 根线走（每段 6.4s 窗口，0–90% 走完、12–78% 峰值 .35），沿路径 transform: translate 关键帧（motion.js 按弧长均匀取 25 点/段生成 @keyframes wvm-dot-seq）
 * 只动 transform / opacity；不用 offset-path / offset-distance（衡舟 17:52 不追认）。 ===================== */
.wvm-dots { position: absolute; left: 0; top: 0; width: 0; height: 0; pointer-events: none; overflow: visible; }   /* 0×0 锚点：容器被上方内容推动时不把整块面积算进 CLS */
.wvm-dot {
  position: absolute; left: -1.5px; top: -1.5px; width: 3px; height: 3px; margin: 0; border-radius: var(--wv-r-pill, 999px);
  background: var(--wv-dot, var(--wv-text-3, var(--v2-text-3))); opacity: 0; transform: translate(0, 0);
}
html:not([data-motion-loop="off"]):not([data-motion-ambient="off"]) .v2-preview.wvm-play .wvm-dot { animation: var(--wvm-kf, none) calc(var(--wvm-dot-period) * var(--wvm-legs, 3)) linear 560ms infinite; }   /* 单动画：周期 = 6.4s × 段数（3 → 19.2s） */
html[data-motion-loop="off"] .wvm-dots, html[data-motion-ambient="off"] .wvm-dots { display: none; }
html .v2-preview.wvm-play.wvm-idle-off .wvm-dot, html.wvm-hidden .v2-preview.wvm-play .wvm-dot { animation-play-state: paused !important; }   /* s13 修：原 .wvm-idle-off .wvm-dot 特异性低于 L111 简写（简写把 play-state 重置为 running），隐藏/离屏实测未暂停 */

/* ===================== M07 输入框光标（loop ②）：.wv-input-caret 仅在聚焦且为空时闪（steps(1)，1000ms）；输入或失焦即停并隐藏 ===================== */
#page-home .input-wrapper { position: relative; }
.wv-input-caret { opacity: 0; visibility: hidden; }   /* 几何与显隐 display 在 v2-theme.css（墨川）；这里只管闪烁与可见性 */
/* 三态（V2U-VISUAL 第 5 版 M07）：空且聚焦 = 自绘光标显示并闪、原生 caret 透明；输入中 = 自绘立即隐藏、原生 caret 接管；失焦 = 都不显示。
 * 同一个 :focus:placeholder-shown 条件同帧切换，无过渡。DOM 要求：.wv-input-caret 是 #word-input 之后的同级元素（~ 选择器）；
 * :has() 写法兜底（caret 不在同级时也成立）。 */
#page-home #word-input { caret-color: var(--wv-text-1, var(--v2-text-1)); }
#page-home #word-input:focus:placeholder-shown { caret-color: transparent; }
/* s16c-bgate 光标修复（流光 2026-10-30，衡舟条件 2）：B 首页把自绘 .wv-input-caret 设为 display:none !important（editorial-b.css:194），
 * 上一行与 v2-theme.css:202 却仍把原生 caret 设成透明 → B 首页空输入框聚焦时没有任何光标。B 下恢复原生 caret，颜色 var(--wv-text-1)（亮 #111413 / 暗 #EDEEED，v2-theme.css:22/187；衡舟 05:39 裁定：站内无 --t1，不新增 token）。
 * B 判定：主判据 html[data-wv-layout="b"]；兜底（仅 <html> 无 data-wv-layout 属性时）= editorial-b.css 已链接（:has，与 motion.js isB() 同一规则）。
 * 特异性 (2,7,4) 高于 :122 的 (2,2,0) 和 v2-theme.css:202 的 (1,3,0)，两者都不带 !important → 不需要 !important。v2-theme.css:202 的清理归墨川（非必需）。 */
html:is([data-wv-layout="b"], :not([data-wv-layout]):has(> head > link[rel~="stylesheet"][href*="editorial-b.css"]:not([disabled]))) body[data-page="home"] #page-home #word-input:focus:placeholder-shown { caret-color: var(--wv-text-1); }
#page-home #word-input:focus:placeholder-shown ~ .wv-input-caret,
#page-home .input-wrapper:has(#word-input:focus:placeholder-shown) .wv-input-caret { visibility: visible; opacity: 1; }
@keyframes wvm-caret { 0% { opacity: 1; } 50% { opacity: 0; } }
html:not([data-motion-loop="off"]):not([data-motion-ambient="off"]) #page-home #word-input:focus:placeholder-shown ~ .wv-input-caret,
html:not([data-motion-loop="off"]):not([data-motion-ambient="off"]) #page-home .input-wrapper:has(#word-input:focus:placeholder-shown) .wv-input-caret { animation: wvm-caret var(--wvm-caret-period) steps(1, end) var(--wvm-caret-blinks) both; }
html[data-motion-caret="3"] #page-home #word-input:focus:placeholder-shown ~ .wv-input-caret, html[data-motion-caret="3"] #page-home .input-wrapper:has(#word-input:focus:placeholder-shown) .wv-input-caret { animation-fill-mode: none; }   /* 3 次后停在常亮 */

/* ===================== M08 输入卡聚焦环：::after opacity，150 ===================== */
#page-home .input-wrapper::after { transition: opacity var(--wvm-fast) var(--wvm-ease); }
#page-home .input-clear { transition: opacity var(--wvm-fast) var(--wvm-ease); }

/* ===================== M09 主按钮：hover 叠层 150 / 按下 .98 150 / busy 文字交叉淡入 150 且宽度锁定 ===================== */
#page-home .btn-primary { transition: transform var(--wvm-fast) var(--wvm-ease); }
#page-home .btn-primary::before { transition: opacity var(--wvm-fast) var(--wvm-ease); }
@media (hover: hover) { html:not([data-motion-hover="off"]) #page-home .btn-primary:hover::before { opacity: 1; } }
html[data-motion-hover="off"] #page-home .btn-primary:hover::before { opacity: 0; }
#page-home .btn-primary:active, #page-home .btn-primary.wvm-press { transform: scale(.98); }
/* 宽度锁定：标签与「生成中…」叠在同一格（grid 1/1），只切 opacity，按钮宽 = 两者较宽者，不跳 */
#page-home #generate-btn { display: inline-grid; grid-template-areas: "s"; place-items: center; }
#page-home #generate-btn > .btn-label, #page-home #generate-btn > .btn-busy { grid-area: s; display: inline-flex; align-items: center; transition: opacity var(--wvm-fast) var(--wvm-ease); }
#page-home #generate-btn > .btn-busy { opacity: 0; }
#page-home #generate-btn.loading > .btn-label { opacity: 0; }
#page-home #generate-btn.loading > .btn-busy { opacity: 1; }
#page-home #generate-btn .wv-dots { display: inline-flex; }
#generate-btn-label { transition: opacity var(--wvm-fast) var(--wvm-ease); }   /* 接管 v2-smart-input.css 的 .v2u-si-out 150 淡出淡入 */
/* 导出弹窗（衡舟 17:36）：按钮文字态「正在导出…」，与生成按钮同一套：标签 / 进行中两层叠放，.loading 时 150 交叉淡化，宽度不跳。
 * 结构与 .loading 状态由墨川提供（HANDOFF §13）；标题 / 说明文字被 app.js 改写时，motion.js 给它们做 150 淡入（.wvm-tx-a / .wvm-tx-b 交替以重启动画）。 */
#export-modal-btn > .btn-label, #export-modal-btn > .btn-busy { grid-area: s; display: inline-flex; align-items: center; justify-content: center; transition: opacity var(--wvm-fast) var(--wvm-ease); }
#export-modal-btn > .btn-busy { opacity: 0; }
#export-modal-btn.loading > .btn-label { opacity: 0; }
#export-modal-btn.loading > .btn-busy { opacity: 1; }
@keyframes wvm-fade2 { from { opacity: 0; } }
#export-modal .wvm-tx-a { animation: wvm-fade var(--wvm-fast) var(--wvm-ease) both; }
#export-modal .wvm-tx-b { animation: wvm-fade2 var(--wvm-fast) var(--wvm-ease) both; }
/* 生成中骨架行（motion.js 插入 aria-hidden 的 .wvm-skel，静态，只淡入一次） */
@keyframes wvm-fade { from { opacity: 0; } }
.wvm-skel { display: grid; gap: 10px; padding: 4px 0 0; animation: wvm-fade var(--wvm-base) var(--wvm-ease) both; }
.wvm-skel i { display: block; height: 10px; border-radius: var(--wv-r-pill, 999px); background: var(--wv-surface, var(--v2-page-bg)); }
.wvm-skel i:nth-child(1) { width: 92%; } .wvm-skel i:nth-child(2) { width: 78%; } .wvm-skel i:nth-child(3) { width: 54%; }
/* s16c-bgate §5.9-6：B 首页不显示骨架行（motion.js 在 B 下也不插入；这里兜底）。B 判定同上（属性为主，:has(link) 兜底） */
html:is([data-wv-layout="b"], :not([data-wv-layout]):has(> head > link[rel~="stylesheet"][href*="editorial-b.css"]:not([disabled]))) body[data-page="home"] .wvm-skel { display: none !important; }

/* ===================== 生成后（V2U-PLAN 第 4 条 / §8.4 M3）：正文逐段淡入 240，间隔 60（最多 6 段）→ 首次出现的生词依次点亮（240，间隔 150）=====================
 * 生词底色 透明 → --wv-green-tint：全站唯一带绿色的「过程」动效（§8.D 生词底色例外）。 */
/* B（衡舟 19:05）：首页→故事页正文 240ms 淡入 + 上移 6px，起始 opacity .4（任何一帧正文都在），只用 opacity / translate，不错峰（总长 240ms ≤300） */
@keyframes wvm-story-in { from { opacity: .4; translate: 0 6px; } }
html:not([data-motion-enter="off"]):not([data-motion-intro="off"]) #page-story .story-paragraph.wvm-in { animation: wvm-story-in var(--wvm-base) var(--wvm-ease) both; }
/* 点亮只动 opacity：底色画在 ::before（isolation 内 z-index:-1 → 在文字下、元素自身背景上），不做 background-color 过渡 */
#page-story .vocab-highlight.wvm-lit-wait, #page-story .vocab-highlight.wvm-lit { position: relative; isolation: isolate; }
#page-story .vocab-highlight.wvm-lit-wait::before, #page-story .vocab-highlight.wvm-lit::before { content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none; background: var(--wv-green-tint, var(--v2-mark-tint)); opacity: 0; }
html:not([data-motion-enter="off"]):not([data-motion-intro="off"]) #page-story .vocab-highlight.wvm-lit::before { opacity: 1; transition: opacity var(--wvm-base) var(--wvm-ease) var(--wvm-d, 0ms); }
/* s16c-bgate §5.9-5：B 故事页不铺首现浅底（motion.js 在 B 下不加 .wvm-lit 并清残留；这里兜底），生词只在 hover / .active / .on 时 --tint（editorial-b.css:413） */
html:is([data-wv-layout="b"], :not([data-wv-layout]):has(> head > link[rel~="stylesheet"][href*="editorial-b.css"]:not([disabled]))) body[data-page="story"] #page-story .vocab-highlight:is(.wvm-lit, .wvm-lit-wait)::before { content: none !important; opacity: 0 !important; }

/* ===================== M10 「随机来一组」：输入框文字 opacity 1→0（换词）→1，150 + 150（motion.js 加 .wvm-txt-out / .wvm-txt-in） ===================== */
#page-home #word-input { transition: opacity var(--wvm-fast) var(--wvm-ease); }
#page-home #word-input.wvm-txt-out { opacity: 0; }

/* ===================== M11 空提交：输入卡 translateX 0→−4→4→−2→0（240）；错误文字 opacity 0→1（150）=====================
 * 触发：v2-smart-input.js 给 #v2u-input-hint 加 .is-error 时（motion.js 观察），不弹 toast、不换描边色。
 * 颜色不归本文件：衡舟 17:36 裁定报错文字为中性灰 var(--wv-text-2)、不用红色（墨川落地，HANDOFF §12.2）；本文件不引用任何报错色。 */
@keyframes wvm-shake { 0%, 100% { transform: none; } 25% { transform: translateX(-4px); } 50% { transform: translateX(4px); } 75% { transform: translateX(-2px); } }
html #page-home .v2-input-card.wvm-shake { animation: wvm-shake var(--wvm-base) var(--wvm-ease) both; }
#page-home .v2u-input-hint.is-error { animation: wvm-fade var(--wvm-fast) var(--wvm-ease) both; }

/* ===================== M12 难度浮层 / 更多菜单：opacity 0→1 + translateY 4px→0（关反向），150 ===================== */
@keyframes wvm-pop { from { opacity: 0; transform: translateY(4px); } }
.v2u-diff-pop, .more-menu.show, #more-menu.show, #more-menu:not([hidden]) { animation: wvm-pop var(--wvm-fast) var(--wvm-ease) both; }
.v2u-diff-pop.wvm-closing { animation: none; opacity: 0; transform: translateY(4px); transition: opacity var(--wvm-fast) var(--wvm-ease), transform var(--wvm-fast) var(--wvm-ease); }
/* 只过渡 opacity / transform；收起态不可点（pointer-events），读屏隐藏由宿主设 inert / aria-hidden（见 HANDOFF §14） */
[data-motion-pop] { opacity: 0; transform: translateY(4px); pointer-events: none; transition: opacity var(--wvm-fast) var(--wvm-ease), transform var(--wvm-fast) var(--wvm-ease); }
[data-motion-pop][data-open="true"] { opacity: 1; transform: none; pointer-events: auto; }
.v2u-diff-opt { transition: transform var(--wvm-fast) var(--wvm-ease); }
.v2u-diff-opt:active { transform: scale(.98); }

/* ===================== M13 故事卡悬浮（hover）：translateY −2px，240，阴影不变；只在 (hover:hover) ===================== */
.v2-pv-card { transition: transform var(--wvm-base) var(--wvm-ease); }
.v2-pv-card::before { transition: none; opacity: 0; }            /* 撤销「第二档阴影淡入」：阴影不变 */
@media (hover: hover) { html:not([data-motion-hover="off"]) .v2-pv-card:hover { transform: translateY(-2px); } }
.v2-pv-card:hover::before { opacity: 0; }

/* ===================== M14 词标签轻弹簧（hover）：scale 1→1.02→1，240（hover / 选中）；新词片 opacity + translateY 4→0 ===================== */
/* A（衡舟 19:05）：原来用 transform:scale 会在动画期间覆盖 style.css:1310 的 translateX(-50%) translateY(-50%) 定位 → 生词卡跳 ~16px。改用独立 scale 属性，不碰 transform */
@keyframes wvm-spring { 0%, 100% { scale: 1; } 50% { scale: 1.02; } }
@media (hover: hover) { html:not([data-motion-hover="off"]) .v2-pv-word:hover { animation: wvm-spring var(--wvm-base) var(--wvm-ease); } }
html:not([data-motion-hover="off"]) .v2-preview.wvm-done .v2-pv-word.wvm-spring { animation: wvm-spring var(--wvm-base) var(--wvm-ease); }
.v2-pv-word::after { transition: opacity var(--wvm-fast) var(--wvm-ease); }
/* A 根因（衡舟 19:05 m1440-5）：.wvm-spring 去掉时 animation 列表从 wvm-spring 换回 wvm-rise，浏览器把 wvm-rise 当新动画重播 → 生词卡从 translate 8px 处再升一次（看起来"低了一截"）。
 * 修法：入场播过（.wvm-play）后，弹簧与 wvm-rise 并列在同一列表里，wvm-rise 名字始终在场、不会重播 */
@media (hover: hover) { html:not([data-motion-hover="off"]) .v2-preview.wvm-play .v2-pv-word:hover { animation-name: wvm-rise, wvm-spring; animation-fill-mode: both, none; animation-duration: var(--wvm-base); animation-timing-function: var(--wvm-ease); } }
html:not([data-motion-hover="off"]) .v2-preview.wvm-play.wvm-done .v2-pv-word.wvm-spring { animation-name: wvm-rise, wvm-spring; animation-fill-mode: both, none; animation-duration: var(--wvm-base); animation-timing-function: var(--wvm-ease); }
@keyframes wvm-chip { 0% { opacity: 0; transform: translateY(4px); } 70% { opacity: 1; transform: translateY(0) scale(1.02); } 100% { opacity: 1; transform: none; } }
#page-home .word-crystal { transform-origin: 50% 60%; }
#page-home .word-crystal.v2u-pending { display: inline-flex; opacity: 0; }   /* 正在输入的末词：透明占位（原 display:none 会让布局跳） */
#page-home .word-crystal.v2u-new { animation: wvm-chip var(--wvm-base) var(--wvm-ease) both; }

/* ===================== M15 导航滚动态：::before opacity，150（scrollY>8，rAF 节流） ===================== */
body[data-page="home"] .nav::before { transition: opacity var(--wvm-fast) var(--wvm-ease); }

/* ===================== 换页（style.css .page.active wv-page-in 从 opacity 0 起 → 录屏有 1 帧整页只剩导航）：改为从 .55 起淡入 240，任何一帧内容都在；enter 关闭时不淡入 ===================== */
@keyframes wvm-page-in { from { opacity: .55; } to { opacity: 1; } }
html .page.active { animation-name: wvm-page-in; animation-duration: var(--wvm-base); }   /* s13（衡舟 20:08）：缓动由 style.css --ease 源头统一，motion 只保留 .55 起点防空帧，时长 240 档 */
html[data-motion-enter="off"] .page.active, html[data-motion-intro="off"] .page.active { animation: none; }

/* ===================== M16 主题切换（衡舟批准的第 2 个例外，只在切换那一刻）：html.wvm-theme-switching 只保留 240ms（+80ms 余量）。
 * 文字、图标和小块底色与 data-theme 同一帧切换，不过渡。只有页面底和主要容器的 background-color 走 240ms：
 * 曲线（s13 统一缓动）cubic-bezier(.2,.8,.2,1)，240ms。
 * 不做对称的交叉淡入，因为中段的灰底会和 --wv-text-3 同亮度，那一帧读不清；按现在的做法每帧最低约 3.6:1。
 * 也不给 * 全体元素和会继承的 color 加过渡（实测会产生 50–76ms 的长任务）。平时不带颜色过渡；reduce / data-motion-theme="off" / 页面隐藏时用 .wvm-theming 瞬切 ===================== */
html.wvm-theming *, html.wvm-theming *::before, html.wvm-theming *::after, html.wvm-theme-switching *, html.wvm-theme-switching *::before, html.wvm-theme-switching *::after { transition: none !important; }
html.wvm-theme-switching, html.wvm-theme-switching body, html.wvm-theme-switching #wv-nav, html.wvm-theme-switching #wv-tabbar, html.wvm-theme-switching .page,
html.wvm-theme-switching #page-home .input-wrapper, html.wvm-theme-switching .v2-pv-card, html.wvm-theme-switching .home-continue, html.wvm-theme-switching .sd-body,
html.wvm-theme-switching .continue-input, html.wvm-theme-switching .reader-toolbar, html.wvm-theme-switching .modal-card, html.wvm-theme-switching .vocab-drawer {
  transition: background-color var(--wvm-base) var(--wvm-ease) !important;
}

/* ===================== M17 390 触控反馈 + 底栏指示器：按压 .98（150），短线 translateX（240） ===================== */
/* 守望 / 衡舟 18:48 裁定：动画只走 transform / opacity，例外只有 K13 stroke-dashoffset 和主题切换 240ms 背景。兜底：导航文字 hover 颜色瞬变；生词点亮只靠 ::before 的 opacity（无 background/color 过渡） */
html #wv-nav .nav-tab, html #wv-nav .nav-brand, html #wv-nav .btn-icon, html #wv-tabbar .tabbar-item, html #story-main .vocab-highlight, html #page-home .v2-pv-en mark { transition-property: opacity, transform; }
html { -webkit-tap-highlight-color: transparent; }   /* 全局兜底（鉴心 18:44）：任何可点元素都不出现蓝色点按高亮 */
/* 去掉移动端默认蓝色点按高亮：只给有我们按压反馈的可点元素（与 motion.js PRESS 同一份清单），反馈交给 .98 */
.tabbar-item, .btn-primary, .btn-secondary, .btn-text, .seg-item, .v2-more-sum, .v2u-diff-opt, .v2-pv-word, .v2-pv-card, .menu-item, #page-home .v2-pv-en mark { -webkit-tap-highlight-color: transparent; }
/* 首页以外 / 原先没有 transform 过渡的元素：用独立 scale 属性做 150 按下 / 150 回弹，不覆盖它们原有的 transition 与 transform */
@keyframes wvm-press-in { from { scale: 1; } to { scale: .98; } }
@keyframes wvm-press-out { from { scale: .98; } to { scale: 1; } }
.wvm-press.wvm-press-s { scale: .98; animation: wvm-press-in var(--wvm-fast) var(--wvm-ease) both; }
.wvm-press-out { animation: wvm-press-out var(--wvm-fast) var(--wvm-ease) both; }
.tabbar-item, #page-home .seg-item, #page-home .btn-text, #page-home .v2-more-sum, .v2-pv-en mark { transition: transform var(--wvm-fast) var(--wvm-ease); }
.tabbar-item:active, .tabbar-item.wvm-press, body[data-page="home"] .tabbar-item:active,
#page-home .seg-item:active, #page-home .btn-text:active, #page-home .v2-more-sum:active,
#page-home .seg-item.wvm-press, #page-home .btn-text.wvm-press, #page-home .v2-more-sum.wvm-press { transform: scale(.98); }
/* s16c-bgate §5.9-7：B 首页 / 故事页（editorial-b.css:39 的作用域）按下不缩放（:active 与触屏 .wvm-press 都不改尺寸），
 * 按下时颜色瞬时变为 var(--wv-text-1)（spec 写作 --t1；衡舟 05:39 裁定站内无 --t1、不新增 token），无过渡。填色主按钮（#generate-btn / .btn-primary，底色即 --wv-text-1）不改字色，否则字会消失。
 * editorial-b.css 里这些元素的 color 规则都不带 !important（最高如 #wv-random-set.v2u-random:hover (2,3,1)），这里用 !important 保证按下态胜出。 */
html:is([data-wv-layout="b"], :not([data-wv-layout]):has(> head > link[rel~="stylesheet"][href*="editorial-b.css"]:not([disabled]))) body:is([data-page="home"], [data-page="story"]) :is(#page-home, #page-story, .nav, .tabbar, .reader-toolbar) :is(.tabbar-item, .btn-primary, .btn-secondary, .btn-text, .seg-item, .v2-more-sum, .v2u-diff-opt, .v2-pv-word, .v2-pv-card, .menu-item, .v2-pv-en mark):is(:active, .wvm-press, .wvm-press-out) { transform: none !important; scale: none !important; animation: none !important; }
html:is([data-wv-layout="b"], :not([data-wv-layout]):has(> head > link[rel~="stylesheet"][href*="editorial-b.css"]:not([disabled]))) body:is([data-page="home"], [data-page="story"]) :is(#page-home, #page-story, .nav, .tabbar, .reader-toolbar) :is(.tabbar-item, .btn-secondary, .btn-text, .seg-item, .v2-more-sum, .v2u-diff-opt, .menu-item):not(.btn-primary):active { color: var(--wv-text-1) !important; transition: none !important; }
.v2u-tab-ind { transition: transform var(--wvm-base) var(--wvm-ease), opacity var(--wvm-fast) var(--wvm-ease) var(--wvm-base); }
body:not([data-page="home"]) .v2u-tab-ind { opacity: 0; }
/* 「调整」面板：分段滑块只平移（240）；展开箭头 240 */
.v2-seg-thumb { transition: transform var(--wvm-base) var(--wvm-ease), opacity var(--wvm-fast) var(--wvm-ease); }
.v2-more-chev { transition: transform var(--wvm-base) var(--wvm-ease); }

/* ===================== M18 宽度过渡改造：阅读进度条 width → transform: scaleX（style.css L527；源头修改见 HANDOFF §4）=====================
 * motion.js 把 app.js 写入的 style.width 百分比同步成 --wvm-rp（0–1）；这里固定宽 100%，只缩放。L1061 分段滑块已在上方只过渡 transform。 */
#read-progress.wvm-rp { width: 100% !important; transform-origin: 0 50%; transform: scaleX(var(--wvm-rp, 0)); transition: transform var(--wvm-fast) var(--wvm-ease); }

/* ===================== M19 全局 reduced-motion：全部直达终态 ===================== */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after, ::view-transition-old(root), ::view-transition-new(root) { animation: none !important; transition-duration: 0s !important; transition-delay: 0s !important; }
  .wvm-dots, .v2-pv-threads path.v2u-flow { display: none !important; }                                  /* M06 隐藏 */
  .v2-pv-word, .v2-pv-card, [data-motion-rise], #page-home .home-title, #page-home .home-sub, #page-home .v2-input-card, #page-home .v2u-input-foot { opacity: 1 !important; translate: none !important; }
  .v2-pv-threads path.v2-thr, [data-motion-draw] { stroke-dashoffset: 0 !important; }                     /* M03 连线直接画好 */
  .v2-pv-en mark::before { transform: none !important; }                                                   /* M04 直接点亮 */
  #page-home #word-input:focus:placeholder-shown ~ .wv-input-caret, #page-home .input-wrapper:has(#word-input:focus:placeholder-shown) .wv-input-caret { opacity: 1 !important; }   /* M07 常亮 */
  .v2-pv-card:hover, .wvm-press, #page-home .btn-primary:active, #page-home .seg-item:active, #page-home .btn-text:active, #page-home .v2-more-sum:active,
  .tabbar-item:active, .v2u-diff-opt:active, #page-home .v2-input-card.wvm-shake { transform: none !important; }  /* M09/M11/M13/M17 去位移 */
  #page-home .word-crystal.v2u-pending { opacity: 1; }
  .wvm-press, .wvm-press-out { scale: none !important; }
}
