From a354ed5b7bcda9d15edb7329a37d6ffbc6314e4c Mon Sep 17 00:00:00 2001 From: jwinterm Date: Tue, 18 Aug 2026 16:25:07 -0400 Subject: [PATCH] fix(splash): stop the handover re-locking scroll for the rest of the visit First visit of a session ended with the page unscrollable. A hard refresh appeared to fix it, which is what made it look like a caching oddity rather than a bug: the session flag then skips the splash and neither lock runs. Two things were managing html.style.overflow and they fought. 1. The splash's own lock sets overflow:hidden while the cover is up, saving the previous value (""). 2. enterFromButton then captured the previous value for the transition lock. By then it was already "hidden", from step 1. 3. `entered` flips, the splash lock cleans up and restores "". Page scrolls. 4. Up to 3s later the transition's release() restores its captured value, which is "hidden". Page is locked, and nothing ever clears it. The release is now unconditional. By the time it runs the splash is gone and the page must scroll, so restoring a captured value is wrong by construction: there is no state worth preserving, only a lock to drop. Verified with a real wheel event 5s after load, past both the 1337ms gate and the 3s release cap where the re-lock used to land: overflow is empty and the page scrolls. --- apps/web/src/components/landing/home-shell.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/web/src/components/landing/home-shell.tsx b/apps/web/src/components/landing/home-shell.tsx index df8e100..cf1c8fb 100644 --- a/apps/web/src/components/landing/home-shell.tsx +++ b/apps/web/src/components/landing/home-shell.tsx @@ -106,7 +106,13 @@ export function HomeShell({ children }: { children: ReactNode }) { // so you land below the hero instead of at the very top (same as a click would). // Freeze scrolling for the transition, then release exactly at the top. const html = document.documentElement; - const prevOverflow = html.style.overflow; + // Do NOT capture-and-restore the previous value here. The splash's own lock is + // already holding overflow at "hidden" when this runs, so capturing it and putting + // it back at release re-applied the lock AFTER the splash had gone and left the page + // unscrollable for the rest of the visit. A hard refresh appeared to fix it only + // because the session flag then skips the splash and neither lock runs. + // By the time this releases the splash is gone and the page MUST scroll, so the + // release is unconditional rather than a restore. html.style.overflow = "hidden"; window.scrollTo(0, 0); // Any scroll that sneaks past the overflow lock gets yanked back to 0. @@ -125,7 +131,7 @@ export function HomeShell({ children }: { children: ReactNode }) { window.removeEventListener("scroll", pin); window.removeEventListener("wheel", note); window.removeEventListener("touchmove", note); - html.style.overflow = prevOverflow; + html.style.overflow = ""; window.scrollTo(0, 0); }; const started = performance.now();