v3: P2 prompt pack (scenes 00, II-V) + pilot bench - regime detect, HUD, hand scrub

This commit is contained in:
2026-08-17 01:33:16 +10:00
parent dcbf896222
commit 5cce8c5cd3
8 changed files with 434 additions and 23 deletions
+59 -23
View File
@@ -96,6 +96,12 @@ body{margin:0;background:var(--void);color:var(--ink);font-family:var(--sans);
.masthead{padding:10vh 22px 6vh}
}
/* ---- pilot bench (HUD + hand scrub) - strip before P3 wiring ---- */
.bench{width:min(720px,100%);margin:18px auto 0;text-align:center}
.bench input{width:100%;accent-color:var(--cyan)}
.bench .hud{font-family:var(--mono);font-size:.68rem;letter-spacing:.06em;
color:var(--dim);margin:.5rem 0 0}
/* ---- no JS: the flat hero and the full legend are the page ---- */
body:not(.fx) .slab{display:none}
body:not(.fx) .legend li{position:static;transform:none;opacity:1}
@@ -157,37 +163,67 @@ body:not(.fx) .legend{height:auto;min-height:0;display:flex;flex-direction:colum
setLit(t);
};
const mm = gsap.matchMedia();
// ---- pilot bench: HUD + hand scrub - strip before P3 wiring ----
const bench = document.createElement("div");
bench.className = "bench";
bench.innerHTML =
'<input type="range" min="0" max="1000" value="0" aria-label="scrub the teardown">' +
'<p class="hud">boot</p>';
document.querySelector(".pin").appendChild(bench);
const hud = bench.querySelector(".hud");
const slider = bench.querySelector("input");
let mode = "boot";
const show = v => {
hud.textContent = mode + " \u00b7 " + innerWidth + "x" + innerHeight +
" \u00b7 scroll " + Math.round(scrollY) + " of " +
Math.max(0, document.documentElement.scrollHeight - innerHeight) +
" \u00b7 t=" + v.toFixed(2);
slider.value = String(Math.round(v * 1000));
};
const drive = v => { apply(v); show(v); };
slider.addEventListener("input", () => drive(+slider.value / 1000));
mm.add("(min-width: 861px) and (prefers-reduced-motion: no-preference)", () => {
const st = ScrollTrigger.create({
// The artifact viewer can size the iframe to the content and scroll the PARENT page:
// then this window never scrolls and a pinned scrub can never fire. Pick the engine
// by what the environment can actually do, not by viewport width alone.
const initPin = () => {
mode = "pin+scrub";
ScrollTrigger.create({
trigger: "#course-1",
start: "top top",
end: "+=2200",
pin: ".pin",
scrub: 0.6,
onUpdate: self => apply(self.progress),
onUpdate: self => drive(self.progress),
onRefreshInit: () => apply(0)
});
apply(0);
return () => { st.kill(); apply(1); };
});
ScrollTrigger.refresh();
drive(0);
};
// Small screens and reduced-motion: no pin, no scrub. The scene assembles once on
// entry so the teardown still *reads*, then stays put.
mm.add("(max-width: 860px), (prefers-reduced-motion: reduce)", () => {
if (RM) { apply(1); return () => {}; }
apply(0);
const st = ScrollTrigger.create({
trigger: "#course-1",
start: "top 70%",
once: true,
onEnter: () => gsap.to({ v: 0 }, {
v: 1, duration: 1.5, ease: "power3.out",
onUpdate: function () { apply(this.targets()[0].v); }
})
});
return () => { st.kill(); apply(1); };
});
const initAssemble = () => {
mode = "assemble";
document.querySelector(".pin").style.minHeight = "0"; // svh is degenerate when the frame is content-sized
drive(0);
const io = new IntersectionObserver(es => {
if (!es.some(e => e.isIntersecting)) return;
io.disconnect();
gsap.to({ v: 0 }, {
v: 1, duration: 1.6, ease: "power3.out",
onUpdate: function () { drive(this.targets()[0].v); }
});
}, { threshold: 0.15 });
io.observe(scene);
};
const boot = () => {
if (RM) { mode = "reduced-motion"; drive(1); return; }
const scrollable = document.documentElement.scrollHeight - innerHeight > 120;
if (scrollable && matchMedia("(min-width: 861px)").matches) initPin();
else initAssemble();
};
const flat = scene.querySelector(".flat");
(flat.decode ? flat.decode().catch(() => {}) : Promise.resolve())
.then(() => requestAnimationFrame(() => requestAnimationFrame(boot)));
})();
</script>