/* V2 路线增量样式 —— 一律挂在 body.v2-route 下 ✗ 不污染原版 page-tool.css
 * 生成器：_t/build-piano2.js ✓ 这个文件手工维护 ✓ 重新生成不会覆盖它 ✓ */
/* ============================================================
 * ★ 浏览器兼容备注（2026-10 ✓ 与 static/js/tool-piano.js 里的「兼容层」配套 ✓）
 * ------------------------------------------------------------
 * 这一整套 V2 样式**大量依赖 CSS 变量**（--pk-wm / --pk-wm-size / --pk-wm-x /
 *   --bar-b / --bar-o / --glow-h / --glow-b / --glow-bsec / --glow-anim …）
 *   ⇒ IE11 **完全没有 CSS 变量** ⇒ 玻璃区背景 / 水印 / 泛光带都出不来 ✓
 *   ⇒ 但 IE11 也**没有 Web Audio** ✗ 本来就弹不响 ✗ 所以 IE11 不在支持范围内 ✓
 *
 * 已经做好的退路 ✓（都写在 static/css/page-tool.css 的对应规则里）：
 *   · inset           → 前面先写一份等价的 top/right/bottom/left（Safari<14.1 ✓）
 *   · color-mix       → 前面先写一份纯色 background（见 .inst-go 那段 ✓）
 *   · 全屏态           → 由 JS 统一打 .is-fs 类 ✗ **不依赖** :fullscreen /
 *                       :-webkit-full-screen / :-moz-full-screen ✓（F11 也认 ✓）
 *   · 现代语法（:is / :where / clamp / dvh …）→ 本文件一条都没用 ✓ 不用退 ✓
 *
 * 已知「老浏览器只是不好看、但功能全在」的点 ✓（不硬修 ✗ 记在这里 ✓）：
 *   · flex 的 gap（Safari<14.1 / Chrome<84 / FF<63）⇒ 工具栏控件间距变 0 ✗
 *     没法用 @supports 区分（gap:1px 在只支持 grid-gap 的浏览器里也返回 true ✓）
 *     ⇒ 只有那一档浏览器会看到「控件贴着」✗ 布局 / 弹奏 / 播放全不受影响 ✓
 * ============================================================ */



/* ============================================================
 * ★★★ 第 1 步：水印 = 一层 CSS 背景（不再是画布内容）
 * ------------------------------------------------------------
 * 原来（每帧）：canvas 里 drawImage 一张整屏离屏图
 *   ⇒ 2400×240 @dpr1.86 ≈ 200 万像素的 alpha 混合 ✗ 每秒 1.2 亿像素 ✓
 * 现在（每帧）：0 ✗ 合成器直接复用这一层背景 ✓✓
 *
 * 分层顺序（从上到下 ✗ 这正是 CSS 背景的合成顺序 ✓）：
 *   ① 水印瓦片      var(--pk-wm)     ← JS 用 toDataURL 挂上来 ✓
 *   ② 绿色荧光径向  从琴键往上透出的光
 *   ③ 深绿纵向渐变  收进背景
 * ⇒ 与原来完全一致（原来水印也压在渐变之上、音条之下 ✓）
 *
 * ★ 变量来自 tool-piano2.js 的 applyRollWmLayer()
 *   --pk-wm      : url("data:image/png;base64,…") 或 none（文字被清空时 ✓）
 *   --pk-wm-size : "<step>px <mh>px"（瓦片尺寸 = 平铺步长 ✓）
 *   --pk-wm-x    : "<X>px"（★ 2026-10 新增 ✓ 让**当前可见区的中心**正对一张水印的中心 ✓
 *                  用户要求"文字要显示在背景居中"✗ 老写法从 0 起铺 ✗
 *                  可见区一变就偏 ✗ 长文字实测"贴着右边被切掉"✓
 *                  值由 tool-piano2.js 的 rollWmPlace 算 ✗ 并随横向滚动补偿 ✓）
 *   ★ 只在 尺寸/文字/主题 变化（+ 滚动）时才重设 ✗ 不是每帧 ✓
 *
 * ★ 这一层不分深色/浅色主题（弹奏区本来就是深色 ✗ 与原版一致 ✓）
 * ============================================================ */
body.v2-route .piano-scroll .pk-roll,
body.v2-route .piano-scroll.is-roll .pk-roll {
    background-image:
        var(--pk-wm, none),
        radial-gradient(170% 150% at 50% 50%,
            hsla(var(--bh, 152), 22%, 96%, var(--pk-glowA, .26)) 0%,
            hsla(var(--bh, 152), 20%, 90%, var(--pk-glowA2, .13)) 55%,
            hsla(var(--bh, 152), 18%, 82%, var(--pk-glowA3, .05)) 82%,
            rgba(0, 0, 0, 0) 100%),
        linear-gradient(180deg,
            hsla(var(--bh, 145), 30%, 6%, 1) 0%,
            hsla(var(--bh, 145), 29%, 9%, 1) 38%,
            hsla(var(--bh, 145), 33%, 12%, 1) 68%,
            hsla(var(--bh, 145), 38%, 16%, 1) 100%);
    background-size:
        var(--pk-wm-size, auto),
        auto,
        auto;
    background-repeat: repeat-x, no-repeat, no-repeat;
    /* ★★ 2026-10 改（用户：背景文字要"按长度整体缩放 + 居中"✓）
       原来写死 `0` ✗ ⇒ 水印网格从元素左边缘起铺 ✗ 与中心对不上 ✓
         ⇒ 水印一宽（长文字 ✓）就"贴着一边被切掉"✓
       现在 ✗ 起点由 JS 算（--pk-wm-x ✓ 见 tool-piano2.js 的 rollWmPlace ✓）
         ⇒ **可见区的中心**上必定压着一张水印的中心 ✓✓
         长文字任何宽度、任何滚动位置都居中 ✗ 永不切边 ✓
       ★ 兜底 0px ✗：JS 还没跑时与老行为一致 ✓ */
    background-position: var(--pk-wm-x, 0px) center, center, center;
}

/* ============================================================
 * ★★★ 2026-10-15 改（用户两条要求，一次解决 ✓）
 * ------------------------------------------------------------
 * ① "这个背景有一种绿油油的颜色 ✗ 也可以给它弄进预设里面去
 *     很明显泛光带上面整个背景有一层朦胧的绿色 ⇒ 处理一下"✓
 *     ⇒ 上面那两条渐变**不再写死绿色** ✗ 全部改成读 `--bh`（色相变量 ✓）：
 *        · 泛光那层：hsla(var(--bh), …) ⇒ 跟着主题一起换色 ✓
 *          （默认 --bh 没设 ⇒ 回退 152/145 ⇒ 与老版那颗绿**几乎一模一样** ✓）
 *        · 底色那层：同上 ⇒ 选 Nord 背景就偏冰蓝 ✗ 选 Gruvbox 就偏暖棕 ✓
 *     ⇒ 强度单独给了旋钮 `--pk-glowA`（面板"背景 → 泛光强度"✓ 第四十一轮去掉"底部")
 *        0 = 那层朦胧的绿**彻底没有** ✓ 只留深底 ✓
 * ② "上传图片当背景（当然不会这么明亮 ⇒ 它就是背景层）"✓
 *     ⇒ 见下面的 .pk-has-bg（由 tool-piano.js 的 applyRollBgImgLayer 挂类 ✓）
 * ============================================================ */

/* ★ 上传了背景图时的分层（从上到下 = CSS 的书写顺序 ✓）：
 *   ① 水印      还是最上（文字要看得见 ✓）
 *   ② 底部泛光  同上面那套（跟随主题色相 ✓）
 *   ③ 压暗层    rgba(主题暗色, --pk-bgdim) ⇒ **图片亮度的总闸** ✓
 *                面板"背景 → 背景图压暗"直接拖它 ✓（0=原亮度 / 1=近乎全黑 ✓）
 *   ④ 背景图    cover 铺满、不重复、居中 ✓（放最后 ⇒ 在最底下 ✓）
 * ⇒ 所以图片**永远不会太亮**✗ 而且音条/水印都压在它上面 ✓ 不影响可读性 ✓ */
body.v2-route .piano-scroll .pk-roll.pk-has-bg,
body.v2-route .piano-scroll.is-roll .pk-roll.pk-has-bg {
    background-image:
        var(--pk-wm, none),
        radial-gradient(170% 150% at 50% 50%,
            hsla(var(--bh, 152), 22%, 96%, var(--pk-glowA, .26)) 0%,
            hsla(var(--bh, 152), 20%, 90%, var(--pk-glowA2, .13)) 55%,
            rgba(0, 0, 0, 0) 100%),
        linear-gradient(180deg,
            hsla(var(--bh, 145), 32%, 5%, var(--pk-bgdim, .62)) 0%,
            hsla(var(--bh, 145), 32%, 5%, var(--pk-bgdim, .62)) 100%),
        var(--pk-bg, none);
    background-size:
        var(--pk-wm-size, auto),
        auto,
        auto,
        cover;
    background-repeat: repeat-x, no-repeat, no-repeat, no-repeat;
    background-position: var(--pk-wm-x, 0px) center, center, center, center;
}

/* 全屏 / 伪全屏同一套 ✗ 只是荧光略强（照抄原版的 .28/.14 ✓）
   ★★ 2026-10-15：与上面一样改成跟随主题色相 + 可调强度 ✓ */
body.v2-route .piano-panel.is-fs .pk-roll,
body.v2-route .piano-panel:fullscreen .pk-roll,
body.v2-route .piano-panel.is-faux-full .pk-roll {
    background-image:
        var(--pk-wm, none),
        radial-gradient(170% 150% at 50% 50%,
            hsla(var(--bh, 152), 22%, 96%, var(--pk-glowA, .26)) 0%,
            hsla(var(--bh, 152), 20%, 90%, var(--pk-glowA2, .13)) 55%,
            hsla(var(--bh, 152), 18%, 82%, var(--pk-glowA3, .05)) 82%,
            rgba(0, 0, 0, 0) 100%),
        linear-gradient(180deg,
            hsla(var(--bh, 145), 30%, 6%, 1) 0%,
            hsla(var(--bh, 145), 29%, 9%, 1) 38%,
            hsla(var(--bh, 145), 33%, 12%, 1) 68%,
            hsla(var(--bh, 145), 38%, 16%, 1) 100%);
    background-size:
        var(--pk-wm-size, auto),
        auto,
        auto;
    background-repeat: repeat-x, no-repeat, no-repeat;
    background-position: var(--pk-wm-x, 0px) center, center, center;
}
/* 全屏 + 有背景图 ⇒ 与上面 .pk-has-bg 同一套（少一条泛光最外圈 ✓） */
body.v2-route .piano-panel.is-fs .pk-roll.pk-has-bg,
body.v2-route .piano-panel:fullscreen .pk-roll.pk-has-bg,
body.v2-route .piano-panel.is-faux-full .pk-roll.pk-has-bg {
    background-image:
        var(--pk-wm, none),
        radial-gradient(170% 150% at 50% 50%,
            hsla(var(--bh, 152), 22%, 96%, var(--pk-glowA, .26)) 0%,
            hsla(var(--bh, 152), 20%, 90%, var(--pk-glowA2, .13)) 55%,
            rgba(0, 0, 0, 0) 100%),
        linear-gradient(180deg,
            hsla(var(--bh, 145), 32%, 5%, var(--pk-bgdim, .62)) 0%,
            hsla(var(--bh, 145), 32%, 5%, var(--pk-bgdim, .62)) 100%),
        var(--pk-bg, none);
    background-size:
        var(--pk-wm-size, auto),
        auto,
        auto,
        cover;
    background-repeat: repeat-x, no-repeat, no-repeat, no-repeat;
    background-position: var(--pk-wm-x, 0px) center, center, center, center;
}



/* ============================================================
 * ★★★ 第 2 步：落键火星（粒子）搬 CSS
 * ------------------------------------------------------------
 * 原来（每帧 · 在画布里）：每颗火星一次 drawImage
 *   ✗ 池子上限 140 ⇒ 一帧最多 140 次绘制（账本里占 11.9%）
 *   ✗ 而且完全在主线程上跑（主线程一卡 ✗ 粒子和画面一起停）
 * 现在：每颗火星 = 一个 <i> 元素
 *   ✓ 轨迹在**生成时**用 JS 一次算好（复刻原物理 ✓ 见 tool-piano2.js 的 cssSparkPath）
 *   ✓ 交给 WAAPI 播（transform / opacity ⇒ **合成器线程** ✗ 主线程不参与 ✓✓）
 *   ✓ 主线程卡顿时粒子照样飞 ✓ 而画布那边则完全不画火星了 ✓
 *
 * ★ 软光图不用图片 —— 直接用 CSS 径向渐变（stops 与画布版 V0 配方**逐档相同** ✓）
 *   ⇒ 零网络请求 ✗ 零 data-URI ✗ 而且矢量 ⇒ 任何尺寸都清晰 ✓✓
 * ★ --h 是色相（每颗按音符颜色给 ✗ 由 JS 写行内样式 ✓）
 * ★ 尺寸 = scale(sz/48) ✗ 盒子固定 48×48，全靠 transform 缩放 ✓（合成器干活 ✓）
 *   margin 负半宽 ⇒ translate 可直接写"火星中心点"✓ 与画布 drawImage(x-sz/2,…) 等价 ✓
 * ============================================================ */
body.v2-route .pk-sparks {
    position: absolute;
    left: 0; top: 0;
    overflow: hidden;              /* 与画布一样：飞出边界就裁掉 ✓ */
    pointer-events: none;
    z-index: 3;                    /* 压在画布之上（原版火星也是最后画 ✗ 在最前面 ✓）*/
    contain: layout style;         /* 这层内部不影响外部布局 ✓ 帮浏览器省事 ✓ */
}
body.v2-route .pk-spark {
    position: absolute;
    left: 0; top: 0;
    width: 48px; height: 48px;
    margin: -24px 0 0 -24px;       /* ★ 让 translate 直接写"中心点"✓ 不必每帧减半宽 ✓ */
    opacity: 0;
    background-repeat: no-repeat;
    background-size: 100% 100%;
    /* 与 tool-piano2.js 里 makeSparkSprites() 的 V0 配方逐档一致 ✓ 观感不变量 ✓ */
    background-image: radial-gradient(circle at 50% 50%,
        hsla(var(--h, 100), 100%, 94%, 1)    0%,
        hsla(var(--h, 100),  99%, 74%, .92) 34%,
        hsla(var(--h, 100),  96%, 60%, .40) 62%,
        hsla(var(--h, 100),  94%, 52%, .10) 86%,
        hsla(var(--h, 100),  90%, 45%, 0)  100%);
    /* ★ 不写 will-change ✗ 也不逐颗提升图层：
       WAAPI 的 transform/opacity 动画走浏览器自带的"合成动画"路径 ✓
       140 个常驻 will-change 反而会逼出 140 个图层 ✗ 低配上更危险 ✓ */
}
/* 保证 z-index 生效：滚动层当定位祖先 ✗ 画布自己起一层 ✓ */
body.v2-route .piano-scroll { position: relative; }
body.v2-route .pk-roll { position: relative; z-index: 1; }

/* ============================================================
 * ★★★ 第 3 步：音条「画一次 + 只位移」
 * ------------------------------------------------------------
 * 层次（**必须与老路完全一致** ✗ 否则观感就变了 ✓）
 *   微尘(画布 z1) → 音条(条带 z2) → 命中线(z3) → 火星(z4) ✓
 *
 *   .pk-strip-wrap   只负责【裁切 + 渐隐】✗ 静态 ✗ 每帧不动 ✓
 *   .pk-strip        只负责【位移】✗ 每帧一次 transform ⇒ 合成器干活 ✓
 *   .pk-strip-tile   一张分块画布 ✗ 画一次就不再动 ✓
 *   .pk-hitline      原来画在画布里的那 2px 亮线（搬过来才不会被音条盖住 ✓）
 *
 * ★ 渐隐为什么能搬过来 ✗ 因为老路那个 alpha 是**可分离**的 ✓
 *     老路：alpha = 固定(n) × (0.72 + 0.28(1 - y/rollH))
 *     现在：块里烤的是【固定(n)】✗ mask 乘上【1 → 0.72】✓✓
 *   ⇒ 逐像素等价 ✓（y 越往上越小 ⇒ mask 越亮 ✗ 与公式同向 ✓）
 * ============================================================ */
body.v2-route .pk-strip-wrap {
    position: absolute;
    left: 0; top: 0;
    overflow: hidden;              /* 音条只在画布那块里出现 ✓ */
    z-index: 2;
    pointer-events: none;
    /* ★★ 2026-10 新增：音条「亮度」与「不透明度」两个旋钮
       ★ 为什么非得挂在这一层 ✗：
           这层**内容不变 ✗ 每帧也一动不动** ⇒ 浏览器只需算一次 ✓
         绝对不能挂到 .pk-strip 上 ✗ 它每帧都在 transform
           ⇒ 那就是当年"卡到 0.5fps"那个错 ✗✗ 记住这条 ✓
       ★ 变量由 TUNE 写进 body（见 tool-piano2.js 的 a6TuneVars ✓）
         默认都是 1 ⇒ 观感与加这两个旋钮之前**完全一致** ✓ */
    filter: brightness(var(--bar-b, 1));
    opacity: var(--bar-o, 1);
    -webkit-mask-image: linear-gradient(to bottom, rgba(0,0,0,1) 0%, rgba(0,0,0,.72) 100%);
            mask-image: linear-gradient(to bottom, rgba(0,0,0,1) 0%, rgba(0,0,0,.72) 100%);
}
body.v2-route .pk-strip {
    position: absolute;
    left: 0; top: 0;
    width: 100%;
    will-change: transform;        /* ★ 整首歌只有这一层在动 ⇒ 合成层就是它 ✓ */
}
body.v2-route .pk-strip-tile {
    position: absolute;
    left: 0;
    display: block;
}
body.v2-route .pk-hitline {
    position: absolute;
    z-index: 3;
    background: rgba(255, 255, 255, .16);
    pointer-events: none;
}
body.v2-route .pk-sparks { z-index: 5; }   /* 火星在最上面 ✓（原来上面还压着光柱层 ✗ 已删 ✓）*/

/* ============================================================
 * ★ 火焰层：已按用户要求**整块删除**（2026-10 ✓ 说"没效果、看着也不爽"）
 *   被删掉的 CSS：.pk-fire-tile / .pk-fire-v
 *   被删掉的 JS ：FIRE_ON / fireEnsure / fireClipPath / fireApplyClip …
 *   老实现完整存在 _t/patch-step7-fire.orig.js ✗ 想捡回来就翻那个 ✓
 * ============================================================ */

/* ============================================================
 * ★ 琴键光芒（**光柱**）：用户说不要 ⇒ **整块删除** ✓（2026-10）
 *   删掉的 CSS：.pk-beams / .pk-beam / .pk-beam.on
 *   删掉的 JS ：beamInit / beamLayout / beamVars / beamTake / beamSet / beamClear
 *               + light() 里那两根桥 A6BEAM / A6BEAMV（一起删干净 ✓）
 *   老实现全量存档：_t/patch-step7-beam.orig.js ✗ 想捡回来翻那个 ✓
 *
 *   ★ 实测教训（留在这儿 ✗ 以后再加"贴视频 / 贴纹理 / 发光叠加"的层要记得）：
 *     千万别把 mix-blend-mode 写在"每帧都在位移"的层上
 *     ⇒ 浏览器每帧重算整块背景 ⇒ 60fps 直接掉到 0.5fps（页面像卡死 ✓）
 * ============================================================ */

/* ============================================================
 * ★★ 键盘上沿那条"泛光带"（.pk-hitline 的加高版）—— **先留着** ✓
 *   用户 2026-10 原话（很重要 ✗ 别删错 ✓）：
 *     · "钢琴上面一点点，它就有一个泛光的东西"  ← 就是这一条 ✓
 *     · "它是要弄成那种……像**闪电**形式的，然后一直在这里**缠绕转动** ✓"
 *     · "现在是**一道光芒** ⇒ 这样不好看 ✓"      ← 当前状态 ✓ 待改造 ✓
 *     · "这个光带就先给他留着吧" ⇒ **先不删** ✓（等闪电版做好再替换 ✓）
 *
 *   ★ 现在怎么做（别改它）：把原来那 2px 的命中线**加高 + 向上淡出的渐变**
 *     ⇒ 底边还是压在画布下沿 ✗ 一行 JS 都不用加 ✓
 *     ★ 纯静态元素 ⇒ 零开销 ✓（绝不能加 filter: blur ✗ 那是性能地雷 ✓）
 *
 *   ★★ 2026-10 新增：高度 + 呼吸感（**都是旋钮** ✗ 变量由 TUNE 写进 body ✓）
 *     --glow-h     高度 px（默认 30 ✓ 原先写死 58 ✗ 用户嫌太高 ⇒ 才有这个旋钮 ✓）
 *     --glow-b     呼吸幅度 0~1（0 = 不呼吸 ✓）
 *     --glow-bsec  呼吸周期 秒（越大越慢 ✓）
 *     --glow-anim  动画名 ✗ 幅度 = 0 时 JS 会把它设成 none ⇒ 动画**彻底不跑** ✓
 *       （不是"跑了但看不见"✗ 那是白烧电 ✓）
 *     ★ 呼吸只动 opacity + transform: scaleY ✗ 全是合成器包办的属性 ✓
 *       每帧 0 JS ✗ 也不重排（scaleY 不改变布局 ✓）
 *     ★ transform-origin 锚在**底边** ⇒ 看着是"从琴键那儿往上呼"✗ 最自然 ✓
 *     ★ 呼吸周期 ⚠ 别小于 1 秒 ✗ 那就不叫呼吸了 ✗ 像抽搐 ✓
 *
 *   ★★ 以后做"闪电"版的方向（记在这儿免得忘 ✓）：
 *     ① canvas 上**烤**几条分叉电弧（画一次 ✓ 成本为零 ✓ 还能带辉光 ✓）
 *     ② 放进独立层 ✗ 只动 transform（循环平移 / 轻微旋转）⇒ 合成器干活 ✓ 每帧 0 JS
 *     ③ 想"缠绕转动"⇒ 叠两层**不同速度**的电弧 ✗ 偶尔来一次 opacity 闪 ✓
 *     ④ 千万别用 SVG 滤镜（feTurbulence）/ filter: blur ✗ 性能直接崩 ✓
 * ============================================================ */
body.v2-route .pk-hitline {
    height: var(--glow-h, 30px);
    /* ★ 底边永远贴在"画布下沿 + 2px"那条命中线上 ✓
       高度一变 ⇒ 元素整体往上长 ✗ 底边不动 ✓（与原来 58/-56 的关系一致 ✓） */
    margin-top: calc(2px - var(--glow-h, 30px));
    transform-origin: 50% 100%;
    animation-name: var(--glow-anim, pkGlowBreath);
    animation-duration: var(--glow-bsec, 4.8s);
    animation-timing-function: ease-in-out;
    animation-iteration-count: infinite;
    background: linear-gradient(to top,
        hsla(var(--bh, 145), 100%, 88%, .60)   0%,
        hsla(var(--bh, 145),  98%, 74%, .28)  26%,
        hsla(var(--bh, 145),  95%, 66%, .10)  58%,
        transparent                          100%);
}
/* ★ 呼吸：亮 → 淡 → 亮 ✗ 同时高度微微收一下（scaleY ✓ 底边锚住不动 ✓）
   幅度全由 --glow-b 定 ✗ 0 就是纹丝不动 ✓ */
@keyframes pkGlowBreath {
    0%, 100% { opacity: 1;    transform: scaleY(1); }
    50%      { opacity: calc(1 - var(--glow-b, .35) * .55);
               transform: scaleY(calc(1 - var(--glow-b, .35) * .18)); }
}
/* ★ 尊重系统"减少动态效果"✗ 这类用户直接不给呼吸 ✓ */
@media (prefers-reduced-motion: reduce) {
    body.v2-route .pk-hitline { animation-name: none; }
}

/* ============================================================
 * ★★★ 第 4 步：微尘搬 CSS
 * ------------------------------------------------------------
 * ★ 层次为什么是 z-index:1（与画布同层 ✗ 靠 DOM 顺序压在它上面 ✓）
 *   画布 .pk-roll 自己带着**不透明的绿色渐变背景**
 *   ⇒ 微尘若放 z0（画布下面）会被那层背景整个盖住 ✗✗ 一顆都看不见
 *   ⇒ 必须放在【画布之上 ✗ 条带之下】：
 *       画布(z1 背景) → 微尘(z1 ✗ 后出现 ⇒ 压上) → 条带(z2) → 命中线(z3) → 火星(z4)
 *   ⇒ 这正好等于老路"微尘最先画 ✗ 然后才是音条"的层次 ✓✓
 * ============================================================ */
body.v2-route .pk-dust-wrap {
    position: absolute;
    left: 0; top: 0;
    overflow: hidden;
    z-index: 1;
    pointer-events: none;
    contain: layout style;
}
body.v2-route .pk-dust {
    position: absolute;
    left: 0; top: 0;
    background-repeat: no-repeat;
    background-size: 100% 100%;
    /* 与 makeDustSprite() 的 stops 逐档相同：1 → .34@40% → 0 */
    background-image: radial-gradient(circle at 50% 50%,
        rgba(255, 255, 255, 1)   0%,
        rgba(255, 255, 255, .34) 40%,
        rgba(255, 255, 255, 0)  100%);
}
/* 老版遇到"减少动效"是直接不画 ✓ 这里整层关掉 ✓ 等价 ✓ */
@media (prefers-reduced-motion: reduce) {
    body.v2-route .pk-dust-wrap { display: none !important; }
}
