/* Palette and type from the approved brand kit (assets/design/brand-kit-approved.png). */
/* Self-hosted OFL fonts (licences in assets/fonts). */
@font-face { font-family: 'Fraunces'; src: url('../assets/fonts/fraunces-opsz.woff2') format('woff2'); font-weight: 100 900; font-style: normal; font-display: swap; }
@font-face { font-family: 'Fraunces'; src: url('../assets/fonts/fraunces-opsz-italic.woff2') format('woff2'); font-weight: 100 900; font-style: italic; font-display: swap; }
@font-face { font-family: 'Nunito'; src: url('../assets/fonts/nunito-wght.woff2') format('woff2'); font-weight: 200 1000; font-display: swap; }
@font-face { font-family: 'Caveat'; src: url('../assets/fonts/caveat-500.woff2') format('woff2'); font-weight: 400 500; font-display: swap; }
@font-face { font-family: 'Caveat'; src: url('../assets/fonts/caveat-700.woff2') format('woff2'); font-weight: 600 700; font-display: swap; }

:root {
  --cream: #fbf4ec;
  --meadow: #7dae5f;
  --pink: #e58fb5;
  --plum: #4a2e45;
  --blush: #f7c9dc;
  --lavender: #a88bd0;
  --bluebell: #8c8edb;
  --magenta: #c03c7e;
  --sun: #f6d58e;
  --frost: #cfe6f2;

  --font-display: 'Fraunces', 'Iowan Old Style', Georgia, serif;
  --font-body: 'Nunito', 'Avenir Next', 'Segoe UI', sans-serif;
  --font-hand: 'Caveat', 'Bradley Hand', 'Segoe Print', cursive;

  --ease-soft: cubic-bezier(.22, .8, .3, 1);
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  height: 100%;
  overflow: hidden;
  background: radial-gradient(circle at 20% 15%, var(--blush), transparent 45%),
              radial-gradient(circle at 80% 85%, #e4efd9, transparent 50%),
              var(--cream);
  color: var(--plum);
  font-family: var(--font-body);
  -webkit-font-smoothing: antialiased;
  -webkit-tap-highlight-color: transparent;
}

/* Portrait stage: full screen on phones, centred column on desktop and landscape. */
#stage {
  position: relative;
  width: 100vw;
  height: 100dvh;
  margin: 0 auto;
  overflow: hidden;
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
  container-type: size; /* type scales with the stage, not the window */
}
@media (min-aspect-ratio: 3/4) {
  #stage {
    width: calc(100dvh * 390 / 844);
    box-shadow: 0 0 60px rgb(74 46 69 / .18);
  }
}
#scene { display: block; width: 100%; height: 100%; }

/* Overlay passes taps through to the canvas except on its interactive pieces. */
#overlay { position: absolute; inset: 0; pointer-events: none; }
#overlay .interactive { pointer-events: auto; }

.layer {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  opacity: 0;
  visibility: hidden;
  transition: opacity .8s var(--ease-soft), visibility 0s linear .8s;
}
.layer.shown { opacity: 1; visibility: visible; transition: opacity .8s var(--ease-soft); }

/* Loading: a petal fills with pink. */
.loader { justify-content: center; background: var(--cream); }
.loader .petal { width: 44px; height: 66px; }
.loader .petal path { fill: var(--blush); }
.loader .petal .fill { fill: var(--pink); transition: transform .3s; transform-origin: 50% 100%; }

/* Title */
.title { padding-top: calc(env(safe-area-inset-top) + 13cqh); text-align: center; cursor: pointer; isolation: isolate; outline: none; }
/* Soft sunlit haze behind the title, so the leaves never fight the type (as in the brand kit). */
.title::before {
  content: '';
  position: absolute;
  top: 4cqh;
  left: 50%;
  z-index: -1;
  width: 130cqw;
  height: 44cqh;
  translate: -50% 0;
  background: radial-gradient(closest-side, rgb(251 244 236 / .78), rgb(251 244 236 / .42) 55%, transparent);
  pointer-events: none;
}
.title h1 {
  max-width: 8.2em; /* breaks as "Happy / birthday, Ida", like the brand kit */
  margin: 0;
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(40px, 15cqw, 66px);
  line-height: 1.02;
  letter-spacing: -.01em;
  text-shadow: 0 0 18px rgb(251 244 236 / .95), 0 0 42px rgb(251 244 236 / .7);
}
.title h1 em { font-style: italic; font-weight: 400; }
.title .ornament { display: block; width: 72px; height: 1px; margin: 18px auto; background: var(--plum); opacity: .5; }
.pill {
  display: inline-block;
  padding: 9px 22px;
  border-radius: 999px;
  background: rgb(251 244 236 / .92);
  font-size: 16px;
  font-weight: 600;
  box-shadow: 0 0 0 6px rgb(246 213 142 / .25), 0 0 28px 8px rgb(246 213 142 / .55);
  animation: pill-glow 2.6s ease-in-out infinite;
}
@keyframes pill-glow {
  50% { box-shadow: 0 0 0 9px rgb(246 213 142 / .18), 0 0 40px 14px rgb(246 213 142 / .7); }
}

/* Sound toggle, always reachable */
.sound {
  position: absolute;
  top: calc(env(safe-area-inset-top) + 14px);
  right: 14px;
  width: 48px;
  height: 48px;
  border: 0;
  border-radius: 50%;
  background: rgb(251 244 236 / .9);
  color: var(--plum);
  box-shadow: 0 2px 10px rgb(74 46 69 / .15);
  display: grid;
  place-items: center;
  cursor: pointer;
}
.sound[hidden] { display: none; }
.sound svg { width: 22px; height: 22px; }
.sound .waves { transition: opacity .3s; }
.sound[aria-pressed='false'] .waves { opacity: 0; }

/* Photo spots found, top-left (the sound toggle is top-right) */
.spots {
  position: absolute;
  top: calc(env(safe-area-inset-top) + 20px);
  left: 14px;
  display: flex;
  align-items: center;
  gap: 6px;
  height: 36px;
  padding: 0 12px 0 10px;
  border-radius: 999px;
  background: rgb(251 244 236 / .82);
  color: var(--plum);
  box-shadow: 0 2px 10px rgb(74 46 69 / .12);
  opacity: 0;
  translate: 0 -6px;
  transition: opacity .6s var(--ease-soft), translate .6s var(--ease-soft);
}
.spots.shown { opacity: 1; translate: 0 0; }
.spots svg { width: 18px; height: 18px; margin-right: 2px; }
.spots .dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  border: 1.5px solid rgb(74 46 69 / .45);
  transition: background .4s, border-color .4s;
}
.spots .dot.on { background: var(--pink); border-color: var(--pink); }
.spots .dot.pop { animation: dot-pop .6s cubic-bezier(.3, 1.6, .5, 1); }
@keyframes dot-pop { 0% { scale: .4; } 60% { scale: 1.5; } 100% { scale: 1; } }
.spots .count { margin-left: 2px; font: 700 13px var(--font-body); font-variant-numeric: tabular-nums; }
.spots.complete { box-shadow: 0 0 0 4px rgb(246 213 142 / .35), 0 0 18px 4px rgb(246 213 142 / .5); }

/* Back from a macro moment */
.back {
  position: absolute;
  bottom: calc(env(safe-area-inset-bottom) + 22px);
  left: 50%;
  translate: -50% 0;
  min-width: 48px;
  height: 48px;
  padding: 0 20px;
  border: 0;
  border-radius: 999px;
  background: rgb(251 244 236 / .9);
  color: var(--plum);
  font: 600 15px var(--font-body);
  box-shadow: 0 2px 10px rgb(74 46 69 / .15);
  opacity: 0;
  pointer-events: none;
  transition: opacity .5s;
}
.back.shown { opacity: 1; pointer-events: auto; }
#overlay:has(.config-warning) .back { bottom: calc(env(safe-area-inset-bottom) + 64px); }

/* Handwritten caption on a close-up */
.caption {
  position: absolute;
  top: calc(env(safe-area-inset-top) + 14cqh);
  left: 50%;
  width: 82%;
  margin: 0;
  translate: -50% 8px;
  color: var(--cream);
  font: 600 clamp(28px, 8.5cqw, 36px)/1.1 var(--font-hand);
  text-align: center;
  text-shadow: 0 1px 3px rgb(74 46 69 / .6), 0 0 22px rgb(74 46 69 / .45);
  /* A soft plum haze behind the words, so they stay readable over bright paint. */
  padding: 14px 10px;
  background: radial-gradient(closest-side, rgb(74 46 69 / .32), rgb(74 46 69 / .12) 70%, transparent);
  opacity: 0;
  transition: opacity .9s var(--ease-soft), translate .9s var(--ease-soft);
  pointer-events: none;
}
.caption.shown { opacity: 1; translate: -50% 0; transition-delay: .5s; }

/* Mtani's speech bubble */
.bubble {
  position: absolute;
  width: max-content; /* don't shrink when anchored near the stage edge */
  max-width: 64%;
  padding: 8px 16px 10px;
  border-radius: 22px;
  background: var(--cream);
  font: 600 25px/1.05 var(--font-hand);
  text-align: center;
  box-shadow: 0 3px 14px rgb(74 46 69 / .18);
  translate: -50% -100%;
  scale: .6;
  opacity: 0;
  transition: opacity .35s, scale .5s cubic-bezier(.3, 1.6, .5, 1);
}
.bubble::after {
  content: '';
  position: absolute;
  left: 50%;
  bottom: -9px;
  translate: -50% 0;
  border: 10px solid transparent;
  border-bottom: 0;
  border-top-color: var(--cream);
}
.bubble.shown { opacity: 1; scale: 1; }

/* Voucher */
/* Card high, button under it, and the lower right free for Mtani's belly roll. */
.voucher { justify-content: flex-start; padding: calc(env(safe-area-inset-top) + 9cqh) 24px 24px; }
.card-wrap {
  position: relative;
  width: min(100%, 340px);
  translate: 0 40px;
  rotate: -1.5deg;
}
.bow {
  position: absolute;
  top: -34px;
  right: -26px;
  width: clamp(96px, 28cqw, 132px);
  rotate: 18deg;
  filter: drop-shadow(0 6px 8px rgb(74 46 69 / .25));
  pointer-events: none;
}
.card {
  position: relative;
  width: 100%;
  padding: 30px 26px 24px;
  background: var(--cream);
  text-align: center;
  border-radius: 4px;
  box-shadow: 0 18px 40px rgb(74 46 69 / .22);
  /* soft deckled edge */
  clip-path: polygon(0 1%, 4% 0, 12% 1%, 23% 0, 35% 1%, 48% 0, 60% 1%, 73% 0, 86% 1%, 96% 0, 100% 2%, 99% 15%, 100% 30%, 99% 47%, 100% 63%, 99% 80%, 100% 97%, 95% 100%, 82% 99%, 68% 100%, 54% 99%, 40% 100%, 26% 99%, 12% 100%, 2% 99%, 0 96%, 1% 80%, 0 62%, 1% 45%, 0 28%, 1% 12%);
}
/* Room under the bow, so it never sits on the heading. */
.card { padding-top: 46px; }
.card h2 { margin: 0; font: 500 clamp(25px, 7.3cqw, 30px)/1.1 var(--font-display); }
.card h2 em { font-style: italic; font-weight: 400; }
.card .rule { width: 60px; height: 1px; margin: 14px auto; background: var(--plum); opacity: .4; }
.card .amount { margin: 6px 0 12px; font: 600 46px/1 var(--font-display); color: var(--magenta); }
.card .intro { margin: 0 0 6px; font: 700 12px var(--font-body); letter-spacing: .14em; text-transform: uppercase; opacity: .7; }
.card ul { margin: 0; padding: 0; list-style: none; font-size: 15px; line-height: 1.6; }
.card li::before { content: '♡'; margin-right: .35em; color: var(--pink); }
.card .outro { margin: 8px 0 0; font: italic 400 16px var(--font-display); }
.card .delivery { margin: 12px 0 0; font-size: 13px; opacity: .8; }
.card .message { margin: 16px 0 0; font: 500 22px/1.2 var(--font-hand); white-space: pre-line; }
.card .signoff { margin: 6px 0 0; font: 600 22px var(--font-hand); color: var(--magenta); }
.again {
  margin-top: 22px;
  min-height: 48px;
  padding: 0 28px;
  border: 0;
  border-radius: 999px;
  background: var(--cream);
  color: var(--plum);
  font: 700 16px var(--font-body);
  box-shadow: 0 4px 14px rgb(74 46 69 / .2);
  cursor: pointer;
}

/* Config placeholders still present */
.config-warning {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 10;
  padding: 6px 12px calc(env(safe-area-inset-bottom) + 6px);
  background: var(--magenta);
  color: #fff;
  font: 700 12px/1.3 var(--font-body);
  text-align: center;
}

/* ================================================================ motion
   Everything here is DOM-side choreography; the canvas has its own. Reduced motion switches it off below. */

/* Words that arrive one by one (title, bubbles, captions). */
.w { display: inline-block; }

/* Loader: the petal sways while it fills. */
.loader .petal { animation: petal-sway 2.4s ease-in-out infinite alternate; transform-origin: 50% 100%; }
@keyframes petal-sway { from { rotate: -7deg; } to { rotate: 7deg; } }

/* Title: words rise out of a soft blur, "Ida" is written in, then the ornament draws and the pill arrives. */
.title .w { opacity: 0; translate: 0 .35em; filter: blur(6px); }
.title.shown .w { animation: word-in 1s var(--ease-soft) forwards; animation-delay: calc(.25s + var(--i) * .16s); }
.title .name { clip-path: inset(-20% 100% -20% -5%); filter: none; translate: 0 0; opacity: 1; }
.title.shown .name { animation: write-in 1.1s cubic-bezier(.45, .05, .35, 1) .8s forwards; }
.title .ornament { scale: 0 1; }
.title.shown .ornament { animation: draw-x .8s var(--ease-soft) 1.5s forwards; }
.title .pill { opacity: 0; translate: 0 8px; }
.title.shown .pill { animation: word-in .9s var(--ease-soft) 1.75s forwards, pill-glow 2.6s ease-in-out 2.6s infinite; }
@keyframes word-in { to { opacity: 1; translate: 0 0; filter: blur(0); } }
@keyframes write-in { to { clip-path: inset(-20% -5% -20% -5%); } }
@keyframes draw-x { to { scale: 1 1; } }

/* Title exit on the wake tap: the words drift up and dissolve, one after another. */
.title.leaving .w,
.title.leaving .name { animation: word-out .7s cubic-bezier(.5, 0, .7, .4) forwards; animation-delay: calc(var(--i) * .06s); clip-path: none; }
.title.leaving .ornament,
.title.leaving .pill { animation: fade-out .35s forwards; }
@keyframes word-out { from { opacity: 1; translate: 0 0; filter: blur(0); } to { opacity: 0; translate: 0 -.5em; filter: blur(8px); } }
@keyframes fade-out { from { opacity: 1; } to { opacity: 0; } }

/* Bubbles: the bubble springs open and Mtani's words follow. */
/* Not gated on .shown: the spans are new for every line, and they must stay visible while the bubble fades out. */
.bubble .w { opacity: 0; translate: 0 5px; animation: word-in .38s var(--ease-soft) forwards; animation-delay: calc(.12s + var(--i) * .065s); }

/* Close-up captions: written softly, word by word, after the shutter opens. */
.caption.shown { transition-delay: 0s; }
.caption .w { opacity: 0; filter: blur(5px); translate: 0 6px; animation: word-in .9s var(--ease-soft) forwards; animation-delay: calc(.7s + var(--i) * .1s); }

/* Camera shutter into a close-up: an iris closes and reopens, with a soft flash as it opens. */
@property --iris { syntax: '<percentage>'; inherits: false; initial-value: 110%; }
.shutter {
  position: absolute;
  inset: 0;
  z-index: 5;
  pointer-events: none;
  background: #1c1219;
  --iris: 110%;
  -webkit-mask: radial-gradient(circle at 50% 46%, transparent var(--iris), #000 calc(var(--iris) + 1.2%));
  mask: radial-gradient(circle at 50% 46%, transparent var(--iris), #000 calc(var(--iris) + 1.2%));
  opacity: 0;
}
/* Fast close (so the iris covers the switch underneath), a beat shut, then a slower, eased open. */
.shutter.snap { animation: iris .62s linear; }
@keyframes iris {
  0% { --iris: 110%; opacity: 1; animation-timing-function: cubic-bezier(.15, .55, .35, 1); }
  30% { --iris: 0%; opacity: 1; }
  42% { --iris: 0%; opacity: 1; animation-timing-function: cubic-bezier(.55, 0, .25, 1); }
  100% { --iris: 110%; opacity: 1; }
}
.flash { position: absolute; inset: 0; z-index: 5; pointer-events: none; background: var(--cream); opacity: 0; }
.flash.pop { animation: flash .55s ease-out .26s; }
@keyframes flash { 0% { opacity: 0; } 18% { opacity: .55; } 100% { opacity: 0; } }

/* Photo-spot pill: a little camera flash each time a new spot is found. */
.spots.flash { animation: spot-flash .9s ease-out; }
@keyframes spot-flash {
  0% { box-shadow: 0 0 0 0 rgb(255 255 255 / .9), 0 2px 10px rgb(74 46 69 / .12); }
  100% { box-shadow: 0 0 0 14px rgb(255 255 255 / 0), 0 2px 10px rgb(74 46 69 / .12); }
}

/* Voucher: the card swings up and settles, its lines arrive in turn, the bow drops on with a bounce. */
.card-wrap { translate: 0 0; transition: none; }
.voucher.shown .card-wrap {
  animation: card-in 1.3s cubic-bezier(.2, .9, .25, 1.12) both, card-float 6s ease-in-out 1.6s infinite alternate;
}
@keyframes card-in {
  from { opacity: 0; translate: 0 90px; rotate: 6deg; scale: .92; }
  to { opacity: 1; translate: 0 0; rotate: -1.5deg; scale: 1; }
}
@keyframes card-float { from { transform: translateY(0); } to { transform: translateY(-5px); } }
.card > * { opacity: 0; }
.voucher.shown .card > * { animation: item-in .75s var(--ease-soft) forwards; animation-delay: calc(.6s + var(--i) * .13s); }
.voucher.shown .card .message { animation: write-in-block 1.5s cubic-bezier(.45, .05, .35, 1) forwards; animation-delay: calc(.6s + var(--i) * .13s); }
@keyframes item-in { from { opacity: 0; translate: 0 10px; } to { opacity: 1; translate: 0 0; } }
@keyframes write-in-block { from { opacity: 1; clip-path: inset(0 100% 0 0); } to { opacity: 1; clip-path: inset(0 0 0 0); } }
.voucher.shown .card li::before { display: inline-block; animation: heart-pop .6s cubic-bezier(.3, 1.7, .5, 1) both; animation-delay: calc(.9s + var(--i) * .13s); }
@keyframes heart-pop { from { scale: 0; } to { scale: 1; } }
.voucher.shown .bow { animation: bow-drop .9s cubic-bezier(.3, 1.5, .5, 1) .45s both, bow-sway 4.5s ease-in-out 1.4s infinite alternate; transform-origin: 50% 80%; }
@keyframes bow-drop { from { opacity: 0; translate: 0 -40px; scale: 1.4; } to { opacity: 1; translate: 0 0; scale: 1; } }
@keyframes bow-sway { from { transform: rotate(-3deg); } to { transform: rotate(3deg); } }
.again { opacity: 0; }
.voucher.shown .again { animation: item-in .8s var(--ease-soft) 2.2s forwards; }

/* Reduced motion keeps the choreography but drops the movement: everything fades or wipes in place,
   nothing slides, scales, sways or bobs. (Android reports this whenever the system animations are off.) */
@media (prefers-reduced-motion: reduce) {
  @keyframes word-in { from { opacity: 0; } to { opacity: 1; translate: 0 0; filter: none; } }
  @keyframes word-out { from { opacity: 1; } to { opacity: 0; } }
  @keyframes item-in { from { opacity: 0; } to { opacity: 1; translate: 0 0; } }
  @keyframes card-in { from { opacity: 0; } to { opacity: 1; translate: 0 0; rotate: -1.5deg; scale: 1; } }
  @keyframes bow-drop { from { opacity: 0; } to { opacity: 1; translate: 0 0; scale: 1; } }
  @keyframes heart-pop { from { opacity: 0; } to { opacity: 1; scale: 1; } }
  @keyframes dot-pop { from { opacity: .4; } to { opacity: 1; } }
  .title .w, .bubble .w, .caption .w { translate: 0 0; filter: none; }
  .title .ornament { scale: 1 1; opacity: 0; }
  @keyframes draw-x { to { opacity: .5; } }
  .pill, .loader .petal { animation: none; }
  .title.shown .pill { animation: word-in .9s ease 1.75s forwards; }
  .voucher.shown .card-wrap { animation: card-in 1s ease both; }
  .voucher.shown .bow { animation: bow-drop .8s ease .45s both; }
  .bubble { transition: opacity .35s; scale: 1; }
}
