<section class="demo" id="stage">
<div class="demo__spot" id="spot"></div>
<div class="demo__copy">
<p>Look closer</p>
<h1>What stays in shadow is still designed.</h1>
</div>
</section>.demo {
position: relative;
min-height: 100%;
height: 100%;
display: grid;
place-items: center;
padding: 32px;
background: #08080a;
color: #f4f4f2;
font-family: "Helvetica Neue", system-ui, sans-serif;
--sx: 50%;
--sy: 42%;
}
.demo__spot {
position: absolute;
inset: 0;
pointer-events: none;
background: radial-gradient(circle 150px at var(--sx) var(--sy), transparent 0%, rgba(8, 8, 10, 0.88) 62%);
}
.demo__copy {
position: relative;
z-index: 1;
max-width: 16em;
text-align: center;
}
.demo__copy p {
margin: 0 0 12px;
font-size: 11px;
letter-spacing: 0.2em;
text-transform: uppercase;
color: #5a5a62;
}
.demo__copy h1 {
margin: 0;
font-size: clamp(28px, 6vw, 48px);
font-weight: 500;
letter-spacing: -0.04em;
line-height: 1.1;
}const stage = document.querySelector("#stage");
const pos = { x: 50, y: 42 };
const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
let idle = null;
function apply() {
stage.style.setProperty("--sx", pos.x + "%");
stage.style.setProperty("--sy", pos.y + "%");
}
stage.addEventListener("pointermove", function (event) {
if (idle) {
idle.kill();
idle = null;
}
const rect = stage.getBoundingClientRect();
gsap.to(pos, {
x: ((event.clientX - rect.left) / rect.width) * 100,
y: ((event.clientY - rect.top) / rect.height) * 100,
duration: 0.45,
ease: "power3.out",
onUpdate: apply,
overwrite: true,
});
});
if (!reduce) {
idle = gsap.to(pos, {
x: 64,
y: 58,
duration: 3.2,
ease: "sine.inOut",
yoyo: true,
repeat: -1,
onUpdate: apply,
});
}
apply();