Layered landscape
Five mountain ridges slide at different speeds to build depth out of flat shapes.
A full-bleed hero shrinks into a framed card while its title drifts up and fades.
The hero is pinned for 200vh. Progress drives three things at once: the image’s scale and inset (so it pulls away from the edges), its corner radius, and the title’s translateY and opacity. Content below rises over it when the pin ends.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Hero zoom-out</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: #f4f1ea; color: #1d3557; }
.track { height: 220vh; }
.stage { position: sticky; top: 0; height: 100vh; overflow: hidden; display: grid; place-items: center; }
.hero { position: absolute; inset: 0; will-change: transform;
background: radial-gradient(ellipse at 50% 120%, #1d3557 0 30%, transparent 31%), radial-gradient(circle at 70% 25%, #fefae0 0 6%, transparent 6.5%), linear-gradient(#a8dadc, #f1faee 60%, #e9c46a); }
h1 { position: relative; font: 900 clamp(3rem, 13vw, 9rem)/1 system-ui, sans-serif; letter-spacing: -.05em; margin: 0; color: #1d3557; }
.content { max-width: 680px; margin: 0 auto; padding: 10vh 24px 40vh; }
.content h2 { font: 800 2.4rem system-ui, sans-serif; }
.content p { font-size: 1.2rem; line-height: 1.6; }
</style>
</head>
<body>
<section class="track">
<div class="stage">
<div class="hero" id="hero"></div>
<h1 id="title">Wide open</h1>
</div>
</section>
<main class="content"><h2>Then the page begins.</h2><p>The hero has stepped back into a frame, and the story can start.</p></main>
<script>
const hero = document.getElementById('hero'), title = document.getElementById('title');
const track = document.querySelector('.track');
function update() {
const r = track.getBoundingClientRect();
const p = Math.min(1, Math.max(0, -r.top / (r.height - innerHeight)));
const inset = p * 8;
hero.style.inset = inset + 'vh ' + inset + 'vw';
hero.style.borderRadius = (p * 32) + 'px';
hero.style.transform = 'scale(' + (1.25 - p * 0.25) + ')';
title.style.transform = 'translateY(' + (-p * 30) + 'vh)';
title.style.opacity = (1 - p * 1.4).toFixed(2);
}
addEventListener('scroll', update, { passive: true });
update();
</script>
</body>
</html>