Wedding — Registry
An editorial blush-and-gold wedding gift registry where guests browse a grid of curated presents with prices and photos, reserve any item with a single tap to mark it claimed and gently grey it out, watch a live progress bar count how many gifts are spoken for, and filter the grid to show only what is still available.
MCP
Code
:root {
--blush: #e8b7b0;
--blush-50: #fbeeec;
--rose: #c98a86;
--gold: #c9a24b;
--gold-d: #a8862f;
--ink: #3a2b2b;
--ink-2: #6b5555;
--muted: #9a8585;
--bg: #fbf7f4;
--cream: #f4ece4;
--white: #fff;
--line: rgba(58, 43, 43, 0.12);
--r-sm: 8px;
--r-md: 16px;
--r-lg: 26px;
--serif: "Cormorant Garamond", Georgia, serif;
--sans: "Inter", system-ui, -apple-system, sans-serif;
}
* {
box-sizing: border-box;
}
html {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
margin: 0;
font-family: var(--sans);
color: var(--ink);
line-height: 1.5;
background:
radial-gradient(1200px 500px at 100% -10%, var(--blush-50), transparent 60%),
radial-gradient(900px 500px at -10% 0%, var(--cream), transparent 55%),
var(--bg);
min-height: 100vh;
}
.page {
display: flex;
flex-direction: column;
min-height: 100vh;
}
/* ---------- Topbar ---------- */
.topbar {
position: sticky;
top: 0;
z-index: 20;
background: rgba(251, 247, 244, 0.86);
backdrop-filter: saturate(180%) blur(12px);
border-bottom: 1px solid var(--line);
}
.topbar__inner {
max-width: 1120px;
margin: 0 auto;
padding: 14px 24px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 28px;
}
.brand {
display: grid;
grid-template-columns: auto 1fr;
grid-template-rows: auto auto;
column-gap: 12px;
align-items: center;
}
.brand__mono {
grid-row: 1 / 3;
display: grid;
place-items: center;
width: 46px;
height: 46px;
border-radius: 50%;
border: 1px solid var(--gold);
color: var(--gold-d);
font-family: var(--serif);
font-size: 18px;
letter-spacing: 0.02em;
}
.brand__names {
font-family: var(--serif);
font-size: 22px;
font-weight: 600;
line-height: 1.1;
color: var(--ink);
}
.brand__date {
font-size: 11.5px;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--muted);
}
.progress {
min-width: 240px;
}
.progress__meta {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 12px;
margin-bottom: 7px;
}
.progress__label {
font-size: 11px;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--muted);
}
.progress__count {
font-size: 13px;
color: var(--ink-2);
}
.progress__count strong {
font-family: var(--serif);
font-size: 18px;
color: var(--gold-d);
}
.progress__track {
height: 6px;
border-radius: 999px;
background: var(--cream);
overflow: hidden;
box-shadow: inset 0 0 0 1px var(--line);
}
.progress__fill {
height: 100%;
width: 0%;
border-radius: 999px;
background: linear-gradient(90deg, var(--blush), var(--gold));
transition: width 0.55s cubic-bezier(0.22, 1, 0.36, 1);
}
/* ---------- Layout ---------- */
.wrap {
flex: 1;
width: 100%;
max-width: 1120px;
margin: 0 auto;
padding: 48px 24px 64px;
}
.intro {
text-align: center;
max-width: 640px;
margin: 0 auto 40px;
}
.intro__eyebrow {
margin: 0 0 10px;
font-size: 12px;
letter-spacing: 0.28em;
text-transform: uppercase;
color: var(--rose);
}
.intro__title {
margin: 0 0 16px;
font-family: var(--serif);
font-weight: 500;
font-size: clamp(40px, 8vw, 64px);
line-height: 1;
color: var(--ink);
}
.intro__lede {
margin: 0;
color: var(--ink-2);
font-size: 16px;
}
/* ---------- Toolbar ---------- */
.toolbar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
padding: 0 4px 20px;
border-bottom: 1px solid var(--line);
margin-bottom: 30px;
}
.toolbar__stats {
font-family: var(--serif);
font-size: 20px;
color: var(--ink);
}
.switch {
display: inline-flex;
align-items: center;
gap: 10px;
cursor: pointer;
user-select: none;
font-size: 13.5px;
color: var(--ink-2);
}
.switch input {
position: absolute;
opacity: 0;
width: 1px;
height: 1px;
}
.switch__track {
position: relative;
width: 42px;
height: 24px;
border-radius: 999px;
background: var(--cream);
box-shadow: inset 0 0 0 1px var(--line);
transition: background 0.25s ease;
}
.switch__thumb {
position: absolute;
top: 3px;
left: 3px;
width: 18px;
height: 18px;
border-radius: 50%;
background: var(--white);
box-shadow: 0 1px 3px rgba(58, 43, 43, 0.25);
transition: transform 0.25s cubic-bezier(0.22, 1, 0.36, 1);
}
.switch input:checked + .switch__track {
background: linear-gradient(90deg, var(--blush), var(--gold));
}
.switch input:checked + .switch__track .switch__thumb {
transform: translateX(18px);
}
.switch input:focus-visible + .switch__track {
outline: 2px solid var(--gold-d);
outline-offset: 2px;
}
/* ---------- Grid ---------- */
.grid {
list-style: none;
margin: 0;
padding: 0;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 24px;
}
.card {
display: flex;
flex-direction: column;
background: var(--white);
border: 1px solid var(--line);
border-radius: var(--r-lg);
overflow: hidden;
transition: transform 0.3s cubic-bezier(0.22, 1, 0.36, 1),
box-shadow 0.3s ease, opacity 0.3s ease;
box-shadow: 0 1px 2px rgba(58, 43, 43, 0.04);
}
.card:hover {
transform: translateY(-4px);
box-shadow: 0 18px 40px -22px rgba(58, 43, 43, 0.4);
}
.card__media {
position: relative;
aspect-ratio: 4 / 3;
background-size: cover;
background-position: center;
}
.card__badge {
position: absolute;
top: 12px;
left: 12px;
padding: 5px 11px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.9);
color: var(--ink-2);
font-size: 10.5px;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
backdrop-filter: blur(4px);
}
.card__body {
padding: 18px 18px 20px;
display: flex;
flex-direction: column;
gap: 6px;
flex: 1;
}
.card__name {
margin: 0;
font-family: var(--serif);
font-weight: 600;
font-size: 21px;
line-height: 1.15;
color: var(--ink);
}
.card__desc {
margin: 0;
font-size: 13px;
color: var(--muted);
}
.card__foot {
margin-top: auto;
padding-top: 14px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.card__price {
font-family: var(--serif);
font-size: 24px;
font-weight: 600;
color: var(--gold-d);
}
.reserve {
appearance: none;
border: 1px solid var(--gold);
background: var(--white);
color: var(--gold-d);
font-family: var(--sans);
font-size: 13px;
font-weight: 600;
letter-spacing: 0.02em;
padding: 9px 16px;
border-radius: 999px;
cursor: pointer;
transition: background 0.2s ease, color 0.2s ease, transform 0.12s ease,
border-color 0.2s ease;
}
.reserve:hover {
background: linear-gradient(90deg, var(--blush), var(--gold));
color: var(--white);
border-color: transparent;
}
.reserve:active {
transform: scale(0.95);
}
.reserve:focus-visible {
outline: 2px solid var(--gold-d);
outline-offset: 2px;
}
/* claimed */
.card.is-claimed {
opacity: 0.62;
}
.card.is-claimed .card__media {
filter: grayscale(1) contrast(0.92);
}
.card.is-claimed .card__price {
color: var(--muted);
text-decoration: line-through;
text-decoration-color: rgba(154, 133, 133, 0.6);
}
.card.is-claimed .card__badge {
background: rgba(201, 138, 134, 0.92);
color: var(--white);
}
.reserve.is-claimed {
background: var(--cream);
border-color: transparent;
color: var(--ink-2);
cursor: pointer;
}
.reserve.is-claimed::before {
content: "\2713 ";
color: var(--rose);
}
.reserve.is-claimed:hover {
background: var(--blush-50);
color: var(--rose);
}
/* ---------- Empty ---------- */
.empty {
text-align: center;
padding: 70px 20px;
color: var(--ink-2);
}
.empty__mark {
font-size: 42px;
color: var(--rose);
}
.empty__title {
margin: 12px 0 6px;
font-family: var(--serif);
font-size: 26px;
color: var(--ink);
}
.empty__text {
margin: 0;
font-size: 14px;
color: var(--muted);
}
/* ---------- Footer ---------- */
.foot {
text-align: center;
padding: 40px 24px 56px;
color: var(--muted);
font-size: 13px;
}
.foot__rule {
display: block;
width: 70px;
height: 1px;
margin: 0 auto 18px;
background: linear-gradient(90deg, transparent, var(--gold), transparent);
}
.foot p {
margin: 0;
font-family: var(--serif);
font-size: 17px;
color: var(--ink-2);
}
/* ---------- Toast ---------- */
.toast {
position: fixed;
left: 50%;
bottom: 28px;
transform: translate(-50%, 24px);
background: var(--ink);
color: var(--white);
padding: 12px 20px;
border-radius: 999px;
font-size: 13.5px;
font-weight: 500;
letter-spacing: 0.01em;
box-shadow: 0 14px 32px -12px rgba(58, 43, 43, 0.7);
opacity: 0;
pointer-events: none;
transition: opacity 0.3s ease, transform 0.3s cubic-bezier(0.22, 1, 0.36, 1);
z-index: 40;
}
.toast::before {
content: "\2665 ";
color: var(--blush);
}
.toast.is-visible {
opacity: 1;
transform: translate(-50%, 0);
}
/* ---------- Responsive ---------- */
@media (max-width: 760px) {
.topbar__inner {
flex-direction: column;
align-items: stretch;
gap: 14px;
}
.progress {
min-width: 0;
}
}
@media (max-width: 520px) {
.wrap {
padding: 34px 16px 48px;
}
.topbar__inner {
padding: 12px 16px;
}
.grid {
grid-template-columns: 1fr;
gap: 18px;
}
.toolbar {
flex-direction: column;
align-items: flex-start;
gap: 12px;
}
.intro__title {
font-size: 44px;
}
.brand__names {
font-size: 20px;
}
}
@media (prefers-reduced-motion: reduce) {
* {
transition: none !important;
}
}(function () {
"use strict";
// Registry data — realistic but clearly fictional gifts.
var GIFTS = [
{
id: "linen-set",
name: "Belgian Linen Bedding",
desc: "Stonewashed king set in warm oat",
category: "Home",
price: 220,
claimed: false,
img: "https://images.unsplash.com/photo-1522771739844-6a9f6d5f14af?auto=format&fit=crop&w=640&q=70",
grad: "linear-gradient(135deg,#f4ece4,#e8b7b0)",
},
{
id: "dutch-oven",
name: "Enameled Dutch Oven",
desc: "7-qt cast iron for slow Sundays",
category: "Kitchen",
price: 165,
claimed: false,
img: "https://images.unsplash.com/photo-1585515320310-259814833e62?auto=format&fit=crop&w=640&q=70",
grad: "linear-gradient(135deg,#e8b7b0,#c98a86)",
},
{
id: "espresso",
name: "Stovetop Espresso Maker",
desc: "Polished six-cup Italian moka",
category: "Kitchen",
price: 58,
claimed: true,
img: "https://images.unsplash.com/photo-1495474472287-4d71bcdd2085?auto=format&fit=crop&w=640&q=70",
grad: "linear-gradient(135deg,#f4ece4,#c9a24b)",
},
{
id: "flatware",
name: "Matte Gold Flatware",
desc: "20-piece service, brushed finish",
category: "Dining",
price: 140,
claimed: false,
img: "https://images.unsplash.com/photo-1550583724-b2692b85b150?auto=format&fit=crop&w=640&q=70",
grad: "linear-gradient(135deg,#c9a24b,#a8862f)",
},
{
id: "coupe-glasses",
name: "Crystal Coupe Glasses",
desc: "Set of six for the first toast",
category: "Dining",
price: 96,
claimed: false,
img: "https://images.unsplash.com/photo-1470337458703-46ad1756a187?auto=format&fit=crop&w=640&q=70",
grad: "linear-gradient(135deg,#fbeeec,#e8b7b0)",
},
{
id: "throw",
name: "Alpaca Wool Throw",
desc: "Handwoven blush herringbone",
category: "Home",
price: 118,
claimed: false,
img: "https://images.unsplash.com/photo-1616486338812-3dadae4b4ace?auto=format&fit=crop&w=640&q=70",
grad: "linear-gradient(135deg,#e8b7b0,#f4ece4)",
},
{
id: "knife-block",
name: "Japanese Knife Trio",
desc: "Chef, paring & santoku, steel",
category: "Kitchen",
price: 189,
claimed: false,
img: "https://images.unsplash.com/photo-1593618998160-e34014e67546?auto=format&fit=crop&w=640&q=70",
grad: "linear-gradient(135deg,#c98a86,#a8862f)",
},
{
id: "vase",
name: "Hand-thrown Stoneware Vase",
desc: "Blush glaze from a Sonoma studio",
category: "Home",
price: 72,
claimed: true,
img: "https://images.unsplash.com/photo-1578500494198-246f612d3b3d?auto=format&fit=crop&w=640&q=70",
grad: "linear-gradient(135deg,#fbeeec,#c98a86)",
},
{
id: "stand-mixer",
name: "Tilt-Head Stand Mixer",
desc: "5-qt in soft almond cream",
category: "Kitchen",
price: 380,
claimed: false,
img: "https://images.unsplash.com/photo-1578020190125-f4f7c18bc9cb?auto=format&fit=crop&w=640&q=70",
grad: "linear-gradient(135deg,#f4ece4,#e8b7b0)",
},
{
id: "candle-set",
name: "Beeswax Taper Candles",
desc: "Two dozen ivory dinner tapers",
category: "Home",
price: 44,
claimed: false,
img: "https://images.unsplash.com/photo-1602874801007-bd458bb1b8b6?auto=format&fit=crop&w=640&q=70",
grad: "linear-gradient(135deg,#f4ece4,#c9a24b)",
},
{
id: "picnic",
name: "Weekend Picnic Basket",
desc: "Wicker set for coastal escapes",
category: "Outdoor",
price: 130,
claimed: false,
img: "https://images.unsplash.com/photo-1526401281623-3d888e7c1cf6?auto=format&fit=crop&w=640&q=70",
grad: "linear-gradient(135deg,#e8b7b0,#c9a24b)",
},
{
id: "honeymoon",
name: "Honeymoon Fund",
desc: "A night in the Amalfi Coast",
category: "Experience",
price: 250,
claimed: false,
img: "https://images.unsplash.com/photo-1533105079780-92b9be482077?auto=format&fit=crop&w=640&q=70",
grad: "linear-gradient(135deg,#c98a86,#c9a24b)",
},
];
var STATE = { availableOnly: false };
var grid = document.getElementById("grid");
var empty = document.getElementById("empty");
var reservedCountEl = document.getElementById("reservedCount");
var totalCountEl = document.getElementById("totalCount");
var progressFill = document.getElementById("progressFill");
var availableStat = document.getElementById("availableStat");
var availableOnly = document.getElementById("availableOnly");
var toastEl = document.getElementById("toast");
var toastTimer = null;
function money(n) {
return "$" + n.toLocaleString("en-US");
}
function toast(msg) {
toastEl.innerHTML = msg;
toastEl.classList.add("is-visible");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () {
toastEl.classList.remove("is-visible");
}, 2600);
}
function visibleGifts() {
return STATE.availableOnly
? GIFTS.filter(function (g) {
return !g.claimed;
})
: GIFTS;
}
function updateSummary() {
var reserved = GIFTS.filter(function (g) {
return g.claimed;
}).length;
var total = GIFTS.length;
var available = total - reserved;
reservedCountEl.textContent = reserved;
totalCountEl.textContent = total;
progressFill.style.width = (reserved / total) * 100 + "%";
availableStat.textContent = STATE.availableOnly
? available + (available === 1 ? " gift still available" : " gifts still available")
: "All gifts (" + available + " available)";
}
function buildCard(g) {
var li = document.createElement("li");
li.className = "card" + (g.claimed ? " is-claimed" : "");
li.dataset.id = g.id;
var media = document.createElement("div");
media.className = "card__media";
media.style.backgroundImage =
'url("' + g.img + '"), ' + g.grad;
var badge = document.createElement("span");
badge.className = "card__badge";
badge.textContent = g.claimed ? "Reserved" : g.category;
media.appendChild(badge);
var body = document.createElement("div");
body.className = "card__body";
var name = document.createElement("h3");
name.className = "card__name";
name.innerHTML = g.name;
var desc = document.createElement("p");
desc.className = "card__desc";
desc.innerHTML = g.desc;
var foot = document.createElement("div");
foot.className = "card__foot";
var price = document.createElement("span");
price.className = "card__price";
price.textContent = money(g.price);
var btn = document.createElement("button");
btn.type = "button";
btn.className = "reserve" + (g.claimed ? " is-claimed" : "");
btn.textContent = g.claimed ? "Reserved" : "Reserve";
btn.setAttribute(
"aria-label",
(g.claimed ? "Cancel reservation for " : "Reserve ") + g.name.replace(/&/g, "and")
);
btn.setAttribute("aria-pressed", g.claimed ? "true" : "false");
btn.addEventListener("click", function () {
toggle(g.id);
});
foot.appendChild(price);
foot.appendChild(btn);
body.appendChild(name);
body.appendChild(desc);
body.appendChild(foot);
li.appendChild(media);
li.appendChild(body);
return li;
}
function render() {
var items = visibleGifts();
grid.innerHTML = "";
if (items.length === 0) {
empty.hidden = false;
} else {
empty.hidden = true;
var frag = document.createDocumentFragment();
items.forEach(function (g) {
frag.appendChild(buildCard(g));
});
grid.appendChild(frag);
}
updateSummary();
}
function toggle(id) {
var g = GIFTS.find(function (x) {
return x.id === id;
});
if (!g) return;
g.claimed = !g.claimed;
var plain = g.name.replace(/&/g, "and");
if (g.claimed) {
toast("Thank you! You reserved the " + plain + ".");
} else {
toast("Reservation released for the " + plain + ".");
}
render();
}
availableOnly.addEventListener("change", function () {
STATE.availableOnly = availableOnly.checked;
render();
});
render();
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Amara & Elias — Wedding Registry</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=Cormorant+Garamond:wght@400;500;600&family=Inter:wght@400;500;600;700;800&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="page">
<header class="topbar" role="banner">
<div class="topbar__inner">
<div class="brand">
<span class="brand__mono" aria-hidden="true">A & E</span>
<span class="brand__names">Amara & Elias</span>
<span class="brand__date">September 12, 2026 · Sonoma, California</span>
</div>
<div class="progress" role="group" aria-label="Registry progress">
<div class="progress__meta">
<span class="progress__label">Gifts reserved</span>
<span class="progress__count"><strong id="reservedCount">0</strong> of <span id="totalCount">0</span></span>
</div>
<div class="progress__track" aria-hidden="true">
<div class="progress__fill" id="progressFill"></div>
</div>
</div>
</div>
</header>
<main class="wrap" id="main">
<section class="intro">
<p class="intro__eyebrow">With love & gratitude</p>
<h1 class="intro__title">Our Registry</h1>
<p class="intro__lede">
Your presence is the greatest gift — but if you would like to help us begin our
new home, we have gathered a few favorites below. Reserve anything with a single tap.
</p>
</section>
<div class="toolbar" role="region" aria-label="Registry filters">
<div class="toolbar__stats" id="availableStat">All gifts</div>
<label class="switch">
<input type="checkbox" id="availableOnly" />
<span class="switch__track" aria-hidden="true"><span class="switch__thumb"></span></span>
<span class="switch__text">Available only</span>
</label>
</div>
<ul class="grid" id="grid" aria-live="polite"></ul>
<div class="empty" id="empty" hidden>
<span class="empty__mark" aria-hidden="true">♡</span>
<p class="empty__title">Every gift has been reserved</p>
<p class="empty__text">Thank you — you are all so generous. Turn off the filter to view the full registry.</p>
</div>
</main>
<footer class="foot">
<span class="foot__rule" aria-hidden="true"></span>
<p>Thank you for celebrating with us. — Amara & Elias</p>
</footer>
</div>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>A romantic, editorial gift registry for a wedding, built around a responsive product grid. Each card pairs a soft gradient or photographic thumbnail with the gift name, a short line of copy, a category badge, and a price set in an elegant Cormorant Garamond serif. A single Reserve button toggles an item to a claimed state, dimming the card, swapping the label to Reserved, and firing a small toast to confirm the guest has it covered.
A sticky header keeps the couple’s monogram, a live progress bar, and the running count of reserved versus total gifts in view. As items are claimed the bar fills with a thin gold gradient and the numbers update instantly. A filter toggle lets guests hide everything already taken so they can focus only on gifts still available, and the empty state appears gracefully when nothing matches.
Everything runs on vanilla JavaScript with no dependencies: state lives in a small array, the grid re-renders on each change, and reservations are announced politely for screen readers. Buttons are fully keyboard operable with visible focus rings, contrast meets WCAG AA, and the layout collapses to a single comfortable column below 520px.