Layered landscape
Five mountain ridges slide at different speeds to build depth out of flat shapes.
Rows of oversized words drift left and right in alternating directions as you scroll.
Each row is far wider than the screen. On scroll, odd rows translate by +scrollY × 0.4 and even rows by −scrollY × 0.4, starting from an offset so there is always text on both sides.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Opposing rows</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: #fdf041; color: #111; overflow-x: hidden; }
.pad { height: 60vh; }
.rows { display: grid; gap: 1vh; }
.row {
white-space: nowrap; font: 900 clamp(3rem, 11vw, 9rem)/1 system-ui, sans-serif;
text-transform: uppercase; letter-spacing: -.03em; will-change: transform;
}
.row.out { color: transparent; -webkit-text-stroke: 2px #111; }
</style>
</head>
<body>
<div class="pad"></div>
<div class="rows">
<div class="row">scroll · box · scroll · box · scroll · box · scroll · box ·</div>
<div class="row out">move · move · move · move · move · move · move · move ·</div>
<div class="row">left · right · left · right · left · right · left · right ·</div>
<div class="row out">depth · speed · depth · speed · depth · speed · depth ·</div>
<div class="row">scroll · box · scroll · box · scroll · box · scroll · box ·</div>
<div class="row out">again · again · again · again · again · again · again ·</div>
</div>
<div class="pad"></div>
<script>
const rows = document.querySelectorAll('.row');
function update() {
rows.forEach((r, i) => {
const dir = i % 2 ? 1 : -1;
const x = -400 + dir * scrollY * 0.4 + (dir > 0 ? -600 : 0);
r.style.transform = 'translate3d(' + x + 'px,0,0)';
});
}
addEventListener('scroll', update, { passive: true });
update();
</script>
</body>
</html>