ui: Motion-driven animation layer (motion.dev)
build-and-deploy / build (push) Failing after 14m48s

Unify scroll reveals, hero entrance, nav active-underline, button springs,
the scroll-progress bar and backdrop parallax under one Motion system
(src/scripts/anim.ts), bundled into a self-hosted /_astro module so CSP
script-src 'self' holds. Progress bar + parallax now scroll()-driven and
cross-browser (was Chromium-only animation-timeline). Fail-open, reduced-motion
aware, content fully visible with JS off.
This commit is contained in:
2026-06-28 23:23:58 +10:00
parent 5d439752de
commit f6d92e5c87
8 changed files with 259 additions and 2195 deletions
+9 -54
View File
@@ -12,6 +12,11 @@
document.documentElement.dataset.theme = "dark";
}
// Mark JS as live BEFORE first paint. CSS only hides the pre-reveal animation
// state under `html.js`, so with JS off every element stays visible (the
// Motion layer in /_astro reveals them once it runs). Progressive enhancement.
document.documentElement.classList.add("js");
function onReady(fn) {
if (document.readyState !== "loading") fn();
else document.addEventListener("DOMContentLoaded", fn);
@@ -33,59 +38,9 @@
});
}
// --- Reveal on scroll (progressive enhancement, motion-aware) ----------
var reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
var targets = document.querySelectorAll("[data-reveal]");
if (!reduce && "IntersectionObserver" in window) {
targets.forEach(function (el) {
el.classList.add("reveal");
});
var io = new IntersectionObserver(
function (entries) {
entries.forEach(function (entry) {
if (entry.isIntersecting) {
entry.target.classList.add("is-visible");
io.unobserve(entry.target);
}
});
},
{ rootMargin: "0px 0px -10% 0px", threshold: 0.1 },
);
targets.forEach(function (el) {
io.observe(el);
});
}
// --- Scroll-coupled background pan -------------------------------------
// The Bat-Computer backdrop pans vertically in lock-step with scroll —
// moving ONLY while you scroll, still at rest. Plain scroll listener so it
// works in every browser (unlike CSS animation-timeline: scroll(), which is
// Chromium-only). rAF-throttled; sets a CSS var the transform reads.
var bgImgs = document.querySelectorAll(".site-bg__img");
if (bgImgs.length) {
var ticking = false;
function updateBg() {
ticking = false;
var doc = document.documentElement;
var max = doc.scrollHeight - window.innerHeight;
var p = max > 0 ? Math.min(1, Math.max(0, window.scrollY / max)) : 0;
// +half a range at the top → -half at the bottom: scrolling down pans
// the camera down the board. Range kept within the image's overhang so
// an edge never shows (image is 215% tall → 57.5vh spare each side).
var shift = (0.5 - p) * window.innerHeight * 0.7;
for (var i = 0; i < bgImgs.length; i++) {
bgImgs[i].style.setProperty("--bg-shift", shift.toFixed(1) + "px");
}
}
function onScroll() {
if (!ticking) {
ticking = true;
window.requestAnimationFrame(updateBg);
}
}
window.addEventListener("scroll", onScroll, { passive: true });
window.addEventListener("resize", onScroll, { passive: true });
updateBg();
}
// Scroll reveals, hero entrance, backdrop parallax, scroll-progress bar,
// nav underline and button springs all live in the Motion layer
// (src/scripts/anim.ts → bundled /_astro/*.js). This file owns only the
// pre-paint theme so it can stay a tiny blocking <head> script.
});
})();