Magazine page turn
A two-page magazine spread whose leaves turn one by one, with a shadow that follows the curl.
Two panelled doors swing open towards you to reveal the room behind them.
The doors are the two halves of the stage, hinged on the outer edges with transform-origin. Scroll progress rotates the left door to −110° and the right to +110° around the Y axis, and the scene behind scales down slightly to feel deeper.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Double doors</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: #1e1b18; color: #fff; }
.track { height: 260vh; }
.stage { position: sticky; top: 0; height: 100vh; display: grid; place-items: center; perspective: 1200px; }
.frame { position: relative; width: min(80vw, 560px); aspect-ratio: .78; border: 14px solid #5c4033; border-bottom: 0; transform-style: preserve-3d; }
.room { position: absolute; inset: 0; display: grid; place-content: center; text-align: center;
background: radial-gradient(circle at 50% 30%, #ffe8a3, #f4a259 50%, #8c4a2f); color: #3b1f12; }
.room h2 { font: 900 clamp(2.4rem, 8vw, 4rem)/1 system-ui, sans-serif; margin: 0 0 .3em; }
.door { position: absolute; top: 0; bottom: 0; width: 50%; background: #7b4b2a; padding: 10% 9%; display: grid; gap: 8%; grid-template-rows: 1fr 1.6fr; will-change: transform; }
.door i { border: 4px solid #5e3720; box-shadow: inset 0 0 0 6px #8d5a35; }
.door b { position: absolute; top: 52%; width: 12px; height: 12px; border-radius: 50%; background: #e7c66b; }
.l { left: 0; transform-origin: 0 50%; box-shadow: inset -2px 0 0 #3d2414; }
.l b { right: 10%; }
.r { right: 0; transform-origin: 100% 50%; }
.r b { left: 10%; }
</style>
</head>
<body>
<section class="track">
<div class="stage">
<div class="frame">
<div class="room" id="room"><h2>Come in.</h2><p>The next page starts here.</p></div>
<div class="door l" id="dl"><i></i><i></i><b></b></div>
<div class="door r" id="dr"><i></i><i></i><b></b></div>
</div>
</div>
</section>
<script>
const dl = document.getElementById('dl'), dr = document.getElementById('dr'), room = document.getElementById('room');
const track = document.querySelector('.track');
const ease = t => 1 - Math.pow(1 - t, 2);
function update() {
const r = track.getBoundingClientRect();
const p = ease(Math.min(1, Math.max(0, -r.top / (r.height - innerHeight))));
dl.style.transform = 'rotateY(' + (-110 * p) + 'deg)';
dr.style.transform = 'rotateY(' + (110 * p) + 'deg)';
dl.style.filter = dr.style.filter = 'brightness(' + (1 - p * 0.5) + ')';
room.style.transform = 'scale(' + (0.9 + p * 0.1) + ')';
}
addEventListener('scroll', update, { passive: true });
update();
</script>
</body>
</html>