fix: step the reading plates aside at click time on long navigator jumps

This commit is contained in:
2026-10-11 00:12:37 +03:00
parent 10b867143b
commit 2cbf42f7f8
2 changed files with 36 additions and 4 deletions

View File

@@ -94,6 +94,35 @@ export function validateSectionAnchor(
return heading === null ? null : { id, target };
}
/** How long a pre-armed interlude waits for the scene loop to take over. */
const PRE_ARM_TIMEOUT_MS = 1400;
/** Matches INTERLUDE_RISE_MS in the environment loop. */
const PRE_ARM_RISE_MS = 1100;
/**
* A long navigator scroll passes the chapters in between. While the scene runs
* (the loop has set data-gantry), the reading plates step aside at click time
* instead of when the sign starts its approach, so those chapters do not flash
* past. The loop takes the attribute over as soon as the sign's interlude
* starts; if no interlude follows, the plates rise again after a timeout.
*/
const preArmInterlude = (): void => {
const root = document.documentElement;
if (root.dataset.gantry === undefined || root.dataset.interlude === "on") return;
root.dataset.interlude = "on";
root.dataset.interludeArmed = "nav";
window.setTimeout(() => {
if (root.dataset.interludeArmed !== "nav") return;
root.dataset.interlude = "rise";
root.dataset.interludeArmed = "rise";
window.setTimeout(() => {
if (root.dataset.interludeArmed !== "rise") return;
delete root.dataset.interlude;
delete root.dataset.interludeArmed;
}, PRE_ARM_RISE_MS);
}, PRE_ARM_TIMEOUT_MS);
};
const browserRuntime = (): NavigationRuntime => ({
getHash: () => window.location.hash,
pushHash: (hash) => window.history.pushState(window.history.state, "", hash),
@@ -107,10 +136,10 @@ const browserRuntime = (): NavigationRuntime => ({
return;
}
const margin = parseFloat(getComputedStyle(target).scrollMarginTop) || 0;
window.scrollTo({
top: window.scrollY + target.getBoundingClientRect().top - margin,
behavior,
});
const delta = target.getBoundingClientRect().top - margin;
if (behavior === "smooth" && Math.abs(delta) > window.innerHeight * 0.6)
preArmInterlude();
window.scrollTo({ top: window.scrollY + delta, behavior });
},
focusHeading: (target) => {
// The live reading stage can start or stop while a long journey is still

View File

@@ -590,6 +590,8 @@ export async function createProceduralSceneGeneration(
interludeOn = interlude;
riseUntil = interlude ? 0 : now + INTERLUDE_RISE_MS;
document.documentElement.dataset.interlude = interlude ? 'on' : 'rise';
// A navigator click may have pre-armed the interlude; the loop owns it now.
delete document.documentElement.dataset.interludeArmed;
} else if (riseUntil > 0 && now > riseUntil) {
riseUntil = 0;
delete document.documentElement.dataset.interlude;
@@ -630,6 +632,7 @@ export async function createProceduralSceneGeneration(
disposed = true;
delete document.documentElement.dataset.gantry;
delete document.documentElement.dataset.interlude;
delete document.documentElement.dataset.interludeArmed;
// Quality changes reuse the canvas context. Leave texture upload flags at
// defaults before the next renderer creates its empty array textures.
const gl = renderer.getContext();