Scroll cube
A pinned 3D cube turns to a new face for each quarter of the section.
Cards lie back like pages on a desk and stand upright as they reach the middle of the screen.
Each card has perspective applied through its parent and a rotateX that depends on its distance below the centre line. At the bottom edge it is tilted 70 degrees; by the centre it is flat.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Tilt-in cards</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: #e0fbfc; color: #293241; }
.list { perspective: 900px; padding: 40vh 8vw 50vh; display: grid; gap: 32px; max-width: 700px; margin: 0 auto; }
.card {
height: 36vh; border-radius: 18px; background: #fff; padding: 28px;
box-shadow: 0 30px 50px -20px rgba(41,50,65,.35); transform-origin: 50% 100%;
display: flex; flex-direction: column; justify-content: space-between; will-change: transform;
}
.card b { font: 800 2.2rem system-ui, sans-serif; }
.card span { height: 10px; width: 50%; border-radius: 5px; background: var(--c); }
</style>
</head>
<body>
<main class="list" id="list"></main>
<script>
const list = document.getElementById('list');
const colors = ['#ee6c4d', '#3d5a80', '#98c1d9', '#f4d35e', '#7bc950'];
for (let i = 1; i <= 10; i++) list.insertAdjacentHTML('beforeend', '<article class="card" style="--c:' + colors[i % 5] + '"><b>Card ' + i + '</b><span></span></article>');
const cards = list.querySelectorAll('.card');
function update() {
const vh = innerHeight;
cards.forEach(c => {
const r = c.getBoundingClientRect();
const d = Math.min(1, Math.max(0, (r.top - vh * 0.35) / (vh * 0.65)));
c.style.transform = 'rotateX(' + (d * 70).toFixed(1) + 'deg)';
c.style.opacity = (1 - d * 0.6).toFixed(2);
});
}
addEventListener('scroll', update, { passive: true });
update();
</script>
</body>
</html>