Opposing rows
Rows of oversized words drift left and right in alternating directions as you scroll.
Five mountain ridges slide at different speeds to build depth out of flat shapes.
The scene sits in a sticky container inside a tall section. Each layer has a data-speed; on scroll, the script moves it by scrollY × speed. Distant layers move least, near layers most — the classic parallax depth cue.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Layered landscape</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: #2a1340; color: #fff; }
.scene { height: 200vh; }
.sticky { position: sticky; top: 0; height: 100vh; overflow: hidden; }
.layer { position: absolute; left: 0; right: 0; bottom: 0; width: 100%; height: 60%; will-change: transform; }
.sky { top: 0; height: 100%; background: linear-gradient(#ffd3a5, #fd9f8e 45%, #c06c84); }
.sun { left: 50%; bottom: 35%; width: 22vmin; height: 22vmin; margin-left: -11vmin; border-radius: 50%; background: #fff3c4; right: auto; }
.title { position: absolute; top: 18%; width: 100%; text-align: center; margin: 0;
font: 800 clamp(3rem, 12vw, 8rem)/1 system-ui, sans-serif; letter-spacing: -.04em; color: #4a2463; }
.after { min-height: 80vh; background: #4a2463; display: grid; place-content: center; padding: 0 10vw; }
.after p { font-size: 1.6rem; max-width: 30ch; text-align: center; }
</style>
</head>
<body>
<section class="scene">
<div class="sticky">
<div class="layer sky" data-speed="0"></div>
<div class="layer sun" data-speed="0.15"></div>
<svg class="layer" data-speed="0.25" viewBox="0 0 1200 400" preserveAspectRatio="none"><path fill="#f7a072" d="M0 260 L150 170 L300 230 L460 120 L620 220 L800 140 L980 230 L1200 150 V400 H0Z"/></svg>
<svg class="layer" data-speed="0.4" viewBox="0 0 1200 400" preserveAspectRatio="none"><path fill="#e0617a" d="M0 300 L120 220 L280 290 L420 200 L600 300 L760 210 L940 290 L1080 230 L1200 270 V400 H0Z"/></svg>
<svg class="layer" data-speed="0.6" viewBox="0 0 1200 400" preserveAspectRatio="none"><path fill="#9c3d7a" d="M0 330 L200 260 L380 330 L560 250 L760 340 L920 270 L1200 330 V400 H0Z"/></svg>
<svg class="layer" data-speed="0.85" viewBox="0 0 1200 400" preserveAspectRatio="none"><path fill="#4a2463" d="M0 360 L240 300 L480 370 L700 310 L900 370 L1200 320 V400 H0Z"/></svg>
<h1 class="title" data-speed="-0.3">Horizon</h1>
</div>
</section>
<section class="after"><p>Depth from flat shapes: the farther a layer, the slower it moves.</p></section>
<script>
const layers = document.querySelectorAll('[data-speed]');
function update() {
const y = scrollY;
layers.forEach(l => {
const s = parseFloat(l.dataset.speed);
l.style.transform = 'translate3d(0,' + (y * s) + 'px,0)';
});
}
addEventListener('scroll', update, { passive: true });
update();
</script>
</body>
</html>