<div class="swipe-app">
<div class="stack-container" id="card-stack">
<!-- Bottom Card -->
<div class="swipe-card">
<img src="https://placehold.co/320x450/8b5cf6/ffffff?text=Card+Three" alt="Card Three">
<div class="card-info">
<h3>Mountain Retreat</h3>
<p>Peaceful and quiet vibes</p>
</div>
</div>
<!-- Middle Card -->
<div class="swipe-card">
<img src="https://placehold.co/320x450/ec4899/ffffff?text=Card+Two" alt="Card Two">
<div class="card-info">
<h3>City Lights</h3>
<p>The city that never sleeps</p>
</div>
</div>
<!-- Top Card -->
<div class="swipe-card">
<img src="https://placehold.co/320x450/3b82f6/ffffff?text=Card+One" alt="Card One">
<div class="card-info">
<h3>Ocean Breeze</h3>
<p>Listen to the calming waves</p>
</div>
</div>
</div>
<div class="action-buttons">
<button id="btn-nope" class="btn-action btn-nope" aria-label="Reject">
<svg viewBox="0 0 24 24" width="32" height="32" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
<button id="btn-yep" class="btn-action btn-yep" aria-label="Accept">
<svg viewBox="0 0 24 24" width="32" height="32" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round">
<polyline points="20 6 9 17 4 12"></polyline>
</svg>
</button>
</div>
</div>*, *::before, *::after {
box-sizing: border-box;
}
body {
font-family: system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
background: #f1f5f9;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
overflow: hidden;
}
.swipe-app {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
}
.stack-container {
position: relative;
width: 320px;
height: 450px;
/* Prevents native mobile page scrolling when dragging the cards */
touch-action: none;
}
.swipe-card {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #ffffff;
border-radius: 24px;
box-shadow: 0 15px 25px -5px rgba(0, 0, 0, 0.15);
overflow: hidden;
will-change: transform;
transform-origin: 50% 100%;
cursor: grab;
user-select: none;
}
.swipe-card.dragging {
cursor: grabbing;
}
/* Dynamic stacking trick: targets elements from bottom to top */
.swipe-card:nth-last-child(1) {
transform: translateY(0px) scale(1);
z-index: 3;
}
.swipe-card:nth-last-child(2) {
transform: translateY(16px) scale(0.95);
z-index: 2;
opacity: 0.9;
}
.swipe-card:nth-last-child(3) {
transform: translateY(32px) scale(0.9);
z-index: 1;
opacity: 0.8;
}
.swipe-card img {
width: 100%;
height: 100%;
object-fit: cover;
/* Prevents native drag ghosting on desktop */
pointer-events: none;
}
.card-info {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
padding: 40px 24px 24px 24px;
background: linear-gradient(to top, rgba(0,0,0,0.85) 0%, rgba(0,0,0,0) 100%);
color: #ffffff;
pointer-events: none;
}
.card-info h3 {
margin: 0 0 4px 0;
font-size: 1.75rem;
letter-spacing: -0.5px;
}
.card-info p {
margin: 0;
font-size: 1rem;
color: #cbd5e1;
}
.action-buttons {
display: flex;
justify-content: center;
gap: 24px;
margin-top: 50px;
z-index: 10;
}
.btn-action {
width: 72px;
height: 72px;
border-radius: 50%;
border: none;
background: #ffffff;
display: flex;
justify-content: center;
align-items: center;
box-shadow: 0 10px 20px -5px rgba(0, 0, 0, 0.1);
cursor: pointer;
transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 0.2s;
}
.btn-action:active {
transform: scale(0.85);
box-shadow: 0 4px 10px -2px rgba(0, 0, 0, 0.1);
}
.btn-nope {
color: #ef4444;
}
.btn-yep {
color: #10b981;
}const stack = document.getElementById("card-stack");
const btnNope = document.getElementById("btn-nope");
const btnYep = document.getElementById("btn-yep");
let isDragging = false;
let startX = 0;
let currentX = 0;
let activeCard = null;
// Pointer events handle both mouse and touch flawlessly
stack.addEventListener("pointerdown", function(e) {
// Always grab the top-most card in the DOM
activeCard = stack.lastElementChild;
if (!activeCard) return;
isDragging = true;
startX = e.clientX;
activeCard.classList.add("dragging");
activeCard.style.transition = "none";
activeCard.setPointerCapture(e.pointerId);
});
stack.addEventListener("pointermove", function(e) {
if (!isDragging || !activeCard) return;
currentX = e.clientX - startX;
// Calculate rotation based on drag distance
const rotate = currentX * 0.05;
activeCard.style.transform = "translate(" + currentX + "px, 0px) rotate(" + rotate + "deg)";
});
function endDrag(e) {
if (!isDragging || !activeCard) return;
isDragging = false;
activeCard.classList.remove("dragging");
activeCard.style.transition = "transform 0.4s cubic-bezier(0.2, 0.8, 0.2, 1)";
// If dragged far enough, swipe it away
if (Math.abs(currentX) > 100) {
const direction = currentX > 0 ? 1 : -1;
swipeOut(activeCard, direction);
} else {
// Snap back to center
activeCard.style.transform = "translate(0px, 0px) rotate(0deg)";
}
activeCard = null;
currentX = 0;
}
stack.addEventListener("pointerup", endDrag);
stack.addEventListener("pointercancel", endDrag);
// Helper function to animate card out and remove it
function swipeOut(card, direction) {
const throwX = window.innerWidth * direction;
const throwRotate = direction * 30;
card.style.transition = "transform 0.5s ease-out";
card.style.transform = "translate(" + throwX + "px, 0px) rotate(" + throwRotate + "deg)";
setTimeout(function() {
if (card.parentElement) {
card.parentElement.removeChild(card);
}
}, 500);
}
// Button controls
btnNope.addEventListener("click", function() {
const topCard = stack.lastElementChild;
if (topCard) swipeOut(topCard, -1);
});
btnYep.addEventListener("click", function() {
const topCard = stack.lastElementChild;
if (topCard) swipeOut(topCard, 1);
});Nothing to show yetType HTML, CSS or JavaScript and your page appears here.
Click any element to find its code Esc
Check this code before you run it
It came from a shared link. It runs with fewer permissions, so it cannot open pop-ups, submit forms or start downloads.
0.00s / 0.00s
Nothing has animated yet. Hover, click or type in the preview and each CSS animation, transition or
element.animate() call shows up here as it plays.