Wedding — Schedule Day
An editorial day-of wedding timeline in blush and gold: a vertical ribbon of milestones from first look to last dance, each stop carrying a time, an illustrated icon, a venue location and a soft badge. Guests tap any event to expand its details, filter by moment, and watch a live marker glide to whatever is happening now — a keepsake schedule that feels printed on fine stationery yet stays fully interactive.
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 44px -24px rgba(58, 43, 43, 0.4);
}
* {
box-sizing: border-box;
}
html {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
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% -8%, var(--blush-50), transparent 60%),
var(--bg);
min-height: 100vh;
}
.page {
max-width: 760px;
margin: 0 auto;
padding: 48px 22px 64px;
}
/* ---------- Masthead ---------- */
.masthead {
text-align: center;
}
.eyebrow {
margin: 0 0 6px;
font-size: 0.72rem;
letter-spacing: 0.34em;
text-transform: uppercase;
color: var(--rose);
font-weight: 600;
}
.monogram {
font-family: "Cormorant Garamond", Georgia, serif;
font-weight: 500;
font-size: clamp(2.6rem, 8vw, 4rem);
line-height: 1.05;
margin: 0 0 12px;
color: var(--ink);
}
.amp {
color: var(--gold);
font-style: italic;
padding: 0 0.12em;
}
.date-line {
margin: 0 auto;
display: inline-flex;
flex-wrap: wrap;
justify-content: center;
gap: 10px;
align-items: center;
font-size: 0.9rem;
color: var(--ink-2);
}
.date-line .dot {
color: var(--gold);
}
.clock {
margin: 18px auto 0;
display: inline-flex;
align-items: center;
gap: 10px;
padding: 8px 16px;
background: var(--white);
border: 1px solid var(--line);
border-radius: 999px;
box-shadow: var(--shadow);
}
.clock-label {
font-size: 0.68rem;
letter-spacing: 0.16em;
text-transform: uppercase;
color: var(--muted);
font-weight: 600;
}
.clock-time {
font-variant-numeric: tabular-nums;
font-weight: 700;
color: var(--gold-d);
font-size: 0.95rem;
}
.rule {
border: 0;
height: 1px;
width: 120px;
margin: 26px auto 0;
background: linear-gradient(90deg, transparent, var(--gold), transparent);
}
/* ---------- Filters ---------- */
.filters {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 10px;
margin: 30px 0 40px;
}
.chip {
font-family: inherit;
font-size: 0.82rem;
font-weight: 600;
color: var(--ink-2);
background: var(--white);
border: 1px solid var(--line);
border-radius: 999px;
padding: 9px 18px;
cursor: pointer;
transition: transform 0.15s ease, background 0.2s ease, color 0.2s ease,
border-color 0.2s ease, box-shadow 0.2s ease;
}
.chip:hover {
transform: translateY(-1px);
border-color: var(--blush);
box-shadow: 0 8px 18px -12px rgba(201, 138, 134, 0.7);
}
.chip:focus-visible {
outline: 2px solid var(--gold);
outline-offset: 2px;
}
.chip.is-active {
background: linear-gradient(180deg, var(--rose), #b8776f);
color: var(--white);
border-color: transparent;
}
.chip-now {
color: var(--gold-d);
border-color: var(--gold);
background: linear-gradient(180deg, #fff, var(--cream));
}
.chip-now:hover {
border-color: var(--gold-d);
}
/* ---------- Timeline ---------- */
.timeline {
list-style: none;
margin: 0;
padding: 0;
position: relative;
}
.timeline::before {
content: "";
position: absolute;
left: 74px;
top: 8px;
bottom: 8px;
width: 2px;
background: linear-gradient(180deg, var(--blush), var(--gold) 60%, var(--blush));
opacity: 0.55;
}
.event {
position: relative;
display: grid;
grid-template-columns: 84px 1fr;
gap: 8px;
margin-bottom: 22px;
transition: opacity 0.35s ease, transform 0.35s ease;
}
.event.is-hidden {
display: none;
}
.node {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
padding-top: 20px;
}
.node-time {
font-family: "Cormorant Garamond", Georgia, serif;
font-size: 1.15rem;
font-weight: 600;
color: var(--ink-2);
font-variant-numeric: tabular-nums;
}
.node-dot {
position: absolute;
right: -12px;
top: 26px;
width: 15px;
height: 15px;
border-radius: 50%;
background: var(--white);
border: 3px solid var(--gold);
box-shadow: 0 0 0 4px var(--bg);
z-index: 1;
transition: transform 0.2s ease, border-color 0.2s ease;
}
.event.is-now .node-dot {
border-color: var(--rose);
background: var(--rose);
transform: scale(1.25);
animation: pulse 2s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% {
box-shadow: 0 0 0 4px var(--bg), 0 0 0 0 rgba(201, 138, 134, 0.5);
}
50% {
box-shadow: 0 0 0 4px var(--bg), 0 0 0 8px rgba(201, 138, 134, 0);
}
}
/* ---------- Card ---------- */
.card {
background: var(--white);
border: 1px solid var(--line);
border-radius: var(--r-md);
box-shadow: var(--shadow);
overflow: hidden;
cursor: pointer;
transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
}
.card:hover {
transform: translateY(-2px);
border-color: var(--blush);
}
.card:focus-visible {
outline: 2px solid var(--gold);
outline-offset: 3px;
}
.event.is-now .card {
border-color: var(--rose);
box-shadow: 0 18px 44px -20px rgba(201, 138, 134, 0.6);
}
.card-head {
display: grid;
grid-template-columns: auto 1fr auto auto;
align-items: center;
gap: 14px;
padding: 16px 18px;
}
.icon {
width: 44px;
height: 44px;
display: grid;
place-items: center;
font-size: 1.25rem;
border-radius: 50%;
background: var(--blush-50);
border: 1px solid rgba(201, 138, 134, 0.28);
flex-shrink: 0;
}
.card-title h2 {
font-family: "Cormorant Garamond", Georgia, serif;
font-weight: 600;
font-size: 1.4rem;
line-height: 1.15;
margin: 0 0 2px;
color: var(--ink);
}
.loc {
margin: 0;
font-size: 0.8rem;
color: var(--muted);
}
.card-meta {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 6px;
text-align: right;
}
.badge {
font-size: 0.64rem;
font-weight: 700;
letter-spacing: 0.1em;
text-transform: uppercase;
padding: 4px 9px;
border-radius: 999px;
white-space: nowrap;
}
.badge-ceremony {
background: var(--blush-50);
color: var(--rose);
}
.badge-reception {
background: var(--cream);
color: var(--gold-d);
}
.badge-party {
background: #efe6f0;
color: #8a5a86;
}
.time {
font-size: 0.82rem;
font-weight: 600;
color: var(--ink-2);
font-variant-numeric: tabular-nums;
}
.chevron {
font-size: 1.5rem;
color: var(--gold);
transition: transform 0.28s ease;
line-height: 1;
}
.card.is-open .chevron {
transform: rotate(90deg);
}
.card-body {
max-height: 0;
opacity: 0;
padding: 0 18px;
transition: max-height 0.35s ease, opacity 0.3s ease, padding 0.35s ease;
}
.card.is-open .card-body {
max-height: 320px;
opacity: 1;
padding: 0 18px 18px;
}
.card-body p {
margin: 0 0 10px;
font-size: 0.9rem;
color: var(--ink-2);
}
.card-body .cue {
font-size: 0.84rem;
color: var(--ink);
padding-top: 10px;
border-top: 1px dashed var(--line);
}
.card-body .cue strong {
color: var(--rose);
}
.add-btn {
font-family: inherit;
font-size: 0.8rem;
font-weight: 600;
color: var(--white);
background: linear-gradient(180deg, var(--gold), var(--gold-d));
border: 0;
border-radius: 999px;
padding: 9px 20px;
cursor: pointer;
transition: transform 0.15s ease, box-shadow 0.2s ease, filter 0.2s ease;
box-shadow: 0 8px 18px -10px rgba(168, 134, 47, 0.8);
}
.add-btn:hover {
transform: translateY(-1px);
filter: brightness(1.05);
}
.add-btn:active {
transform: translateY(0);
}
.add-btn:focus-visible {
outline: 2px solid var(--gold-d);
outline-offset: 2px;
}
.add-btn.is-added {
background: linear-gradient(180deg, var(--rose), #b8776f);
box-shadow: none;
}
/* ---------- Footer ---------- */
.foot {
text-align: center;
margin-top: 44px;
}
.foot p {
font-family: "Cormorant Garamond", Georgia, serif;
font-size: 1.1rem;
color: var(--ink-2);
margin: 20px 0 0;
font-style: italic;
}
/* ---------- Toast ---------- */
.toast {
position: fixed;
left: 50%;
bottom: 26px;
transform: translate(-50%, 20px);
background: var(--ink);
color: var(--white);
font-size: 0.86rem;
font-weight: 500;
padding: 12px 22px;
border-radius: 999px;
box-shadow: var(--shadow);
opacity: 0;
pointer-events: none;
transition: opacity 0.3s ease, transform 0.3s ease;
z-index: 50;
}
.toast.is-visible {
opacity: 1;
transform: translate(-50%, 0);
}
/* ---------- Responsive ---------- */
@media (max-width: 520px) {
.page {
padding: 36px 16px 52px;
}
.timeline::before {
left: 7px;
}
.event {
grid-template-columns: 1fr;
gap: 0;
padding-left: 26px;
}
.node {
flex-direction: row;
justify-content: flex-start;
gap: 8px;
padding: 0 0 8px;
}
.node-time {
font-size: 1rem;
}
.node-dot {
left: -26px;
right: auto;
top: 4px;
}
.card-head {
grid-template-columns: auto 1fr auto;
gap: 10px;
}
.card-title h2 {
font-size: 1.2rem;
}
.card-meta {
grid-column: 2 / -1;
flex-direction: row;
align-items: center;
justify-content: flex-start;
margin-top: 4px;
}
.chevron {
grid-row: 1;
grid-column: 3;
}
.icon {
width: 38px;
height: 38px;
font-size: 1.05rem;
}
}(function () {
"use strict";
var timeline = document.getElementById("timeline");
var events = Array.prototype.slice.call(
document.querySelectorAll(".event")
);
var chips = Array.prototype.slice.call(
document.querySelectorAll(".chip[data-filter]")
);
var jumpNow = document.getElementById("jumpNow");
var clockEl = document.getElementById("liveClock");
var toastEl = document.getElementById("toast");
var toastTimer = null;
/* ---------- Toast helper ---------- */
function toast(msg) {
toastEl.textContent = msg;
toastEl.classList.add("is-visible");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () {
toastEl.classList.remove("is-visible");
}, 2400);
}
/* ---------- Time helpers ---------- */
// Demo "now" anchored to the wedding day so the live marker always lands
// on a real event. Uses the current minute-of-day for a lively clock.
function minutesFromStr(str) {
var parts = str.split(":");
return parseInt(parts[0], 10) * 60 + parseInt(parts[1], 10);
}
function nowMinutes() {
var d = new Date();
// Map real time onto the wedding window (14:30–23:00) so the demo is
// always active. Scale seconds of the day into that band.
var startBand = minutesFromStr("14:30");
var endBand = minutesFromStr("23:00");
var frac =
(d.getHours() * 3600 + d.getMinutes() * 60 + d.getSeconds()) / 86400;
return Math.round(startBand + frac * (endBand - startBand));
}
function fmt(mins) {
var h = Math.floor(mins / 60);
var m = mins % 60;
var ampm = h >= 12 ? "PM" : "AM";
var h12 = h % 12;
if (h12 === 0) h12 = 12;
return h12 + ":" + (m < 10 ? "0" + m : m) + " " + ampm;
}
/* ---------- Live "now" marker ---------- */
function currentEvent() {
var mins = nowMinutes();
var active = null;
var upcoming = null;
events.forEach(function (ev) {
var s = minutesFromStr(ev.getAttribute("data-start"));
var e = minutesFromStr(ev.getAttribute("data-end"));
if (mins >= s && mins < e) active = ev;
if (!active && !upcoming && mins < s) upcoming = ev;
});
return active || upcoming || events[events.length - 1];
}
function updateNow() {
var now = nowMinutes();
clockEl.textContent = fmt(now);
var target = currentEvent();
events.forEach(function (ev) {
ev.classList.toggle("is-now", ev === target);
});
}
/* ---------- Filtering ---------- */
function applyFilter(filter) {
events.forEach(function (ev) {
var match = filter === "all" || ev.getAttribute("data-cat") === filter;
ev.classList.toggle("is-hidden", !match);
});
}
chips.forEach(function (chip) {
chip.addEventListener("click", function () {
chips.forEach(function (c) {
c.classList.remove("is-active");
c.setAttribute("aria-pressed", "false");
});
chip.classList.add("is-active");
chip.setAttribute("aria-pressed", "true");
applyFilter(chip.getAttribute("data-filter"));
});
});
/* ---------- Expand / collapse cards ---------- */
function toggleCard(card) {
var open = card.classList.toggle("is-open");
card.setAttribute("aria-expanded", open ? "true" : "false");
}
events.forEach(function (ev) {
var card = ev.querySelector(".card");
card.addEventListener("click", function (e) {
// Don't toggle when interacting with the inner action button.
if (e.target.closest(".add-btn")) return;
toggleCard(card);
});
card.addEventListener("keydown", function (e) {
if (e.target !== card) return;
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
toggleCard(card);
}
});
var addBtn = card.querySelector(".add-btn");
if (addBtn) {
addBtn.addEventListener("click", function () {
var title = card.querySelector(".card-title h2").textContent;
if (addBtn.classList.contains("is-added")) {
addBtn.classList.remove("is-added");
addBtn.textContent = "Add to my plan";
toast("Removed “" + title + "” from your plan");
} else {
addBtn.classList.add("is-added");
addBtn.textContent = "✓ In your plan";
toast("Added “" + title + "” to your plan");
}
});
}
});
/* ---------- Jump to now ---------- */
jumpNow.addEventListener("click", function () {
updateNow();
var target = document.querySelector(".event.is-now");
if (!target) return;
// Make sure it's visible under the active filter.
if (target.classList.contains("is-hidden")) {
chips.forEach(function (c) {
c.classList.remove("is-active");
c.setAttribute("aria-pressed", "false");
});
chips[0].classList.add("is-active");
chips[0].setAttribute("aria-pressed", "true");
applyFilter("all");
}
var card = target.querySelector(".card");
card.classList.add("is-open");
card.setAttribute("aria-expanded", "true");
target.scrollIntoView({ behavior: "smooth", block: "center" });
toast("Happening now: " + target.querySelector("h2").textContent);
});
/* ---------- Init ---------- */
updateNow();
setInterval(updateNow, 1000);
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Amelia & Julian — Day-of Schedule</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>
<main class="page">
<header class="masthead">
<p class="eyebrow">The wedding of</p>
<h1 class="monogram">Amelia <span class="amp">&</span> Julian</h1>
<p class="date-line">
<span>Saturday, the fourteenth of June</span>
<span class="dot" aria-hidden="true">•</span>
<span>Rosewood Estate, Sonoma</span>
</p>
<div class="clock" role="status" aria-live="polite">
<span class="clock-label">Right now</span>
<span class="clock-time" id="liveClock">—</span>
</div>
<hr class="rule" />
</header>
<nav class="filters" aria-label="Filter schedule by moment">
<button class="chip is-active" data-filter="all" aria-pressed="true">All moments</button>
<button class="chip" data-filter="ceremony" aria-pressed="false">Ceremony</button>
<button class="chip" data-filter="reception" aria-pressed="false">Reception</button>
<button class="chip" data-filter="party" aria-pressed="false">Party</button>
<button class="chip chip-now" id="jumpNow" type="button">Jump to now</button>
</nav>
<ol class="timeline" id="timeline">
<!-- First Look -->
<li class="event" data-cat="ceremony" data-start="14:30" data-end="15:15">
<div class="node" aria-hidden="true">
<span class="node-time">2:30</span>
<span class="node-dot"></span>
</div>
<article class="card" tabindex="0" role="button" aria-expanded="false">
<div class="card-head">
<span class="icon" aria-hidden="true">📷</span>
<div class="card-title">
<h2>First Look & Portraits</h2>
<p class="loc">Willow Garden, west lawn</p>
</div>
<div class="card-meta">
<span class="badge badge-ceremony">Ceremony</span>
<span class="time">2:30 PM</span>
</div>
<span class="chevron" aria-hidden="true">›</span>
</div>
<div class="card-body">
<p>The couple share a private first look beneath the willow before family portraits. Guests, please enjoy welcome lemonade on the terrace.</p>
<p class="cue"><strong>Find:</strong> Clara, the wedding planner, in sage green.</p>
<button class="add-btn" type="button">Add to my plan</button>
</div>
</article>
</li>
<!-- Ceremony -->
<li class="event" data-cat="ceremony" data-start="15:30" data-end="16:15">
<div class="node" aria-hidden="true">
<span class="node-time">3:30</span>
<span class="node-dot"></span>
</div>
<article class="card" tabindex="0" role="button" aria-expanded="false">
<div class="card-head">
<span class="icon" aria-hidden="true">💐</span>
<div class="card-title">
<h2>The Ceremony</h2>
<p class="loc">Rose Arbor, main lawn</p>
</div>
<div class="card-meta">
<span class="badge badge-ceremony">Ceremony</span>
<span class="time">3:30 PM</span>
</div>
<span class="chevron" aria-hidden="true">›</span>
</div>
<div class="card-body">
<p>Please be seated by 3:20 PM. Vows will be exchanged beneath the rose arbor, followed by a ring blessing and recessional.</p>
<p class="cue"><strong>Dress:</strong> Garden formal — block heels recommended for the lawn.</p>
<button class="add-btn" type="button">Add to my plan</button>
</div>
</article>
</li>
<!-- Cocktail Hour -->
<li class="event" data-cat="reception" data-start="16:15" data-end="17:15">
<div class="node" aria-hidden="true">
<span class="node-time">4:15</span>
<span class="node-dot"></span>
</div>
<article class="card" tabindex="0" role="button" aria-expanded="false">
<div class="card-head">
<span class="icon" aria-hidden="true">🍸</span>
<div class="card-title">
<h2>Cocktail Hour</h2>
<p class="loc">Stone Terrace & vineyard edge</p>
</div>
<div class="card-meta">
<span class="badge badge-reception">Reception</span>
<span class="time">4:15 PM</span>
</div>
<span class="chevron" aria-hidden="true">›</span>
</div>
<div class="card-body">
<p>Signature cocktails — the Blush Spritz and Gold Rush — plus passed hors d'oeuvres while the couple finishes portraits.</p>
<p class="cue"><strong>Note:</strong> Live acoustic guitar by the fountain.</p>
<button class="add-btn" type="button">Add to my plan</button>
</div>
</article>
</li>
<!-- Dinner -->
<li class="event" data-cat="reception" data-start="17:30" data-end="19:00">
<div class="node" aria-hidden="true">
<span class="node-time">5:30</span>
<span class="node-dot"></span>
</div>
<article class="card" tabindex="0" role="button" aria-expanded="false">
<div class="card-head">
<span class="icon" aria-hidden="true">🍽</span>
<div class="card-title">
<h2>Dinner & Toasts</h2>
<p class="loc">The Grand Barn</p>
</div>
<div class="card-meta">
<span class="badge badge-reception">Reception</span>
<span class="time">5:30 PM</span>
</div>
<span class="chevron" aria-hidden="true">›</span>
</div>
<div class="card-body">
<p>A seated three-course dinner. Find your table on the escort card wall. Toasts from the maid of honor and best man follow the main course.</p>
<p class="cue"><strong>Menu:</strong> Heirloom tomato, herb-roasted chicken or wild mushroom risotto.</p>
<button class="add-btn" type="button">Add to my plan</button>
</div>
</article>
</li>
<!-- First Dance / Cake -->
<li class="event" data-cat="party" data-start="19:15" data-end="19:45">
<div class="node" aria-hidden="true">
<span class="node-time">7:15</span>
<span class="node-dot"></span>
</div>
<article class="card" tabindex="0" role="button" aria-expanded="false">
<div class="card-head">
<span class="icon" aria-hidden="true">💓</span>
<div class="card-title">
<h2>First Dance & Cake</h2>
<p class="loc">The Grand Barn, dance floor</p>
</div>
<div class="card-meta">
<span class="badge badge-party">Party</span>
<span class="time">7:15 PM</span>
</div>
<span class="chevron" aria-hidden="true">›</span>
</div>
<div class="card-body">
<p>Gather round for the couple's first dance, followed by cake cutting — a three-tier almond & elderflower creation.</p>
<p class="cue"><strong>Tip:</strong> Best photo spot is the loft balcony above the floor.</p>
<button class="add-btn" type="button">Add to my plan</button>
</div>
</article>
</li>
<!-- Dancing -->
<li class="event" data-cat="party" data-start="20:00" data-end="23:00">
<div class="node" aria-hidden="true">
<span class="node-time">8:00</span>
<span class="node-dot"></span>
</div>
<article class="card" tabindex="0" role="button" aria-expanded="false">
<div class="card-head">
<span class="icon" aria-hidden="true">🎵</span>
<div class="card-title">
<h2>Dancing & Late Night</h2>
<p class="loc">The Grand Barn</p>
</div>
<div class="card-meta">
<span class="badge badge-party">Party</span>
<span class="time">8:00 PM</span>
</div>
<span class="chevron" aria-hidden="true">›</span>
</div>
<div class="card-body">
<p>The floor opens with DJ Vela. Late-night sliders and a coffee cart arrive at 9:30. Sparkler send-off at 11:00 PM sharp.</p>
<p class="cue"><strong>Send-off:</strong> Sparklers provided at the barn doors — line the drive.</p>
<button class="add-btn" type="button">Add to my plan</button>
</div>
</article>
</li>
</ol>
<footer class="foot">
<hr class="rule" />
<p>With love, Amelia & Julian — #TheRosewoodVow</p>
</footer>
</main>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>A romantic, print-inspired day-of schedule for a wedding. Events run down a thin gold ribbon as a vertical timeline — ceremony, cocktail hour, dinner, toasts and dancing — each with a start time, a hand-drawn style icon, the venue location and a category badge. The header carries the couple’s monogram, the date and a live clock, while a “now” marker slides to the current or next moment so guests always know where they should be.
The layout is airy and editorial: Cormorant Garamond headings paired with Inter body text, generous whitespace and hairline gold rules. Filter chips at the top narrow the timeline to ceremony, reception or party moments, and each event card expands on tap to reveal a short note, dress cue or “who to find” detail with a smooth height transition.
Everything is vanilla HTML, CSS and JavaScript — no frameworks. Interactions include keyboard-accessible expand/collapse cards, filter chips, a jump-to-now button that scrolls the active event into view, and a toast confirmation when a guest adds a moment to their personal plan. The design is fully responsive down to ~360px, where the timeline collapses to a single elegant column.