Staggered fade-up
Tiles rise into place in a short wave as each row enters the viewport.
Artwork panels open from the centre like a curtain, tied directly to scroll position.
Each panel reads its own position on every scroll frame and turns it into a 0–1 progress value stored in --p. The clip-path inset shrinks from 50% to 0 as --p grows, so the reveal follows your scroll exactly, forwards and backwards.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Clip-path curtain</title>
<style>
*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; }
body { font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; -webkit-font-smoothing: antialiased; }
body { background: #14161f; color: #e9e6ff; }
.top { height: 60vh; display: grid; place-content: center; text-align: center; }
.top h1 { font-size: 3rem; margin: 0; }
.top p { opacity: .7; }
.stack { display: grid; gap: 14vh; padding: 0 6vw 40vh; }
.panel {
--p: 0; margin: 0; height: 70vh; border-radius: 18px; position: relative;
clip-path: inset(0 calc((1 - var(--p)) * 50%) round 18px);
}
.panel figcaption {
position: absolute; left: 24px; bottom: 20px; font-size: 2.4rem; font-weight: 700;
opacity: var(--p);
}
.a { background: radial-gradient(circle at 30% 80%, #ffb36b, transparent 50%), linear-gradient(160deg, #3b1d6e, #c2417a); }
.b { background: radial-gradient(circle at 70% 30%, #9ff2ff, transparent 40%), linear-gradient(200deg, #063a5c, #19a5a1); }
.c { background: radial-gradient(circle at 50% 50%, #ffe46b, transparent 35%), linear-gradient(120deg, #1e4d1a, #8fcf4b); }
.d { background: repeating-linear-gradient(45deg, #2c2c44 0 12px, #3d3d5e 12px 24px); }
</style>
</head>
<body>
<header class="top"><h1>Curtain reveal</h1><p>Each panel opens as it travels up the screen.</p></header>
<main class="stack">
<figure class="panel a"><figcaption>Dusk</figcaption></figure>
<figure class="panel b"><figcaption>Tide</figcaption></figure>
<figure class="panel c"><figcaption>Bloom</figcaption></figure>
<figure class="panel d"><figcaption>Static</figcaption></figure>
</main>
<script>
const panels = document.querySelectorAll('.panel');
const clamp = (v, a, b) => Math.min(b, Math.max(a, v));
function update() {
const vh = innerHeight;
panels.forEach(p => {
const top = p.getBoundingClientRect().top;
p.style.setProperty('--p', clamp((vh - top) / (vh * 0.75), 0, 1).toFixed(3));
});
}
addEventListener('scroll', update, { passive: true });
addEventListener('resize', update);
update();
</script>
</body>
</html>