/* ============================================================
   components.css — Screen-Specific Component Styles
   ============================================================

   Contains all layout and component styles for:
   - Landing screen (gift box, CTA button)
   - Main birthday screen (greeting, cake, letter, gallery)
   - Overlays (lightbox, confetti canvas, petal container)

   ============================================================ */

/* ============================================================
   LANDING SCREEN — Gift Box + CTA
   ============================================================ */

#landing-screen {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  /* Transparent so the sakura corner trees (z-index 0) show through.
     The page gradient itself is on <html> — see css/style.css. */
  background: transparent;
  z-index: var(--z-screen);
}

.landing-content {
  text-align: center;
  padding: var(--space-xl);
}

/* --- Envelope Wrapper --- */
.evnelope-wrapper {
  height: 280px;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: var(--space-md);
}

/* --- Envelope --- */
/* Kertas luar putih; #evnelope sendiri adalah bagian DALAM amplop —
   warnanya dibuat gelap supaya rongga di balik flap terlihat dalam
   saat amplop terbuka. */
#evnelope {
  position: relative;
  width: 280px;
  height: 180px;
  border-bottom-left-radius: 6px;
  border-bottom-right-radius: 6px;
  margin-left: auto;
  margin-right: auto;
  background-color: #F6EEF0;
  box-shadow: 0 4px 20px rgba(90, 62, 75, 0.28);
  cursor: pointer;
  transition: transform var(--transition-fast);
}

#evnelope:hover {
  transform: scale(1.04);
}

.front {
  position: absolute;
  width: 0;
  height: 0;
  z-index: 3;
}

/* Flap penutup — sisi luar, putih bersih. drop-shadow memberi garis
   tepi tipis supaya siluet segitiganya tetap terbaca di atas kantong
   yang juga berwarna terang. */
.flap {
  border-left: 140px solid transparent;
  border-right: 140px solid transparent;
  border-bottom: 82px solid transparent;
  border-top: 98px solid #FFFFFF;
  transform-origin: top;
  filter: drop-shadow(0 2px 3px rgba(90, 62, 75, 0.22));
}

/* Kantong depan — putih, dengan dasar sedikit lebih hangat agar
   lipatan amplop terlihat. */
.pocket {
  border-left: 140px solid #FFFFFF;
  border-right: 140px solid #FFFFFF;
  border-bottom: 90px solid #F6EEF0;
  border-top: 90px solid transparent;
  border-bottom-left-radius: 6px;
  border-bottom-right-radius: 6px;
}

/* Kertas surat di dalam amplop. Karena kantong amplop sekarang putih,
   kertasnya diberi nuansa krem hangat + garis tepi tipis supaya tetap
   terbaca sebagai lembar terpisah saat naik keluar dari amplop. */
.letter {
  position: relative;
  background-color: #FFFDF8;
  width: 90%;
  margin-left: auto;
  margin-right: auto;
  height: 90%;
  top: 5%;
  border-radius: 6px;
  border: 1px solid rgba(183, 110, 121, 0.18);
  box-shadow: 0 2px 26px rgba(90, 62, 75, 0.18);
}

.letter::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  background-image: linear-gradient(
    180deg,
    rgba(255, 255, 255, 0.00) 25%,
    rgba(255, 214, 221, 0.55) 55%,
    rgba(255, 182, 193, 0.35) 100%
  );
  border-radius: 6px;
}

.words {
  position: absolute;
  left: 10%;
  width: 80%;
  height: 14%;
  background-color: #f3e8eb;
  border-radius: 2px;
}

.words.line1 {
  top: 15%;
  width: 20%;
  height: 7%;
}

.words.line2 {
  top: 30%;
}

.words.line3 {
  top: 50%;
}

.words.line4 {
  top: 70%;
}

/* --- Flap open/close transitions --- */
.open .flap {
  transform: rotateX(180deg);
  transition: transform 0.4s ease, z-index 0.6s;
  z-index: 1;
}

.close .flap {
  transform: rotateX(0deg);
  transition: transform 0.4s 0.6s ease, z-index 1s;
  z-index: 5;
}

/* --- Letter slide transitions --- */
.close .letter {
  transform: translateY(0px);
  transition: transform 0.4s ease, z-index 1s;
  z-index: 1;
}

.open .letter {
  transform: translateY(-60px);
  transition: transform 0.4s 0.6s ease, z-index 0.6s;
  z-index: 2;
}

/* --- Hearts container --- */
.hearts {
  position: absolute;
  top: 90px;
  left: 0;
  right: 0;
  z-index: 2;
}

.heart {
  position: absolute;
  bottom: 0;
  right: 10%;
}

.heart::before, .heart::after {
  position: absolute;
  content: "";
  left: 50px;
  top: 0;
  width: 50px;
  height: 80px;
  background-color: #E8899A;
  border-radius: 50px 50px 0 0;
  transform: rotate(-45deg);
  transform-origin: 0 100%;
}

.heart::after {
  left: 0;
  transform: rotate(45deg);
  transform-origin: 100% 100%;
}

.close .heart {
  opacity: 0;
  animation: none;
}

.a1 {
  left: 20%;
  transform: scale(0.6);
  opacity: 1;
  animation: heart-float-up 4s linear 1, heart-side-sway 2s ease-in-out 4 alternate;
  animation-fill-mode: forwards;
  animation-delay: 0.7s;
}

.a2 {
  left: 55%;
  transform: scale(1);
  opacity: 1;
  animation: heart-float-up 5s linear 1, heart-side-sway 4s ease-in-out 2 alternate;
  animation-fill-mode: forwards;
  animation-delay: 0.7s;
}

.a3 {
  left: 10%;
  transform: scale(0.8);
  opacity: 1;
  animation: heart-float-up 7s linear 1, heart-side-sway 2s ease-in-out 6 alternate;
  animation-fill-mode: forwards;
  animation-delay: 0.7s;
}

/* --- Landing Title --- */
.landing-title {
  font-family: var(--font-heading);
  font-size: var(--font-size-xl);
  color: var(--color-rose-gold);
  margin-bottom: var(--space-md);
  letter-spacing: 0.05em;
}

/* --- CTA Button --- */
.btn-open {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
  padding: 14px 36px;
  font-family: var(--font-body);
  font-size: var(--font-size-base);
  font-weight: 500;
  letter-spacing: 0.04em;
  color: var(--color-white);
  background: linear-gradient(135deg, var(--color-pink), var(--color-rose-gold));
  border: none;
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-md);
  cursor: pointer;
  transition: all var(--transition-normal);
  position: relative;
  overflow: hidden;
}

.btn-open::before {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(135deg, var(--color-rose-gold), var(--color-pink-dark));
  opacity: 0;
  transition: opacity var(--transition-fast);
}

.btn-open:hover::before { opacity: 1; }

.btn-open span {
  position: relative;
  z-index: 1;
}

.btn-open:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-lg);
}

.btn-open:active {
  transform: translateY(0) scale(0.98);
}

/* ============================================================
   MAIN BIRTHDAY SCREEN
   ============================================================ */

#main-screen {
  display: flex;
  flex-direction: column;
  align-items: center;
  /* Transparent — the sakura trees sit behind the screens (see the
     SAKURA CORNER TREES block near the bottom of this file). */
  background: transparent;
  /* Mobile: keep side padding tight so the text column stays wide */
  padding: var(--space-2xl) var(--space-md);
  z-index: var(--z-screen);
  min-height: 100vh;
}

/* Hide main screen initially */
#main-screen.hidden {
  display: none;
}

/* --- Greeting --- */
.greeting-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  margin-top: var(--space-md);
  margin-bottom: var(--space-lg);
  gap: var(--space-md);
  width: 100%;
}

.greeting-text {
  font-family: var(--font-heading);
  /* Scales with the viewport so the greeting never overflows a narrow
     phone mid-word while still reaching full size on desktop */
  font-size: clamp(1.6rem, 8vw, var(--font-size-2xl));
  color: var(--color-rose-gold);
  line-height: 1.3;
  position: relative;
  display: inline-block;
  max-width: 100%;
  overflow-wrap: break-word;
  /* Reserve two lines so the layout below does not jump as text types out */
  min-height: 2.6em;
}

.person-name {
  font-family: var(--font-heading);
  font-size: clamp(1.6rem, 8vw, var(--font-size-2xl));
  color: var(--color-rose-gold);
  line-height: 1.3;
  position: relative;
  display: inline-block;
  min-height: 1.3em;
  max-width: 100%;
  overflow-wrap: break-word;
}

/* Frame Lingkaran Foto 1:1 */
.greeting-photo-frame {
  width: 200px;
  height: 200px;
  border-radius: 50%;
  padding: 5px;
  background: linear-gradient(135deg, var(--color-pink), var(--color-rose-gold));
  box-shadow: 0 8px 25px rgba(183, 110, 121, 0.3);
  transition: transform var(--transition-normal), box-shadow var(--transition-normal);
  display: flex;
  align-items: center;
  justify-content: center;
  margin: var(--space-xs) auto;
}

.greeting-photo-frame:hover {
  transform: scale(1.06) rotate(3deg);
  box-shadow: 0 12px 30px rgba(183, 110, 121, 0.45);
}

.greeting-photo {
  width: 100%;
  height: 100%;
  object-fit: cover;
  aspect-ratio: 1 / 1;
  border-radius: 50%;
  border: 3px solid var(--color-white);
  display: block;
}

/* Teks Nama di bawah foto */
/* .person-name {
  font-family: var(--font-heading);
  font-size: var(--font-size-xl);
  font-weight: 600;
  color: var(--color-text);
  letter-spacing: 0.05em;
  margin-top: var(--space-xs);
} */

.greeting-text .cursor {
  display: inline-block;
  width: 2px;
  height: 1em;
  background-color: var(--color-rose-gold);
  margin-left: 2px;
  vertical-align: text-bottom;
  animation: typing-cursor 0.8s ease-in-out infinite;
}

/* Typing paused (greeting scrolled out of view) — hold the cursor still */
.greeting-text .cursor.paused {
  animation-play-state: paused;
}

/* --- Decorative divider --- */
.divider {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-sm);
  margin: var(--space-lg) 0;
  color: var(--color-accent);
  font-size: var(--font-size-sm);
}

.divider::before,
.divider::after {
  content: '';
  flex: 1;
  max-width: 120px;
  height: 1px;
  background: linear-gradient(
    to right,
    transparent,
    var(--color-accent),
    transparent
  );
}

/* ============================================================
   PORTRAIT FRAME — Circular photo between greeting and cake
   ============================================================
   Layout is a centred square box. The photo, the dashed ring and the
   glow are stacked concentrically; the charms are positioned by
   rotating each one out to the ring's radius from the centre.

   --portrait-size:   diameter of the photo
   --portrait-radius: how far the charms sit from the centre
   ============================================================ */

.portrait-frame {
  --portrait-size: 190px;
  --portrait-radius: 122px;

  position: relative;
  width: var(--portrait-size);
  height: var(--portrait-size);
  margin: 0 auto var(--space-2xl);
  /* Always visible from the start; the photo's fallback gradient
     fills the circle immediately. The animation only adds a subtle
     scale-in so there's no "empty" flash while the real photo loads. */
  animation: portrait-in 0.8s ease both;
}

/* --- Photo, clipped to a circle --- */
.portrait-photo {
  position: absolute;
  inset: 0;
  border-radius: var(--radius-full);
  overflow: hidden;
  /* Double ring: white paper edge, then a soft rose outline */
  border: 5px solid var(--color-white);
  box-shadow:
    0 0 0 2px var(--color-pink-light),
    var(--shadow-lg);
  z-index: 2;
}

.portrait-photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  /* Gradient shows immediately while the real photo downloads.
     Once loaded, the photo replaces the gradient. */
  background:
    radial-gradient(circle, var(--color-cream) 0%, var(--color-pink-light) 100%);
  background-color: var(--color-pink-light);
}

/* --- Rotating dashed ring --- */
.portrait-ring {
  position: absolute;
  /* Sits outside the photo, at the same radius the charms ride on */
  inset: calc(var(--portrait-size) / 2 - var(--portrait-radius) + 14px);
  border: 2px dashed var(--color-accent);
  border-radius: var(--radius-full);
  opacity: 0.55;
  animation: portrait-ring-spin 28s linear infinite;
  z-index: 1;
}

/* --- Soft halo behind everything --- */
.portrait-glow {
  position: absolute;
  inset: -26px;
  border-radius: var(--radius-full);
  background: radial-gradient(
    circle,
    var(--color-pink-light) 0%,
    rgba(255, 214, 221, 0.35) 55%,
    transparent 72%
  );
  animation: portrait-glow-pulse 4s ease-in-out infinite;
  z-index: 0;
}

/* --- Charms around the ring ---
   Positioning trick: pin each charm to the centre, rotate by its own
   --charm-angle, push it out along the radius, then counter-rotate so
   the emoji itself stays upright. */
.portrait-charm {
  position: absolute;
  top: 50%;
  left: 50%;
  font-size: 1.4rem;
  line-height: 1;
  transform:
    rotate(var(--charm-angle, 0deg))
    translate(0, calc(var(--portrait-radius) * -1))
    rotate(calc(var(--charm-angle, 0deg) * -1));
  /* -50% of the glyph box so it centres on the ring */
  margin: -0.7rem 0 0 -0.7rem;
  filter: drop-shadow(0 2px 3px rgba(90, 62, 75, 0.25));
  animation: portrait-charm-bob 3s ease-in-out infinite;
  z-index: 3;
}

/* Stagger the bob so the charms do not move as one block.
   nth-of-type is safe here: the charms are the only <span> children
   of .portrait-frame, so 1..7 maps to the seven charms in order. */
.portrait-charm:nth-of-type(3n + 1) { animation-delay: 0s; }
.portrait-charm:nth-of-type(3n + 2) { animation-delay: 0.6s; }
.portrait-charm:nth-of-type(3n)     { animation-delay: 1.2s; }

/* --- Caption below the circle --- */
.portrait-caption {
  position: absolute;
  top: calc(100% + var(--space-md));
  left: 50%;
  transform: translateX(-50%);
  width: max-content;
  max-width: 80vw;
  text-align: center;
  font-family: var(--font-heading);
  font-size: var(--font-size-base);
  font-style: italic;
  color: var(--color-rose-gold);
}

/* ============================================================
   CAKE — Animated SVG cake + CSS candle & fire
   ============================================================
   The cake is an SVG that draws itself via SMIL <animate> path
   morphing. The candle (.velas) and its flame layers (.fuego)
   are plain DOM elements positioned on top of the cake.
   ============================================================ */

.cake-container {
  position: relative;
  width: 200px;
  /* Candle (42px, starting at top:0) + cropped cake (105px), minus the
     16px of empty space above the cake inside its own viewBox. */
  height: 124px;
  margin: 0 auto var(--space-md);
}

.cake-svg {
  display: block;
  position: relative;
  width: 200px;
  /* Cropped viewBox — only the finished cake, no empty space above */
  height: 105px;
  /* The top sponge layer settles at y=411.5 in a viewBox starting at
     y=385, i.e. ~26px below the SVG's own top edge. Offsetting the SVG
     by 16px puts that surface at ~42px — exactly where the 42px candle
     ends — so the candle sits on the cake instead of floating above it. */
  margin: 16px auto 0;
}

/* --- Candle body --- */
/* Drops in right after the sponge layers finish stacking (~2.2s) so it
   lands while the cream is still being drawn. */
.velas {
  background: #ffffff;
  border-radius: 10px;
  position: absolute;
  /* Bottom of the candle meets the top of the cake at y=42px */
  top: 0;
  left: 50%;
  margin-left: -2.5px;
  width: 5px;
  height: 42px;
  transform: translateY(-300px);
  backface-visibility: hidden;
  animation: candle-in 400ms 2.3s ease-out forwards;
  z-index: 2;
}

/* Two decorative pink stripes on the candle */
.velas::after,
.velas::before {
  background: rgba(255, 105, 135, 0.45);
  content: "";
  position: absolute;
  width: 100%;
  height: 3px;
}

.velas::after  { top: 25%; left: 0; }
.velas::before { top: 45%; left: 0; }

/* --- Fire --- */
/* 5 stacked layers, each with a different duration so the flame
   never repeats the same shape twice. */
.fuego {
  border-radius: 100%;
  position: absolute;
  top: -20px;
  left: 50%;
  margin-left: -3.3px;
  width: 7px;
  height: 18px;
}

/* Flame lights up just after the candle has landed */
.fuego:nth-child(1) { animation: fuego 2s   2.8s infinite; }
.fuego:nth-child(2) { animation: fuego 1.5s 2.8s infinite; }
.fuego:nth-child(3) { animation: fuego 1s   2.8s infinite; }
.fuego:nth-child(4) { animation: fuego 0.5s 2.8s infinite; }
.fuego:nth-child(5) { animation: fuego 0.2s 2.8s infinite; }

/* Extinguished state — candle.js adds .flame-out to #cake-container */
.cake-container.flame-out .fuego {
  animation: none;
  opacity: 0;
  transition: opacity 0.3s ease;
}

/* --- Controls below the cake (normal flow) --- */
.cake-controls {
  width: 280px;
  margin: 0 auto;
  text-align: center;
}

/* --- Smoke wisps (generated by candle.js) --- */
.smoke-wisp {
  position: absolute;
  width: 8px;
  height: 20px;
  background: radial-gradient(
    ellipse,
    rgba(180, 170, 175, 0.5) 0%,
    transparent 70%
  );
  border-radius: 50%;
  animation: smoke-rise 2s ease-out forwards;
  pointer-events: none;
  z-index: 3;
}

.smoke-wisp--alt {
  animation: smoke-rise-alt 2.2s ease-out forwards;
  animation-delay: 0.15s;
}

/* --- Blow Button --- */
.btn-blow {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
  padding: 12px 32px;
  font-family: var(--font-body);
  font-size: var(--font-size-base);
  font-weight: 500;
  color: var(--color-white);
  background: linear-gradient(135deg, var(--color-rose-gold), var(--color-pink-dark));
  border: none;
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-md);
  cursor: pointer;
  transition: all var(--transition-normal);
  margin-bottom: var(--space-md);
}

.btn-blow:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-lg);
}

.btn-blow:disabled {
  opacity: 0.4;
  cursor: not-allowed;
  transform: none;
}

/* Relight mode — the candle is out and the button offers to light it again.
   Warmer gradient so it reads as a different action than blowing. */
.btn-blow.is-relight {
  background: linear-gradient(135deg, var(--color-pink), var(--color-rose-gold));
}

/* --- Mic blow indicator --- */
.mic-status {
  font-size: var(--font-size-xs);
  color: var(--color-text-light);
  margin-top: var(--space-sm);
  text-align: center;
  font-style: italic;
}

.mic-status .active { color: var(--color-rose-gold); font-weight: 600; }

/* ============================================================
   SURPRISE LETTER — Romantic letter card
   ============================================================ */

.letter-section {
  width: 100%;
  max-width: 640px;
  margin: var(--space-2xl) auto;
  padding: 0 var(--space-xs);
  opacity: 0;
  transform: translateY(40px);
  transition: all var(--transition-slow) ease;
}

.letter-section.revealed {
  opacity: 1;
  transform: translateY(0);
}

.letter-card {
  background: var(--color-white);
  border-radius: var(--radius-lg);
  /* Mobile keeps the horizontal padding modest — every 8px of padding is
     8px the text column loses, and a narrow column is what makes the
     paragraphs look ragged in the first place. */
  padding: var(--space-xl) var(--space-md);
  box-shadow: var(--shadow-lg);
  position: relative;
  overflow: hidden;
  line-height: 1.9;
}

/* Decorative paper texture overlay */
.letter-card::before {
  content: '';
  position: absolute;
  inset: 0;
  background:
    linear-gradient(
      to bottom,
      rgba(245, 230, 211, 0.15) 0%,
      transparent 20%
    ),
    repeating-linear-gradient(
      to bottom,
      transparent 0px,
      transparent 27px,
      rgba(183, 110, 121, 0.06) 27px,
      rgba(183, 110, 121, 0.06) 28px
    );
  pointer-events: none;
}

.letter-card h3 {
  font-size: var(--font-size-xl);
  color: var(--color-rose-gold);
  margin-bottom: var(--space-lg);
  text-align: center;
  position: relative;
  z-index: 1;
  overflow-wrap: break-word;
}

.letter-card .letter-body {
  font-family: var(--font-body);
  font-size: var(--font-size-sm);
  color: var(--color-text);
  position: relative;
  z-index: 1;
}

/* Mobile typography: a ~320px column is too narrow to justify. Forcing
   flush edges there stretches word spaces into visible "rivers", which is
   what makes the letter look ragged. Left-align instead, and let long
   words break rather than overflow the card. */
.letter-card .letter-body p {
  margin-bottom: var(--space-md);
  text-align: left;
  text-indent: 0;
  overflow-wrap: break-word;
  word-break: normal;
  hyphens: auto;
  -webkit-hyphens: auto;
  /* Keep single trailing words off their own line where supported */
  text-wrap: pretty;
}

.letter-card .letter-signature {
  text-align: right;
  font-family: var(--font-heading);
  font-style: italic;
  font-size: var(--font-size-lg);
  color: var(--color-rose-gold);
  margin-top: var(--space-xl);
  text-indent: 0;
  hyphens: none;
  -webkit-hyphens: none;
}

/* ============================================================
   PHOTO GALLERY — Polaroid swipe stack
   ============================================================
   Two layouts share the same markup, toggled by JS:

   1. `.photo-stack`          → cards piled on top of each other,
                                only the front one is draggable
   2. `.photo-stack.spread`   → the original side-by-side grid,
                                revealed after the last photo
   ============================================================ */

.gallery-section {
  width: 100%;
  max-width: 900px;
  margin: var(--space-2xl) auto;
  padding: 0 var(--space-md);
  opacity: 0;
  transform: translateY(40px);
  transition: all var(--transition-slow) ease;
}

.gallery-section.revealed {
  opacity: 1;
  transform: translateY(0);
}

.gallery-title {
  text-align: center;
  font-family: var(--font-heading);
  font-size: var(--font-size-xl);
  color: var(--color-rose-gold);
  margin-bottom: var(--space-xl);
  overflow-wrap: break-word;
}

/* --- Stack container ---
   Height is reserved so the absolutely-positioned cards do not
   collapse the layout. Kept in sync with .polaroid sizing below. */
.photo-stack {
  position: relative;
  width: 240px;
  height: 340px;
  margin: 0 auto;
  /* Vertical swipes should still scroll the page */
  touch-action: pan-y;
}

/* --- Polaroid card --- */
.polaroid {
  background: var(--color-white);
  padding: var(--space-sm) var(--space-sm) 40px;
  box-shadow: var(--shadow-md);
  border-radius: 2px;
  cursor: pointer;
  width: 240px;
}

/* Cards in the pile: stacked, slightly rotated, scaled back by depth.
   --depth (0 = front) and --z are written by js/photo-stack.js. */
.photo-stack:not(.spread) .polaroid {
  position: absolute;
  top: 0;
  left: 0;
  z-index: var(--z, 1);
  transform:
    translate(var(--drag-x, 0px), calc(var(--depth, 0) * 8px))
    rotate(calc(var(--polaroid-rotation, -3deg) + var(--drag-rot, 0deg)))
    scale(calc(1 - var(--depth, 0) * 0.04));
  transition: transform var(--transition-normal), opacity var(--transition-normal);
  will-change: transform;
}

/* The front card is the only interactive one */
.photo-stack:not(.spread) .polaroid.is-front {
  cursor: grab;
}

.photo-stack:not(.spread) .polaroid:not(.is-front) {
  pointer-events: none;
}

/* While a finger/mouse is down we drive the transform frame by frame,
   so the easing transition must get out of the way. */
.photo-stack:not(.spread) .polaroid.is-dragging {
  transition: none;
  cursor: grabbing;
}

/* Card flying off screen after a committed swipe */
.photo-stack:not(.spread) .polaroid.is-leaving {
  opacity: 0;
}

/* Cards deeper than the third are hidden to keep the pile tidy */
.photo-stack:not(.spread) .polaroid.is-buried {
  opacity: 0;
}

/* --- Spread layout: the original grid, shown after the last photo --- */
.photo-stack.spread {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-xl);
  width: 100%;
  height: auto;
}

.photo-stack.spread .polaroid {
  position: static;
  transform: rotate(var(--polaroid-rotation, -4deg));
  transition: all var(--transition-normal);
  animation: float-polaroid 6s ease-in-out infinite;
  animation-delay: var(--float-delay, 0s);
}

.photo-stack.spread .polaroid:hover {
  transform: rotate(0deg) scale(1.05);
  box-shadow: var(--shadow-xl);
  animation-play-state: paused;
  z-index: 10;
}

.polaroid:nth-child(1) { --polaroid-rotation: -3deg; --float-delay: 0s; }
.polaroid:nth-child(2) { --polaroid-rotation: 3deg;  --float-delay: 1s; }
.polaroid:nth-child(3) { --polaroid-rotation: -5deg; --float-delay: 2s; }
.polaroid:nth-child(4) { --polaroid-rotation: 4deg;  --float-delay: 0.5s; }

.polaroid img {
  width: 100%;
  height: 240px;
  object-fit: cover;
  display: block;
  background: var(--color-cream);
  border: 1px solid rgba(183, 110, 121, 0.1);
  pointer-events: none; /* let drag handlers own the gesture */
  -webkit-user-drag: none;
  user-select: none;
}

.photo-stack.spread .polaroid img {
  pointer-events: auto;
}

.polaroid .caption {
  font-family: var(--font-heading);
  font-size: var(--font-size-sm);
  color: var(--color-text-light);
  text-align: center;
  margin-top: var(--space-sm);
  font-style: italic;
}

/* --- Hint text + progress dots --- */
.stack-hint {
  text-align: center;
  font-size: var(--font-size-sm);
  color: var(--color-text-light);
  font-style: italic;
  margin-top: var(--space-lg);
  transition: opacity var(--transition-normal);
}

.stack-dots {
  display: flex;
  justify-content: center;
  gap: var(--space-sm);
  margin-top: var(--space-sm);
  transition: opacity var(--transition-normal);
}

.stack-dots .dot {
  width: 8px;
  height: 8px;
  border-radius: var(--radius-full);
  background: var(--color-pink-light);
  transition: all var(--transition-fast);
}

.stack-dots .dot.active {
  background: var(--color-rose-gold);
  transform: scale(1.3);
}

/* In spread mode the swipe affordances are no longer meaningful */
.gallery-section.is-spread .stack-hint,
.gallery-section.is-spread .stack-dots {
  opacity: 0;
  pointer-events: none;
  height: 0;
  margin: 0;
  overflow: hidden;
}

/* --- "Tumpuk lagi" button, injected in spread mode --- */
.stack-reset {
  display: block;
  margin: var(--space-xl) auto 0;
  padding: var(--space-sm) var(--space-lg);
  font-family: var(--font-body);
  font-size: var(--font-size-sm);
  color: var(--color-rose-gold);
  background: var(--color-white);
  border: 1px solid var(--color-accent);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-sm);
  cursor: pointer;
  transition: all var(--transition-fast);
}

.stack-reset:hover {
  background: var(--color-pink-light);
  box-shadow: var(--shadow-md);
}

/* Respect reduced-motion preferences */
@media (prefers-reduced-motion: reduce) {
  .photo-stack.spread .polaroid {
    animation: none;
  }
}

/* ============================================================
   LIGHTBOX — Photo zoom overlay
   ============================================================ */

.lightbox {
  position: fixed;
  inset: 0;
  background: rgba(30, 20, 25, 0.88);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: var(--z-lightbox);
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--transition-normal), visibility var(--transition-normal);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  padding: var(--space-xl);
}

.lightbox.open {
  opacity: 1;
  visibility: visible;
}

.lightbox img {
  max-width: 90vw;
  max-height: 85vh;
  object-fit: contain;
  border-radius: var(--radius-md);
  box-shadow: 0 20px 80px rgba(0, 0, 0, 0.4);
  transform: scale(0.85);
  transition: transform var(--transition-normal) ease;
}

.lightbox.open img {
  transform: scale(1);
}

.lightbox-close {
  position: absolute;
  top: var(--space-lg);
  right: var(--space-lg);
  width: 44px;
  height: 44px;
  background: rgba(255, 255, 255, 0.15);
  border: none;
  border-radius: var(--radius-full);
  color: var(--color-white);
  font-size: var(--font-size-xl);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background var(--transition-fast);
  backdrop-filter: blur(4px);
}

.lightbox-close:hover {
  background: rgba(255, 255, 255, 0.3);
}

/* ============================================================
   CONFETTI CANVAS
   ============================================================ */

#confetti-canvas {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: var(--z-particles);
}

/* ============================================================
   SAKURA CORNER TREES — fixed decoration in both bottom corners
   ============================================================
   The trees live in their own fixed layer *behind* the screens. That
   only works because the screens themselves are transparent and the
   page gradient moved to <body> (see style.css) — an opaque screen
   background would paint straight over the artwork.
   ============================================================ */

/* The <symbol>/<defs> holder must not take up layout space */
.sakura-defs {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
}

.sakura-corners {
  position: fixed;
  inset: 0;
  pointer-events: none;
  overflow: hidden;
  /* Behind .screen (--z-screen: 1) but above the body gradient */
  z-index: 0;
}

.sakura-corner {
  position: absolute;
  bottom: 0;
  /* Tuned per breakpoint below. Taller than the artwork strictly needs so
     the raised crown has room — the SVG letterboxes inside this box
     (preserveAspectRatio), it does not stretch. */
  width: 175px;
  height: 320px;
}

.sakura-corner--left {
  left: 0;
}

/* Same artwork mirrored, so the two corners lean away from the content */
.sakura-corner--right {
  right: 0;
  transform: scaleX(-1);
}

.sakura-tree {
  position: absolute;
  bottom: 0;
  left: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  /* Soft, watercolour-ish presence — never competes with the text.
     The canopy is dense now, so this sits lower than it would for a
     sparse tree. */
  opacity: 0.5;
}

.tree-wood {
  stroke: #8d6a5b;
}

/* Canopy tints, painted back to front. The back layer is the most
   transparent so the dense overlap reads as depth rather than a flat
   pink blob; each layer forward gets lighter and more opaque. */
.tree-blossom--back  { fill: #a35a68;                 opacity: 0.42; }
.tree-blossom--deep  { fill: var(--color-rose-gold);  opacity: 0.55; }
.tree-blossom--mid   { fill: var(--color-pink);       opacity: 0.78; }
.tree-blossom--light { fill: var(--color-pink-light); opacity: 0.9;  }
.tree-blossom--stray { fill: var(--color-pink-light); opacity: 0.7;  }

/* Gentle sway of the whole canopy — transform-origin at the trunk base
   so the tree bends rather than slides */
@media (prefers-reduced-motion: no-preference) {
  .sakura-corner--left .sakura-tree {
    transform-origin: 20% 100%;
    animation: sakura-sway 7s ease-in-out infinite;
  }

  .sakura-corner--right .sakura-tree {
    transform-origin: 20% 100%;
    animation: sakura-sway 8.5s ease-in-out infinite reverse;
  }
}

/* Zone the falling petals spawn from. Matches where the canopy actually
   lands on screen: the artwork letterboxes to the bottom of .sakura-corner
   (preserveAspectRatio="xMinYMax meet"), and the crown now reaches y≈62 of
   the 420-unit viewBox, which works out to roughly 23%–77% of the box
   height at every breakpoint. Petals must appear to detach from real
   blossoms, not from empty sky above the tree. */
.sakura-drop {
  position: absolute;
  left: 0;
  top: 23%;
  width: 100%;
  height: 54%;
}

/* Petals dropped by the trees. Unlike .petal (full-screen ambience)
   these start inside the canopy and drift outward toward the centre. */
.tree-petal {
  position: absolute;
  width: 10px;
  height: 10px;
  background: var(--color-pink);
  border-radius: 50% 0 50% 50%;
  opacity: 0;
  filter: blur(0.4px);
  animation: tree-petal-fall var(--duration, 7s) linear forwards;
}

.tree-petal--rose  { background: var(--color-rose-gold); }
.tree-petal--light { background: var(--color-pink-light); }
.tree-petal--white { background: var(--color-white); }

/* Hide the decoration on very short viewports (landscape phones), where
   it would crowd the little vertical space there is */
@media (max-height: 480px) {
  .sakura-corners {
    display: none;
  }
}

/* ============================================================
   SAKURA PETALS — Falling petal elements
   ============================================================ */

.petal-container {
  position: fixed;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  z-index: var(--z-particles);
  /* petals won't block clicks */
}

.petal {
  position: absolute;
  width: 14px;
  height: 14px;
  background: var(--color-pink);
  border-radius: 50% 0 50% 50%;
  opacity: 0;
  animation: petal-fall var(--duration, 6s) linear forwards;
  animation-delay: var(--delay, 0s);
  filter: blur(0.5px);
}

/* Petal color variants */
.petal--rose { background: var(--color-rose-gold); opacity: 0.6; }
.petal--light { background: var(--color-pink-light); opacity: 0.7; }
.petal--white { background: var(--color-white); opacity: 0.5; }

/* ============================================================
   RESPONSIVE — Tablet & up
   ============================================================ */

@media (min-width: 768px) {
  #main-screen {
    padding: var(--space-2xl) var(--space-lg);
  }

  /* Bigger trees once there is room beside the content column */
  .sakura-corner {
    width: 250px;
    height: 440px;
  }

  .sakura-tree {
    opacity: 0.55;
  }

  .greeting-text {
    font-size: var(--font-size-3xl);
  }

  .person-name {
    font-size: var(--font-size-2xl);
  }

  /* Bigger frame on wider screens — the charm radius scales with it */
  .portrait-frame {
    --portrait-size: 230px;
    --portrait-radius: 146px;
  }

  .portrait-charm {
    font-size: 1.7rem;
  }

  .photo-stack:not(.spread) {
    width: 260px;
    height: 360px;
  }

  .polaroid {
    width: 260px;
  }

  .polaroid img {
    height: 260px;
  }

  .letter-section {
    padding: 0;
  }

  .letter-card {
    padding: var(--space-3xl) var(--space-2xl);
    line-height: 2;
  }

  .letter-card .letter-body {
    font-size: var(--font-size-base);
  }

  /* Wide enough for justified text to look intentional rather than gappy */
  .letter-card .letter-body p {
    text-align: justify;
    text-indent: 1.5em;
    hyphens: none;
    -webkit-hyphens: none;
  }
}

@media (min-width: 1024px) {
  #main-screen {
    padding: var(--space-3xl) var(--space-2xl);
  }

  .sakura-corner {
    width: 310px;
    height: 540px;
  }

  .photo-stack:not(.spread) {
    width: 280px;
    height: 385px;
  }

  .polaroid {
    width: 280px;
  }

  .polaroid img {
    height: 280px;
  }
}

/* ============================================================
   VIDEO GALLERY — Portrait clips in polaroid frames
   ============================================================
   Same card treatment as .polaroid (white border, bottom lip for the
   caption, slight tilt) but sized 9:16 for portrait video and laid out
   as a scroll-snap row instead of a swipe pile — a clip needs to stay
   still while it plays.
   ============================================================ */

.video-section {
  width: 100%;
  max-width: 900px;
  margin: var(--space-2xl) auto;
  padding: 0 var(--space-md);
  opacity: 0;
  transform: translateY(40px);
  transition: all var(--transition-slow) ease;
}

.video-section.revealed {
  opacity: 1;
  transform: translateY(0);
}

.video-title {
  text-align: center;
  font-family: var(--font-heading);
  font-size: var(--font-size-xl);
  color: var(--color-rose-gold);
  margin-bottom: var(--space-xl);
  overflow-wrap: break-word;
}

/* --- Scroll-snap row ---
   The 50% side padding is what lets a single card sit dead-centre in the
   row: without it, the first and last cards can never reach the middle,
   so scroll-snap-align: center has nothing to snap to at the ends. */
.video-row {
  display: flex;
  gap: var(--space-xl);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
  padding: var(--space-md) 50%;
  scrollbar-width: none;
}

.video-row::-webkit-scrollbar {
  display: none;
}

/* --- Video polaroid card --- */
.video-polaroid {
  position: relative;
  flex: 0 0 auto;
  width: 210px;
  background: var(--color-white);
  padding: var(--space-sm) var(--space-sm) 40px;
  box-shadow: var(--shadow-md);
  border-radius: 2px;
  cursor: pointer;
  scroll-snap-align: center;
  transform: rotate(var(--video-rotation, -3deg));
  transition: transform var(--transition-normal), box-shadow var(--transition-normal);
}

.video-polaroid:hover,
.video-polaroid:focus-visible {
  transform: rotate(0deg) scale(1.03);
  box-shadow: var(--shadow-xl);
  outline: none;
}

/* A playing clip straightens up and lifts, so it reads as "the active one" */
.video-polaroid.is-playing {
  transform: rotate(0deg) scale(1.04);
  box-shadow: var(--shadow-xl);
}

.video-polaroid:nth-child(1) { --video-rotation: -3deg; }
.video-polaroid:nth-child(2) { --video-rotation: 2.5deg; }
.video-polaroid:nth-child(3) { --video-rotation: -4deg; }
.video-polaroid:nth-child(4) { --video-rotation: 3.5deg; }
.video-polaroid:nth-child(5) { --video-rotation: -2deg; }

/* 9:16 portrait window. object-fit: cover crops rather than letterboxes,
   so the one landscape clip in the set fills the frame (centre-cropped)
   instead of sitting in black bars. */
.video-polaroid video {
  display: block;
  width: 100%;
  aspect-ratio: 9 / 16;
  object-fit: cover;
  background: var(--color-text);
  border: 1px solid rgba(183, 110, 121, 0.1);
}

.video-polaroid .caption {
  font-family: var(--font-heading);
  font-size: var(--font-size-sm);
  color: var(--color-text-light);
  text-align: center;
  margin-top: var(--space-sm);
  font-style: italic;
}

/* --- Play badge: centred over the frame, hidden while playing --- */
.video-play {
  position: absolute;
  top: calc(50% - 16px);
  left: 50%;
  transform: translate(-50%, -50%);
  width: 56px;
  height: 56px;
  border-radius: var(--radius-full);
  background: rgba(255, 255, 255, 0.88);
  color: var(--color-rose-gold);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.5rem;
  box-shadow: var(--shadow-md);
  pointer-events: none;
  transition: opacity var(--transition-fast), transform var(--transition-fast);
}

.video-polaroid.is-playing .video-play {
  opacity: 0;
  transform: translate(-50%, -50%) scale(0.7);
}

/* --- Mute toggle: small pill in the frame's top-right --- */
.video-sound {
  position: absolute;
  top: calc(var(--space-sm) + 8px);
  right: calc(var(--space-sm) + 8px);
  padding: 2px 8px;
  font-size: var(--font-size-xs);
  line-height: 1.6;
  background: rgba(255, 255, 255, 0.85);
  color: var(--color-text);
  border: none;
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-sm);
  cursor: pointer;
  z-index: 2;
  transition: background var(--transition-fast);
}

.video-sound:hover {
  background: var(--color-pink-light);
}

.video-sound .sound-on  { display: inline; }
.video-sound .sound-off { display: none; }

.video-polaroid.is-muted .video-sound .sound-on  { display: none; }
.video-polaroid.is-muted .video-sound .sound-off { display: inline; }

/* --- Missing file / unsupported codec --- */
.video-polaroid.has-error video {
  opacity: 0.25;
}

.video-polaroid .video-error {
  display: none;
  position: absolute;
  inset: var(--space-sm) var(--space-sm) 40px;
  align-items: center;
  justify-content: center;
  padding: var(--space-md);
  font-size: var(--font-size-xs);
  text-align: center;
  color: var(--color-text-light);
  background: var(--color-cream);
}

.video-polaroid.has-error .video-error {
  display: flex;
}

.video-polaroid.has-error .video-play {
  opacity: 0;
}

/* --- Hint + dots, matching the photo stack's affordances --- */
.video-hint {
  text-align: center;
  font-size: var(--font-size-sm);
  color: var(--color-text-light);
  font-style: italic;
  margin-top: var(--space-lg);
}

.video-dots {
  display: flex;
  justify-content: center;
  gap: var(--space-sm);
  margin-top: var(--space-sm);
}

.video-dots .dot {
  width: 8px;
  height: 8px;
  border-radius: var(--radius-full);
  background: var(--color-pink-light);
  cursor: pointer;
  transition: all var(--transition-fast);
}

.video-dots .dot.active {
  background: var(--color-rose-gold);
  transform: scale(1.3);
}

@media (min-width: 768px) {
  .video-polaroid { width: 240px; }
}

@media (min-width: 1024px) {
  .video-polaroid { width: 260px; }
  /* Wide screens fit three cards, so the row no longer needs to centre
     a single card with half-viewport padding. */
  .video-row {
    padding: var(--space-md) var(--space-xl);
    justify-content: flex-start;
  }
}

/* ============================================================
   WISH CARD — Visitor writes a wish, emailed on submit
   ============================================================ */

.wish-section {
  width: 100%;
  max-width: 640px;
  margin: var(--space-2xl) auto;
  padding: 0 var(--space-md);
  opacity: 0;
  transform: translateY(40px);
  transition: all var(--transition-slow) ease;
}

.wish-section.revealed {
  opacity: 1;
  transform: translateY(0);
}

/* Paper card, same family as .letter-card but with a pink wash so it
   reads as an input surface rather than something already written. */
.wish-card {
  position: relative;
  padding: var(--space-2xl) var(--space-lg);
  background: linear-gradient(
    160deg,
    var(--color-white) 0%,
    var(--color-bg-warm) 60%,
    var(--color-pink-light) 100%
  );
  border: 1px solid var(--color-accent);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
}

.wish-card h3 {
  text-align: center;
  color: var(--color-rose-gold);
  margin-bottom: var(--space-sm);
}

.wish-intro {
  text-align: center;
  font-size: var(--font-size-sm);
  color: var(--color-text-light);
  margin-bottom: var(--space-xl);
}

.wish-field {
  margin-bottom: var(--space-lg);
}

.wish-field label {
  display: block;
  font-size: var(--font-size-sm);
  font-weight: 500;
  color: var(--color-text);
  margin-bottom: var(--space-xs);
}

.wish-field textarea {
  width: 100%;
  min-height: 160px;
  padding: var(--space-sm) var(--space-md);
  font-family: var(--font-body);
  font-size: var(--font-size-base);
  line-height: 1.7;
  color: var(--color-text);
  background: rgba(255, 255, 255, 0.75);
  border: 1px solid var(--color-accent);
  border-radius: var(--radius-md);
  resize: vertical;
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}

.wish-field textarea:focus {
  outline: none;
  border-color: var(--color-rose-gold);
  box-shadow: 0 0 0 3px rgba(183, 110, 121, 0.15);
}

.wish-field textarea::placeholder {
  color: var(--color-text-light);
  opacity: 0.7;
}

.wish-counter {
  display: block;
  text-align: right;
  font-size: var(--font-size-xs);
  color: var(--color-text-light);
  margin-top: var(--space-xs);
}

.btn-wish {
  display: block;
  width: 100%;
  padding: var(--space-md) var(--space-lg);
  font-family: var(--font-body);
  font-size: var(--font-size-base);
  font-weight: 500;
  color: var(--color-white);
  background: linear-gradient(135deg, var(--color-pink-dark), var(--color-rose-gold));
  border: none;
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-md);
  cursor: pointer;
  transition: all var(--transition-fast);
}

.btn-wish:hover:not(:disabled) {
  transform: translateY(-2px);
  box-shadow: var(--shadow-lg);
}

.btn-wish:disabled {
  opacity: 0.6;
  cursor: wait;
}

/* --- Status line: colour carries the outcome --- */
.wish-status {
  min-height: 1.6em;
  margin-top: var(--space-md);
  text-align: center;
  font-size: var(--font-size-sm);
  transition: color var(--transition-fast);
}

.wish-status.is-pending { color: var(--color-text-light); }
.wish-status.is-success { color: var(--color-rose-gold); font-weight: 500; }
.wish-status.is-error   { color: #C0392B; }

/* Brief confirmation flash across the whole card after a successful send */
.wish-form.is-sent {
  animation: wish-sent 2.6s ease;
}

/* ============================================================
   FINALE — "Last Celebrate" button + heart-of-photos overlay
   ============================================================ */

.finale-section {
  width: 100%;
  max-width: 640px;
  margin: var(--space-2xl) auto var(--space-3xl);
  padding: 0 var(--space-md);
  text-align: center;
  opacity: 0;
  transform: translateY(40px);
  transition: all var(--transition-slow) ease;
}

.finale-section.revealed {
  opacity: 1;
  transform: translateY(0);
}

.finale-hint {
  font-size: var(--font-size-sm);
  color: var(--color-text-light);
  font-style: italic;
  margin-bottom: var(--space-lg);
}

/* The page's loudest button: a warm gradient that drifts across itself,
   plus a breathing glow, so it stays visibly the final action. */
.btn-finale {
  position: relative;
  padding: var(--space-md) var(--space-2xl);
  font-family: var(--font-heading);
  font-size: var(--font-size-lg);
  color: var(--color-white);
  background: linear-gradient(120deg, #FF6B9E, #FF8E53, #FFD93D, #FF6BF5);
  background-size: 300% 300%;
  border: none;
  border-radius: var(--radius-xl);
  box-shadow: 0 8px 30px rgba(255, 107, 158, 0.45);
  cursor: pointer;
  overflow: hidden;
  animation: finale-shimmer 6s ease infinite,
             finale-pulse 2.4s ease-in-out infinite;
  transition: transform var(--transition-fast), box-shadow var(--transition-fast);
}

.btn-finale:hover:not(:disabled) {
  transform: translateY(-3px) scale(1.03);
  box-shadow: 0 14px 44px rgba(255, 107, 158, 0.6);
}

.btn-finale:active:not(:disabled) {
  transform: translateY(-1px) scale(0.99);
}

.btn-finale:disabled {
  cursor: default;
  animation-play-state: paused;
}

.btn-finale.is-fired {
  animation: none;
}

/* --- Heart overlay --- */
.heart-overlay {
  position: fixed;
  inset: 0;
  z-index: 10000;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Dark enough that bright confetti and photo tiles both pop */
  background: radial-gradient(
    circle at 50% 45%,
    rgba(70, 30, 45, 0.82) 0%,
    rgba(25, 12, 20, 0.94) 100%
  );
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--transition-slow), visibility var(--transition-slow);
}

.heart-overlay.open {
  opacity: 1;
  visibility: visible;
}

.heart-stage {
  position: relative;
  text-align: center;
}

/* Square box the heart curve is mapped into. Sized in vmin so the shape
   stays proportional and on-screen in both orientations. */
.heart-photos {
  position: relative;
  width: 84vmin;
  height: 84vmin;
  max-width: 620px;
  max-height: 620px;
  margin: 0 auto;
}

.heart-photos.is-beating {
  animation: heart-beat 1.4s ease-in-out infinite;
}

/* One small photo tile sitting on the curve. JS sets left/top; the
   translate(-50%,-50%) here is what centres each tile on its point. */
.heart-photo {
  position: absolute;
  width: 9vmin;
  height: 9vmin;
  max-width: 68px;
  max-height: 68px;
  padding: 2px;
  background: var(--color-white);
  border-radius: 3px;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.35);
  opacity: 0;
  animation: heart-tile-in 0.7s cubic-bezier(0.22, 1.2, 0.36, 1) forwards;
}

.heart-photo img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 2px;
}

.heart-caption {
  margin-top: var(--space-lg);
  font-family: var(--font-heading);
  font-size: var(--font-size-lg);
  color: var(--color-pink-light);
  text-shadow: 0 2px 12px rgba(0, 0, 0, 0.5);
  animation: heart-caption-in 1s ease 2.4s both;
}

.heart-close {
  position: absolute;
  top: var(--space-lg);
  right: var(--space-lg);
  width: 44px;
  height: 44px;
  font-size: 1.75rem;
  line-height: 1;
  color: var(--color-white);
  background: rgba(255, 255, 255, 0.15);
  border: 1px solid rgba(255, 255, 255, 0.3);
  border-radius: var(--radius-full);
  cursor: pointer;
  transition: background var(--transition-fast);
}

.heart-close:hover {
  background: rgba(255, 255, 255, 0.3);
}

/* Finale confetti canvas — above the overlay, below the close button */
#finale-canvas {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 10001;
}

@media (prefers-reduced-motion: reduce) {
  .btn-finale { animation: none; }
  .heart-photos.is-beating { animation: none; }
  .heart-photo { animation-duration: 0.01s; }
}
