Skip to content

Commit aefaac8

Browse files
Stop button: fade the starfield out after the cube, leaving no animation
The "calm" state still left the starfield drifting forever, which is too distracting. Add a second wind-down stage: after the cube and chunk streams recede (unchanged), fade the starfield to nothing, then park the render loop so there is no animated background at all. Resume reverses both stages and restarts the loop. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 5083a54 commit aefaac8

1 file changed

Lines changed: 32 additions & 8 deletions

File tree

index2.html

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1298,8 +1298,9 @@ <h2>Trusted by sysadmins, hoarders and the merely paranoid.</h2>
12981298
}
12991299

13001300
/* --- scroll-driven state --- */
1301-
/* calm: 0 = full animation, 1 = cube gone, chunks stopped, stars only */
1302-
const state = { progress: 0, mouseX: 0, mouseY: 0, calm: 0 };
1301+
/* calm: 0 = full animation, 1 = cube gone, chunks stopped, stars only
1302+
starFade: 0 = stars at full brightness, 1 = stars gone (no animation left) */
1303+
const state = { progress: 0, mouseX: 0, mouseY: 0, calm: 0, starFade: 0 };
13031304

13041305
window.addEventListener('pointermove', (e) => {
13051306
state.mouseX = (e.clientX / window.innerWidth - 0.5) * 2;
@@ -1371,6 +1372,9 @@ <h2>Trusted by sysadmins, hoarders and the merely paranoid.</h2>
13711372
sp.needsUpdate = true;
13721373
// barely-there lateral sway; bounded so the wrap plane stays behind the camera
13731374
stars.rotation.y = Math.sin(t * 0.05) * 0.04;
1375+
// second wind-down stage: once the cube is gone, the starfield fades to nothing
1376+
stars.material.opacity = 0.7 * (1 - state.starFade);
1377+
stars.visible = state.starFade < 0.999;
13741378

13751379
// lattice: home position pushed along explode direction by scroll
13761380
if (cubeGroup.visible) {
@@ -1435,14 +1439,34 @@ <h2>Trusted by sysadmins, hoarders and the merely paranoid.</h2>
14351439
renderer.setAnimationLoop(render);
14361440
}
14371441

1438-
/* --- stop button: wind the scene down to a calm starfield --- */
1442+
/* --- stop button: wind the scene down in two stages, then to nothing ---
1443+
stop: cube + chunks recede (as before), then the starfield fades out and
1444+
the render loop is parked, leaving no animated background at all.
1445+
resume: restart the loop, fade the stars back, then bring the cube back. */
14391446
const stopBtn = document.getElementById('stopBtn');
1447+
let stopped = false;
1448+
let windTween = null;
14401449
stopBtn.addEventListener('click', () => {
1441-
const toCalm = state.calm < 0.5;
1442-
stopBtn.textContent = toCalm ? 'Resume' : 'Stop';
1443-
stopBtn.setAttribute('aria-pressed', String(toCalm));
1444-
if (reduced) { state.calm = toCalm ? 1 : 0; render(); return; }
1445-
gsap.to(state, { calm: toCalm ? 1 : 0, duration: 2.8, ease: 'power2.inOut', overwrite: 'auto' });
1450+
stopped = !stopped;
1451+
stopBtn.textContent = stopped ? 'Resume' : 'Stop';
1452+
stopBtn.setAttribute('aria-pressed', String(stopped));
1453+
if (reduced) {
1454+
state.calm = state.starFade = stopped ? 1 : 0;
1455+
render();
1456+
return;
1457+
}
1458+
if (windTween) windTween.kill();
1459+
if (stopped) {
1460+
windTween = gsap.timeline()
1461+
.to(state, { calm: 1, duration: 2.8, ease: 'power2.inOut' })
1462+
.to(state, { starFade: 1, duration: 1.6, ease: 'power2.inOut',
1463+
onComplete: () => renderer.setAnimationLoop(null) });
1464+
} else {
1465+
renderer.setAnimationLoop(render); // wake the loop before reversing
1466+
windTween = gsap.timeline()
1467+
.to(state, { starFade: 0, duration: 1.0, ease: 'power2.out' })
1468+
.to(state, { calm: 0, duration: 2.8, ease: 'power2.inOut' });
1469+
}
14461470
});
14471471

14481472
// debugging/testing handle (this page is a prototype)

0 commit comments

Comments
 (0)