body { margin: 0; font-family: "Avenir Next", sans-serif; background: #070b14; color: #f3f7ff; }
.back { position: fixed; top: 14px; left: 14px; color: #86e8ff; text-decoration: none; z-index: var(--z-ui); }
.hero { min-height: 100vh; display: grid; place-content: center; text-align: center; gap: 0.8rem; position: relative; overflow: hidden; }
.ambient { position: absolute; inset: 0; z-index: var(--z-bg); }
.orb { position: absolute; border-radius: 50%; filter: blur(10px); opacity: 0.6; }
.a { width: 30vw; height: 30vw; left: 8%; top: 20%; background: #6cc9ff55; }
.b { width: 24vw; height: 24vw; right: 12%; top: 12%; background: #d077ff55; }
.c { width: 18vw; height: 18vw; right: 22%; bottom: 14%; background: #ffe7a355; }
.kicker { margin: 0; color: #86e8ff; letter-spacing: .12em; text-transform: uppercase; }
h1 { margin: 0; font-size: clamp(2.2rem, 8vw, 5rem); }
.subtitle { margin: 0; color: #c0cee2; }
.actions { display: flex; gap: .7rem; justify-content: center; flex-wrap: wrap; }
button { border: 0; border-radius: 999px; padding: .75rem 1rem; font-weight: 700; cursor: pointer; }
button:first-child { background: #1f2f49; color: #fff; }
button:last-child { background: #86e8ff; color: #062030; }
if (!window.MotionPreference) {
const __mql = window.matchMedia("(prefers-reduced-motion: reduce)");
const __listeners = new Set();
const MotionPreference = {
prefersReducedMotion() {
return __mql.matches;
},
setOverride(value) {
const reduced = Boolean(value);
document.documentElement.classList.toggle("reduced-motion", reduced);
window.dispatchEvent(new CustomEvent("motion-preference", { detail: { reduced } }));
for (const listener of __listeners) {
try {
listener({ reduced, override: reduced, systemReduced: __mql.matches });
} catch {}
}
},
onChange(listener) {
__listeners.add(listener);
try {
listener({
reduced: __mql.matches,
override: null,
systemReduced: __mql.matches,
});
} catch {}
return () => __listeners.delete(listener);
},
getState() {
return { reduced: __mql.matches, override: null, systemReduced: __mql.matches };
},
};
window.MotionPreference = MotionPreference;
}
const toggle = document.getElementById("toggle");
let reduced = window.MotionPreference.prefersReducedMotion();
const active = [];
function track(animation) {
active.push(animation);
return animation;
}
function teardown() {
active.forEach((animation) => animation.kill());
active.length = 0;
document.querySelectorAll(".orb").forEach((n) => {
n.style.transform = "none";
});
document.querySelectorAll(".kicker, h1, .subtitle, .actions").forEach((n) => {
n.style.opacity = "1";
n.style.transform = "none";
});
}
function play() {
if (reduced || !window.gsap) return;
const tl = track(window.gsap.timeline());
tl.from(".kicker", { y: 24, opacity: 0, duration: 0.4 })
.from("h1", { y: 40, opacity: 0, duration: 0.6, ease: "power3.out" }, "<0.08")
.from(".subtitle", { y: 18, opacity: 0, duration: 0.45 }, "<0.08")
.from(".actions", { y: 20, opacity: 0, duration: 0.4 }, "<0.12");
track(window.gsap.to(".a", { x: 24, y: -16, duration: 4, repeat: -1, yoyo: true, ease: "sine.inOut" }));
track(window.gsap.to(".b", { x: -22, y: 16, duration: 5, repeat: -1, yoyo: true, ease: "sine.inOut" }));
track(window.gsap.to(".c", { x: 16, y: -12, duration: 3.2, repeat: -1, yoyo: true, ease: "sine.inOut" }));
}
function apply() {
teardown();
toggle.textContent = reduced ? "Enable motion" : "Disable motion";
play();
}
toggle.addEventListener("click", () => {
reduced = !reduced;
apply();
});
apply();
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Demo 02 - GSAP Hero Intro</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<a class="back" href="../">Back</a>
<main class="hero scene-intro-hero">
<div class="ambient" aria-hidden="true">
<span class="orb a"></span>
<span class="orb b"></span>
<span class="orb c"></span>
</div>
<p class="kicker">Demo 02</p>
<h1>GSAP Hero Intro</h1>
<p class="subtitle">Headline, CTA, and ambient layers in a controlled timeline.</p>
<div class="actions">
<button id="toggle">Disable motion</button>
<button>Start Project</button>
</div>
</main>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/gsap.min.js"></script>
<script src="script.js"></script>
</body>
</html>