Wedding — Real Gallery
An editorial real-weddings gallery in blush and gold: a featured grid of couples where each card opens a story lightbox with multiple images, venue and date captions, prev and next navigation, keyboard control, and a filmstrip. Includes a category filter, favorite hearts with a toast, thin gold rules, generous serif headings, and a fully responsive layout that stays elegant down to small phones.
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;
--shadow: 0 18px 50px rgba(58, 43, 43, 0.14);
}
* {
box-sizing: border-box;
}
html {
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
body {
margin: 0;
font-family: "Inter", system-ui, -apple-system, sans-serif;
line-height: 1.5;
color: var(--ink);
background:
radial-gradient(1200px 500px at 50% -10%, var(--blush-50), transparent 60%),
var(--bg);
padding: 0 clamp(16px, 5vw, 64px) 64px;
}
/* ---------- Masthead ---------- */
.masthead {
text-align: center;
padding: clamp(40px, 8vw, 80px) 0 28px;
max-width: 720px;
margin: 0 auto;
}
.eyebrow {
margin: 0 0 14px;
font-size: 0.72rem;
letter-spacing: 0.32em;
text-transform: uppercase;
font-weight: 600;
color: var(--gold-d);
}
.masthead__title {
font-family: "Cormorant Garamond", Georgia, serif;
font-weight: 500;
font-size: clamp(2.8rem, 9vw, 5rem);
line-height: 1.02;
margin: 0;
color: var(--ink);
}
.masthead__sub {
margin: 14px auto 0;
max-width: 44ch;
color: var(--ink-2);
font-size: 1.02rem;
}
.rule {
display: flex;
align-items: center;
justify-content: center;
margin: 26px auto 0;
max-width: 240px;
}
.rule span {
flex: 1;
height: 1px;
background: linear-gradient(90deg, transparent, var(--gold), transparent);
}
.rule::before {
content: "\2727";
color: var(--gold);
font-size: 0.9rem;
padding: 0 12px;
order: 2;
}
.rule span:first-child {
order: 1;
}
.rule span:last-child {
order: 3;
}
/* ---------- Filters ---------- */
.filters {
display: flex;
flex-wrap: wrap;
gap: 10px;
justify-content: center;
margin: 32px auto 44px;
max-width: 720px;
}
.chip {
font-family: inherit;
font-size: 0.82rem;
font-weight: 500;
letter-spacing: 0.04em;
color: var(--ink-2);
background: var(--white);
border: 1px solid var(--line);
padding: 9px 18px;
border-radius: 999px;
cursor: pointer;
transition: transform 0.18s ease, background 0.18s ease, color 0.18s ease,
border-color 0.18s ease, box-shadow 0.18s ease;
}
.chip:hover {
transform: translateY(-1px);
border-color: var(--blush);
color: var(--ink);
}
.chip.is-active {
background: var(--ink);
color: var(--white);
border-color: var(--ink);
box-shadow: 0 8px 20px rgba(58, 43, 43, 0.22);
}
.chip:focus-visible {
outline: 2px solid var(--gold);
outline-offset: 2px;
}
/* ---------- Gallery grid ---------- */
.gallery {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: clamp(20px, 3vw, 34px);
max-width: 1180px;
margin: 0 auto;
}
.card {
position: relative;
border: 1px solid var(--line);
border-radius: var(--r-lg);
background: var(--white);
overflow: hidden;
cursor: pointer;
padding: 0;
text-align: left;
font-family: inherit;
color: inherit;
box-shadow: 0 6px 20px rgba(58, 43, 43, 0.06);
transition: transform 0.28s ease, box-shadow 0.28s ease;
animation: rise 0.5s ease backwards;
}
.card:hover {
transform: translateY(-6px);
box-shadow: var(--shadow);
}
.card:focus-visible {
outline: 2px solid var(--gold);
outline-offset: 3px;
}
.card__cover {
position: relative;
aspect-ratio: 4 / 5;
background-size: cover;
background-position: center;
}
.card__cover::after {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(180deg, transparent 45%, rgba(58, 43, 43, 0.55));
}
.card__frame {
position: absolute;
inset: 12px;
border: 1px solid rgba(255, 255, 255, 0.55);
border-radius: 14px;
pointer-events: none;
z-index: 2;
}
.card__badge {
position: absolute;
left: 20px;
top: 20px;
z-index: 3;
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 0.7rem;
font-weight: 600;
letter-spacing: 0.05em;
color: var(--ink);
background: rgba(255, 255, 255, 0.92);
padding: 5px 11px;
border-radius: 999px;
backdrop-filter: blur(4px);
}
.card__setting {
position: absolute;
right: 20px;
top: 20px;
z-index: 3;
font-size: 0.66rem;
font-weight: 600;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--white);
background: rgba(58, 43, 43, 0.42);
border: 1px solid rgba(255, 255, 255, 0.4);
padding: 5px 11px;
border-radius: 999px;
backdrop-filter: blur(4px);
}
.heart {
position: absolute;
right: 16px;
bottom: 16px;
z-index: 4;
width: 40px;
height: 40px;
border-radius: 50%;
border: none;
cursor: pointer;
display: grid;
place-items: center;
background: rgba(255, 255, 255, 0.92);
color: var(--rose);
font-size: 1.1rem;
line-height: 1;
transition: transform 0.16s ease, background 0.16s ease, color 0.16s ease;
}
.heart:hover {
transform: scale(1.12);
}
.heart:focus-visible {
outline: 2px solid var(--gold);
outline-offset: 2px;
}
.heart.is-fav {
background: var(--rose);
color: var(--white);
animation: pop 0.32s ease;
}
.card__body {
padding: 20px 22px 24px;
}
.card__names {
font-family: "Cormorant Garamond", Georgia, serif;
font-weight: 600;
font-size: 1.7rem;
line-height: 1.05;
margin: 0;
color: var(--ink);
}
.card__meta {
margin: 8px 0 0;
font-size: 0.86rem;
color: var(--muted);
}
.card__venue {
color: var(--gold-d);
font-weight: 500;
}
/* ---------- Empty ---------- */
.empty {
text-align: center;
color: var(--muted);
font-style: italic;
max-width: 1180px;
margin: 40px auto 0;
}
/* ---------- Footer ---------- */
.foot {
text-align: center;
margin-top: 72px;
color: var(--muted);
font-size: 0.82rem;
letter-spacing: 0.06em;
}
.foot .rule {
margin-bottom: 18px;
}
/* ---------- Lightbox ---------- */
.lightbox {
position: fixed;
inset: 0;
z-index: 60;
display: grid;
place-items: center;
padding: clamp(12px, 4vw, 40px);
}
.lightbox[hidden] {
display: none;
}
.lightbox__backdrop {
position: absolute;
inset: 0;
background: rgba(43, 30, 30, 0.72);
backdrop-filter: blur(6px);
animation: fade 0.3s ease;
}
.lightbox__panel {
position: relative;
z-index: 1;
width: min(1000px, 100%);
max-height: 92vh;
overflow: auto;
display: grid;
grid-template-columns: 1.35fr 1fr;
background: var(--white);
border-radius: var(--r-lg);
box-shadow: 0 30px 90px rgba(43, 30, 30, 0.5);
animation: rise 0.34s ease;
}
.lightbox__close {
position: absolute;
right: 14px;
top: 12px;
z-index: 5;
width: 38px;
height: 38px;
border-radius: 50%;
border: none;
cursor: pointer;
background: rgba(255, 255, 255, 0.9);
color: var(--ink);
font-size: 1.5rem;
line-height: 1;
display: grid;
place-items: center;
transition: transform 0.16s ease, background 0.16s ease;
}
.lightbox__close:hover {
transform: rotate(90deg);
background: var(--white);
}
.lightbox__close:focus-visible {
outline: 2px solid var(--gold);
outline-offset: 2px;
}
.stage {
position: relative;
display: flex;
align-items: center;
background: var(--cream);
}
.stage__figure {
margin: 0;
width: 100%;
position: relative;
}
.stage__img {
width: 100%;
aspect-ratio: 4 / 5;
background-size: cover;
background-position: center;
transition: opacity 0.25s ease;
}
.stage__count {
position: absolute;
left: 16px;
bottom: 16px;
font-size: 0.74rem;
font-weight: 600;
letter-spacing: 0.08em;
color: var(--white);
background: rgba(58, 43, 43, 0.5);
padding: 4px 10px;
border-radius: 999px;
}
.nav {
position: absolute;
top: 50%;
transform: translateY(-50%);
z-index: 3;
width: 42px;
height: 42px;
border-radius: 50%;
border: none;
cursor: pointer;
background: rgba(255, 255, 255, 0.9);
color: var(--ink);
font-size: 1.5rem;
line-height: 1;
display: grid;
place-items: center;
transition: transform 0.16s ease, background 0.16s ease;
}
.nav:hover {
background: var(--white);
transform: translateY(-50%) scale(1.08);
}
.nav:focus-visible {
outline: 2px solid var(--gold);
outline-offset: 2px;
}
.nav--prev {
left: 12px;
}
.nav--next {
right: 12px;
}
.story {
padding: clamp(24px, 3vw, 40px);
display: flex;
flex-direction: column;
}
.story__badge {
align-self: flex-start;
font-size: 0.64rem;
font-weight: 700;
letter-spacing: 0.16em;
text-transform: uppercase;
color: var(--gold-d);
background: var(--blush-50);
border: 1px solid var(--line);
padding: 5px 12px;
border-radius: 999px;
}
.story__couple {
font-family: "Cormorant Garamond", Georgia, serif;
font-weight: 600;
font-size: clamp(2rem, 5vw, 2.7rem);
line-height: 1.02;
margin: 16px 0 10px;
color: var(--ink);
}
.story__meta {
margin: 0;
color: var(--ink-2);
font-size: 0.92rem;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
}
.story__meta .dot {
color: var(--gold);
}
.story__by {
margin: 12px 0 0;
font-size: 0.86rem;
color: var(--muted);
}
.story__by em {
color: var(--rose);
font-style: normal;
font-weight: 600;
}
.filmstrip {
display: flex;
gap: 10px;
margin-top: auto;
padding-top: 24px;
overflow-x: auto;
scrollbar-width: thin;
}
.thumb {
flex: 0 0 auto;
width: 64px;
height: 64px;
border-radius: var(--r-sm);
border: 2px solid transparent;
background-size: cover;
background-position: center;
cursor: pointer;
padding: 0;
opacity: 0.66;
transition: opacity 0.18s ease, border-color 0.18s ease, transform 0.18s ease;
}
.thumb:hover {
opacity: 1;
transform: translateY(-2px);
}
.thumb.is-active {
opacity: 1;
border-color: var(--gold);
}
.thumb:focus-visible {
outline: 2px solid var(--gold);
outline-offset: 2px;
}
/* ---------- Toast ---------- */
.toast {
position: fixed;
left: 50%;
bottom: 28px;
transform: translate(-50%, 20px);
z-index: 90;
background: var(--ink);
color: var(--white);
padding: 12px 20px;
border-radius: 999px;
font-size: 0.85rem;
font-weight: 500;
box-shadow: var(--shadow);
opacity: 0;
pointer-events: none;
transition: opacity 0.25s ease, transform 0.25s ease;
}
.toast.is-show {
opacity: 1;
transform: translate(-50%, 0);
}
/* ---------- Animations ---------- */
@keyframes rise {
from {
opacity: 0;
transform: translateY(14px);
}
}
@keyframes fade {
from {
opacity: 0;
}
}
@keyframes pop {
50% {
transform: scale(1.3);
}
}
/* ---------- Responsive ---------- */
@media (max-width: 780px) {
.lightbox__panel {
grid-template-columns: 1fr;
}
}
@media (max-width: 520px) {
body {
padding: 0 16px 48px;
}
.gallery {
grid-template-columns: 1fr;
gap: 20px;
}
.filters {
gap: 8px;
margin: 24px auto 32px;
}
.chip {
padding: 8px 14px;
font-size: 0.78rem;
}
.card__names {
font-size: 1.5rem;
}
.story {
padding: 22px;
}
.nav {
width: 36px;
height: 36px;
font-size: 1.3rem;
}
}
@media (prefers-reduced-motion: reduce) {
* {
animation: none !important;
transition: none !important;
}
}// ---- Real Weddings gallery data (fictional) ----
// Each story uses layered CSS gradients as royalty-free "cover" imagery.
const G = (a, b, c) =>
`linear-gradient(135deg, ${a}, ${b} 55%, ${c})`;
const WEDDINGS = [
{
id: "elena-marcus",
couple: "Elena & Marcus",
setting: "garden",
settingLabel: "Garden",
venue: "Willowmere Estate",
city: "Sonoma, CA",
date: "June 14, 2025",
photographer: "Ines Aubry",
photos: [
G("#e8b7b0", "#c98a86", "#a8862f"),
G("#fbeeec", "#e8b7b0", "#c98a86"),
G("#c9a24b", "#e8b7b0", "#fbeeec"),
G("#c98a86", "#a8862f", "#3a2b2b"),
G("#f4ece4", "#e8b7b0", "#c9a24b"),
],
},
{
id: "priya-daniel",
couple: "Priya & Daniel",
setting: "coastal",
settingLabel: "Coastal",
venue: "The Saltwater Pavilion",
city: "Amalfi Coast, IT",
date: "September 2, 2025",
photographer: "Luca Ferrante",
photos: [
G("#9ec7d4", "#c98a86", "#e8b7b0"),
G("#e8b7b0", "#9ec7d4", "#fbeeec"),
G("#c9a24b", "#9ec7d4", "#e8b7b0"),
G("#3a2b2b", "#c98a86", "#9ec7d4"),
],
},
{
id: "amara-theo",
couple: "Amara & Theo",
setting: "ballroom",
settingLabel: "Ballroom",
venue: "The Vestry Grand Hall",
city: "London, UK",
date: "November 22, 2025",
photographer: "Marguerite Dawes",
photos: [
G("#3a2b2b", "#a8862f", "#c9a24b"),
G("#c9a24b", "#e8b7b0", "#3a2b2b"),
G("#a8862f", "#c98a86", "#fbeeec"),
G("#e8b7b0", "#c9a24b", "#a8862f"),
G("#6b5555", "#c98a86", "#c9a24b"),
G("#fbeeec", "#c9a24b", "#a8862f"),
],
},
{
id: "noor-sami",
couple: "Noor & Sami",
setting: "elopement",
settingLabel: "Elopement",
venue: "Cliffside at Dawn",
city: "Big Sur, CA",
date: "March 30, 2025",
photographer: "Ren Okafor",
photos: [
G("#c98a86", "#6b5555", "#e8b7b0"),
G("#e8b7b0", "#c98a86", "#a8862f"),
G("#fbeeec", "#c98a86", "#6b5555"),
],
},
{
id: "clara-julien",
couple: "Clara & Julien",
setting: "garden",
settingLabel: "Garden",
venue: "Maison des Roses",
city: "Provence, FR",
date: "July 19, 2025",
photographer: "Sylvie Renard",
photos: [
G("#fbeeec", "#e8b7b0", "#c9a24b"),
G("#c9a24b", "#c98a86", "#e8b7b0"),
G("#e8b7b0", "#a8862f", "#fbeeec"),
G("#c98a86", "#c9a24b", "#3a2b2b"),
G("#f4ece4", "#c98a86", "#c9a24b"),
],
},
{
id: "maya-oliver",
couple: "Maya & Oliver",
setting: "coastal",
settingLabel: "Coastal",
venue: "Dune & Driftwood",
city: "Nantucket, MA",
date: "August 9, 2025",
photographer: "Beatrix Cole",
photos: [
G("#9ec7d4", "#e8b7b0", "#c9a24b"),
G("#e8b7b0", "#fbeeec", "#9ec7d4"),
G("#c9a24b", "#9ec7d4", "#c98a86"),
G("#6b5555", "#9ec7d4", "#e8b7b0"),
],
},
];
// ---- Elements ----
const gallery = document.getElementById("gallery");
const empty = document.getElementById("empty");
const chips = Array.from(document.querySelectorAll(".chip"));
const toastEl = document.getElementById("toast");
const lightbox = document.getElementById("lightbox");
const lbImg = document.getElementById("lb-img");
const lbIndex = document.getElementById("lb-index");
const lbTotal = document.getElementById("lb-total");
const lbSetting = document.getElementById("lb-setting");
const lbCouple = document.getElementById("lb-couple");
const lbVenue = document.getElementById("lb-venue");
const lbCity = document.getElementById("lb-city");
const lbDate = document.getElementById("lb-date");
const lbPhotog = document.getElementById("lb-photog");
const lbStrip = document.getElementById("lb-strip");
const favorites = new Set();
let activeStory = null;
let photoIndex = 0;
let lastFocused = null;
// ---- Toast helper ----
let toastTimer;
function toast(msg) {
toastEl.textContent = msg;
toastEl.classList.add("is-show");
clearTimeout(toastTimer);
toastTimer = setTimeout(() => toastEl.classList.remove("is-show"), 2200);
}
// ---- Render grid ----
function render(filter = "all") {
const list = WEDDINGS.filter((w) => filter === "all" || w.setting === filter);
gallery.innerHTML = "";
empty.hidden = list.length !== 0;
list.forEach((w, i) => {
const card = document.createElement("button");
card.className = "card";
card.style.animationDelay = `${i * 60}ms`;
card.setAttribute("aria-label", `Open ${w.couple} — ${w.venue}, ${w.city}`);
const isFav = favorites.has(w.id);
card.innerHTML = `
<div class="card__cover" style="background-image:${w.photos[0]}">
<span class="card__frame"></span>
<span class="card__badge">▢ ${w.photos.length} photos</span>
<span class="card__setting">${w.settingLabel}</span>
<button class="heart ${isFav ? "is-fav" : ""}" type="button"
aria-pressed="${isFav}" aria-label="Save ${w.couple} to favorites">
${isFav ? "❤" : "♡"}
</button>
</div>
<div class="card__body">
<h2 class="card__names">${w.couple}</h2>
<p class="card__meta">
<span class="card__venue">${w.venue}</span> · ${w.city}<br />${w.date}
</p>
</div>
`;
card.addEventListener("click", () => openStory(w.id, card));
const heart = card.querySelector(".heart");
heart.addEventListener("click", (e) => {
e.stopPropagation();
toggleFav(w, heart);
});
gallery.appendChild(card);
});
}
function toggleFav(w, heart) {
if (favorites.has(w.id)) {
favorites.delete(w.id);
heart.classList.remove("is-fav");
heart.innerHTML = "♡";
heart.setAttribute("aria-pressed", "false");
toast(`Removed ${w.couple} from favorites`);
} else {
favorites.add(w.id);
heart.classList.add("is-fav");
heart.innerHTML = "❤";
heart.setAttribute("aria-pressed", "true");
toast(`Saved ${w.couple} — ${favorites.size} favorite${favorites.size > 1 ? "s" : ""}`);
}
}
// ---- Filters ----
chips.forEach((chip) => {
chip.addEventListener("click", () => {
chips.forEach((c) => {
c.classList.remove("is-active");
c.setAttribute("aria-pressed", "false");
});
chip.classList.add("is-active");
chip.setAttribute("aria-pressed", "true");
render(chip.dataset.filter);
});
});
// ---- Lightbox story ----
function openStory(id, trigger) {
activeStory = WEDDINGS.find((w) => w.id === id);
if (!activeStory) return;
lastFocused = trigger || document.activeElement;
photoIndex = 0;
lbSetting.textContent = activeStory.settingLabel;
lbCouple.textContent = activeStory.couple;
lbVenue.textContent = activeStory.venue;
lbCity.textContent = activeStory.city;
lbDate.textContent = activeStory.date;
lbPhotog.textContent = activeStory.photographer;
lbTotal.textContent = activeStory.photos.length;
buildStrip();
showPhoto(0);
lightbox.hidden = false;
document.body.style.overflow = "hidden";
document.getElementById("lb-close").focus();
}
function buildStrip() {
lbStrip.innerHTML = "";
activeStory.photos.forEach((bg, i) => {
const t = document.createElement("button");
t.className = "thumb";
t.type = "button";
t.style.backgroundImage = bg;
t.setAttribute("aria-label", `View photo ${i + 1}`);
t.addEventListener("click", () => showPhoto(i));
lbStrip.appendChild(t);
});
}
function showPhoto(i) {
const total = activeStory.photos.length;
photoIndex = (i + total) % total;
lbImg.style.opacity = "0";
requestAnimationFrame(() => {
lbImg.style.backgroundImage = activeStory.photos[photoIndex];
lbImg.style.opacity = "1";
});
lbIndex.textContent = photoIndex + 1;
Array.from(lbStrip.children).forEach((t, idx) =>
t.classList.toggle("is-active", idx === photoIndex)
);
}
function closeStory() {
lightbox.hidden = true;
document.body.style.overflow = "";
activeStory = null;
if (lastFocused) lastFocused.focus();
}
document.getElementById("lb-prev").addEventListener("click", () => showPhoto(photoIndex - 1));
document.getElementById("lb-next").addEventListener("click", () => showPhoto(photoIndex + 1));
document.getElementById("lb-close").addEventListener("click", closeStory);
lightbox.querySelector("[data-close]").addEventListener("click", closeStory);
// ---- Keyboard ----
document.addEventListener("keydown", (e) => {
if (lightbox.hidden) return;
if (e.key === "Escape") closeStory();
else if (e.key === "ArrowLeft") showPhoto(photoIndex - 1);
else if (e.key === "ArrowRight") showPhoto(photoIndex + 1);
});
// ---- Init ----
render("all");<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Real Weddings — Aurelia & Co.</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>
<header class="masthead">
<p class="eyebrow">Aurelia & Co. — Fine Art Weddings</p>
<h1 class="masthead__title">Real Weddings</h1>
<p class="masthead__sub">
Love stories, told frame by frame — from garden vows to seaside first dances.
</p>
<div class="rule" aria-hidden="true"><span></span></div>
</header>
<nav class="filters" aria-label="Filter weddings by setting">
<button class="chip is-active" data-filter="all" aria-pressed="true">All Stories</button>
<button class="chip" data-filter="garden" aria-pressed="false">Garden</button>
<button class="chip" data-filter="coastal" aria-pressed="false">Coastal</button>
<button class="chip" data-filter="ballroom" aria-pressed="false">Ballroom</button>
<button class="chip" data-filter="elopement" aria-pressed="false">Elopement</button>
</nav>
<main class="gallery" id="gallery" aria-live="polite">
<!-- cards injected by script.js -->
</main>
<p class="empty" id="empty" hidden>No stories in this setting yet — try another filter.</p>
<footer class="foot">
<div class="rule" aria-hidden="true"><span></span></div>
<p>Aurelia & Co. · Photography & Films · Est. 2014</p>
</footer>
<!-- Lightbox story -->
<div class="lightbox" id="lightbox" hidden role="dialog" aria-modal="true" aria-labelledby="lb-couple">
<div class="lightbox__backdrop" data-close></div>
<div class="lightbox__panel" role="document">
<button class="lightbox__close" id="lb-close" aria-label="Close story">×</button>
<div class="stage">
<button class="nav nav--prev" id="lb-prev" aria-label="Previous photo">‹</button>
<figure class="stage__figure">
<div class="stage__img" id="lb-img"></div>
<figcaption class="stage__count"><span id="lb-index">1</span> / <span id="lb-total">1</span></figcaption>
</figure>
<button class="nav nav--next" id="lb-next" aria-label="Next photo">›</button>
</div>
<div class="story">
<span class="story__badge" id="lb-setting">Garden</span>
<h2 class="story__couple" id="lb-couple">Couple</h2>
<p class="story__meta">
<span id="lb-venue">Venue</span>
<span class="dot" aria-hidden="true">•</span>
<span id="lb-city">City</span>
<span class="dot" aria-hidden="true">•</span>
<time id="lb-date">Date</time>
</p>
<p class="story__by">Captured by <em id="lb-photog">Photographer</em></p>
<div class="filmstrip" id="lb-strip" aria-label="All photos in this story"></div>
</div>
</div>
</div>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>A romantic, editorial real-weddings gallery. The page opens with a serif masthead and a row of quiet filter chips (All, Garden, Coastal, Ballroom, Elopement) that instantly narrow a grid of featured couples. Each card layers a gradient-block cover, a gold-hairline frame, an image-count badge, a favorite heart, and the couple’s names set in Cormorant Garamond above a venue and date line.
Selecting any card lifts a full story lightbox: a large hero image, a scrollable filmstrip of every frame from that wedding, and a caption block naming the venue, city, date, and photographer. Readers move through the story with the prev and next arrows, the filmstrip thumbnails, or the left and right arrow keys, and can close with the button or the Escape key.
Everything runs on vanilla JavaScript — filtering, favoriting with a small toast confirmation, lightbox state, and focus handling — with no libraries or build step. Buttons and chips are keyboard-usable with visible focus rings, the color system follows the blush-and-gold palette, and the whole layout reflows gracefully to a single column below 520px.