Add logo loading screen gated on animation readiness

- Full-screen loader whose only content is the logo animating: the "B"
  draws in and fills, the base facets trace in, then a wave runs along
  them until the page is ready (pure CSS, plays before JS)
- Loader gate holds the loader until GSAP + fonts and every 3D scene's
  first frame are ready, then waits 1s (min 1.4s total) before leaving;
  hard 8s limit, head-script fallback if JS never boots
- Scroll reveals, SplitText and scene entry animations start only once
  the loader leaves, so above-the-fold entries play in view
- Internal links fade the loader back in before navigating, so page
  changes are one seamless dark transition
- No loader for no-JS or reduced-motion visitors

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-11-05 07:17:54 +01:00
co-authored by Claude Opus 5.5
parent 6e7219d30b
commit c3514d2607
9 changed files with 432 additions and 21 deletions
+19 -4
View File
@@ -1,15 +1,30 @@
import React from "react";
import { holdLoader } from "../loader/gate";
/** Boot the site-wide GSAP motion system (see ./motion.ts). */
/**
* Boot the site-wide GSAP motion system (see ./motion.ts). Holds the page
* loader until GSAP is loaded and fonts are settled, so SplitText measures
* final line breaks.
*/
export default function useMotion() {
React.useEffect(() => {
let destroy: (() => void) | undefined;
let cancelled = false;
import("./motion").then(({ initMotion }) => {
if (!cancelled) destroy = initMotion();
});
const release = holdLoader("motion");
Promise.all([
import("./motion"),
document.fonts?.ready.catch(() => {}),
])
.then(([{ initMotion }]) => {
if (!cancelled) destroy = initMotion();
})
.catch((error) => console.warn("Motion failed to load", error))
.finally(release);
return () => {
cancelled = true;
release();
destroy?.();
};
}, []);