Card swipe deck

Cards fly off a pinned deck to the left and right, each one stamped as it goes, like swiping through profiles.

How it works

Every card owns a slice of scroll progress. Inside its slice it translates sideways and rotates in an alternating direction, while a stamp fades in. Cards below scale up slightly as they move to the top of the deck.

Full file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Card swipe deck</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: #fff0f3; color: #2b2d42; overflow-x: hidden; }
.track { height: 520vh; }
.stage { position: sticky; top: 0; height: 100vh; display: grid; place-items: center; overflow: hidden; }
.deck { position: relative; width: min(74vw, 340px); aspect-ratio: .7; }
.card {
  position: absolute; inset: 0; border-radius: 22px; padding: 22px; color: #fff;
  display: flex; flex-direction: column; justify-content: flex-end; will-change: transform;
  box-shadow: 0 20px 40px -18px rgba(43,45,66,.5);
}
.card b { font: 800 2rem system-ui, sans-serif; }
.card span { opacity: .85; }
.stamp {
  position: absolute; top: 28px; padding: 4px 12px; border: 4px solid; border-radius: 8px;
  font: 900 1.8rem system-ui, sans-serif; letter-spacing: .08em; opacity: 0;
}
.stamp.yes { left: 22px; color: #3ddc97; transform: rotate(-14deg); }
.stamp.no { right: 22px; color: #ff4d6d; transform: rotate(14deg); }
</style>
</head>
<body>
<section class="track">
  <div class="stage"><div class="deck" id="deck"></div></div>
</section>
<script>
const people = [['Mika', 'Scroll designer', '#ff7b9c', '#ffc75f'], ['Jon', 'Parallax fan', '#6a4c93', '#1982c4'], ['Ines', 'Sticky enthusiast', '#ff595e', '#ffca3a'], ['Tao', 'Snaps everything', '#2ec4b6', '#3a86ff'], ['Rae', 'Reads slowly', '#8ac926', '#1982c4'], ['Ola', 'Last one', '#ff924c', '#c77dff']];
const deck = document.getElementById('deck');
const cards = people.map(([name, role, a, b], i) => {
  const c = document.createElement('article');
  c.className = 'card';
  c.style.background = 'linear-gradient(160deg,' + a + ',' + b + ')';
  c.style.zIndex = people.length - i;
  c.innerHTML = '<div class="stamp ' + (i % 2 ? 'no' : 'yes') + '">' + (i % 2 ? 'NOPE' : 'LIKE') + '</div><b>' + name + '</b><span>' + role + '</span>';
  deck.appendChild(c);
  return c;
});
const track = document.querySelector('.track');
const n = cards.length - 1;
function update() {
  const r = track.getBoundingClientRect();
  const p = Math.min(1, Math.max(0, -r.top / (r.height - innerHeight)));
  const pos = p * n;
  cards.forEach((c, i) => {
    const t = Math.min(1, Math.max(0, pos - i));
    const dir = i % 2 ? -1 : 1;
    const depth = Math.max(0, i - pos);
    const s = 1 - Math.min(2, depth) * 0.05;
    c.style.transform = 'translate(' + (dir * t * 140) + '%,' + (Math.min(2, depth) * 14 - t * 10) + 'px) rotate(' + (dir * t * 28) + 'deg) scale(' + s + ')';
    c.querySelector('.stamp').style.opacity = Math.min(1, t * 3).toFixed(2);
  });
}
addEventListener('scroll', update, { passive: true });
update();
</script>
</body>
</html>

More page turn effects