style: trim the interlude to about 1.5 s

This commit is contained in:
2026-10-11 00:43:25 +03:00
parent fc897e534c
commit c8bc75cdcc
5 changed files with 26 additions and 19 deletions

View File

@@ -97,7 +97,7 @@ export function validateSectionAnchor(
/** 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;
const PRE_ARM_RISE_MS = 800;
/**
* A long navigator scroll passes the chapters in between. While the scene runs

View File

@@ -649,7 +649,7 @@ const OUTRO_RETURN_SECONDS = 1.6;
const OUTRO_REACH = 0.62;
const ENGINE_OFF_NIGHT = new THREE.Color(0x000000);
/** Plates rise back over 720 ms with up to three 90 ms staggers. */
const INTERLUDE_RISE_MS = 1100;
const INTERLUDE_RISE_MS = 800;
function easeInOutCubic(t: number): number {
return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;

View File

@@ -47,8 +47,12 @@ const TILT_RADIANS = 0.07;
const HANDOFF_MS = 200;
/** Above this scroll drive speed a passive crossing skips the interlude. */
const BRISK_DRIVE_SPEED = 0.5;
/** Share of the approach spent on the truss before the arc into the window. */
const ON_TRUSS = 0.7;
/** Share of the approach spent on the truss before the arc into the window.
* Round 6 trimmed the hold so the arc carries more of the approach. */
const ON_TRUSS = 0.62;
/** The reading plates start to rise about 180 ms (of a 900 ms approach)
* before the sign docks, so the interlude closes in about 1.5 s. */
const PLATE_RISE_PROGRESS = 0.8;
const SIGN_MARKER_FONT =
'600 60px "Geist Mono Variable", ui-monospace, monospace';
@@ -524,7 +528,8 @@ export function createSign(
return state;
},
interlude() {
return !brisk && state === 'incoming';
if (brisk || state !== 'incoming') return false;
return !(snapshot?.phase === 'approach' && snapshot.progress >= PLATE_RISE_PROGRESS);
},
compose(camera, width, height, dock, now, tilt = REST_TILT) {
const dt = Math.min(0.05, Math.max(0, (now - previousNow) / 1000));

View File

@@ -708,45 +708,47 @@ html .drive-controls__audio-status {
/* ---------- The interlude under the gantry ---------- */
/* While a sign approaches (environment.ts sets data-interlude='on'), the
reading plates step up and out in 260 ms, so the road and the sign have
the frame to themselves; then ('rise') they come back up from below in
sequence. Headings only fade: journey and audio measure them. */
reading plates step up and out in 180 ms, so the road and the sign have
the frame to themselves; the headings follow once the plates are gone, so
a departing plate never ghosts over an arriving title. Then ('rise', about
180 ms before the sign docks) they come back up from below in sequence.
Headings only fade: journey and audio measure them. */
@media (prefers-reduced-motion: no-preference) {
html[data-interlude='on'] .reading-stage .portfolio-main > * > * {
opacity: 0;
transition: opacity 260ms cubic-bezier(0.5, 0, 0.75, 0);
transition: opacity 200ms cubic-bezier(0.5, 0, 0.75, 0) 160ms;
}
html[data-interlude='on'] .reading-stage .portfolio-main > * > :not(h1, h2, :has(h1, h2)) {
transform: translateY(-24px);
transition:
opacity 260ms cubic-bezier(0.5, 0, 0.75, 0),
transform 260ms cubic-bezier(0.5, 0, 0.75, 0);
opacity 180ms cubic-bezier(0.5, 0, 0.75, 0),
transform 180ms cubic-bezier(0.5, 0, 0.75, 0);
}
/* The exit tag's strike-on animation restarts as the plates return. */
html[data-interlude='on'] .reading-stage .portfolio-section::before,
html[data-interlude='on'] .reading-stage .portfolio-section[data-exit='08']::after {
opacity: 0;
animation: none;
transition: opacity 260ms cubic-bezier(0.5, 0, 0.75, 0);
transition: opacity 180ms cubic-bezier(0.5, 0, 0.75, 0);
}
html[data-interlude='rise'] .reading-stage .portfolio-section[data-exit='08']::after,
html[data-interlude='rise'] .reading-stage .portfolio-main > .portfolio-section > *,
html[data-interlude='rise'] .reading-stage .portfolio-main > .hero-journey > * {
animation: plate-fade 720ms cubic-bezier(0.22, 1, 0.36, 1) both;
animation: plate-fade 400ms cubic-bezier(0.22, 1, 0.36, 1) both;
}
html[data-interlude='rise'] .reading-stage .portfolio-main > .portfolio-section > :not(h1, h2, :has(h1, h2)),
html[data-interlude='rise'] .reading-stage .portfolio-main > .hero-journey > :not(h1, h2, :has(h1, h2)) {
animation: plate-rise 720ms cubic-bezier(0.22, 1, 0.36, 1) both;
animation: plate-rise 400ms cubic-bezier(0.22, 1, 0.36, 1) both;
}
/* The chapter body keeps its light-edge sweep alongside the rise. */
html[data-interlude='rise'] .reading-stage .portfolio-main > .portfolio-section[data-current] > .chapter-body {
animation:
plate-rise 720ms cubic-bezier(0.22, 1, 0.36, 1) both,
plate-rise 400ms cubic-bezier(0.22, 1, 0.36, 1) both,
exit-panel-light 1.6s cubic-bezier(0.2, 0.75, 0.2, 1) both;
}
html[data-interlude='rise'] .reading-stage .portfolio-main > * > :nth-child(2) { animation-delay: 90ms; }
html[data-interlude='rise'] .reading-stage .portfolio-main > * > :nth-child(3) { animation-delay: 180ms; }
html[data-interlude='rise'] .reading-stage .portfolio-main > * > :nth-child(n + 4) { animation-delay: 270ms; }
html[data-interlude='rise'] .reading-stage .portfolio-main > * > :nth-child(2) { animation-delay: 40ms; }
html[data-interlude='rise'] .reading-stage .portfolio-main > * > :nth-child(3) { animation-delay: 80ms; }
html[data-interlude='rise'] .reading-stage .portfolio-main > * > :nth-child(n + 4) { animation-delay: 120ms; }
}
@keyframes plate-rise {
from { opacity: 0; transform: translateY(28px); }

View File

@@ -164,7 +164,7 @@ describe('composed route signs', () => {
const h = setup();
h.render({ ...profile, phase: 'approach', progress: 0, opacity: 1 }, 30);
const early = h.render({ ...profile, phase: 'approach', progress: 0.2, opacity: 1 })!.bounds;
const big = h.render({ ...profile, phase: 'approach', progress: 0.69, opacity: 1 })!.bounds;
const big = h.render({ ...profile, phase: 'approach', progress: 0.61, opacity: 1 })!.bounds;
const arcing = h.render({ ...profile, phase: 'approach', progress: 0.85, opacity: 1 })!.bounds;
const docked = h.render(profile)!.bounds;
expect(h.sign.dockState()).toBe('docked');