Dating — Icebreakers
A playful icebreaker deck for dating apps: a shuffleable stack of conversation-starter prompt cards sorted by mood categories like Flirty, Deep, and Silly. Tap a card to copy or send it with a springy toast confirmation, tap the heart to save favorites into a mini collection, and hit shuffle to deal a fresh prompt. Wrapped in a rounded phone frame with gradient coral-to-violet accents, pill controls, and smooth micro-interactions.
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.1);
--white: #fff;
--r-sm: 12px;
--r-md: 20px;
--r-lg: 28px;
}
* {
box-sizing: border-box;
}
html {
-webkit-text-size-adjust: 100%;
}
body {
margin: 0;
min-height: 100vh;
display: grid;
place-items: center;
padding: 28px 16px;
font-family: "Inter", system-ui, sans-serif;
line-height: 1.5;
color: var(--ink);
background:
radial-gradient(1200px 600px at 15% -10%, rgba(255, 143, 177, 0.35), transparent 55%),
radial-gradient(1000px 600px at 110% 20%, rgba(139, 92, 246, 0.22), transparent 55%),
var(--bg);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* Phone frame */
.phone {
width: 100%;
max-width: 430px;
background: var(--surface);
border-radius: 38px;
padding: 22px 20px 24px;
box-shadow:
0 2px 0 rgba(255, 255, 255, 0.6) inset,
0 30px 70px -30px rgba(124, 58, 237, 0.5),
0 12px 30px -18px rgba(230, 57, 80, 0.4);
border: 1px solid rgba(255, 255, 255, 0.7);
position: relative;
overflow: hidden;
}
.phone::before {
content: "";
position: absolute;
top: 12px;
left: 50%;
transform: translateX(-50%);
width: 96px;
height: 6px;
border-radius: 999px;
background: var(--line);
}
/* Header */
.phone__top {
padding-top: 18px;
}
.topline {
display: flex;
align-items: center;
justify-content: space-between;
}
.brand {
font-weight: 800;
font-size: 15px;
letter-spacing: 0.2px;
background: var(--grad);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.fav-count {
display: inline-flex;
align-items: center;
gap: 6px;
border: 1px solid var(--line);
background: var(--bg);
color: var(--ink-2);
font: inherit;
font-size: 12.5px;
font-weight: 600;
padding: 6px 12px;
border-radius: 999px;
cursor: pointer;
transition: transform 0.15s ease, box-shadow 0.15s ease, background 0.15s ease;
}
.fav-count:hover {
background: var(--white);
box-shadow: 0 6px 16px -10px rgba(230, 57, 80, 0.6);
}
.fav-count .heart-mini {
color: var(--coral);
}
.phone__title {
font-family: "Fraunces", serif;
font-weight: 600;
font-size: 30px;
margin: 14px 0 4px;
letter-spacing: -0.5px;
}
.phone__sub {
margin: 0;
color: var(--muted);
font-size: 13.5px;
}
/* Categories */
.cats {
margin: 18px -20px 0;
padding: 0 20px;
overflow-x: auto;
scrollbar-width: none;
}
.cats::-webkit-scrollbar {
display: none;
}
.cats__row {
display: flex;
gap: 8px;
width: max-content;
padding-bottom: 4px;
}
.chip {
border: 1px solid var(--line);
background: var(--white);
color: var(--ink-2);
font: inherit;
font-size: 13px;
font-weight: 600;
padding: 8px 14px;
border-radius: 999px;
cursor: pointer;
white-space: nowrap;
transition: transform 0.15s ease, color 0.15s ease, border-color 0.15s ease,
background 0.2s ease, box-shadow 0.2s ease;
}
.chip:hover {
transform: translateY(-1px);
border-color: var(--pink);
}
.chip[aria-selected="true"] {
background: var(--grad);
color: var(--white);
border-color: transparent;
box-shadow: 0 10px 22px -12px rgba(230, 57, 80, 0.8);
}
/* Card stage */
.stage {
margin-top: 20px;
perspective: 1200px;
}
.card {
position: relative;
border-radius: var(--r-lg);
padding: 26px 22px 20px;
min-height: 226px;
display: flex;
flex-direction: column;
color: var(--white);
background:
radial-gradient(120% 120% at 0% 0%, rgba(255, 255, 255, 0.28), transparent 45%),
var(--grad);
box-shadow: 0 24px 44px -22px rgba(124, 58, 237, 0.75);
transform-style: preserve-3d;
transition: transform 0.4s cubic-bezier(0.2, 0.9, 0.25, 1);
outline: none;
}
.card::after {
content: "“";
position: absolute;
top: 4px;
right: 22px;
font-family: "Fraunces", serif;
font-size: 92px;
line-height: 1;
color: rgba(255, 255, 255, 0.22);
pointer-events: none;
}
.card:focus-visible {
box-shadow:
0 24px 44px -22px rgba(124, 58, 237, 0.75),
0 0 0 4px rgba(255, 255, 255, 0.85),
0 0 0 7px var(--violet);
}
.card.flip {
transform: rotateY(90deg) scale(0.96);
}
.card__badge {
align-self: flex-start;
font-size: 11.5px;
font-weight: 700;
letter-spacing: 0.6px;
text-transform: uppercase;
padding: 5px 11px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.22);
backdrop-filter: blur(4px);
}
.card__text {
font-family: "Fraunces", serif;
font-weight: 600;
font-size: 23px;
line-height: 1.32;
margin: 16px 0 auto;
letter-spacing: -0.3px;
}
.card__meta {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 18px;
}
.card__num {
font-size: 12.5px;
font-weight: 600;
color: rgba(255, 255, 255, 0.82);
letter-spacing: 0.4px;
}
.heart {
width: 42px;
height: 42px;
border-radius: 50%;
border: none;
cursor: pointer;
font-size: 20px;
color: rgba(255, 255, 255, 0.85);
background: rgba(255, 255, 255, 0.18);
display: grid;
place-items: center;
transition: transform 0.15s ease, background 0.2s ease, color 0.2s ease;
}
.heart:hover {
background: rgba(255, 255, 255, 0.3);
}
.heart[aria-pressed="true"] {
color: var(--white);
background: rgba(255, 255, 255, 0.95);
}
.heart[aria-pressed="true"] span {
color: var(--coral-d);
}
.heart.pop {
animation: pop 0.4s ease;
}
@keyframes pop {
0% { transform: scale(1); }
35% { transform: scale(1.4); }
60% { transform: scale(0.85); }
100% { transform: scale(1); }
}
.heart:focus-visible {
outline: 3px solid rgba(255, 255, 255, 0.9);
outline-offset: 2px;
}
/* Actions */
.actions {
display: grid;
grid-template-columns: 1fr 1.25fr;
gap: 10px;
margin-top: 16px;
}
.btn {
font: inherit;
font-weight: 700;
font-size: 14.5px;
border-radius: 999px;
padding: 13px 16px;
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 7px;
transition: transform 0.14s ease, box-shadow 0.2s ease, background 0.2s ease;
}
.btn:active {
transform: translateY(1px) scale(0.99);
}
.btn--ghost {
background: var(--bg);
color: var(--coral-d);
border: 1px solid var(--line);
}
.btn--ghost:hover {
background: var(--white);
box-shadow: 0 8px 18px -12px rgba(230, 57, 80, 0.6);
}
.btn--grad {
background: var(--grad);
color: var(--white);
border: none;
box-shadow: 0 14px 26px -14px rgba(230, 57, 80, 0.9);
}
.btn--grad:hover {
box-shadow: 0 16px 30px -12px rgba(124, 58, 237, 0.9);
}
.btn:focus-visible {
outline: 3px solid var(--violet);
outline-offset: 2px;
}
/* Shuffle */
.shuffle {
width: 100%;
margin-top: 10px;
border: 1px dashed rgba(230, 57, 80, 0.4);
background: transparent;
color: var(--ink-2);
font: inherit;
font-weight: 600;
font-size: 13.5px;
padding: 12px;
border-radius: var(--r-md);
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
transition: background 0.2s ease, border-color 0.2s ease, color 0.2s ease;
}
.shuffle:hover {
background: var(--white);
border-color: var(--coral);
color: var(--ink);
}
.shuffle:focus-visible {
outline: 3px solid var(--violet);
outline-offset: 2px;
}
.shuffle__ico {
display: inline-block;
font-size: 16px;
transition: transform 0.5s ease;
}
.shuffle.spin .shuffle__ico {
transform: rotate(360deg);
}
/* Drawer */
.drawer {
margin-top: 18px;
border-top: 1px solid var(--line);
padding-top: 16px;
animation: rise 0.28s ease;
}
@keyframes rise {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
.drawer__head {
display: flex;
align-items: center;
justify-content: space-between;
}
.drawer__head h2 {
margin: 0;
font-size: 15px;
font-weight: 700;
}
.drawer__close {
border: none;
background: var(--bg);
color: var(--ink-2);
width: 30px;
height: 30px;
border-radius: 50%;
cursor: pointer;
font-size: 13px;
}
.drawer__close:hover {
background: var(--pink);
color: var(--white);
}
.drawer__list {
list-style: none;
margin: 12px 0 0;
padding: 0;
display: grid;
gap: 8px;
}
.fav-item {
display: flex;
align-items: center;
gap: 10px;
background: var(--bg);
border: 1px solid var(--line);
border-radius: var(--r-sm);
padding: 10px 12px;
animation: rise 0.24s ease;
}
.fav-item__cat {
font-size: 10px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--violet-d);
background: rgba(139, 92, 246, 0.12);
padding: 3px 7px;
border-radius: 999px;
flex: none;
}
.fav-item__text {
font-size: 13px;
color: var(--ink);
flex: 1;
}
.fav-item__rm {
flex: none;
border: none;
background: transparent;
color: var(--muted);
cursor: pointer;
font-size: 15px;
padding: 2px 4px;
border-radius: 8px;
}
.fav-item__rm:hover {
color: var(--coral-d);
background: rgba(230, 57, 80, 0.1);
}
.drawer__empty {
margin: 12px 0 0;
font-size: 13px;
color: var(--muted);
text-align: center;
padding: 8px;
}
/* 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 -16px rgba(42, 26, 46, 0.8);
opacity: 0;
pointer-events: none;
transition: opacity 0.25s ease, transform 0.25s cubic-bezier(0.2, 0.9, 0.25, 1);
z-index: 50;
max-width: 88vw;
}
.toast.show {
opacity: 1;
transform: translate(-50%, 0);
}
.toast--love {
background: var(--grad);
}
@media (max-width: 520px) {
body {
padding: 14px 10px;
}
.phone {
border-radius: 30px;
padding: 18px 16px 20px;
}
.phone__title {
font-size: 26px;
}
.card {
min-height: 208px;
padding: 22px 18px 18px;
}
.card__text {
font-size: 21px;
}
.actions {
grid-template-columns: 1fr 1fr;
}
}(function () {
"use strict";
/* ---- Data ---- */
var CATEGORIES = [
{ id: "flirty", label: "Flirty" },
{ id: "deep", label: "Deep" },
{ id: "silly", label: "Silly" },
{ id: "firstdate", label: "First Date" },
{ id: "travel", label: "Travel" },
];
var PROMPTS = [
{ cat: "flirty", label: "Flirty", text: "You get one word to describe your energy today — what is it, and can I steal some?" },
{ cat: "flirty", label: "Flirty", text: "Two truths and a flirt: tell me something true, something wild, and something you'd only say to me." },
{ cat: "flirty", label: "Flirty", text: "If we split a dessert on a first date, are you a one-bite thief or a whole-plate romantic?" },
{ cat: "flirty", label: "Flirty", text: "Rate your texting-back speed on a scale from carrier pigeon to instant. Be honest." },
{ cat: "deep", label: "Deep", text: "What's a small thing that instantly makes you trust someone new?" },
{ cat: "deep", label: "Deep", text: "When was the last time something genuinely surprised you — in a good way?" },
{ cat: "deep", label: "Deep", text: "What does a perfectly ordinary, quietly happy day look like for you?" },
{ cat: "deep", label: "Deep", text: "What's a belief you changed your mind about in the last couple of years?" },
{ cat: "silly", label: "Silly", text: "You're a cereal mascot now. What's your cereal called and what's the slogan?" },
{ cat: "silly", label: "Silly", text: "Defend your most controversial snack pairing like it's a court case." },
{ cat: "silly", label: "Silly", text: "If your last three purchases had to explain your whole personality, how doomed are you?" },
{ cat: "silly", label: "Silly", text: "Assign me a completely made-up superpower based on nothing but this conversation." },
{ cat: "firstdate", label: "First Date", text: "Coffee, cocktails, or a chaotic walk somewhere neither of us planned — pick our first date." },
{ cat: "firstdate", label: "First Date", text: "What's your green flag that people don't notice until date three?" },
{ cat: "firstdate", label: "First Date", text: "If our first date had a soundtrack, give me the opening song." },
{ cat: "firstdate", label: "First Date", text: "What's a first-date question you secretly wish someone would ask you?" },
{ cat: "travel", label: "Travel", text: "One-way ticket, leaves in an hour, money's handled — where are we landing?" },
{ cat: "travel", label: "Travel", text: "Beach that does nothing or city that never stops — which one recharges you?" },
{ cat: "travel", label: "Travel", text: "What's the best meal you've had abroad, and would you fly back just for it?" },
{ cat: "travel", label: "Travel", text: "Window seat philosopher or aisle seat escape artist — who am I flying with?" },
];
/* ---- State ---- */
var activeCat = "flirty";
var current = null;
var favorites = [];
/* ---- Elements ---- */
var $ = function (id) { return document.getElementById(id); };
var catsEl = $("cats");
var card = $("card");
var cardCat = $("cardCat");
var cardText = $("cardText");
var cardNum = $("cardNum");
var heartBtn = $("heartBtn");
var copyBtn = $("copyBtn");
var sendBtn = $("sendBtn");
var shuffleBtn = $("shuffleBtn");
var favToggle = $("favToggle");
var favNum = $("favNum");
var favDrawer = $("favDrawer");
var favClose = $("favClose");
var favList = $("favList");
var favEmpty = $("favEmpty");
var toastEl = $("toast");
/* ---- Toast helper ---- */
var toastTimer;
function toast(msg, love) {
toastEl.textContent = msg;
toastEl.classList.toggle("toast--love", !!love);
toastEl.classList.add("show");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () {
toastEl.classList.remove("show");
}, 2200);
}
/* ---- Utils ---- */
function keyOf(p) { return p.cat + "::" + p.text; }
function pool() {
return PROMPTS.filter(function (p) { return p.cat === activeCat; });
}
function isFav(p) {
return favorites.some(function (f) { return keyOf(f) === keyOf(p); });
}
/* ---- Render category chips ---- */
function renderCats() {
CATEGORIES.forEach(function (c) {
var b = document.createElement("button");
b.className = "chip";
b.type = "button";
b.textContent = c.label;
b.setAttribute("role", "tab");
b.setAttribute("aria-selected", String(c.id === activeCat));
b.addEventListener("click", function () {
if (activeCat === c.id) return;
activeCat = c.id;
Array.prototype.forEach.call(catsEl.children, function (child) {
child.setAttribute("aria-selected", String(child === b));
});
dealNew(true);
});
catsEl.appendChild(b);
});
}
/* ---- Render card ---- */
function renderCard() {
if (!current) return;
cardCat.textContent = current.label;
cardText.textContent = current.text;
var list = pool();
var idx = list.indexOf(current) + 1;
cardNum.textContent =
String(idx).padStart(2, "0") + " / " + String(list.length).padStart(2, "0");
var fav = isFav(current);
heartBtn.setAttribute("aria-pressed", String(fav));
heartBtn.setAttribute("aria-label", fav ? "Remove from favorites" : "Save to favorites");
}
function dealNew(fromCategory) {
var list = pool();
var next;
do {
next = list[Math.floor(Math.random() * list.length)];
} while (list.length > 1 && next === current);
card.classList.add("flip");
setTimeout(function () {
current = next;
renderCard();
card.classList.remove("flip");
}, 200);
if (fromCategory) toast("Dealt a " + current.label + " prompt");
}
/* ---- Favorites ---- */
function renderFavs() {
favNum.textContent = String(favorites.length);
favList.innerHTML = "";
favEmpty.style.display = favorites.length ? "none" : "block";
favorites.forEach(function (p) {
var li = document.createElement("li");
li.className = "fav-item";
var cat = document.createElement("span");
cat.className = "fav-item__cat";
cat.textContent = p.label;
var txt = document.createElement("span");
txt.className = "fav-item__text";
txt.textContent = p.text;
var rm = document.createElement("button");
rm.className = "fav-item__rm";
rm.type = "button";
rm.setAttribute("aria-label", "Remove this saved prompt");
rm.textContent = "✕";
rm.addEventListener("click", function () {
favorites = favorites.filter(function (f) { return keyOf(f) !== keyOf(p); });
renderFavs();
renderCard();
toast("Removed from saved");
});
li.appendChild(cat);
li.appendChild(txt);
li.appendChild(rm);
favList.appendChild(li);
});
}
function toggleFav() {
if (!current) return;
if (isFav(current)) {
favorites = favorites.filter(function (f) { return keyOf(f) !== keyOf(current); });
toast("Removed from saved");
} else {
favorites.unshift({ cat: current.cat, label: current.label, text: current.text });
toast("Saved to your collection", true);
}
heartBtn.classList.remove("pop");
void heartBtn.offsetWidth;
heartBtn.classList.add("pop");
renderCard();
renderFavs();
}
/* ---- Clipboard ---- */
function copyText(text) {
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).then(
function () { toast("Copied to clipboard"); },
function () { fallbackCopy(text); }
);
} else {
fallbackCopy(text);
}
}
function fallbackCopy(text) {
var ta = document.createElement("textarea");
ta.value = text;
ta.setAttribute("readonly", "");
ta.style.position = "absolute";
ta.style.left = "-9999px";
document.body.appendChild(ta);
ta.select();
try { document.execCommand("copy"); toast("Copied to clipboard"); }
catch (e) { toast("Press to copy manually"); }
document.body.removeChild(ta);
}
/* ---- Drawer ---- */
function setDrawer(open) {
favDrawer.hidden = !open;
favToggle.setAttribute("aria-expanded", String(open));
if (open) {
favDrawer.scrollIntoView({ behavior: "smooth", block: "nearest" });
}
}
/* ---- Wire up ---- */
heartBtn.addEventListener("click", toggleFav);
copyBtn.addEventListener("click", function () {
if (current) copyText(current.text);
});
sendBtn.addEventListener("click", function () {
if (current) toast("Sent to the chat ♥", true);
});
shuffleBtn.addEventListener("click", function () {
shuffleBtn.classList.remove("spin");
void shuffleBtn.offsetWidth;
shuffleBtn.classList.add("spin");
dealNew(false);
});
card.addEventListener("keydown", function (e) {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
dealNew(false);
}
});
favToggle.addEventListener("click", function () {
setDrawer(favDrawer.hidden);
});
favClose.addEventListener("click", function () { setDrawer(false); });
/* ---- Init ---- */
renderCats();
current = pool()[0];
renderCard();
renderFavs();
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Dating — Icebreakers</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=Fraunces:opsz,[email protected],500;9..144,600&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<main class="phone" aria-label="Icebreaker deck app screen">
<header class="phone__top">
<div class="topline">
<span class="brand">Sparks</span>
<button
id="favToggle"
class="fav-count"
type="button"
aria-expanded="false"
aria-controls="favDrawer"
>
<span class="heart-mini" aria-hidden="true">♥</span>
<span id="favNum">0</span> saved
</button>
</div>
<h1 class="phone__title">Break the ice</h1>
<p class="phone__sub">Tap a card, copy it, or send it straight to the chat.</p>
</header>
<nav class="cats" aria-label="Prompt categories">
<div class="cats__row" id="cats" role="tablist"></div>
</nav>
<section class="stage" aria-live="polite">
<article class="card" id="card" tabindex="0" aria-label="Current icebreaker prompt">
<span class="card__badge" id="cardCat">Flirty</span>
<p class="card__text" id="cardText">Loading a good one…</p>
<div class="card__meta">
<span class="card__num" id="cardNum">01 / 04</span>
<button
class="heart"
id="heartBtn"
type="button"
aria-pressed="false"
aria-label="Save to favorites"
>
<span aria-hidden="true">♥</span>
</button>
</div>
</article>
</section>
<div class="actions">
<button class="btn btn--ghost" id="copyBtn" type="button">
<span aria-hidden="true">⧉</span> Copy
</button>
<button class="btn btn--grad" id="sendBtn" type="button">
Send <span aria-hidden="true">➔</span>
</button>
</div>
<button class="shuffle" id="shuffleBtn" type="button">
<span class="shuffle__ico" aria-hidden="true">⤮</span> Shuffle a new prompt
</button>
<section
id="favDrawer"
class="drawer"
aria-label="Saved icebreakers"
hidden
>
<div class="drawer__head">
<h2>Saved icebreakers</h2>
<button class="drawer__close" id="favClose" type="button" aria-label="Close saved list">✕</button>
</div>
<ul class="drawer__list" id="favList"></ul>
<p class="drawer__empty" id="favEmpty">No saved prompts yet — tap the heart on a card.</p>
</section>
</main>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>A self-contained icebreaker component built for dating-app product screens. The UI lives inside a ~430px phone frame: a gradient header with the deck title and favorite count, a horizontal row of category chips (Flirty, Deep, Silly, First Date, Travel), and a large prompt card centered on the stage. Each card shows the current category badge, the prompt text, and two actions — Copy and Send — plus a heart toggle to favorite it.
Interactions are all vanilla JS. Tapping a category chip filters the deck and deals a matching prompt; the Shuffle button spins to a random new prompt with a card flip animation. Copy writes the prompt to the clipboard and fires a toast, Send fires a different toast, and the heart animates on toggle while updating the favorites counter. A collapsible favorites drawer lists everything you have saved, each removable with a tap.
The stylesheet leads with the dating palette custom properties (coral, violet, pink, gradient) and pill-shaped buttons with soft radii. It is fully keyboard accessible with visible focus rings and aria labels, and collapses cleanly below 520px so it feels native on the smallest phones.