Wedding — Countdown Hero
An editorial save-the-date hero pairing a serif monogram with the couple names, ceremony date and venue, and a live days-hours-minutes-seconds countdown that ticks in real time. Blush-and-gold styling, thin gold rules, and an add-to-calendar button that downloads a ready-made .ics invite make it a romantic, self-contained opener for any wedding microsite or invitation landing page.
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,
body {
margin: 0;
}
body {
font-family: var(--sans);
color: var(--ink);
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background:
radial-gradient(120% 80% at 50% -10%, var(--blush-50) 0%, var(--bg) 55%),
radial-gradient(90% 60% at 100% 110%, var(--cream) 0%, transparent 60%),
var(--bg);
min-height: 100vh;
}
.hero {
position: relative;
min-height: 100vh;
display: grid;
place-items: center;
padding: 40px 20px;
overflow: hidden;
}
/* floating petals */
.petals {
position: absolute;
inset: 0;
pointer-events: none;
}
.petal {
position: absolute;
top: -24px;
width: 16px;
height: 16px;
background: radial-gradient(circle at 30% 30%, var(--blush), var(--rose));
border-radius: 0 100% 0 100%;
opacity: 0.55;
animation: fall linear infinite;
}
.petal:nth-child(1) { left: 8%; animation-duration: 13s; animation-delay: 0s; }
.petal:nth-child(2) { left: 24%; animation-duration: 17s; animation-delay: 3s; transform: scale(0.7); }
.petal:nth-child(3) { left: 47%; animation-duration: 15s; animation-delay: 6s; transform: scale(1.15); }
.petal:nth-child(4) { left: 66%; animation-duration: 19s; animation-delay: 1.5s; transform: scale(0.85); }
.petal:nth-child(5) { left: 82%; animation-duration: 14s; animation-delay: 4.5s; }
.petal:nth-child(6) { left: 93%; animation-duration: 21s; animation-delay: 8s; transform: scale(0.6); }
@keyframes fall {
0% { transform: translateY(0) rotate(0deg); opacity: 0; }
10% { opacity: 0.55; }
90% { opacity: 0.5; }
100% { transform: translateY(105vh) rotate(320deg); opacity: 0; }
}
/* card */
.card {
position: relative;
z-index: 1;
width: min(680px, 100%);
text-align: center;
padding: 56px 48px 48px;
background: rgba(255, 255, 255, 0.72);
border: 1px solid var(--line);
border-radius: var(--r-lg);
box-shadow:
0 1px 0 rgba(255, 255, 255, 0.9) inset,
0 30px 70px -30px rgba(58, 43, 43, 0.28);
backdrop-filter: blur(6px);
}
.card::before {
content: "";
position: absolute;
inset: 12px;
border: 1px solid var(--gold);
border-radius: calc(var(--r-lg) - 10px);
opacity: 0.35;
pointer-events: none;
}
.eyebrow {
margin: 0 0 18px;
font-size: 12px;
font-weight: 600;
letter-spacing: 0.28em;
text-transform: uppercase;
color: var(--rose);
}
.monogram {
font-family: var(--serif);
font-size: 34px;
color: var(--gold-d);
letter-spacing: 0.08em;
margin-bottom: 6px;
}
.monogram span {
color: var(--rose);
font-size: 22px;
vertical-align: middle;
}
.names {
font-family: var(--serif);
font-weight: 500;
font-size: clamp(46px, 9vw, 78px);
line-height: 1.02;
margin: 4px 0 0;
color: var(--ink);
}
.names em {
font-style: italic;
color: var(--gold-d);
font-size: 0.7em;
padding: 0 0.1em;
}
.rule {
display: flex;
align-items: center;
justify-content: center;
gap: 14px;
margin: 22px auto 20px;
color: var(--gold);
font-size: 15px;
}
.rule span {
height: 1px;
width: 70px;
background: linear-gradient(90deg, transparent, var(--gold));
}
.rule span:last-child {
background: linear-gradient(90deg, var(--gold), transparent);
}
.date-line {
display: flex;
flex-direction: column;
gap: 4px;
margin: 0 0 34px;
color: var(--ink-2);
}
.date-line .dow {
font-family: var(--serif);
font-size: 22px;
color: var(--gold-d);
letter-spacing: 0.04em;
}
.date-line .full {
font-size: 14px;
font-weight: 600;
letter-spacing: 0.02em;
color: var(--ink);
}
.date-line .venue {
font-size: 13px;
color: var(--muted);
letter-spacing: 0.05em;
}
/* countdown */
.countdown {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 14px;
margin: 0 auto 32px;
}
.unit {
background: var(--white);
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 18px 8px 12px;
box-shadow: 0 10px 24px -18px rgba(58, 43, 43, 0.4);
transition: transform 0.25s ease, box-shadow 0.25s ease;
}
.unit:hover {
transform: translateY(-3px);
box-shadow: 0 16px 30px -18px rgba(201, 138, 134, 0.55);
}
.value {
display: block;
font-family: var(--serif);
font-weight: 600;
font-size: clamp(34px, 7vw, 48px);
line-height: 1;
color: var(--ink);
font-variant-numeric: tabular-nums;
}
.value.pulse {
animation: pulse 0.42s ease;
}
@keyframes pulse {
0% { transform: scale(1); color: var(--ink); }
40% { transform: scale(1.18); color: var(--gold-d); }
100% { transform: scale(1); color: var(--ink); }
}
.label {
display: block;
margin-top: 8px;
font-size: 10.5px;
font-weight: 700;
letter-spacing: 0.22em;
text-transform: uppercase;
color: var(--muted);
}
.arrived {
font-family: var(--serif);
font-size: 28px;
color: var(--gold-d);
margin: 0 0 30px;
}
/* actions */
.actions {
display: flex;
gap: 12px;
justify-content: center;
flex-wrap: wrap;
margin-bottom: 22px;
}
.btn {
display: inline-flex;
align-items: center;
gap: 9px;
font-family: var(--sans);
font-size: 13.5px;
font-weight: 600;
letter-spacing: 0.04em;
padding: 13px 24px;
border-radius: 999px;
border: 1px solid transparent;
cursor: pointer;
text-decoration: none;
transition: transform 0.15s ease, box-shadow 0.2s ease, background 0.2s ease, color 0.2s ease;
}
.btn:focus-visible {
outline: 2px solid var(--gold-d);
outline-offset: 3px;
}
.btn:active {
transform: translateY(1px);
}
.btn-primary {
background: linear-gradient(180deg, var(--gold), var(--gold-d));
color: #fff;
box-shadow: 0 12px 24px -12px rgba(168, 134, 47, 0.85);
}
.btn-primary:hover {
box-shadow: 0 16px 30px -12px rgba(168, 134, 47, 0.95);
transform: translateY(-2px);
}
.btn-ghost {
background: var(--white);
color: var(--ink);
border-color: var(--line);
}
.btn-ghost:hover {
background: var(--blush-50);
border-color: var(--blush);
color: var(--rose);
}
.hashtag {
margin: 0;
font-size: 12px;
font-weight: 600;
letter-spacing: 0.16em;
color: var(--rose);
text-transform: uppercase;
}
/* toast */
.toast {
position: fixed;
left: 50%;
bottom: 28px;
transform: translate(-50%, 20px);
background: var(--ink);
color: #fff;
font-size: 13px;
font-weight: 500;
padding: 12px 20px;
border-radius: 999px;
box-shadow: 0 16px 34px -14px rgba(58, 43, 43, 0.6);
opacity: 0;
pointer-events: none;
transition: opacity 0.3s ease, transform 0.3s ease;
z-index: 20;
}
.toast.show {
opacity: 1;
transform: translate(-50%, 0);
}
@media (max-width: 520px) {
.card {
padding: 40px 22px 36px;
}
.card::before {
inset: 8px;
}
.countdown {
gap: 8px;
}
.unit {
padding: 14px 4px 10px;
border-radius: var(--r-sm);
}
.label {
letter-spacing: 0.12em;
font-size: 9px;
}
.rule span {
width: 46px;
}
.date-line .full {
font-size: 13px;
}
.actions {
flex-direction: column;
}
.btn {
justify-content: center;
}
}(function () {
"use strict";
// --- Event details -------------------------------------------------------
var WEDDING = {
title: "Amara & Julian — Wedding Ceremony",
location: "The Rosewood Orchard, Napa Valley, California",
description: "Join us as we say I do. Ceremony at 4:00 PM, reception to follow. #AmaraAndJulian2026",
// Target date: September 12, 2026, 4:00 PM (local time)
start: new Date(2026, 8, 12, 16, 0, 0),
durationHours: 6,
};
var els = {
days: document.getElementById("days"),
hours: document.getElementById("hours"),
minutes: document.getElementById("minutes"),
seconds: document.getElementById("seconds"),
countdown: document.querySelector(".countdown"),
arrived: document.getElementById("arrived"),
calBtn: document.getElementById("calBtn"),
toast: document.getElementById("toast"),
};
// --- Countdown -----------------------------------------------------------
function pad(n) {
return String(n).padStart(2, "0");
}
function setValue(el, next) {
if (el.textContent !== next) {
el.textContent = next;
el.classList.remove("pulse");
// force reflow so the animation can restart
void el.offsetWidth;
el.classList.add("pulse");
}
}
var finished = false;
function tick() {
var diff = WEDDING.start.getTime() - Date.now();
if (diff <= 0) {
if (!finished) {
finished = true;
els.countdown.hidden = true;
els.arrived.hidden = false;
}
return;
}
var totalSeconds = Math.floor(diff / 1000);
var days = Math.floor(totalSeconds / 86400);
var hours = Math.floor((totalSeconds % 86400) / 3600);
var minutes = Math.floor((totalSeconds % 3600) / 60);
var seconds = totalSeconds % 60;
setValue(els.days, pad(days));
setValue(els.hours, pad(hours));
setValue(els.minutes, pad(minutes));
setValue(els.seconds, pad(seconds));
}
tick();
setInterval(tick, 1000);
// --- Toast helper --------------------------------------------------------
var toastTimer;
function toast(msg) {
els.toast.textContent = msg;
els.toast.classList.add("show");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () {
els.toast.classList.remove("show");
}, 2600);
}
// --- Add to calendar (.ics) ---------------------------------------------
function formatICSDate(date) {
// Local-time floating value (no Z): YYYYMMDDTHHMMSS
return (
date.getFullYear() +
pad(date.getMonth() + 1) +
pad(date.getDate()) +
"T" +
pad(date.getHours()) +
pad(date.getMinutes()) +
pad(date.getSeconds())
);
}
function escapeICS(text) {
return String(text)
.replace(/\\/g, "\\\\")
.replace(/;/g, "\\;")
.replace(/,/g, "\\,")
.replace(/\n/g, "\\n");
}
function buildICS() {
var end = new Date(WEDDING.start.getTime() + WEDDING.durationHours * 3600 * 1000);
var stamp = formatICSDate(new Date());
var uid = "amara-julian-" + WEDDING.start.getTime() + "@stealthis.dev";
var lines = [
"BEGIN:VCALENDAR",
"VERSION:2.0",
"PRODID:-//Stealthis//Wedding Countdown Hero//EN",
"CALSCALE:GREGORIAN",
"METHOD:PUBLISH",
"BEGIN:VEVENT",
"UID:" + uid,
"DTSTAMP:" + stamp,
"DTSTART:" + formatICSDate(WEDDING.start),
"DTEND:" + formatICSDate(end),
"SUMMARY:" + escapeICS(WEDDING.title),
"DESCRIPTION:" + escapeICS(WEDDING.description),
"LOCATION:" + escapeICS(WEDDING.location),
"STATUS:CONFIRMED",
"BEGIN:VALARM",
"TRIGGER:-P1D",
"ACTION:DISPLAY",
"DESCRIPTION:" + escapeICS(WEDDING.title),
"END:VALARM",
"END:VEVENT",
"END:VCALENDAR",
];
return lines.join("\r\n");
}
els.calBtn.addEventListener("click", function () {
try {
var blob = new Blob([buildICS()], { type: "text/calendar;charset=utf-8" });
var url = URL.createObjectURL(blob);
var a = document.createElement("a");
a.href = url;
a.download = "amara-and-julian-save-the-date.ics";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
setTimeout(function () {
URL.revokeObjectURL(url);
}, 1000);
toast("Save the date added to your calendar ♡");
} catch (err) {
toast("Could not create the calendar file.");
}
});
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Amara & Julian — Save the Date</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="hero" aria-labelledby="couple-names">
<div class="petals" aria-hidden="true">
<span class="petal"></span><span class="petal"></span><span class="petal"></span>
<span class="petal"></span><span class="petal"></span><span class="petal"></span>
</div>
<section class="card">
<p class="eyebrow">Together with their families</p>
<div class="monogram" aria-hidden="true">A <span>&</span> J</div>
<h1 id="couple-names" class="names">Amara <em>&</em> Julian</h1>
<div class="rule" aria-hidden="true"><span></span>❦<span></span></div>
<p class="date-line">
<span class="dow">Saturday</span>
<span class="full">September 12, 2026 · 4:00 in the afternoon</span>
<span class="venue">The Rosewood Orchard · Napa Valley, California</span>
</p>
<div class="countdown" role="timer" aria-live="polite" aria-label="Time remaining until the wedding">
<div class="unit">
<span class="value" id="days">00</span>
<span class="label">Days</span>
</div>
<div class="unit">
<span class="value" id="hours">00</span>
<span class="label">Hours</span>
</div>
<div class="unit">
<span class="value" id="minutes">00</span>
<span class="label">Minutes</span>
</div>
<div class="unit">
<span class="value" id="seconds">00</span>
<span class="label">Seconds</span>
</div>
</div>
<p class="arrived" id="arrived" hidden>Today is the day — we are getting married!</p>
<div class="actions">
<button type="button" class="btn btn-primary" id="calBtn">
<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true">
<path fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"
d="M7 3v3M17 3v3M4 8h16M5 6h14a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1Z" />
</svg>
Add to calendar
</button>
<a class="btn btn-ghost" href="#rsvp" id="rsvpBtn">RSVP</a>
</div>
<p class="hashtag">#AmaraAndJulian2026</p>
</section>
</main>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>A romantic save-the-date hero built for a wedding microsite. A serif monogram and the couple’s names sit above a thin gold rule, followed by the full ceremony date and venue line. The centerpiece is a live countdown that splits the time remaining into days, hours, minutes, and seconds, each value animating gently as it flips over so the page always feels alive.
The countdown recalculates every second in vanilla JavaScript against a fixed target date. When the moment finally arrives, the four cards gracefully collapse into a single celebratory message. Each digit change triggers a subtle scale pulse, and the whole layout is centered over a soft blush gradient backdrop with floating petal accents.
The add-to-calendar button generates a standards-compliant .ics file on the fly and downloads it, so guests can drop the event straight into Apple Calendar, Google Calendar, or Outlook. A lightweight toast confirms the action. Everything is keyboard-accessible with visible focus rings, contrast-checked for WCAG AA, and responsive down to about 360px.