/* 04 幕「一条旅程」——正片模块样式
   作者: 陈涛 (Alan Chen) <1315333@qq.com>

   来源：docs/home/lab/act04.html 的样机样式（评分 8.0，§二十四「原样并入 + 三点」），
   按 §二十四.2 的模块契约改造：
   · 选择器全部以 `.act04*` 作用域，id 全部 `a04-` 前缀，**没有一处 position:fixed**；
   · 太阳/手机/线/Lin 一律不自建 —— 这里没有 .sun、没有 #routesvg，
     那两样由 shared.sun / shared.thread 提供，本文件只画「世界」（天、云、城、HUD）。
   · 每帧只动 transform / opacity；所有渐变按色温预烘成层，跨淡只改 opacity。
   · 本幕不用 backdrop-filter（§十五.2 全页 ≤ 2 已被顶栏与手机壳占满）。

   层序说明（为什么分成三组，见 act04.js 的 relocate()）：
   .act04-sky  → 舞台 z-index 2，插在 #sun **之前**（太阳压在天空之上）
   .act04-bg   → 舞台 z-index 2，插在 #sun **之后**（云与城压在太阳之上）
   .act04-fore → 舞台 z-index 3，追加到**最后**（近层云絮压在共享线之上 → 金线被云遮）
   共享线（#thread，在 #layers z-index 3 里）因此天然落在 bg 与 fore 之间，
   不需要集成者改它的层级。 */

/* ── 三组的公共底盘 ── */
.act04-sky, .act04-bg, .act04-fore {
  position: absolute; inset: 0; pointer-events: none;
  contain: paint;                    /* §十五.2：照片层收进自己的绘制盒 */
}
.act04-sky { z-index: 2 }
.act04-bg  { z-index: 2 }
.act04-fore{ z-index: 3 }
/* 未挂载/未启用时整块不占画面（update 会写 opacity，这里只是首帧的安全值） */
.act04-sky, .act04-bg, .act04-fore { opacity: 0 }

.act04-sky > *, .act04-bg > *, .act04-fore > * {
  position: absolute; inset: 0; pointer-events: none;
}
.act04 img { display: block; max-width: 100% }

/* ══════════════════ 天空：每个色温一层，预烘一次、跨淡只改 opacity ══════════════════ */
.act04-skies i, .act04-tints i, .act04-glows i, .act04-hazes i {
  position: absolute; inset: 0; display: none; opacity: 0; will-change: opacity;
}
.act04-skies { z-index: 1 }
.act04-deck  { z-index: 2 }
.act04-tints { z-index: 3 }          /* 每层自己写 mix-blend-mode: multiply */
.act04-hazes { z-index: 4 }
.act04-glows { z-index: 5 }          /* 每层自己写 mix-blend-mode: screen */

/* ══════════════════ 远景云带（§二十四.3 必改①：镜像纹理打散）══════════════════
   sea.jpg 是**镜像拼接**出来的无缝图：一旦看得清，画面中间就是一条左右对称的花纹，
   眼睛一眼认出「这是贴图」。样机只靠 blur(5px) 压，1728 上仍读得出对称轴。
   这里用三件事打散，任何一件单独都不够：
   ① 两层错开：两层同一张图，缩放比 1 : 1.37（无理数比，两层的对称轴永远不重合）、
      推进速度不同、其中一层 scaleX(-1) 再翻一次；
   ② 更重的虚化 + 更低的对比：远景本来就该是「一条淡淡的云线」，不是主角；
   ③ 一层高频噪声盖在上面（overlay），把残留的对称边缘打碎。
   验证（1728×875，pAct=0.2333 即杭州段飞行、holdF=0 云带满开，只留云带层抓 42px 高的
   一条，量高通后的**平移自相关**）：b1 的贴图周期 = 0.075H×3.4 = 223px，b2 = 305.6px。
   · 样机写法（__a04probe.plainBand = true）：@223 = 0.999（0.999 也出现在 lag 446 = 2 个周期），
     纹理 rms 17.56 —— 机器和眼睛都一眼认出「同一张图在重复」；
   · 本版：@223 掉到 0.435，全域最高峰 0.624 落在 lag 307（那是 b2 自己的周期，不是 b1 的），
     纹理 rms 6.16。「对比 × 相关」17.55 → 3.83，弱到 1/4.6。
   打开 plainBand，这三个数当场回到 0.999 / 17.56 —— 这是本条的探针，验过。
   两个坑记下来：
   ① **不要用「镜像最大互相关」当指标**：两版都在 0.97 以上（重度虚化后几乎任何对齐
      都相关），那条曲线分不开这两种画面，肉眼却一眼能分。上一版就是被它骗过去的。
   ② 抓图顺序是 render **在先**、强开图层在后。反过来做会被 act04.js 里 setOp 的
      「和上一次比」缓存吃掉：先写 opacity:1、再 render 时 deckOn 算出来仍等于缓存值，
      于是这一帧压根没写回去，抓到的是一张 deck 只有 0.02 的图 —— 量出来 rms 1.4、
      两版看着「都没问题」。那是没在验，不是验过了。 */
.act04-deck { overflow: hidden }
.act04-band {
  position: absolute; left: -18%; width: 136%; overflow: hidden; contain: paint;
  top: 39%; height: 15%; opacity: .5;
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 34%, #000 66%, transparent 100%);
          mask-image: linear-gradient(to bottom, transparent 0, #000 34%, #000 66%, transparent 100%);
}
.act04-band i {
  position: absolute; left: 0; right: 0; top: -100%; height: 300%; display: block;
  background-image: url(../assets/clouds/sea.jpg); background-repeat: repeat;
  will-change: transform;
}
.act04-band i.b1 { filter: blur(9px)  contrast(1.05) saturate(1.05) brightness(.94) }
.act04-band i.b2 { filter: blur(13px) contrast(.92)  saturate(1.0)  brightness(.98);
                   transform-origin: 50% 50%; opacity: .72 }
/* 噪声：把残留的镜像边缘打碎。预烘的 data URI，逐帧零成本。 */
.act04-band::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  mix-blend-mode: overlay; opacity: .38;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='180' height='180'><filter id='b'><feTurbulence type='fractalNoise' baseFrequency='.55' numOctaves='3' stitchTiles='stitch'/></filter><rect width='180' height='180' filter='url(%23b)' opacity='.9'/></svg>");
  background-size: 180px 180px;
}
/* 探针：退回样机的单层写法（自验用，默认不生效） */
.act04-plainband .act04-band i.b2, .act04-plainband .act04-band::after { display: none }
.act04-plainband .act04-band i.b1 { filter: blur(5px) contrast(1.2) saturate(1.1) brightness(.92) }

/* ══════════════════ 云絮 sprite：云海本体 ══════════════════ */
.act04-wisps b {
  position: absolute; left: 0; top: 0; display: block; width: 1024px; height: 512px;
  margin: -256px 0 0 -512px; background-size: 100% 100%; background-repeat: no-repeat;
  will-change: transform, opacity; transform-origin: 50% 50%;
}
.act04-wfar  { z-index: 7 }
.act04-wdeck { z-index: 8 }
.act04-wmid  { z-index: 9 }
.act04-wnear { z-index: 30 }         /* 在 fore 组里 → 压在共享金线之上（金线被云遮） */

/* ══════════════════ 城市：同一张照片三层带遮罩的视差（2.5D）══════════════════ */
.act04-cities { z-index: 10 }
.act04-city { position: absolute; inset: 0; margin: 0 }
.act04-cimgs { position: absolute; inset: 0; overflow: hidden; opacity: 0;
  will-change: opacity; contain: paint }
.a04-cl { position: absolute; left: -4%; top: -4%; width: 108%; height: 108%;
  will-change: transform }
.a04-cl img { width: 100%; height: 100%; object-fit: cover; max-width: none }
.a04-cl picture { display: block; width: 100%; height: 100% }
.a04-cl-mid {
  -webkit-mask-image: linear-gradient(to bottom, transparent 26%, #000 42%);
          mask-image: linear-gradient(to bottom, transparent 26%, #000 42%);
}
.a04-cl-near {
  -webkit-mask-image: linear-gradient(to bottom, transparent 54%, #000 72%);
          mask-image: linear-gradient(to bottom, transparent 54%, #000 72%);
}
.a04-flare-city { position: absolute; inset: 0; opacity: 0; will-change: opacity;
  background: radial-gradient(38% 38% at var(--fx,70%) var(--fy,26%),
    rgba(255,236,200,.85), rgba(255,214,160,.28) 42%, rgba(255,200,140,0) 72%) }

/* ══════════════════ 站名（左下大字，§十九.5「站名不进 3D」）══════════════════ */
.a04-cap { position: absolute; left: 5.2vw; bottom: 15vh; max-width: 44vw; opacity: 0;
  will-change: transform, opacity;
  text-shadow: 0 2px 18px rgba(8,18,40,.55), 0 1px 3px rgba(8,18,40,.35) }
.a04-day { display: inline-block; font: 500 11px/1 var(--sans, sans-serif);
  letter-spacing: .16em; text-transform: uppercase; color: #fff;
  background: rgba(8,18,40,.48); padding: 6px 10px; border-radius: 99px; margin-bottom: 14px }
/* §二十五：站名与它上下两行原来只隔 −3.0（日期药丸）/ −5.1（副标）——
   line-height .98 比字体的 ascent+descent 矮，Range 量到的墨迹盒上下各探出约半个行距，
   于是「看着分开、量着相交」。留白按**站名自己的字号**给（em），
   46px 到 104px 同一个比例，不用每档回来调数字。 */
.a04-name { display: block; font: 400 clamp(46px,6.2vw,104px)/.98 var(--serif, Georgia, serif);
  color: #fff; letter-spacing: -.01em; margin: .18em 0 .20em }
.a04-sub { display: block; margin-top: 10px;
  font: 300 clamp(15px,1.25vw,21px)/1.45 var(--serif, Georgia, serif);
  font-style: italic; color: rgba(255,255,255,.92) }
.a04-arr { display: inline-flex; align-items: center; gap: 8px; margin-top: 18px;
  font: 400 13px/1 var(--sans, sans-serif); color: #0F1F3D;
  background: rgba(255,255,255,.82); padding: 9px 14px 9px 11px; border-radius: 99px;
  border: 1px solid rgba(15,31,61,.06); text-shadow: none }
.a04-arr i { width: 7px; height: 7px; border-radius: 50%; background: var(--teal, #2FA88C);
  box-shadow: 0 0 0 4px rgba(47,168,140,.18) }
.a04-mob { display: none }
.a04-subm { display: none }

/* ══════════════════ scrim 与站名层的层序 ══════════════════
   depart(40) → scrim(41) → caps(42)：scrim 压在所有照片之上、站名之下，
   所以「加纱」只影响照片、不影响字（§二十.3 的对比是量字形像素与其**底**的）。 */
.a04-scrim { z-index: 41 }
.act04-caps { z-index: 42 }

/* ══════════════════ 站名底下的 scrim（§二十.1 可读性）══════════════════
   桌面：一块**径向**渐变，在矩形边缘之前就衰减到 0 —— 线性渐变会在屏幕中间切出硬边。
   手机：一条贴底的横向渐变（见手机段），保证 44px 主句压在照片上仍有 4.5:1。 */
.a04-scrim { position: absolute; inset: 0; opacity: 0; will-change: opacity;
  background: radial-gradient(58% 62% at 16% 86%,
    rgba(8,18,40,.62) 0%, rgba(8,18,40,.30) 44%, rgba(8,18,40,0) 74%) }

/* ══════════════════ 发车：C6 满幅 + 车窗反光 + 车票卡 ══════════════════ */
.act04-depart { z-index: 40; will-change: opacity }
.a04-dimgs { position: absolute; inset: 0; overflow: hidden; will-change: transform }
.a04-dl { position: absolute; left: -4%; top: -4%; width: 108%; height: 108%; will-change: transform }
.a04-dl img { width: 100%; height: 100%; object-fit: cover; max-width: none }
.a04-dl picture { display: block; width: 100%; height: 100% }
.a04-dl-near {
  -webkit-mask-image: linear-gradient(to bottom, transparent 46%, #000 66%);
          mask-image: linear-gradient(to bottom, transparent 46%, #000 66%);
}
.a04-refl { position: absolute; top: -40%; left: 0; width: 46%; height: 180%;
  will-change: transform, opacity; transform-origin: 50% 50%; opacity: 0 }
.a04-r1 { background: linear-gradient(100deg, rgba(255,255,255,0) 0%, rgba(255,255,255,.34) 44%,
  rgba(255,255,255,.52) 52%, rgba(255,255,255,0) 100%) }
.a04-r2 { width: 12%; background: linear-gradient(100deg, rgba(255,255,255,0) 0%,
  rgba(255,247,226,.9) 50%, rgba(255,255,255,0) 100%) }
.a04-sheen { position: absolute; inset: 0; opacity: 0; will-change: opacity;
  background: radial-gradient(60% 46% at 30% 58%, rgba(255,226,180,.55), rgba(255,200,140,0) 70%) }

.a04-ticket { position: absolute; right: 5vw; bottom: 16vh; width: 340px; inset: auto 5vw 16vh auto;
  opacity: 0; will-change: transform, opacity;
  display: flex; gap: 14px; align-items: center; padding: 12px; border-radius: 20px;
  background: rgba(255,255,255,.72); border: 1px solid rgba(15,31,61,.06);
  box-shadow: 0 24px 60px rgba(15,31,61,.18) }
.a04-ticket picture { flex: 0 0 auto }
.a04-ticket img { width: 110px; height: 78px; object-fit: cover; border-radius: 12px }
.a04-tkt { display: flex; flex-direction: column; gap: 4px; min-width: 0 }
.a04-tkt b { font: 500 14px/1.2 var(--sans, sans-serif); color: var(--ink, #0F1F3D) }
.a04-tkt span { font: 300 12px/1.35 var(--sans, sans-serif); color: var(--body, #3B4A63) }
.a04-tkok { color: var(--teal, #2FA88C) !important; font-weight: 500 !important;
  display: inline-flex; align-items: center; gap: 6px }
.a04-tkok i { width: 6px; height: 6px; border-radius: 50%; background: var(--teal, #2FA88C) }

/* ══════════════════ 到站的一道光带 + 镜头鬼影 ══════════════════ */
.a04-sweep { position: absolute; top: -30%; left: 0; width: 26%; height: 160%; z-index: 33;
  opacity: 0; transform-origin: 50% 50%; will-change: transform, opacity;
  background: linear-gradient(102deg, rgba(255,255,255,0) 0%, rgba(255,246,224,.30) 38%,
    rgba(255,255,255,.55) 50%, rgba(255,240,210,.28) 62%, rgba(255,255,255,0) 100%) }
.act04-flare { z-index: 34 }
.act04-flare i { position: absolute; left: 0; top: 0; border-radius: 50%; opacity: 0;
  will-change: transform, opacity }
.act04-flare .f1 { width: 120px; height: 120px; margin: -60px 0 0 -60px;
  background: radial-gradient(circle, rgba(255,220,170,.55), rgba(255,200,140,0) 70%) }
.act04-flare .f2 { width: 52px; height: 52px; margin: -26px 0 0 -26px;
  background: radial-gradient(circle, rgba(190,225,255,.6), rgba(190,225,255,0) 70%) }
.act04-flare .f3 { width: 210px; height: 210px; margin: -105px 0 0 -105px;
  background: radial-gradient(circle, rgba(255,255,255,.22), rgba(255,255,255,0) 70%) }

/* ══════════════════ 列车 = 沿共享金线跑的一枚光（§十九.3，不做几何车体）══════════════════
   线本身是 shared.thread 的那条 <path>，这里只有「光点 + 拖尾 + 站点小圆」，
   位置全部由 act04.js 采样共享 <path> 反算 —— 不自建线。 */
.act04-train { z-index: 32 }
.a04-loco { position: absolute; left: 0; top: 0; width: 26px; height: 26px;
  margin: -13px 0 0 -13px; opacity: 0; will-change: transform, opacity }
.a04-loco i { position: absolute; inset: 0; border-radius: 50%;
  background: radial-gradient(circle, #fff 0 18%, #FFDFA6 34%, rgba(255,158,88,.72) 54%, rgba(255,130,60,0) 78%);
  box-shadow: 0 0 26px 8px rgba(255,168,90,.55), 0 0 4px 1px rgba(160,80,20,.35) }
.a04-trail b { position: absolute; left: 0; top: 0; width: 12px; height: 12px;
  margin: -6px 0 0 -6px; border-radius: 50%; opacity: 0;
  background: radial-gradient(circle, rgba(255,214,150,.95), rgba(255,150,80,0) 72%);
  will-change: transform, opacity }
.a04-nodes b { position: absolute; left: 0; top: 0; width: 11px; height: 11px;
  margin: -5.5px 0 0 -5.5px; border-radius: 50%; opacity: 0;
  background: #FFF3D8; box-shadow: 0 0 0 2px rgba(198,120,48,.55);
  will-change: transform, opacity }

/* ══════════════════ HUD：右上标语 + 底部进度轨 ══════════════════ */
/* §二十五：HUD 现在**只包着底部那条进度轨**，而且自己就是轨的定位盒 ——
   它的盒子从「整屏」收成「轨那一条」，重叠判定量到的才是画面上真有的那块。
   淡入淡出也搬到这里（原来打在 .a04-rail 上）：不搬的话轨看不见时 HUD 仍算「在场」，
   一个透明的整块照样去跟别人相交，等于没修。 */
/* top: auto 是必须写的：上面那条 `.act04-fore > * { inset: 0 }` 把每个直接子元素都
   撑满整屏，只写 bottom 的话 top:0 还在，盒子从 0 拉到 910px —— 进度轨会被顶到
   屏幕最上沿（实测 hudRect top=0 height=910，站名压在顶栏上）。 */
.act04-hud { position: absolute; left: 5.2vw; right: 5.2vw; top: auto; bottom: 5.4vh; z-index: 50;
  opacity: 0; will-change: opacity }
/* top 原来是 6vh：样机里没有顶栏，正片有 —— 1728×875 上 6vh = 52.5px，而顶栏底沿
   在 51px（--hdrH 由页面 JS 量出来写），眉题第一行「Heal in China.」整行钻进顶栏底下
   （实测墨迹顶 53 < 顶栏底 72，走查截图上被切掉半行）。--hdrH 会随语言/字号/safe-area
   变，所以不写死 71，取两者较大的一个：视口高时仍是 6vh 的呼吸感，视口矮时让开顶栏。 */
.a04-kicker { position: absolute; z-index: 50; right: 4.6vw; top: max(6vh, calc(var(--hdrH, 52px) + 20px)); left: auto; bottom: auto; margin: 0;
  text-align: right; font: 400 clamp(19px,1.7vw,29px)/1.28 var(--serif, Georgia, serif);
  color: #fff; text-shadow: 0 2px 16px rgba(8,18,40,.72), 0 1px 3px rgba(8,18,40,.5);
  opacity: 0; will-change: transform, opacity }
.a04-kicker em { font-style: italic }

/* 定位与淡入淡出都搬到了 #a04-hud（§二十五）；轨自己只是那个盒子里的内容 */
.a04-rail { position: relative; pointer-events: auto }
.a04-track { position: relative; height: 2px; background: rgba(255,255,255,.30);
  border-radius: 2px; overflow: hidden }
.a04-fill { position: absolute; left: 0; top: 0; width: 100%; height: 100%;
  transform-origin: 0 50%; transform: scaleX(0); will-change: transform;
  background: linear-gradient(to right, rgba(255,231,184,.9), #FFE7B8) }
.a04-dots { list-style: none; margin: 0; padding: 0; position: relative; height: 26px }
.a04-dots li { position: absolute; top: 0; transform: translateX(-50%); text-align: center;
  will-change: opacity }
.a04-dots li button { display: block; margin: 0 auto; padding: 6px 8px 0; border: 0;
  background: none; cursor: pointer; font: inherit; color: inherit }
.a04-dots li i { display: block; width: 7px; height: 7px; border-radius: 50%;
  margin: -4px auto 7px; background: rgba(255,255,255,.55);
  will-change: transform, background-color }
.a04-dots li span { display: block; font: 400 10px/1 var(--sans, sans-serif); letter-spacing: .13em;
  text-transform: uppercase; color: rgba(255,255,255,.72); white-space: nowrap }
.a04-dots li.on i { background: #FFE7B8; box-shadow: 0 0 0 5px rgba(255,231,184,.20) }
.a04-dots li.on span { color: #fff }
.a04-dots li.hot i { background: #fff; box-shadow: 0 0 0 7px rgba(255,231,184,.28) }
/* 到站弹跳：全幕唯一一个**时间驱动**的点睛动作（§十五.2「回弹类动作走非 scrub 的短 tween」）。
   0.42s 跑完落回 scale(1) —— 抓帧脚本按完 __jump 之后它已经在终态，同一个 p 抓两次结果相同。 */
@keyframes a04pop { 0% { transform: scale(1) } 46% { transform: scale(2.2) } 100% { transform: scale(1) } }
.a04-dots li i.a04-pop { animation: a04pop .42s cubic-bezier(.34,1.56,.64,1) both }
@media (prefers-reduced-motion: reduce) {
  .a04-dots li i.a04-pop { animation: none }
}
/* 悬停站名时露出的一行副标（§二十一.4「悬停站名显示一行副标」） */
.a04-tip { position: absolute; left: 0; bottom: 34px; transform: translateX(-50%);
  white-space: nowrap; opacity: 0; will-change: opacity, transform;
  font: 300 13px/1 var(--serif, Georgia, serif); font-style: italic; color: #fff;
  text-shadow: 0 1px 10px rgba(8,18,40,.7); pointer-events: none }

.act04-grain { z-index: 44; opacity: .045; mix-blend-mode: overlay; background-image:
  url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='140' height='140'><filter id='n'><feTurbulence baseFrequency='.85' numOctaves='2'/></filter><rect width='140' height='140' filter='url(%23n)' opacity='.5'/></svg>") }

/* ══════════════════ 手机端（§十八 / §二十 / §十六）══════════════════
   §十八：04 幕手机端是「站点故事」—— 满幅照片逐张溶解、左下大字站名、2px 进度轨，
   没有 3D、没有雾、没有路线线（金线只在 cross04「收进手机」的那一下出现）。
   §二十：一屏一主一次一辅 —— 主 = 站名 44px，次 = 满幅照片，辅 = 一行副标 + 进度轨。
   所以手机端删掉：右上标语（每屏重复的那句）、日期药丸、Confirmed 药丸、
   远景云带、鬼影、中层/远层云絮、2.5D 的中/近两层。留一层近云絮做「穿云到站」那一个动作。 */
@media (max-width: 900px) {
  .act04-deck, .act04-flare, .act04-wfar, .act04-wmid, .a04-kicker,
  .a04-day, .a04-arr, .a04-cl-mid, .a04-cl-near, .a04-dl-near { display: none }
  .a04-dsk { display: none }
  .a04-mob { display: block }

  /* 站名落顶槽（§十六.2 顶槽 = 顶栏之下到 26dvh；§二十.1 主句 44px/1.05、≤ 2 行）。
     和正片其余五幕同一套变量：--sA0 / --m1 / --m3 / --sCB 由页面 :root 给，
     harness 里没有时用同值兜底，两边像素一致。 */
  .a04-cap { left: 5vw; right: 5vw; max-width: none; top: var(--sA0, 72px); bottom: auto }
  .a04-name { font: 400 var(--m1, 44px)/1.05 var(--serif, Georgia, serif);
    letter-spacing: -.015em; color: var(--paper, #FBF8F3); padding-bottom: .06em; text-wrap: balance }
  /* 桌面那行副标是主句的第二行，手机没有（§二十.1「辅 = 一行正文**或**一行 chip」）：
     手机用独立的 .a04-subm 贴底槽下沿排，与 .chips / #mcap 同一处落位。 */
  .a04-cap .a04-sub { display: none }
  /* 底槽两行的排布：进度轨在最下，副标排在它**上方**。
     `+14px` 那一版（=底槽下沿再抬 14px）实测与进度轨咬在一起：390×844 上副标盒
     738.7–762.5、轨 752.5–780.5，那条 2px 的轨线正好横穿副标的下半截 ——
     截图上看就是「一行被划掉的字」（contact-m390 的 p 0.24–0.34 / 0.52–0.62 / 0.90 都是）。
     轨顶距视口底 = --sCB - 4px + 28px(轨高) = 91.5px，副标要整条落在它之上，
     所以 bottom 取 --sCB + 36px（= 103.5px），下沿到轨顶留 12px。 */
  .a04-subm { display: block; position: absolute; left: 5vw; right: 5vw;
    top: auto; bottom: calc(var(--sCB, 8dvh) + 36px); margin: 0;
    font: 400 var(--m3, 17px)/1.4 var(--sans, sans-serif);
    color: var(--paper, #FBF8F3); opacity: 0; will-change: transform, opacity }
  /* 手机端 scrim：顶（压站名）+ 底（压副标与进度轨）两段。
     §二十四.3 必改②「手机端 p≈0.80 文字对比度下限」—— 三亚那张（A6）是全幕最亮的
     沙滩，顶部近白，白字压上去几乎读不了。峰值取 .80/.74 是量出来的：
     390×844 逐 1% 扫全幕，56 个「主句在屏上」的帧里，核心字形像素（覆盖度 > 0.985，
     排除抗锯齿边）对底色的对比 **最低 4.56:1**（A6，p 0.86–0.92 一带），中位 ≥ 5.27。
     底色不是猜的：同一帧拍两张（带字 / 把 #a04-caps 隐藏），按 A = cov·paper + (1−cov)·B
     反解出字形覆盖度，再去 B 上取字底下那一片的真实亮度。
     探针：__a04probe.noScrim = true 把这两段纱关掉，同样三帧 p5 当场掉到 1.15–1.17
     （白字压白沙，肉眼读不出） —— 这条检查会不会红，验过。 */
  .a04-scrim { background:
      linear-gradient(180deg, rgba(15,31,61,.80), rgba(15,31,61,.74) 42%,
        rgba(15,31,61,.34) 74%, rgba(15,31,61,0)) top/100% 40dvh no-repeat,
      linear-gradient(0deg,   rgba(15,31,61,.74), rgba(15,31,61,.68) 42%,
        rgba(15,31,61,.30) 76%, rgba(15,31,61,0)) bottom/100% 28dvh no-repeat }
  /* §二十五：车票卡下沿原来是 15dvh，360 档与 .a04-subm 咬住（间隙 −2.8）。
     改成从底槽那两行往上推 —— 与 .a04-subm 同一个基准（--sCB + 36px 是副标下沿），
     再让出副标自己的行高（34px）与 12px 槽间距，写死的 15dvh 换成算出来的。 */
  .a04-ticket { left: 6vw; right: 6vw; width: auto;
    inset: auto 6vw calc(var(--sCB, 8dvh) + 82px) auto }
  .a04-ticket img { width: 92px; height: 66px }
  .act04-hud { left: 5vw; right: 5vw; bottom: calc(var(--sCB, 8dvh) - 4px) }
  .a04-dots li span { display: none }
  .a04-dots li button { padding: 4px 6px 0 }
  .a04-tip { display: none }
  .a04-sweep { width: 19% }
}

/* ══════════════════ 静态退化（prefers-reduced-motion / 无 JS）══════════════════
   §二十四.2「prefers-reduced-motion 静态可读」：六张照片 + 站名 + 副标，顺序即故事。
   这一档不搬家（三组仍留在 #lay04 里），跟着页面退化流的 .lay 一起落回文档流。 */
.act04.is-static .act04-sky, .act04.is-static .act04-fore { display: none }
.act04.is-static .act04-bg { position: static; opacity: 1; contain: none }
.act04.is-static .act04-bg > * { position: static }
.act04.is-static .act04-wisps, .act04.is-static .act04-depart,
.act04.is-static .a04-scrim, .act04.is-static .a04-flare-city { display: none }
.act04.is-static .act04-cities { display: block }
.act04.is-static .act04-city { position: relative; inset: auto; display: block; margin: 0 0 28px }
.act04.is-static .act04-city[data-under] { display: none }
.act04.is-static .act04-cimgs { position: relative; height: 44vh; opacity: 1 !important;
  transform: none !important }
.act04.is-static .a04-cl { position: absolute; left: 0; top: 0; width: 100%; height: 100%;
  transform: none !important }
.act04.is-static .a04-cl-mid, .act04.is-static .a04-cl-near { display: none }
.act04.is-static .a04-cap { position: relative; left: auto; right: auto; top: auto; bottom: auto;
  max-width: none; opacity: 1 !important; transform: none !important;
  padding: 20px 6vw 0; text-shadow: none }
.act04.is-static .a04-day { color: var(--body, #3B4A63); background: rgba(15,31,61,.06) }
.act04.is-static .a04-name { color: var(--ink, #0F1F3D); font-size: clamp(30px, 5vw, 52px) }
.act04.is-static .a04-sub { color: var(--body, #3B4A63); position: static }
.act04.is-static .a04-arr { color: var(--ink, #0F1F3D); background: rgba(15,31,61,.05) }
.act04.is-static .a04-subm { display: none }
/* 静态档里站名要跟着自己那张照片走 —— caps 是独立一层，按 data-cap 重新插回去做不到，
   所以这一档把 caps 整层排在照片之后当作图注序列（顺序与照片一致，读起来仍是六段行程）。 */
.act04.is-static .act04-caps { position: static; display: block }
.act04.is-static .a04-cap { display: block; margin: 0 0 22px }

/* 顺带保证：模块被 dispose 之后剩下的空壳不占画面 */
.act04[hidden] { display: none !important }
