Wedding — Story About
An editorial couple story page in blush and gold: a how-we-met vertical timeline with alternating milestone cards, a soft photo mosaic, a rotating love-note quote carousel, and a wedding-party grid of bridesmaid and groomsman cards. Filter the party by side, step through quotes, and reveal timeline moments as they scroll into view, all wrapped in airy serif headings and thin gold rules for a romantic keepsake feel.
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 { scroll-behavior: smooth; }
body {
margin: 0;
font-family: var(--sans);
line-height: 1.5;
color: var(--ink);
background: var(--bg);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
img { display: block; max-width: 100%; }
.sr-only {
position: absolute;
width: 1px; height: 1px;
padding: 0; margin: -1px;
overflow: hidden; clip: rect(0 0 0 0);
white-space: nowrap; border: 0;
}
.page {
max-width: 960px;
margin: 0 auto;
padding: 0 20px 80px;
}
/* ---- shared bits ---- */
.rule {
width: 120px;
height: 1px;
background: linear-gradient(90deg, transparent, var(--gold), transparent);
margin: 22px 0;
}
.rule.center { margin: 22px auto 34px; }
.rule.gold { background: linear-gradient(90deg, transparent, var(--gold-d), transparent); }
.kicker,
.eyebrow {
font-family: var(--sans);
font-size: 12px;
font-weight: 600;
letter-spacing: 0.32em;
text-transform: uppercase;
color: var(--gold-d);
margin: 0 0 10px;
}
.kicker { text-align: center; }
.kicker.light { color: var(--rose); }
.section-title {
font-family: var(--serif);
font-weight: 500;
font-size: clamp(30px, 6vw, 46px);
text-align: center;
margin: 0;
color: var(--ink);
}
/* ---- HERO ---- */
.hero {
text-align: center;
padding: 72px 0 48px;
}
.eyebrow { color: var(--rose); }
.monogram {
font-family: var(--serif);
font-size: 56px;
font-weight: 600;
color: var(--gold-d);
letter-spacing: 0.04em;
}
.monogram span { color: var(--blush); font-style: italic; }
.monogram.sm { font-size: 34px; }
.hero-title {
font-family: var(--serif);
font-weight: 500;
font-size: clamp(46px, 11vw, 88px);
margin: 6px 0 0;
color: var(--ink);
line-height: 1.05;
}
.hero-sub {
max-width: 520px;
margin: 0 auto 18px;
color: var(--ink-2);
font-size: 16px;
}
.hero-date {
font-family: var(--serif);
font-size: 20px;
font-style: italic;
color: var(--rose);
margin: 0;
}
/* ---- SECTION spacing ---- */
.section { padding: 48px 0; }
/* ---- TIMELINE ---- */
.timeline {
list-style: none;
margin: 0 auto;
padding: 0;
position: relative;
max-width: 780px;
}
.timeline::before {
content: "";
position: absolute;
top: 0; bottom: 0;
left: 50%;
width: 1px;
transform: translateX(-50%);
background: linear-gradient(180deg, transparent, var(--gold) 8%, var(--gold) 92%, transparent);
}
.tl-item {
position: relative;
width: 50%;
padding: 18px 42px;
opacity: 0;
transform: translateY(26px);
transition: opacity 0.7s ease, transform 0.7s ease;
}
.tl-item.in { opacity: 1; transform: none; }
.tl-item[data-side="left"] { left: 0; text-align: right; }
.tl-item[data-side="right"] { left: 50%; text-align: left; }
.tl-card {
display: inline-block;
text-align: left;
background: var(--white);
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 20px 22px;
box-shadow: 0 10px 30px -20px rgba(58, 43, 43, 0.5);
transition: transform 0.25s ease, box-shadow 0.25s ease, border-color 0.25s ease;
}
.tl-card:hover {
transform: translateY(-4px);
box-shadow: 0 18px 40px -22px rgba(201, 138, 134, 0.65);
border-color: var(--blush);
}
.tl-badge {
display: inline-block;
font-size: 11px;
font-weight: 600;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--gold-d);
background: var(--blush-50);
border: 1px solid rgba(201, 162, 75, 0.35);
padding: 4px 10px;
border-radius: 999px;
margin-bottom: 10px;
}
.tl-card h3 {
font-family: var(--serif);
font-weight: 600;
font-size: 24px;
margin: 4px 0 8px;
}
.tl-card p { margin: 0; color: var(--ink-2); font-size: 15px; }
.tl-dot {
position: absolute;
top: 30px;
width: 14px; height: 14px;
border-radius: 50%;
background: var(--white);
border: 2px solid var(--gold);
box-shadow: 0 0 0 4px var(--blush-50);
}
.tl-item[data-side="left"] .tl-dot { right: -7px; }
.tl-item[data-side="right"] .tl-dot { left: -7px; }
/* ---- PHOTO MOSAIC ---- */
.mosaic {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-auto-rows: 150px;
gap: 14px;
max-width: 780px;
margin: 0 auto;
}
.ph {
position: relative;
margin: 0;
border-radius: var(--r-md);
overflow: hidden;
border: 1px solid var(--line);
box-shadow: 0 12px 30px -22px rgba(58, 43, 43, 0.6);
}
.ph img {
width: 100%; height: 100%;
object-fit: cover;
transition: transform 0.5s ease;
}
.ph:hover img { transform: scale(1.06); }
.ph-tall { grid-row: span 2; }
.ph-wide { grid-column: span 2; }
.ph-grad {
background: linear-gradient(135deg, var(--blush) 0%, var(--cream) 55%, var(--gold) 130%);
}
.ph figcaption {
position: absolute;
left: 0; right: 0; bottom: 0;
padding: 10px 12px;
font-family: var(--serif);
font-style: italic;
font-size: 15px;
color: var(--white);
background: linear-gradient(0deg, rgba(58, 43, 43, 0.6), transparent);
}
.ph-grad figcaption {
color: var(--ink);
background: linear-gradient(0deg, rgba(255, 255, 255, 0.5), transparent);
}
/* ---- QUOTES ---- */
.quotes {
background: var(--cream);
border-radius: var(--r-lg);
padding: 52px 24px 40px;
margin: 24px 0;
}
.quote-stage {
display: flex;
align-items: center;
justify-content: center;
gap: 14px;
max-width: 680px;
margin: 0 auto;
}
.q-card {
flex: 1;
margin: 0;
min-height: 150px;
text-align: center;
position: relative;
transition: opacity 0.4s ease, transform 0.4s ease;
}
.q-card.fade { opacity: 0; transform: translateY(8px); }
.q-mark {
font-family: var(--serif);
font-size: 56px;
line-height: 0;
color: var(--blush);
display: block;
height: 22px;
}
.q-text {
font-family: var(--serif);
font-size: clamp(20px, 4vw, 27px);
font-weight: 500;
color: var(--ink);
margin: 0 0 14px;
line-height: 1.35;
}
.q-by {
font-size: 12px;
font-weight: 600;
letter-spacing: 0.2em;
text-transform: uppercase;
color: var(--gold-d);
}
.q-arrow {
flex: 0 0 auto;
width: 42px; height: 42px;
border-radius: 50%;
border: 1px solid var(--line);
background: var(--white);
color: var(--ink);
font-size: 18px;
cursor: pointer;
transition: background 0.2s ease, color 0.2s ease, transform 0.2s ease, border-color 0.2s ease;
}
.q-arrow:hover { background: var(--gold); color: var(--white); border-color: var(--gold); transform: scale(1.06); }
.q-arrow:active { transform: scale(0.94); }
.q-dots {
display: flex;
justify-content: center;
gap: 8px;
margin-top: 22px;
}
.q-dot {
width: 8px; height: 8px;
padding: 0;
border-radius: 50%;
border: none;
background: rgba(201, 138, 134, 0.35);
cursor: pointer;
transition: background 0.2s ease, transform 0.2s ease;
}
.q-dot:hover { transform: scale(1.3); }
.q-dot.is-active { background: var(--gold-d); transform: scale(1.3); }
/* ---- WEDDING PARTY ---- */
.filters {
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 10px;
margin-bottom: 30px;
}
.chip {
font-family: var(--sans);
font-size: 13px;
font-weight: 600;
letter-spacing: 0.04em;
color: var(--ink-2);
background: var(--white);
border: 1px solid var(--line);
border-radius: 999px;
padding: 9px 18px;
cursor: pointer;
transition: all 0.2s ease;
}
.chip:hover { border-color: var(--blush); color: var(--rose); }
.chip.is-active {
background: var(--rose);
border-color: var(--rose);
color: var(--white);
}
.party {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: 18px;
max-width: 820px;
margin: 0 auto;
}
.member {
background: var(--white);
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 22px 18px;
text-align: center;
transition: transform 0.28s ease, box-shadow 0.28s ease, border-color 0.28s ease, opacity 0.28s ease;
}
.member.hide {
display: none;
}
.member:hover {
transform: translateY(-5px);
box-shadow: 0 20px 44px -26px rgba(201, 138, 134, 0.75);
border-color: var(--blush);
}
.avatar {
width: 78px; height: 78px;
margin: 0 auto 14px;
border-radius: 50%;
display: grid;
place-items: center;
font-family: var(--serif);
font-size: 28px;
font-weight: 600;
color: var(--white);
border: 3px solid var(--white);
box-shadow: 0 0 0 2px var(--gold);
}
.member h3 {
font-family: var(--serif);
font-weight: 600;
font-size: 21px;
margin: 0 0 4px;
}
.role-badge {
display: inline-block;
font-size: 10px;
font-weight: 700;
letter-spacing: 0.12em;
text-transform: uppercase;
padding: 3px 10px;
border-radius: 999px;
margin-bottom: 10px;
}
.role-badge.bride { color: var(--rose); background: var(--blush-50); }
.role-badge.groom { color: var(--gold-d); background: #f6efe0; }
.member p { margin: 0; font-size: 13px; color: var(--muted); font-style: italic; }
/* ---- CTA ---- */
.cta { text-align: center; }
.cta-sub { color: var(--ink-2); max-width: 460px; margin: 0 auto 24px; }
.cta-form {
display: flex;
flex-wrap: wrap;
gap: 10px;
justify-content: center;
max-width: 560px;
margin: 0 auto;
}
.cta-form input {
flex: 1 1 180px;
font-family: var(--sans);
font-size: 15px;
padding: 13px 16px;
border: 1px solid var(--line);
border-radius: 999px;
background: var(--white);
color: var(--ink);
}
.cta-form input::placeholder { color: var(--muted); }
.cta-form input:focus-visible {
outline: none;
border-color: var(--gold);
box-shadow: 0 0 0 3px rgba(201, 162, 75, 0.25);
}
.btn-gold {
flex: 0 0 auto;
font-family: var(--sans);
font-size: 14px;
font-weight: 700;
letter-spacing: 0.04em;
color: var(--white);
background: linear-gradient(135deg, var(--gold), var(--gold-d));
border: none;
border-radius: 999px;
padding: 13px 26px;
cursor: pointer;
transition: transform 0.2s ease, box-shadow 0.2s ease, filter 0.2s ease;
}
.btn-gold:hover { transform: translateY(-2px); box-shadow: 0 12px 24px -12px rgba(168, 134, 47, 0.8); filter: brightness(1.05); }
.btn-gold:active { transform: translateY(0); }
/* focus visibility across interactive elements */
.chip:focus-visible,
.q-arrow:focus-visible,
.q-dot:focus-visible,
.btn-gold:focus-visible {
outline: 2px solid var(--gold-d);
outline-offset: 3px;
}
/* ---- FOOTER ---- */
.foot {
text-align: center;
padding-top: 40px;
border-top: 1px solid var(--line);
margin-top: 30px;
}
.foot p {
font-family: var(--serif);
font-style: italic;
color: var(--muted);
font-size: 16px;
margin: 6px 0 0;
}
/* ---- TOAST ---- */
.toast {
position: fixed;
left: 50%;
bottom: 26px;
transform: translate(-50%, 30px);
background: var(--ink);
color: var(--white);
font-size: 14px;
font-weight: 500;
padding: 12px 20px;
border-radius: 999px;
box-shadow: 0 14px 34px -14px rgba(58, 43, 43, 0.8);
opacity: 0;
pointer-events: none;
transition: opacity 0.3s ease, transform 0.3s ease;
z-index: 50;
}
.toast::before { content: "\2665 "; color: var(--blush); }
.toast.show { opacity: 1; transform: translate(-50%, 0); }
/* ---- RESPONSIVE ---- */
@media (max-width: 720px) {
.mosaic { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 520px) {
.page { padding: 0 16px 60px; }
.hero { padding: 48px 0 32px; }
.timeline::before { left: 12px; }
.tl-item,
.tl-item[data-side="left"],
.tl-item[data-side="right"] {
width: 100%;
left: 0;
text-align: left;
padding: 12px 0 12px 34px;
}
.tl-item[data-side="left"] .tl-dot,
.tl-item[data-side="right"] .tl-dot { left: 5px; right: auto; }
.mosaic { grid-template-columns: 1fr 1fr; grid-auto-rows: 120px; }
.ph-wide { grid-column: span 2; }
.quotes { padding: 40px 14px 30px; }
.q-arrow { width: 38px; height: 38px; }
.cta-form input { flex-basis: 100%; }
.btn-gold { width: 100%; }
}(function () {
"use strict";
/* ---------- toast helper ---------- */
var toastEl = document.getElementById("toast");
var toastTimer;
function toast(msg) {
if (!toastEl) return;
toastEl.textContent = msg;
toastEl.classList.add("show");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () {
toastEl.classList.remove("show");
}, 2600);
}
/* ---------- timeline reveal ---------- */
var items = document.querySelectorAll(".tl-item");
if ("IntersectionObserver" in window) {
var io = new IntersectionObserver(
function (entries) {
entries.forEach(function (e) {
if (e.isIntersecting) {
e.target.classList.add("in");
io.unobserve(e.target);
}
});
},
{ threshold: 0.25 }
);
items.forEach(function (el) { io.observe(el); });
} else {
items.forEach(function (el) { el.classList.add("in"); });
}
/* ---------- quote carousel ---------- */
var quotes = [
{ text: "You are, and always have been, my home no matter the city.", by: "Julian, in his vows" },
{ text: "He laughs at my worst jokes and that is how I knew.", by: "Amara" },
{ text: "Two people, one umbrella, and not a single dry shoulder.", by: "Priya, maid of honor" },
{ text: "They finish each other's grocery lists. It is unbearably sweet.", by: "Theo, best man" },
{ text: "Love is the quiet 8:14 we always choose to miss together.", by: "Amara & Julian" }
];
var qText = document.getElementById("q-text");
var qBy = document.getElementById("q-by");
var qCard = document.querySelector(".q-card");
var dotsWrap = document.getElementById("q-dots");
var qi = 0;
var autoTimer;
// build dots
quotes.forEach(function (_, i) {
var b = document.createElement("button");
b.className = "q-dot" + (i === 0 ? " is-active" : "");
b.type = "button";
b.setAttribute("role", "tab");
b.setAttribute("aria-label", "Love note " + (i + 1));
b.addEventListener("click", function () { goTo(i, true); });
dotsWrap.appendChild(b);
});
var dots = dotsWrap.querySelectorAll(".q-dot");
function render() {
qCard.classList.add("fade");
setTimeout(function () {
qText.textContent = quotes[qi].text;
qBy.textContent = quotes[qi].by;
dots.forEach(function (d, i) { d.classList.toggle("is-active", i === qi); });
qCard.classList.remove("fade");
}, 200);
}
function goTo(i, userAction) {
qi = (i + quotes.length) % quotes.length;
render();
if (userAction) restartAuto();
}
function restartAuto() {
clearInterval(autoTimer);
autoTimer = setInterval(function () { goTo(qi + 1, false); }, 6000);
}
document.querySelectorAll(".q-arrow").forEach(function (btn) {
btn.addEventListener("click", function () {
goTo(qi + (btn.dataset.q === "next" ? 1 : -1), true);
});
});
// keyboard: left/right when quote section focused/hovered
document.addEventListener("keydown", function (e) {
if (e.key === "ArrowRight") goTo(qi + 1, true);
else if (e.key === "ArrowLeft") goTo(qi - 1, true);
});
render();
restartAuto();
/* ---------- wedding party ---------- */
var party = [
{ name: "Priya Anand", role: "Maid of Honor", side: "bride", note: "Amara's sister & keeper of secrets", initials: "PA", c: "#c98a86" },
{ name: "Delia Cross", role: "Bridesmaid", side: "bride", note: "College roommate, forever co-conspirator", initials: "DC", c: "#e8b7b0" },
{ name: "Mara Ito", role: "Bridesmaid", side: "bride", note: "The one who plans everything", initials: "MI", c: "#c9a24b" },
{ name: "Nina Vale", role: "Bridesmaid", side: "bride", note: "Champagne enthusiast & cheerleader", initials: "NV", c: "#a8862f" },
{ name: "Theo Brandt", role: "Best Man", side: "groom", note: "Julian's brother & worst golfer", initials: "TB", c: "#a8862f" },
{ name: "Sam Okoro", role: "Groomsman", side: "groom", note: "Met on the missed 8:14 train", initials: "SO", c: "#c9a24b" },
{ name: "Luca Reyes", role: "Groomsman", side: "groom", note: "Playlist curator for life", initials: "LR", c: "#c98a86" },
{ name: "Owen Hale", role: "Groomsman", side: "groom", note: "First to say I told you so", initials: "OH", c: "#e8b7b0" }
];
var partyWrap = document.getElementById("party");
party.forEach(function (m) {
var card = document.createElement("article");
card.className = "member";
card.dataset.side = m.side;
card.innerHTML =
'<div class="avatar" style="background:' + m.c + '">' + m.initials + "</div>" +
"<h3>" + m.name + "</h3>" +
'<span class="role-badge ' + m.side + '">' + m.role + "</span>" +
"<p>" + m.note + "</p>";
partyWrap.appendChild(card);
});
var chips = document.querySelectorAll(".chip");
chips.forEach(function (chip) {
chip.addEventListener("click", function () {
chips.forEach(function (c) { c.classList.remove("is-active"); });
chip.classList.add("is-active");
var f = chip.dataset.filter;
var shown = 0;
partyWrap.querySelectorAll(".member").forEach(function (m) {
var match = f === "all" || m.dataset.side === f;
m.classList.toggle("hide", !match);
if (match) shown++;
});
toast(f === "all" ? "Showing the whole party" : "Showing " + shown + " lovely people");
});
});
/* ---------- guestbook ---------- */
var form = document.getElementById("guestbook");
form.addEventListener("submit", function (e) {
e.preventDefault();
var name = document.getElementById("gb-name").value.trim();
if (!name) return;
toast("Thank you, " + name + " — your note is saved");
form.reset();
});
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Amara & Julian — Our Story</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">
<!-- HERO -->
<header class="hero">
<p class="eyebrow">Est. Two Hearts · One Story</p>
<div class="monogram" aria-hidden="true">A <span>&</span> J</div>
<h1 class="hero-title">Amara & Julian</h1>
<div class="rule" aria-hidden="true"></div>
<p class="hero-sub">
A little tale of coffee spilled, trains missed, and a promise made under
September lamplight. Here is how the two of us found our forever.
</p>
<p class="hero-date">Saturday, the twelfth of September · Willowmere Gardens</p>
</header>
<!-- TIMELINE -->
<section class="section" aria-labelledby="story-h">
<p class="kicker">Chapter One</p>
<h2 id="story-h" class="section-title">How We Met</h2>
<div class="rule center" aria-hidden="true"></div>
<ol class="timeline">
<li class="tl-item" data-side="left">
<div class="tl-card">
<span class="tl-badge">2019 · Spring</span>
<h3>A Spilled Latte</h3>
<p>We reached for the same corner table at Marlowe & Co. Julian
knocked my flat white across my notebook. He bought the replacement;
I kept the ruined pages.</p>
</div>
<span class="tl-dot" aria-hidden="true"></span>
</li>
<li class="tl-item" data-side="right">
<div class="tl-card">
<span class="tl-badge">2019 · Autumn</span>
<h3>The Missed Train</h3>
<p>Six months of almost-dates until we both missed the 8:14 to Brighton.
We walked the whole platform and talked until the next one, then let
that one go too.</p>
</div>
<span class="tl-dot" aria-hidden="true"></span>
</li>
<li class="tl-item" data-side="left">
<div class="tl-card">
<span class="tl-badge">2021 · Summer</span>
<h3>A Key, A Kettle</h3>
<p>We painted a tiny flat the color of oat milk and argued happily over
bookshelves. The kettle whistled us awake every morning after.</p>
</div>
<span class="tl-dot" aria-hidden="true"></span>
</li>
<li class="tl-item" data-side="right">
<div class="tl-card">
<span class="tl-badge">2024 · September</span>
<h3>The Question</h3>
<p>Under a lamppost in Willowmere Gardens, Julian forgot his speech and
simply asked. Amara said yes before he finished the sentence.</p>
</div>
<span class="tl-dot" aria-hidden="true"></span>
</li>
</ol>
</section>
<!-- PHOTO MOSAIC -->
<section class="section" aria-labelledby="gallery-h">
<p class="kicker">In Frames</p>
<h2 id="gallery-h" class="section-title">Moments We Kept</h2>
<div class="rule center" aria-hidden="true"></div>
<div class="mosaic">
<figure class="ph ph-tall ph-a" role="img" aria-label="The couple by the sea at dusk">
<img src="https://images.unsplash.com/photo-1519741497674-611481863552?auto=format&fit=crop&w=700&q=70" alt="" loading="lazy" />
<figcaption>Brighton, first winter</figcaption>
</figure>
<figure class="ph ph-b" role="img" aria-label="Two coffee cups on a wooden table">
<img src="https://images.unsplash.com/photo-1509042239860-f550ce710b93?auto=format&fit=crop&w=500&q=70" alt="" loading="lazy" />
<figcaption>Where it began</figcaption>
</figure>
<figure class="ph ph-grad ph-c" role="img" aria-label="Blush and gold gradient">
<figcaption>Golden hour</figcaption>
</figure>
<figure class="ph ph-wide ph-d" role="img" aria-label="Hands holding rings">
<img src="https://images.unsplash.com/photo-1583939003579-730e3918a45a?auto=format&fit=crop&w=800&q=70" alt="" loading="lazy" />
<figcaption>The yes, September</figcaption>
</figure>
</div>
</section>
<!-- QUOTE CAROUSEL -->
<section class="section quotes" aria-labelledby="quotes-h">
<p class="kicker light">Whispered</p>
<h2 id="quotes-h" class="section-title">Love Notes</h2>
<div class="rule center gold" aria-hidden="true"></div>
<div class="quote-stage">
<button class="q-arrow" data-q="prev" aria-label="Previous note">←</button>
<blockquote class="q-card" aria-live="polite">
<span class="q-mark" aria-hidden="true">“</span>
<p class="q-text" id="q-text">Loading…</p>
<footer class="q-by" id="q-by"></footer>
</blockquote>
<button class="q-arrow" data-q="next" aria-label="Next note">→</button>
</div>
<div class="q-dots" id="q-dots" role="tablist" aria-label="Choose a love note"></div>
</section>
<!-- WEDDING PARTY -->
<section class="section" aria-labelledby="party-h">
<p class="kicker">By Our Side</p>
<h2 id="party-h" class="section-title">The Wedding Party</h2>
<div class="rule center" aria-hidden="true"></div>
<div class="filters" role="group" aria-label="Filter wedding party by side">
<button class="chip is-active" data-filter="all">Everyone</button>
<button class="chip" data-filter="bride">Her Side</button>
<button class="chip" data-filter="groom">His Side</button>
</div>
<div class="party" id="party"></div>
</section>
<!-- GUESTBOOK CTA -->
<section class="section cta" aria-labelledby="cta-h">
<h2 id="cta-h" class="section-title">Leave Us A Note</h2>
<div class="rule center gold" aria-hidden="true"></div>
<p class="cta-sub">Add a little blessing to our guestbook before the big day.</p>
<form class="cta-form" id="guestbook">
<label class="sr-only" for="gb-name">Your name</label>
<input id="gb-name" type="text" placeholder="Your name" autocomplete="name" required />
<label class="sr-only" for="gb-msg">Your note</label>
<input id="gb-msg" type="text" placeholder="A wish for the couple…" required />
<button type="submit" class="btn-gold">Send Love</button>
</form>
</section>
<footer class="foot">
<div class="monogram sm" aria-hidden="true">A <span>&</span> J</div>
<p>Amara & Julian · 09.12.2026 · Willowmere Gardens</p>
</footer>
</div>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>A romantic “our story” page for the wedding of Amara and Julian, built for a single-file static site. The hero opens with a serif monogram and a short introduction, followed by a how-we-met timeline whose alternating cards animate into place with an IntersectionObserver as the reader scrolls. A photo mosaic uses soft gradient blocks and Unsplash-style placeholders arranged in an editorial grid.
Below the story sits a love-note carousel that rotates through guest and couple quotes on a timer, with previous and next controls plus keyboard support. The wedding-party section presents bridesmaid and groomsman cards with role badges, and a small filter bar lets you show one side or both. A lightweight toast() helper confirms interactions such as adding a note to the guestbook.
Everything is vanilla JS with no dependencies: the timeline reveal, quote rotation, party filtering, and toast notifications are all wired by hand. The layout is fully responsive down to about 360px, keyboard-usable, and uses aria-live regions so screen readers follow the quote changes.