mirror of
https://github.com/Such-Software/such.software.git
synced 2026-10-17 05:32:05 +02:00
home: scroll-enter lands at the very top; bloomword motion front-loaded; webshops copy
Scroll-enter: the triggering gesture's MOMENTUM outlives the 800ms wave (a trackpad flick trails ~1.5s), and those ticks scrolled the revealed page so you landed below the hero. The scroll lock now holds until the input goes quiet (250ms without a wheel/touch event, 3s cap) and releases exactly at the top. Verified with a simulated 14-tick 1.5s flick: lands scrollY=0, and a fresh gesture afterwards scrolls normally. Bloomword recut: the loop opened with a 1s bloom hold so hover felt dead (movement backloaded). Now a 0.2s hold then a brisk 3x retract into the full regrow, closing bob at the end; still frame0==frameN (seam 0.0000), 390KB. Webshops tagline: 'One platform, unlimited possibilities.'
This commit is contained in:
Binary file not shown.
@@ -107,9 +107,46 @@ export function HomeShell({ children }: { children: ReactNode }) {
|
||||
sessionStorage.setItem("such:entered", "1");
|
||||
setEntered(true);
|
||||
setLeaving(true);
|
||||
// Scroll-intent entries arrive with wheel/touch MOMENTUM still flowing; without a
|
||||
// lock those trailing events scroll the freshly-revealed page down during the wave,
|
||||
// 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;
|
||||
html.style.overflow = "hidden";
|
||||
window.scrollTo(0, 0);
|
||||
// Any scroll that sneaks past the overflow lock gets yanked back to 0.
|
||||
const pin = () => window.scrollTo(0, 0);
|
||||
window.addEventListener("scroll", pin, { passive: true });
|
||||
// The gesture that triggered the enter keeps emitting MOMENTUM (a trackpad flick
|
||||
// can trail for ~1.5s — longer than the 800ms wave). Releasing the lock on a fixed
|
||||
// timer let those trailing ticks scroll the revealed page, so you landed below the
|
||||
// hero instead of at the very top. Hold the lock until the input goes QUIET
|
||||
// (250ms with no wheel/touch), capped at 3s so a deliberate scroller is never trapped.
|
||||
let lastInput = performance.now();
|
||||
const note = () => { lastInput = performance.now(); };
|
||||
window.addEventListener("wheel", note, { passive: true });
|
||||
window.addEventListener("touchmove", note, { passive: true });
|
||||
const release = () => {
|
||||
window.removeEventListener("scroll", pin);
|
||||
window.removeEventListener("wheel", note);
|
||||
window.removeEventListener("touchmove", note);
|
||||
html.style.overflow = prevOverflow;
|
||||
window.scrollTo(0, 0);
|
||||
};
|
||||
const started = performance.now();
|
||||
const tryRelease = () => {
|
||||
const now = performance.now();
|
||||
if (now - lastInput > 250 || now - started > 3000) release();
|
||||
else setTimeout(tryRelease, 100);
|
||||
};
|
||||
// Keep the splash mounted briefly as a fixed, fading overlay so it cross-fades
|
||||
// into the revealed content instead of hard-cutting.
|
||||
setTimeout(() => setSplashUp(false), 800);
|
||||
// into the revealed content instead of hard-cutting; release the scroll lock only
|
||||
// once the wave is done AND the triggering gesture's momentum has died down.
|
||||
setTimeout(() => {
|
||||
setSplashUp(false);
|
||||
tryRelease();
|
||||
}, 800);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -67,7 +67,7 @@ type Item = {
|
||||
// (r2,c3) / (r3,c1-2) / (r3,c3).
|
||||
// Videos play on hover/focus on desktop and while on screen on no-hover (touch) devices.
|
||||
const items: Item[] = [
|
||||
{ name: "Webshops", tagline: "Custom storefronts we build and host. One platform, five completely different shops.", color: "emerald", span: 2, rows: 2, image: "/showcase/reels/webshops.jpg", alt: "Five differently themed demo storefronts built on the same platform", href: "/products/webshops", vignette: { webm: "/showcase/reels/webshops.webm", poster: "/showcase/reels/webshops.jpg", mp4: "/showcase/reels/webshops.mp4", fit: "cover" } },
|
||||
{ name: "Webshops", tagline: "Custom storefronts we build and host. One platform, unlimited possibilities.", color: "emerald", span: 2, rows: 2, image: "/showcase/reels/webshops.jpg", alt: "Differently themed demo storefronts built on the same platform", href: "/products/webshops", vignette: { webm: "/showcase/reels/webshops.webm", poster: "/showcase/reels/webshops.jpg", mp4: "/showcase/reels/webshops.mp4", fit: "cover" } },
|
||||
{ name: "Smirk Wallet", tagline: "Non-custodial browser wallet with social tipping.", color: "amber", span: 1, image: "/images/products/smirk-wallet.png", alt: "Smirk Wallet browser extension", href: "https://smirk.cash", external: true },
|
||||
// Transparent chroma-keyed vignette (the engine can't emit real alpha — BW_FLAT magenta key):
|
||||
// full-bloom tree at rest, hover retracts and regrows it. Palindrome loop, seam 0.0.
|
||||
|
||||
Reference in New Issue
Block a user