Dating — Premium Upsell
A playful dating-app paywall screen wrapped in a phone frame, offering Plus, Gold, and Platinum tiers with a most-popular highlight, a monthly/yearly billing toggle that reveals savings, a scrollable perks comparison, an unlockable feature list, and a gradient subscribe CTA with a live price and toast confirmation.
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 { height: 100%; }
body {
margin: 0;
font-family: "Inter", system-ui, -apple-system, sans-serif;
line-height: 1.5;
color: var(--ink);
background:
radial-gradient(900px 500px at 15% -10%, rgba(255, 94, 108, 0.20), transparent 60%),
radial-gradient(800px 520px at 100% 110%, rgba(139, 92, 246, 0.20), transparent 55%),
var(--bg);
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
min-height: 100vh;
}
.stage {
min-height: 100vh;
display: grid;
place-items: center;
padding: 28px 16px;
}
/* ---------- Phone frame ---------- */
.phone {
width: 100%;
max-width: 430px;
background: var(--surface);
border-radius: 42px;
border: 1px solid var(--line);
box-shadow:
0 2px 0 rgba(255, 255, 255, 0.7) inset,
0 30px 70px -24px rgba(124, 58, 237, 0.45),
0 12px 30px -18px rgba(230, 57, 80, 0.4);
padding: 10px 18px 18px;
position: relative;
overflow: hidden;
}
.status-bar {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 13px;
font-weight: 700;
color: var(--ink-2);
padding: 6px 6px 2px;
}
.status-bar .dots { display: flex; gap: 4px; }
.status-bar .dots i {
width: 6px; height: 6px; border-radius: 50%;
background: var(--ink-2); opacity: 0.55; display: block;
}
/* ---------- Hero ---------- */
.hero {
position: relative;
margin: 8px 0 16px;
padding: 22px 18px 20px;
border-radius: var(--r-lg);
background: var(--grad);
color: var(--white);
overflow: hidden;
isolation: isolate;
}
.hero::after {
content: "";
position: absolute;
inset: 0;
background:
radial-gradient(220px 120px at 90% 0%, rgba(255, 255, 255, 0.28), transparent 60%),
radial-gradient(160px 120px at 8% 110%, rgba(255, 255, 255, 0.18), transparent 60%);
z-index: -1;
}
.close {
position: absolute;
top: 12px; right: 12px;
width: 32px; height: 32px;
border-radius: 50%;
border: none;
background: rgba(255, 255, 255, 0.22);
color: var(--white);
font-size: 22px;
line-height: 1;
cursor: pointer;
display: grid; place-items: center;
transition: background 0.18s ease, transform 0.18s ease;
}
.close:hover { background: rgba(255, 255, 255, 0.34); }
.close:active { transform: scale(0.9); }
.hero-badge {
display: inline-block;
font-weight: 700;
font-size: 12px;
letter-spacing: 0.04em;
text-transform: uppercase;
padding: 5px 12px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.22);
margin-bottom: 12px;
}
.hero-title {
font-family: "Poppins", "Inter", sans-serif;
font-weight: 800;
font-size: 26px;
line-height: 1.15;
margin: 0 0 6px;
}
.hero-sub {
margin: 0;
font-size: 14px;
color: rgba(255, 255, 255, 0.9);
max-width: 30ch;
}
/* ---------- Billing toggle ---------- */
.billing { display: flex; justify-content: center; margin: 0 0 18px; }
.toggle {
position: relative;
display: flex;
background: #f3ecf4;
border: 1px solid var(--line);
border-radius: 999px;
padding: 5px;
gap: 4px;
}
.toggle-opt {
position: relative;
z-index: 2;
border: none;
background: transparent;
font: inherit;
font-weight: 700;
font-size: 13.5px;
color: var(--ink-2);
padding: 9px 18px;
border-radius: 999px;
cursor: pointer;
display: flex;
align-items: center;
gap: 7px;
transition: color 0.2s ease;
}
.toggle-opt.is-active { color: var(--white); }
.save-pill {
font-size: 10.5px;
font-weight: 800;
letter-spacing: 0.02em;
padding: 3px 7px;
border-radius: 999px;
background: #ffe08a;
color: #7a5a00;
line-height: 1;
}
.toggle-opt.is-active .save-pill { background: rgba(255, 255, 255, 0.9); color: var(--violet-d); }
.toggle-thumb {
position: absolute;
z-index: 1;
top: 5px; left: 5px;
height: calc(100% - 10px);
border-radius: 999px;
background: var(--grad);
box-shadow: 0 6px 14px -6px rgba(230, 57, 80, 0.6);
transition: transform 0.28s cubic-bezier(0.34, 1.4, 0.5, 1), width 0.28s ease;
}
/* ---------- Plans ---------- */
.plans {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
margin-bottom: 20px;
}
.plan {
position: relative;
text-align: left;
font: inherit;
cursor: pointer;
background: var(--surface);
border: 1.5px solid var(--line);
border-radius: var(--r-md);
padding: 14px 12px 13px;
display: flex;
flex-direction: column;
gap: 3px;
transition: transform 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;
}
.plan:hover { transform: translateY(-3px); box-shadow: 0 14px 26px -18px rgba(124, 58, 237, 0.5); }
.plan:focus-visible {
outline: 3px solid rgba(139, 92, 246, 0.55);
outline-offset: 2px;
}
.plan.is-selected {
border-color: transparent;
background:
linear-gradient(var(--surface), var(--surface)) padding-box,
var(--grad) border-box;
box-shadow: 0 16px 30px -18px rgba(230, 57, 80, 0.55);
transform: translateY(-4px) scale(1.02);
}
.plan.is-popular { padding-top: 22px; }
.pop-tag {
position: absolute;
top: -1px; left: 50%;
transform: translate(-50%, -50%);
font-size: 9.5px;
font-weight: 800;
letter-spacing: 0.04em;
text-transform: uppercase;
color: var(--white);
background: var(--grad);
padding: 4px 9px;
border-radius: 999px;
white-space: nowrap;
box-shadow: 0 6px 12px -6px rgba(230, 57, 80, 0.7);
}
.plan-name {
font-family: "Poppins", "Inter", sans-serif;
font-weight: 700;
font-size: 14.5px;
}
.plan-price { display: flex; align-items: baseline; gap: 2px; }
.plan-price .amount {
font-family: "Poppins", "Inter", sans-serif;
font-weight: 800;
font-size: 19px;
color: var(--ink);
}
.plan-price .per { font-style: normal; font-size: 11px; color: var(--muted); font-weight: 600; }
.plan-note { font-size: 11px; color: var(--muted); line-height: 1.3; }
/* ---------- Perks ---------- */
.perks { margin-bottom: 22px; }
.perks-title {
font-family: "Poppins", "Inter", sans-serif;
font-weight: 700;
font-size: 15px;
margin: 0 0 12px;
}
.perks-title span {
background: var(--grad);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.perk-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; }
.perk {
display: flex;
align-items: center;
gap: 12px;
padding: 9px 12px;
border-radius: var(--r-sm);
background: #fbf5fb;
border: 1px solid transparent;
transition: background 0.2s ease, border-color 0.2s ease, opacity 0.2s ease;
}
.perk-ico {
width: 30px; height: 30px;
flex: none;
display: grid; place-items: center;
font-size: 15px;
border-radius: 9px;
background: var(--white);
box-shadow: 0 4px 10px -6px rgba(42, 26, 46, 0.3);
}
.perk-text { font-size: 13.5px; font-weight: 500; color: var(--ink); flex: 1; }
.perk-state {
width: 22px; height: 22px;
flex: none;
border-radius: 50%;
display: grid; place-items: center;
font-size: 12px;
font-weight: 800;
background: #ece3ec;
color: var(--muted);
}
.perk-state::before { content: "🔒"; font-size: 11px; }
.perk.is-included {
background: #fff;
border-color: rgba(139, 92, 246, 0.18);
}
.perk.is-included .perk-state {
background: var(--grad);
color: var(--white);
}
.perk.is-included .perk-state::before { content: "✓"; }
.perk.is-locked { opacity: 0.55; }
/* ---------- CTA dock ---------- */
.cta-dock {
position: sticky;
bottom: 0;
background: linear-gradient(to top, var(--surface) 72%, rgba(255, 255, 255, 0));
padding-top: 8px;
margin: 0 -4px;
}
.cta-meta {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 10px;
margin-bottom: 10px;
padding: 0 4px;
}
.cta-total {
font-family: "Poppins", "Inter", sans-serif;
font-weight: 800;
font-size: 22px;
}
.cta-total small { font-size: 12px; color: var(--muted); font-weight: 600; }
.cta-sub { font-size: 11.5px; color: var(--muted); text-align: right; }
.subscribe {
width: 100%;
border: none;
cursor: pointer;
font: inherit;
font-weight: 800;
font-size: 16px;
color: var(--white);
background: var(--grad);
border-radius: 999px;
padding: 15px 20px;
box-shadow: 0 16px 28px -14px rgba(230, 57, 80, 0.7);
transition: transform 0.16s ease, box-shadow 0.2s ease, filter 0.2s ease;
}
.subscribe:hover { filter: brightness(1.05); box-shadow: 0 20px 34px -14px rgba(124, 58, 237, 0.75); }
.subscribe:active { transform: scale(0.97); }
.subscribe:focus-visible { outline: 3px solid rgba(139, 92, 246, 0.6); outline-offset: 3px; }
.fine {
margin: 9px 0 0;
text-align: center;
font-size: 10.5px;
color: var(--muted);
}
/* ---------- Toast ---------- */
.toast {
position: fixed;
left: 50%;
bottom: 26px;
transform: translate(-50%, 24px);
background: var(--ink);
color: var(--white);
font-size: 13.5px;
font-weight: 600;
padding: 12px 18px;
border-radius: 999px;
box-shadow: 0 16px 34px -12px rgba(42, 26, 46, 0.6);
opacity: 0;
pointer-events: none;
transition: opacity 0.25s ease, transform 0.25s ease;
z-index: 50;
max-width: 90vw;
}
.toast.is-show { opacity: 1; transform: translate(-50%, 0); }
/* ---------- Responsive ---------- */
@media (max-width: 520px) {
.stage { padding: 14px 8px; }
.phone { border-radius: 30px; padding: 8px 12px 14px; }
.hero-title { font-size: 23px; }
.plans { gap: 8px; }
.plan { padding: 12px 9px 11px; }
.plan.is-popular { padding-top: 20px; }
.plan-price .amount { font-size: 17px; }
.plan-note { font-size: 10px; }
.cta-sub { font-size: 10.5px; }
.toggle-opt { padding: 8px 13px; font-size: 12.5px; }
}
@media (prefers-reduced-motion: reduce) {
* { transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; }
}(function () {
"use strict";
var TIER_RANK = { plus: 1, gold: 2, platinum: 3 };
var state = { cycle: "monthly", plan: "gold" };
var plans = Array.prototype.slice.call(document.querySelectorAll(".plan"));
var toggle = document.getElementById("billingToggle");
var toggleOpts = Array.prototype.slice.call(toggle.querySelectorAll(".toggle-opt"));
var thumb = toggle.querySelector(".toggle-thumb");
var perks = Array.prototype.slice.call(document.querySelectorAll(".perk"));
var perkPlanName = document.getElementById("perkPlanName");
var ctaTotal = document.getElementById("ctaTotal");
var ctaSub = document.getElementById("ctaSub");
var ctaPlanName = document.getElementById("ctaPlanName");
var subscribeBtn = document.getElementById("subscribeBtn");
var toastEl = document.getElementById("toast");
function money(n) {
return "$" + Number(n).toFixed(2);
}
function planData(el) {
return {
key: el.getAttribute("data-plan"),
name: el.getAttribute("data-name"),
monthly: parseFloat(el.getAttribute("data-monthly")),
yearly: parseFloat(el.getAttribute("data-yearly"))
};
}
var toastTimer;
function toast(msg) {
toastEl.textContent = msg;
toastEl.classList.add("is-show");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () {
toastEl.classList.remove("is-show");
}, 2600);
}
/* Move sliding thumb under the active toggle option */
function positionThumb() {
var active = toggle.querySelector(".toggle-opt.is-active");
if (!active) return;
thumb.style.width = active.offsetWidth + "px";
thumb.style.transform = "translateX(" + (active.offsetLeft - 5) + "px)";
}
/* Update every plan card's displayed price for the current cycle */
function renderPrices() {
plans.forEach(function (el) {
var d = planData(el);
var price = state.cycle === "yearly" ? d.yearly : d.monthly;
el.querySelector(".amount").textContent = money(price);
el.querySelector(".per").textContent = "/mo";
});
}
/* Highlight selected plan + toggle unlocked perks */
function renderSelection() {
plans.forEach(function (el) {
var selected = el.getAttribute("data-plan") === state.plan;
el.classList.toggle("is-selected", selected);
el.setAttribute("aria-checked", selected ? "true" : "false");
});
var current = plans.filter(function (el) {
return el.getAttribute("data-plan") === state.plan;
})[0];
var d = planData(current);
var rank = TIER_RANK[state.plan];
perkPlanName.textContent = d.name;
ctaPlanName.textContent = d.name;
perks.forEach(function (li) {
var included = TIER_RANK[li.getAttribute("data-tier")] <= rank;
li.classList.toggle("is-included", included);
li.classList.toggle("is-locked", !included);
});
renderCta(d);
}
function renderCta(d) {
var price = state.cycle === "yearly" ? d.yearly : d.monthly;
ctaTotal.innerHTML = money(price) + "<small>/mo</small>";
if (state.cycle === "yearly") {
var annual = money(d.yearly * 12);
var saved = money((d.monthly - d.yearly) * 12);
ctaSub.textContent = annual + "/yr · save " + saved + " · cancel anytime";
} else {
ctaSub.textContent = "Billed monthly · cancel anytime";
}
}
/* ---- Events ---- */
toggleOpts.forEach(function (opt) {
opt.addEventListener("click", function () {
state.cycle = opt.getAttribute("data-cycle");
toggleOpts.forEach(function (o) {
var on = o === opt;
o.classList.toggle("is-active", on);
o.setAttribute("aria-pressed", on ? "true" : "false");
});
positionThumb();
renderPrices();
renderSelection();
});
});
plans.forEach(function (el, i) {
el.addEventListener("click", function () {
state.plan = el.getAttribute("data-plan");
renderSelection();
});
/* Arrow-key navigation across the radiogroup */
el.addEventListener("keydown", function (e) {
var dir = 0;
if (e.key === "ArrowRight" || e.key === "ArrowDown") dir = 1;
else if (e.key === "ArrowLeft" || e.key === "ArrowUp") dir = -1;
else return;
e.preventDefault();
var next = plans[(i + dir + plans.length) % plans.length];
state.plan = next.getAttribute("data-plan");
renderSelection();
next.focus();
});
});
subscribeBtn.addEventListener("click", function () {
var current = plans.filter(function (el) {
return el.getAttribute("data-plan") === state.plan;
})[0];
var d = planData(current);
var cadence = state.cycle === "yearly" ? "yearly" : "monthly";
toast("You're on Spark " + d.name + " — " + cadence + " ✦");
subscribeBtn.animate(
[{ transform: "scale(1)" }, { transform: "scale(0.96)" }, { transform: "scale(1)" }],
{ duration: 240, easing: "ease-out" }
);
});
document.querySelector(".close").addEventListener("click", function () {
toast("Maybe next time 💜");
});
window.addEventListener("resize", positionThumb);
/* ---- Init ---- */
renderPrices();
renderSelection();
positionThumb();
/* re-run once fonts settle so thumb width is accurate */
window.setTimeout(positionThumb, 120);
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Spark Premium — Upgrade</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&family=Poppins:wght@600;700;800&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<main class="stage">
<section class="phone" aria-label="Spark Premium upgrade screen">
<div class="status-bar" aria-hidden="true">
<span>9:41</span>
<span class="dots"><i></i><i></i><i></i></span>
</div>
<header class="hero">
<button class="close" type="button" aria-label="Close upgrade screen">×</button>
<div class="hero-badge">✦ Spark Premium</div>
<h1 class="hero-title">Find your spark, faster</h1>
<p class="hero-sub">Unlock likes, boosts & the whole experience.</p>
</header>
<div class="billing" role="group" aria-label="Billing period">
<div class="toggle" id="billingToggle">
<button class="toggle-opt is-active" type="button" data-cycle="monthly" aria-pressed="true">Monthly</button>
<button class="toggle-opt" type="button" data-cycle="yearly" aria-pressed="false">
Yearly <span class="save-pill">Save 40%</span>
</button>
<span class="toggle-thumb" aria-hidden="true"></span>
</div>
</div>
<div class="plans" role="radiogroup" aria-label="Choose a plan">
<button class="plan" type="button" role="radio" aria-checked="false" data-plan="plus"
data-monthly="9.99" data-yearly="5.99" data-name="Plus">
<span class="plan-name">Plus</span>
<span class="plan-price"><b class="amount">$9.99</b><em class="per">/mo</em></span>
<span class="plan-note">The essentials</span>
</button>
<button class="plan is-popular is-selected" type="button" role="radio" aria-checked="true" data-plan="gold"
data-monthly="19.99" data-yearly="11.99" data-name="Gold">
<span class="pop-tag">Most Popular</span>
<span class="plan-name">Gold</span>
<span class="plan-price"><b class="amount">$19.99</b><em class="per">/mo</em></span>
<span class="plan-note">See who likes you</span>
</button>
<button class="plan" type="button" role="radio" aria-checked="false" data-plan="platinum"
data-monthly="29.99" data-yearly="17.99" data-name="Platinum">
<span class="plan-name">Platinum</span>
<span class="plan-price"><b class="amount">$29.99</b><em class="per">/mo</em></span>
<span class="plan-note">The full experience</span>
</button>
</div>
<section class="perks" aria-label="What you unlock">
<h2 class="perks-title">Everything in <span id="perkPlanName">Gold</span></h2>
<ul class="perk-list" id="perkList">
<li class="perk" data-tier="plus"><span class="perk-ico">💜</span><span class="perk-text">Unlimited likes</span><span class="perk-state" aria-hidden="true"></span></li>
<li class="perk" data-tier="plus"><span class="perk-ico">↩️</span><span class="perk-text">Rewind your last swipe</span><span class="perk-state" aria-hidden="true"></span></li>
<li class="perk" data-tier="plus"><span class="perk-ico">🚫</span><span class="perk-text">No ads, ever</span><span class="perk-state" aria-hidden="true"></span></li>
<li class="perk" data-tier="gold"><span class="perk-ico">👀</span><span class="perk-text">See who likes you</span><span class="perk-state" aria-hidden="true"></span></li>
<li class="perk" data-tier="gold"><span class="perk-ico">⭐</span><span class="perk-text">5 Super Likes a week</span><span class="perk-state" aria-hidden="true"></span></li>
<li class="perk" data-tier="gold"><span class="perk-ico">🚀</span><span class="perk-text">1 free Boost each month</span><span class="perk-state" aria-hidden="true"></span></li>
<li class="perk" data-tier="platinum"><span class="perk-ico">🌍</span><span class="perk-text">Passport to any city</span><span class="perk-state" aria-hidden="true"></span></li>
<li class="perk" data-tier="platinum"><span class="perk-ico">💬</span><span class="perk-text">Message before matching</span><span class="perk-state" aria-hidden="true"></span></li>
<li class="perk" data-tier="platinum"><span class="perk-ico">🔝</span><span class="perk-text">Priority likes — be seen first</span><span class="perk-state" aria-hidden="true"></span></li>
</ul>
</section>
<div class="cta-dock">
<div class="cta-meta">
<span class="cta-total" id="ctaTotal">$19.99<small>/mo</small></span>
<span class="cta-sub" id="ctaSub">Billed monthly · cancel anytime</span>
</div>
<button class="subscribe" type="button" id="subscribeBtn">
Subscribe to <span id="ctaPlanName">Gold</span>
</button>
<p class="fine">Renews automatically. Manage in Settings. Terms apply.</p>
</div>
</section>
</main>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>A mobile-first premium paywall for a dating app, centered inside a ~430px phone frame. Three subscription tiers — Plus, Gold, and Platinum — sit in a horizontal card row with the Gold plan flagged as Most Popular. A pill toggle switches billing between monthly and yearly, animating each card’s price and surfacing the yearly savings badge, while the sticky subscribe CTA updates its total live.
Below the tiers, an unlock feature list showcases perks like unlimited likes, See Who Likes You, Passport, and weekly Boosts, each with an icon and a lock that flips to a checkmark as you preview a plan. Selecting a card highlights it with a gradient border and scales it subtly for a tactile, app-like feel.
Every control is keyboard reachable with visible focus rings, uses AA-contrast text on the coral-to-violet gradient, and reflows to a single readable column below 520px. Tapping subscribe fires a toast confirming the chosen plan and cadence, all driven by dependency-free vanilla JS.