/* ── Tokens ─────────────────────────────────────────────────── */
:root {
  --bg: #140d2b;
  --bg-2: #24123f;
  --bg-3: #3a1240;
  --ink: #f6efff;
  --ink-dim: #c3b3dd;
  --pink: #ff5da2;
  --peach: #ffb37a;
  --gold: #ffd76e;
  --mint: #6ff0c8;
  --violet: #a97bff;

  --radius: 20px;
  --shadow: 0 24px 60px -20px rgb(0 0 0 / 0.6);
  --measure: 62ch;
  --gutter: clamp(1.25rem, 5vw, 4rem);
}

/* ── Base ───────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; }

html {
  scroll-behavior: smooth;
  /* Stop iOS from inflating text when the phone is turned sideways. */
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

body {
  margin: 0;
  min-height: 100vh;
  min-height: 100dvh;
  font-family: Outfit, ui-sans-serif, system-ui, sans-serif;
  font-size: clamp(1rem, 0.95rem + 0.3vw, 1.15rem);
  line-height: 1.65;
  color: var(--ink);
  background: var(--bg);
  overflow-x: hidden;
}

/*
 * The gradient lives on a fixed pseudo-element rather than using
 * `background-attachment: fixed`, which iOS Safari either ignores or repaints
 * on every scroll frame.
 */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  background:
    radial-gradient(120% 80% at 15% -10%, var(--bg-3) 0%, transparent 55%),
    radial-gradient(100% 70% at 85% 0%, #1b2f5e 0%, transparent 50%),
    linear-gradient(180deg, var(--bg) 0%, var(--bg-2) 55%, var(--bg) 100%);
}

h1, h2, h3 {
  font-family: Fraunces, ui-serif, Georgia, serif;
  line-height: 1.05;
  margin: 0 0 0.6em;
  text-wrap: balance;
}

p { text-wrap: pretty; }

code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.85em;
  padding: 0.15em 0.4em;
  border-radius: 6px;
  background: rgb(255 255 255 / 0.08);
}

.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

main {
  position: relative;
  z-index: 2;
  display: grid;
  gap: clamp(3rem, 12vh, 9rem);
  padding-block: clamp(2.5rem, 10vh, 7rem);
  padding-inline: max(var(--gutter), env(safe-area-inset-left)) max(var(--gutter), env(safe-area-inset-right));
  max-width: 68rem;
  margin-inline: auto;
}

section { text-align: center; }

section > p { margin-inline: auto; max-width: var(--measure); }

/* ── Confetti canvas ───────────────────────────────────────── */
#confetti {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 3;
}

/* ── Balloons ──────────────────────────────────────────────── */
.balloons {
  position: fixed;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  z-index: 1;
}

.balloon {
  position: absolute;
  bottom: -30vh;
  width: clamp(38px, 9vw, 58px);
  height: clamp(48px, 11.5vw, 74px);
  border-radius: 50% 50% 48% 48% / 58% 58% 42% 42%;
  opacity: 0.5;
  filter: blur(0.3px);
  animation: float-up linear infinite;
}

/* the string */
.balloon::after {
  content: "";
  position: absolute;
  top: 100%;
  left: 50%;
  width: 1px;
  height: 70px;
  background: linear-gradient(rgb(255 255 255 / 0.5), transparent);
}

/* the shine */
.balloon::before {
  content: "";
  position: absolute;
  top: 14%;
  left: 20%;
  width: 30%;
  height: 22%;
  border-radius: 50%;
  background: rgb(255 255 255 / 0.45);
  filter: blur(2px);
}

.b1 { left:  6%; background: var(--pink);   animation-duration: 19s; animation-delay:  0s;   transform: scale(0.8); }
.b2 { left: 22%; background: var(--gold);   animation-duration: 24s; animation-delay: -6s;   transform: scale(1.1); }
.b3 { left: 44%; background: var(--mint);   animation-duration: 21s; animation-delay: -12s;  transform: scale(0.7); }
.b4 { left: 63%; background: var(--violet); animation-duration: 27s; animation-delay: -3s;   transform: scale(1); }
.b5 { left: 81%; background: var(--peach);  animation-duration: 22s; animation-delay: -16s;  transform: scale(0.9); }
.b6 { left: 93%; background: var(--pink);   animation-duration: 30s; animation-delay: -9s;   transform: scale(0.6); }

@keyframes float-up {
  0%   { transform: translate3d(0, 0, 0) rotate(-6deg); }
  50%  { transform: translate3d(3vw, -70vh, 0) rotate(6deg); }
  100% { transform: translate3d(-2vw, -145vh, 0) rotate(-4deg); }
}

/* Six balloons is a crowd on a phone. Keep the ones that are spread out. */
@media (max-width: 40rem) {
  .b3, .b6 { display: none; }
}

/* ── Hero ──────────────────────────────────────────────────── */
.hero { padding-block: clamp(1rem, 6vh, 4rem); }

.eyebrow {
  font-size: clamp(0.7rem, 0.6rem + 0.5vw, 0.8rem);
  letter-spacing: clamp(0.12em, 0.06em + 0.4vw, 0.22em);
  text-transform: uppercase;
  color: var(--ink-dim);
  margin: 0 0 1.5rem;
}

.hero h1 {
  display: grid;
  gap: 0.05em;
  font-size: clamp(2.75rem, 15vw, 8.5rem);
  font-weight: 700;
  font-variation-settings: "SOFT" 100, "WONK" 1;
  margin-bottom: 1.5rem;
}

.line {
  display: block;
  opacity: 0;
  animation: drop-in 0.9s cubic-bezier(0.22, 1, 0.36, 1) forwards;
}

.line-1 { animation-delay: 0.1s; }
.line-2 { animation-delay: 0.25s; }
.line-3 { animation-delay: 0.4s; }

.name {
  font-style: italic;
  font-size: 1.15em;
  background: linear-gradient(100deg, var(--pink), var(--gold) 45%, var(--mint) 75%, var(--violet));
  background-size: 220% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  animation:
    drop-in 0.9s cubic-bezier(0.22, 1, 0.36, 1) 0.4s forwards,
    shimmer 9s ease-in-out 1.3s infinite;
}

@keyframes drop-in {
  from { opacity: 0; transform: translateY(0.4em) rotate(-1.5deg); filter: blur(8px); }
  to   { opacity: 1; transform: none; filter: none; }
}

@keyframes shimmer {
  0%, 100% { background-position: 0% 50%; }
  50%      { background-position: 100% 50%; }
}

.tagline {
  font-size: clamp(1.05rem, 1rem + 0.3vw, 1.15rem);
  color: var(--ink-dim);
  max-width: 42ch;
  margin: 0 auto 2.5rem;
  opacity: 0;
  animation: drop-in 0.9s ease 0.7s forwards;
}

/* ── Buttons ───────────────────────────────────────────────── */
.cta {
  display: inline-block;
  min-height: 44px;          /* comfortable tap target */
  padding: 0.85em 1.9em;
  border: 0;
  border-radius: 999px;
  font: inherit;
  font-weight: 600;
  color: #24102c;
  text-decoration: none;
  cursor: pointer;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
  background: linear-gradient(120deg, var(--gold), var(--pink));
  box-shadow: 0 12px 30px -10px var(--pink);
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}

.cta:focus-visible {
  transform: translateY(-3px) scale(1.03);
  box-shadow: 0 18px 40px -12px var(--pink);
}

@media (hover: hover) {
  .cta:hover {
    transform: translateY(-3px) scale(1.03);
    box-shadow: 0 18px 40px -12px var(--pink);
  }
}

.cta:active { transform: scale(0.97); }

:focus-visible {
  outline: 3px solid var(--mint);
  outline-offset: 4px;
}

/* ── Cake ──────────────────────────────────────────────────── */
.cake-section h2 { font-size: clamp(1.8rem, 5vw, 2.8rem); }

.hint {
  color: var(--ink-dim);
  font-size: 0.95rem;
  margin-top: -0.4rem;
}

.cake {
  display: block;
  margin: 2.5rem auto 0;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
  user-select: none;
  -webkit-user-select: none;
}

/*
 * The cake is drawn with absolutely-positioned pixel values, so it can't reflow.
 * It scales as a single unit instead, via `scale` — a separate property from
 * `transform`, so the hover lift and the blow-out wobble still compose on top.
 */
.cake-art {
  position: relative;
  display: block;
  width: 220px;
  height: 210px;
  scale: var(--cake-scale, 1);
  transform-origin: 50% 100%;
  transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}

@media (hover: hover) {
  .cake:hover .cake-art { transform: translateY(-6px); }
}

.cake:active .cake-art { transform: translateY(-2px); }

/* 240px plate + gutters needs ~280px of room; below that, shrink to fit. */
@media (max-width: 22rem) {
  .cake-art { --cake-scale: 0.82; }
}

.tier, .plate {
  position: absolute;
  left: 50%;
  translate: -50% 0;
  border-radius: 12px;
}

.tier-top    { bottom: 108px; width: 100px; height: 40px; background: linear-gradient(var(--peach), #f2855f); }
.tier-mid    { bottom:  66px; width: 150px; height: 46px; background: linear-gradient(var(--pink), #d63b83); }
.tier-bottom { bottom:  18px; width: 200px; height: 52px; background: linear-gradient(var(--violet), #6a3fc0); }

/* frosting drips */
.tier::after {
  content: "";
  position: absolute;
  inset: -6px 6px auto 6px;
  height: 16px;
  border-radius: 999px;
  background: repeating-linear-gradient(90deg, #fff5ec 0 14px, transparent 14px 20px);
  mask: radial-gradient(circle at 50% 0, #000 68%, transparent 70%);
  opacity: 0.92;
}

.plate {
  bottom: 4px;
  width: 240px;
  height: 16px;
  background: linear-gradient(rgb(255 255 255 / 0.22), rgb(255 255 255 / 0.06));
  border-radius: 50%;
}

.candle {
  position: absolute;
  bottom: 148px;
  width: 8px;
  height: 34px;
  border-radius: 3px;
  background: repeating-linear-gradient(45deg, #fff 0 5px, var(--mint) 5px 10px);
}

.candle:nth-of-type(1) { left: calc(50% - 30px); }
.candle:nth-of-type(2) { left: calc(50% - 4px); }
.candle:nth-of-type(3) { left: calc(50% + 22px); }

.flame {
  position: absolute;
  bottom: 100%;
  left: 50%;
  width: 14px;
  height: 22px;
  translate: -50% -4px;
  border-radius: 50% 50% 50% 50% / 65% 65% 35% 35%;
  background: radial-gradient(ellipse at 50% 75%, #fff 0%, var(--gold) 35%, #ff7a2f 75%, transparent 100%);
  box-shadow: 0 0 22px 6px rgb(255 176 71 / 0.55);
  transform-origin: 50% 100%;
  animation: flicker 1.1s ease-in-out infinite alternate;
}

@keyframes flicker {
  from { transform: translate(-50%, -4px) scale(1)      rotate(-3deg); opacity: 0.9; }
  to   { transform: translate(-50%, -4px) scale(1.12, 1.2) rotate(3deg); opacity: 1; }
}

/* candles out */
.cake-section.blown .flame {
  animation: none;
  opacity: 0;
  transform: translate(-50%, -18px) scale(0.2);
  box-shadow: none;
  transition: all 0.45s ease;
}

.cake-section.blown .candle:nth-of-type(2) .flame { transition-delay: 0.08s; }
.cake-section.blown .candle:nth-of-type(3) .flame { transition-delay: 0.16s; }

.wish-made {
  margin-top: 2rem;
  font-family: Fraunces, serif;
  font-size: 1.4rem;
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 0.5s ease 0.5s, transform 0.5s ease 0.5s;
}

.cake-section.blown .wish-made { opacity: 1; transform: none; }

/* ── Letter ────────────────────────────────────────────────── */
.letter {
  padding: clamp(2rem, 6vw, 3.5rem);
  border-radius: var(--radius);
  border: 1px solid rgb(255 255 255 / 0.12);
  background: linear-gradient(160deg, rgb(255 255 255 / 0.09), rgb(255 255 255 / 0.03));
  backdrop-filter: blur(10px);
  box-shadow: var(--shadow);
  text-align: left;
}

.letter h2 { font-size: clamp(1.6rem, 4vw, 2.2rem); }
.letter p { max-width: var(--measure); margin: 0 0 1.1em; }
.letter p:last-of-type { margin-bottom: 0; }

.signoff {
  font-family: Fraunces, serif;
  font-style: italic;
  font-size: 1.2rem;
  color: var(--gold);
}

/* ── Cards ─────────────────────────────────────────────────── */
.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr));
  gap: 1.25rem;
  list-style: none;
  padding: 0;
  margin: 2.5rem 0 1.5rem;
  text-align: left;
}

.card {
  padding: 1.6rem;
  border-radius: 16px;
  border: 1px solid rgb(255 255 255 / 0.1);
  background: rgb(255 255 255 / 0.05);
  transition: transform 0.25s ease, background 0.25s ease;
}

@media (hover: hover) {
  .card:hover {
    transform: translateY(-6px) rotate(-0.6deg);
    background: rgb(255 255 255 / 0.09);
  }
}

.card-emoji { font-size: 1.9rem; }
.card h3 { font-size: 1.15rem; margin: 0.5rem 0 0.35rem; }
.card p { margin: 0; color: var(--ink-dim); font-size: 0.95rem; }

/* ── Gallery ───────────────────────────────────────────────── */
.frames {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(15rem, 100%), 1fr));
  justify-content: center;
  gap: clamp(1.25rem, 4vw, 1.75rem);
  margin: 2.5rem 0 1.5rem;
}

.frame {
  /*
   * Capped and centred so a lone photo reads as a deliberate portrait instead
   * of stretching the full 68rem of the page. Still tiles if the other two
   * figures get uncommented.
   */
  max-width: 21rem;
  width: 100%;
  margin: 0 auto;
  padding: 0.75rem 0.75rem 0;
  border-radius: 14px;
  background: rgb(255 255 255 / 0.07);
  border: 1px solid rgb(255 255 255 / 0.1);
  box-shadow: var(--shadow);
  transition: transform 0.3s ease, rotate 0.3s ease;
}

.frame:nth-child(1) { rotate: -2deg; }
.frame:nth-child(3) { rotate: 2deg; }

@media (hover: hover) {
  .frame:hover { transform: scale(1.03); rotate: 0deg; }
}

.frame picture { display: block; }

.photo,
.frame img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  /* Native ratio is ~7/8, so cover trims the sides. Bias up to keep the face
     clear of the crop and off the caption. */
  object-position: 50% 28%;
  border-radius: 8px;
  background: linear-gradient(140deg, rgb(255 255 255 / 0.14), rgb(255 255 255 / 0.04));
}

/* label for the empty placeholders */
.photo {
  display: grid;
  place-items: center;
  color: var(--ink-dim);
  font-size: 0.8rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

.photo::after { content: attr(data-placeholder); }

.frame figcaption {
  padding: 0.9rem 0.25rem;
  font-size: 0.9rem;
  color: var(--ink-dim);
}

.footnote {
  font-size: 0.85rem;
  color: rgb(195 179 221 / 0.7);
}

/* ── Finale + footer ───────────────────────────────────────── */
.finale h2 { font-size: clamp(1.8rem, 5vw, 2.6rem); }
.finale .cta { margin-top: 1.75rem; }

footer {
  position: relative;
  z-index: 2;
  padding: 3rem var(--gutter);
  padding-bottom: calc(4rem + env(safe-area-inset-bottom));
  text-align: center;
  font-size: 0.9rem;
  color: var(--ink-dim);
}

footer p { margin: 0.2em 0; }

/* ── Scroll reveal ─────────────────────────────────────────── */
/*
 * Gated on `.js` (set by an inline script in <head>). If the script is blocked
 * or throws before the observer is wired up, the content simply stays visible
 * rather than sitting at opacity 0 forever.
 */
.js .reveal {
  opacity: 0;
  transform: translateY(28px);
  transition: opacity 0.7s ease, transform 0.7s cubic-bezier(0.22, 1, 0.36, 1);
}

.js .reveal.visible { opacity: 1; transform: none; }

/* ── Reduced motion ───────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }

  .line, .tagline { opacity: 1; }
  .js .reveal { opacity: 1; transform: none; }
  .balloons { display: none; }
}
