Dating — Likes You
A playful who-liked-you screen for a dating app: a grid of admirer cards where free users see a few revealed profiles with like-back buttons while the rest sit blurred behind frosted locks. A live count badge tallies your admirers, an upgrade overlay teases the paywall, and every reveal, match, and unlock fires a soft toast with springy micro-animations inside a centered phone frame.
MCP
Code
:root {
--bg: #fff5f6;
--surface: #ffffff;
--ink: #2a1a2e;
--ink-2: #5a4a55;
--muted: #8a7a85;
--coral: #ff5e6c;
--coral-d: #e63950;
--violet: #8b5cf6;
--violet-d: #7c3aed;
--pink: #ff8fb1;
--grad: linear-gradient(135deg, #ff5e6c, #8b5cf6);
--line: rgba(42, 26, 46, 0.10);
--white: #fff;
--r-sm: 12px;
--r-md: 20px;
--r-lg: 28px;
}
* { box-sizing: border-box; }
html, body {
margin: 0;
padding: 0;
}
body {
min-height: 100vh;
font-family: "Inter", system-ui, -apple-system, sans-serif;
line-height: 1.5;
color: var(--ink);
background:
radial-gradient(1200px 600px at 12% -10%, #ffe3ea 0%, transparent 55%),
radial-gradient(1000px 600px at 110% 10%, #ece3ff 0%, transparent 55%),
var(--bg);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
display: flex;
justify-content: center;
padding: 28px 16px 48px;
}
/* Phone frame */
.phone {
width: 100%;
max-width: 430px;
background: var(--surface);
border-radius: var(--r-lg);
padding: 18px 16px 26px;
box-shadow:
0 30px 70px -30px rgba(124, 58, 237, 0.35),
0 12px 34px -18px rgba(230, 57, 80, 0.30);
border: 1px solid rgba(255, 255, 255, 0.7);
position: relative;
overflow: hidden;
}
/* App bar */
.app-bar {
display: flex;
align-items: center;
gap: 12px;
}
.app-bar__title { flex: 1; text-align: center; }
.app-bar__title h1 {
margin: 0;
font-size: 1.18rem;
font-weight: 800;
letter-spacing: -0.02em;
background: var(--grad);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.count-badge {
display: inline-block;
margin-top: 3px;
font-size: 0.72rem;
font-weight: 600;
color: var(--coral-d);
background: rgba(255, 94, 108, 0.10);
padding: 2px 10px;
border-radius: 999px;
}
.icon-btn {
width: 40px;
height: 40px;
border-radius: 999px;
border: 1px solid var(--line);
background: var(--white);
display: grid;
place-items: center;
cursor: pointer;
color: var(--ink-2);
transition: transform 0.15s ease, box-shadow 0.2s ease, background 0.2s ease;
}
.icon-btn svg {
width: 20px;
height: 20px;
fill: none;
stroke: currentColor;
stroke-width: 2.2;
stroke-linecap: round;
stroke-linejoin: round;
}
.icon-btn:hover { transform: translateY(-1px); box-shadow: 0 8px 18px -10px rgba(42, 26, 46, 0.4); }
.icon-btn:active { transform: scale(0.94); }
.icon-btn--grad { background: var(--grad); border-color: transparent; color: var(--white); }
.lede {
margin: 14px 2px 16px;
font-size: 0.86rem;
color: var(--ink-2);
text-align: center;
}
/* Grid */
.grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12px;
}
.card {
position: relative;
border-radius: var(--r-md);
overflow: hidden;
aspect-ratio: 3 / 4;
background: var(--grad);
border: 1px solid var(--line);
box-shadow: 0 12px 26px -18px rgba(42, 26, 46, 0.5);
transition: transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 0.25s ease, opacity 0.35s ease;
animation: cardIn 0.45s cubic-bezier(0.34, 1.56, 0.64, 1) both;
}
@keyframes cardIn {
from { opacity: 0; transform: translateY(14px) scale(0.94); }
to { opacity: 1; transform: none; }
}
.card:hover { transform: translateY(-3px); box-shadow: 0 18px 34px -18px rgba(124, 58, 237, 0.5); }
.card__photo {
position: absolute;
inset: 0;
background-size: cover;
background-position: center;
}
.card__scrim {
position: absolute;
inset: 0;
background: linear-gradient(to top, rgba(20, 8, 24, 0.82) 0%, rgba(20, 8, 24, 0.10) 46%, transparent 70%);
}
.compat {
position: absolute;
top: 8px;
left: 8px;
font-size: 0.66rem;
font-weight: 700;
color: var(--white);
background: rgba(20, 8, 24, 0.42);
backdrop-filter: blur(4px);
padding: 3px 8px;
border-radius: 999px;
border: 1px solid rgba(255, 255, 255, 0.25);
}
.compat b { color: #a9ffb0; }
.card__meta {
position: absolute;
left: 10px;
right: 10px;
bottom: 48px;
color: var(--white);
text-shadow: 0 1px 6px rgba(0, 0, 0, 0.5);
}
.card__name {
font-size: 0.95rem;
font-weight: 700;
display: flex;
align-items: baseline;
gap: 5px;
}
.card__name span { font-weight: 500; opacity: 0.9; }
.card__sub {
font-size: 0.7rem;
opacity: 0.9;
display: flex;
align-items: center;
gap: 4px;
}
.card__sub svg { width: 11px; height: 11px; fill: currentColor; }
.card__actions {
position: absolute;
left: 8px;
right: 8px;
bottom: 8px;
display: flex;
gap: 8px;
}
.act {
flex: 1;
height: 38px;
border-radius: 999px;
border: none;
cursor: pointer;
display: grid;
place-items: center;
transition: transform 0.15s ease, box-shadow 0.2s ease, filter 0.2s ease;
}
.act svg { width: 20px; height: 20px; }
.act--pass {
background: rgba(255, 255, 255, 0.92);
color: var(--ink-2);
max-width: 46px;
}
.act--pass svg { fill: none; stroke: currentColor; stroke-width: 2.4; stroke-linecap: round; }
.act--like {
background: var(--white);
color: var(--coral);
box-shadow: 0 8px 18px -8px rgba(230, 57, 80, 0.6);
}
.act--like svg { fill: currentColor; }
.act:hover { transform: translateY(-1px); }
.act--like:hover { filter: saturate(1.1); }
.act:active { transform: scale(0.9); }
.act:focus-visible,
.icon-btn:focus-visible,
.btn-upgrade:focus-visible,
.card--locked:focus-visible {
outline: 3px solid var(--violet);
outline-offset: 2px;
}
/* Locked cards */
.card--locked {
cursor: pointer;
background:
linear-gradient(135deg, rgba(255, 94, 108, 0.6), rgba(139, 92, 246, 0.6));
}
.card--locked .card__photo { filter: blur(14px) brightness(0.9) saturate(1.2); transform: scale(1.15); }
.card--locked .card__blur {
position: absolute;
inset: 0;
background: rgba(42, 26, 46, 0.18);
backdrop-filter: blur(2px);
}
.lock-chip {
position: absolute;
inset: 0;
margin: auto;
width: 52px;
height: 52px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.9);
display: grid;
place-items: center;
color: var(--violet-d);
box-shadow: 0 10px 22px -10px rgba(124, 58, 237, 0.6);
transition: transform 0.2s ease;
}
.lock-chip svg { width: 22px; height: 22px; fill: none; stroke: currentColor; stroke-width: 2; }
.card--locked:hover .lock-chip { transform: scale(1.08); }
.card--locked .fake-name {
position: absolute;
left: 10px;
bottom: 12px;
height: 10px;
width: 58%;
border-radius: 999px;
background: rgba(255, 255, 255, 0.55);
}
/* Heart burst */
.burst {
position: absolute;
inset: 0;
pointer-events: none;
display: grid;
place-items: center;
z-index: 4;
}
.burst svg {
width: 60px;
height: 60px;
fill: var(--coral);
filter: drop-shadow(0 6px 14px rgba(230, 57, 80, 0.6));
animation: burstPop 0.7s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
}
@keyframes burstPop {
0% { transform: scale(0.2); opacity: 0; }
35% { transform: scale(1.25); opacity: 1; }
100% { transform: scale(1.1) translateY(-10px); opacity: 0; }
}
.card--matched { animation: matchPulse 0.5s cubic-bezier(0.34, 1.56, 0.64, 1); }
@keyframes matchPulse {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
.matched-tag {
position: absolute;
top: 8px;
right: 8px;
z-index: 5;
font-size: 0.66rem;
font-weight: 800;
color: var(--white);
background: var(--grad);
padding: 4px 9px;
border-radius: 999px;
box-shadow: 0 6px 14px -6px rgba(230, 57, 80, 0.7);
animation: cardIn 0.4s both;
}
.card--gone { opacity: 0; transform: scale(0.8) translateY(10px); }
/* Paywall */
.paywall {
position: relative;
margin-top: 16px;
border-radius: var(--r-lg);
padding: 20px 18px;
text-align: center;
color: var(--white);
background: var(--grad);
overflow: hidden;
box-shadow: 0 22px 44px -22px rgba(124, 58, 237, 0.7);
transition: opacity 0.4s ease, transform 0.4s ease;
}
.paywall__glow {
position: absolute;
top: -60%;
left: -20%;
width: 140%;
height: 160%;
background: radial-gradient(circle at 30% 30%, rgba(255, 255, 255, 0.4), transparent 55%);
animation: drift 6s ease-in-out infinite alternate;
}
@keyframes drift { to { transform: translate(20px, 14px); } }
.paywall__body { position: relative; }
.paywall__pill {
display: inline-block;
font-size: 0.68rem;
font-weight: 800;
letter-spacing: 0.08em;
text-transform: uppercase;
background: rgba(255, 255, 255, 0.22);
border: 1px solid rgba(255, 255, 255, 0.4);
padding: 3px 12px;
border-radius: 999px;
}
.paywall h2 { margin: 10px 0 4px; font-size: 1.15rem; font-weight: 800; letter-spacing: -0.01em; }
.paywall p { margin: 0 0 14px; font-size: 0.82rem; opacity: 0.92; }
.btn-upgrade {
border: none;
cursor: pointer;
background: var(--white);
color: var(--coral-d);
font-family: inherit;
font-size: 0.92rem;
font-weight: 800;
padding: 12px 26px;
border-radius: 999px;
box-shadow: 0 12px 24px -10px rgba(20, 8, 24, 0.4);
transition: transform 0.15s ease, box-shadow 0.2s ease;
}
.btn-upgrade:hover { transform: translateY(-2px); box-shadow: 0 18px 30px -12px rgba(20, 8, 24, 0.5); }
.btn-upgrade:active { transform: scale(0.96); }
.paywall--done { background: linear-gradient(135deg, #16a34a, #7c3aed); }
.paywall--done .btn-upgrade { color: #16a34a; }
/* Toast */
.toast {
position: fixed;
left: 50%;
bottom: 28px;
transform: translate(-50%, 24px);
background: var(--ink);
color: var(--white);
font-size: 0.86rem;
font-weight: 600;
padding: 12px 20px;
border-radius: 999px;
box-shadow: 0 18px 40px -14px rgba(42, 26, 46, 0.6);
opacity: 0;
pointer-events: none;
transition: opacity 0.28s ease, transform 0.28s cubic-bezier(0.34, 1.56, 0.64, 1);
z-index: 40;
max-width: 90vw;
}
.toast--show { opacity: 1; transform: translate(-50%, 0); }
.toast--love { background: var(--grad); }
@media (max-width: 520px) {
body { padding: 14px 10px 34px; }
.phone { border-radius: var(--r-md); padding: 16px 12px 22px; }
.grid { gap: 10px; }
.lede { font-size: 0.82rem; }
.card__name { font-size: 0.9rem; }
}(function () {
"use strict";
var grid = document.getElementById("grid");
var countBadge = document.getElementById("countBadge");
var paywall = document.getElementById("paywall");
var paywallCount = document.getElementById("paywallCount");
var upgradeBtn = document.getElementById("upgradeBtn");
var toastEl = document.getElementById("toast");
var toastTimer;
// --- Data: realistic but clearly fictional admirers ---
var people = [
{ name: "Mara", age: 27, dist: "2 km away", compat: 94, locked: false,
photo: "https://images.unsplash.com/photo-1544005313-94ddf0286df2?w=400&q=60&auto=format&fit=crop" },
{ name: "Priya", age: 25, dist: "5 km away", compat: 88, locked: false,
photo: "https://images.unsplash.com/photo-1524504388940-b1c1722653e1?w=400&q=60&auto=format&fit=crop" },
{ name: "Ivy", age: 29, dist: "1 km away", compat: 91, locked: true,
photo: "https://images.unsplash.com/photo-1487412720507-e7ab37603c6f?w=400&q=60&auto=format&fit=crop" },
{ name: "Noor", age: 24, dist: "3 km away", compat: 82, locked: true,
photo: "https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=400&q=60&auto=format&fit=crop" },
{ name: "Sena", age: 31, dist: "8 km away", compat: 77, locked: true,
photo: "https://images.unsplash.com/photo-1517841905240-472988babdf9?w=400&q=60&auto=format&fit=crop" },
{ name: "Elle", age: 26, dist: "4 km away", compat: 85, locked: true,
photo: "https://images.unsplash.com/photo-1502823403499-6ccfcf4fb453?w=400&q=60&auto=format&fit=crop" },
{ name: "Tavi", age: 28, dist: "6 km away", compat: 79, locked: true,
photo: "https://images.unsplash.com/photo-1508214751196-bcfd4ca60f91?w=400&q=60&auto=format&fit=crop" },
{ name: "Juno", age: 23, dist: "2 km away", compat: 90, locked: true,
photo: "https://images.unsplash.com/photo-1506863530036-1efeddceb993?w=400&q=60&auto=format&fit=crop" }
];
var premium = false;
var svg = {
heart: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 21s-7.5-4.9-10-9.2C.4 8.9 1.8 5.5 5 5.1c2-.2 3.4 1 4 2 .6-1 2-2.2 4-2 3.2.4 4.6 3.8 3 6.7C19.5 16.1 12 21 12 21z"/></svg>',
close: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg>',
lock: '<svg viewBox="0 0 24 24" aria-hidden="true"><rect x="5" y="11" width="14" height="9" rx="2"/><path d="M8 11V8a4 4 0 0 1 8 0v3"/></svg>',
pin: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 2a7 7 0 0 0-7 7c0 5 7 13 7 13s7-8 7-13a7 7 0 0 0-7-7zm0 9.5A2.5 2.5 0 1 1 12 6.5a2.5 2.5 0 0 1 0 5z"/></svg>'
};
function toast(msg, love) {
toastEl.textContent = msg;
toastEl.classList.toggle("toast--love", !!love);
toastEl.classList.add("toast--show");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () {
toastEl.classList.remove("toast--show");
}, 2400);
}
function likesLeft() {
return people.filter(function (p) { return !p.removed; }).length;
}
function updateCount() {
var n = likesLeft();
countBadge.textContent = n + (n === 1 ? " admirer" : " admirers");
var hidden = people.filter(function (p) { return p.locked && !p.removed; }).length;
if (premium || hidden === 0) {
paywallCount.textContent = "You have seen everyone";
} else {
paywallCount.textContent = "+" + hidden + " more likes you";
}
}
function buildRevealed(p, i) {
var card = document.createElement("article");
card.className = "card card--revealed";
card.style.animationDelay = (i * 55) + "ms";
card.innerHTML =
'<div class="card__photo" style="background-image:url(' + p.photo + ')"></div>' +
'<div class="card__scrim"></div>' +
'<div class="compat"><b>' + p.compat + '%</b> match</div>' +
'<div class="card__meta">' +
'<div class="card__name">' + p.name + ' <span>' + p.age + '</span></div>' +
'<div class="card__sub">' + svg.pin + p.dist + '</div>' +
'</div>' +
'<div class="card__actions">' +
'<button class="act act--pass" type="button" aria-label="Pass on ' + p.name + '">' + svg.close + '</button>' +
'<button class="act act--like" type="button" aria-label="Like ' + p.name + ' back">' + svg.heart + '</button>' +
'</div>';
card.querySelector(".act--like").addEventListener("click", function () {
likeBack(card, p);
});
card.querySelector(".act--pass").addEventListener("click", function () {
passOn(card, p);
});
return card;
}
function buildLocked(p, i) {
var card = document.createElement("article");
card.className = "card card--locked";
card.style.animationDelay = (i * 55) + "ms";
card.setAttribute("tabindex", "0");
card.setAttribute("role", "button");
card.setAttribute("aria-label", "Locked profile — upgrade to reveal");
card.innerHTML =
'<div class="card__photo" style="background-image:url(' + p.photo + ')"></div>' +
'<div class="card__blur"></div>' +
'<span class="lock-chip">' + svg.lock + '</span>' +
'<span class="fake-name"></span>';
function open() { openPaywall("Upgrade to reveal " + p.name + " and everyone else."); }
card.addEventListener("click", open);
card.addEventListener("keydown", function (e) {
if (e.key === "Enter" || e.key === " ") { e.preventDefault(); open(); }
});
return card;
}
function render() {
grid.innerHTML = "";
people.forEach(function (p, i) {
if (p.removed) return;
if (p.locked && !premium) {
grid.appendChild(buildLocked(p, i));
} else {
grid.appendChild(buildRevealed(p, i));
}
});
updateCount();
}
function heartBurst(card) {
var b = document.createElement("div");
b.className = "burst";
b.innerHTML = '<svg viewBox="0 0 24 24"><path d="M12 21s-7.5-4.9-10-9.2C.4 8.9 1.8 5.5 5 5.1c2-.2 3.4 1 4 2 .6-1 2-2.2 4-2 3.2.4 4.6 3.8 3 6.7C19.5 16.1 12 21 12 21z"/></svg>';
card.appendChild(b);
setTimeout(function () { b.remove(); }, 720);
}
function likeBack(card, p) {
if (p.matched) { toast("Already matched with " + p.name + " 💜", true); return; }
p.matched = true;
heartBurst(card);
card.classList.add("card--matched");
var tag = document.createElement("span");
tag.className = "matched-tag";
tag.textContent = "Matched";
card.appendChild(tag);
var likeBtn = card.querySelector(".act--like");
if (likeBtn) { likeBtn.disabled = true; likeBtn.style.color = "#16a34a"; }
toast("It's a match with " + p.name + "! 🎉", true);
}
function passOn(card, p) {
p.removed = true;
card.classList.add("card--gone");
setTimeout(function () {
card.remove();
updateCount();
}, 360);
toast("Passed on " + p.name);
}
function openPaywall(msg) {
toast(msg || "Upgrade to see everyone who likes you.");
paywall.animate(
[{ transform: "scale(1)" }, { transform: "scale(1.02)" }, { transform: "scale(1)" }],
{ duration: 420, easing: "cubic-bezier(0.34,1.56,0.64,1)" }
);
paywall.scrollIntoView({ behavior: "smooth", block: "nearest" });
}
function unlockAll() {
if (premium) { openPaywall("You already have Gold — enjoy!"); return; }
premium = true;
var locked = people.filter(function (p) { return p.locked; });
people.forEach(function (p) { p.locked = false; });
render();
// stagger the reveal of the newly unlocked cards
var cards = grid.querySelectorAll(".card--revealed");
cards.forEach(function (c, i) {
c.style.animation = "none";
// force reflow then re-run entry animation with delay
void c.offsetWidth;
c.style.animation = "cardIn 0.5s cubic-bezier(0.34,1.56,0.64,1) both";
c.style.animationDelay = (i * 70) + "ms";
});
upgradeBtn.textContent = "Premium active ✓";
paywall.classList.add("paywall--done");
document.getElementById("paywallCount").textContent = "You have seen everyone";
toast("Gold unlocked — " + locked.length + " new profiles revealed! ✨", true);
}
upgradeBtn.addEventListener("click", unlockAll);
render();
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Likes You — Dating</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<main class="phone" aria-label="Likes You screen">
<header class="app-bar">
<button class="icon-btn" type="button" aria-label="Go back">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M15 18l-6-6 6-6"/></svg>
</button>
<div class="app-bar__title">
<h1>Likes You</h1>
<span class="count-badge" id="countBadge" aria-live="polite">0 admirers</span>
</div>
<button class="icon-btn icon-btn--grad" type="button" aria-label="Sort and filter">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 6h16M7 12h10M10 18h4"/></svg>
</button>
</header>
<p class="lede">These people already swiped right on you. Like them back to match instantly.</p>
<section class="grid" id="grid" aria-label="People who liked you">
<!-- cards injected by script.js -->
</section>
<div class="paywall" id="paywall" role="region" aria-label="Upgrade to see everyone">
<div class="paywall__glow" aria-hidden="true"></div>
<div class="paywall__body">
<span class="paywall__pill">Gold</span>
<h2 id="paywallCount">See who else likes you</h2>
<p>Unlock every profile, get unlimited likes, and skip the line.</p>
<button class="btn-upgrade" id="upgradeBtn" type="button">Upgrade to see all</button>
</div>
</div>
</main>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Dating — Likes You
A mobile-first “Likes You” screen rendered inside a ~430px phone frame. The header shows a gradient title with a live count badge tracking how many people liked you. Below it, a two-column grid mixes a handful of fully revealed admirer cards — complete with photo, name, age, distance and a compatibility badge — against blurred, locked cards representing the profiles a free member cannot yet see. A frosted “upgrade to see all” panel floats over the locked region as the paywall call to action.
Revealed cards carry a coral like-back button and a pass button. Liking back triggers a spring pop, a heart burst, and a match toast; passing gently fades the card out and decrements the count. Tapping any locked card, the lock chip, or the upgrade button opens a mock unlock flow that reveals every remaining admirer one by one with staggered animation, swaps the CTA for a “Premium active” state, and celebrates with a toast.
Everything is vanilla JS with an accessible, keyboard-usable control set: buttons expose aria labels, the count badge is a polite live region, locked cards are focusable with visible focus rings, and a small toast helper announces each interaction. The layout collapses to a single comfortable column under 520px.