:root {
  --cream: #f5f0e8;
  --olive: #4a5340;
  --olive-soft: #6b7359;
  --ink: #3a3a32;
  --sheet-max: 900px;
  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);

  /* One place to tune the whole reveal choreography. */
  --rise: 26px;
  --reveal-dur: 0.85s;
  --stagger: 140ms;

  /*
    How wide Canva draws the 1366 canvas, as a multiple of the viewport width.
    Measured, not guessed: a patch of the canvas was template-matched against a
    frame of the phone recording across candidate scales, and the correlation
    peaks sharply at 1.50-1.55 (0.74-0.80) against a ~0.20 floor everywhere
    else.

    That measurement got the artwork into the right neighbourhood; the values
    actually shipped were then set by eye with the ?tune tool, on the phone,
    which is the only judge that matters here. Per-block overrides below.
  */
  --canva-zoom: 1.45;

  /* The envelope carries 23.9% cream margin on its tighter side, so it has
     room to zoom well past the screen before reaching the flowers. */
  --envelope-zoom: 1.6;
}

* {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  padding: 0;
  background: var(--cream);
  color: var(--ink);
  font-family: "Heebo", system-ui, -apple-system, "Segoe UI", sans-serif;
  -webkit-font-smoothing: antialiased;
}

html {
  scroll-behavior: smooth;
  /*
    No scroll snapping. The Canva site scrolls as one continuous page — the
    bottom of the collage runs straight into the countdown and on into the
    details, with no gaps and nothing locking to screen boundaries — and that
    is what the invitation is meant to feel like.
  */
}

/* The envelope owns the viewport until it is opened, so lock scrolling. */
body.is-sealed {
  overflow: hidden;
  height: 100dvh;
}

img {
  display: block;
  max-width: 100%;
  height: auto;
}

/* SCREENS ----------------------------------------------------------- */
/*
  100dvh, not 100vh: on mobile Safari the URL bar shrinks on scroll, and 100vh
  measures the *tall* state. With vh the artwork would be pushed a bar's height
  too low and the next screen would show through at the bottom.
*/
.screen {
  /*
    A section is exactly as tall as what it holds. Forcing every one to a full
    viewport is what produced the cream bands the original does not have.
  */

  /* The artwork is deliberately wider than the screen (see .art). Clip it here
     rather than letting it push the page sideways. */
  overflow: hidden;

  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: clamp(0.75rem, 2.5vh, 1.5rem);

  /*
    No horizontal padding. In the Canva design the artwork is a 1366-wide
    canvas that meets both edges of the screen, and 15px of padding a side was
    scaling every section down by ~9% — which is exactly why the sizes did not
    match the original. Text screens re-add their own inset below.
  */
  padding: 0;
  text-align: center;
}

/* Only the text sections get breathing room; artwork sections butt together. */
.countdown,
.footer {
  padding-block: clamp(1.75rem, 6vh, 3.25rem);
}

.footer {
  padding-bottom: calc(env(safe-area-inset-bottom) + clamp(2rem, 7vh, 3.5rem));
}

/* Screens made of text, not artwork, still need breathing room at the edges. */
.countdown,
.footer {
  padding-inline: clamp(0.75rem, 4vw, 1.5rem);
}

.actions {
  padding-inline: clamp(0.75rem, 4vw, 1.5rem);
}

/* This screen also carries the Waze/Maps buttons under the artwork. */
.screen--with-actions {
  padding-bottom: clamp(1.5rem, 5vh, 2.5rem);

}

/*
  .art shrink-wraps the artwork: aspect-ratio matches the export, so the box is
  exactly the picture and nothing more. The hotspots are positioned in
  percentages of this box, which is the only reason they keep sitting on the
  right part of the collage once the picture is letterboxed inside a screen
  that is taller than it is.
*/
.art {
  position: relative;
  aspect-ratio: var(--ar);

  /*
    Sized from the viewport, never from the loaded image. A lazy picture has no
    intrinsic size until it arrives, and a box that only had max-width/
    max-height to go on collapsed to 0x0 while waiting — taking its hotspots
    with it. The third term is "how wide may I be so that my height still fits
    in what is left of the screen", which is the available height times the
    aspect ratio.
  */
  /*
    Fill the screen rather than fit inside it. The artwork is a 1366x2034
    canvas — far squarer than a phone — so sizing it to fit the height left
    wide cream bands above and below and made everything look small. Canva
    fills the screen and lets the painted background run off the sides, which
    is what makes its elements read as large and spread out.

    max() picks whichever of width-fill or height-fill is bigger, so one axis
    always meets the screen exactly and the other overflows into the clip.
    .art still wraps the whole picture, so the percentage hotspots keep
    landing on the right part of the collage.
  */
  width: calc(100% * var(--canva-zoom));

  /*
    Centre the overflow by hand. `margin: 0 auto` collapses to zero once the
    box is wider than its container, and flex `align-items: center` then anchors
    it to the start edge — which on this RTL page is the right, so the whole
    collage sat 192px too far left with its left side cut off. A percentage
    margin resolves against the container width, so this pulls back exactly
    half the overflow on each side whatever the direction.
  */
  /*
    ...then nudge left. The collage is not symmetric inside its own canvas —
    the centroid of its light elements sits 4.52% of the canvas width right of
    centre — so a mathematically centred box reads as leaning right. Correcting
    by the full 4.52% overshot and read as leaning left, because that centroid
    is area-weighted and the eye is not, and successive guesses at the
    percentage kept over- and under-shooting. The shipped offsets are per-block
    and were set by dragging with the ?tune tool; these are only the fallbacks.
    Physical left/right rather than inline, so RTL cannot flip them.
  */
  --art-nudge: 0px;

  /* Vertical trim, so a block can be pulled up or down against its neighbours.
     tune.js writes both of these live; see README. */
  --art-shift-y: 0px;

  margin-top: var(--art-shift-y);
  margin-left: calc((100% - 100% * var(--canva-zoom)) / 2 - var(--art-nudge));
  margin-right: calc((100% - 100% * var(--canva-zoom)) / 2 + var(--art-nudge));
  flex: none;
}

/*
  Positions set by hand with the ?tune tool, on the phone. A positive nudge
  pulls the block left.
*/
#top .art {
  --canva-zoom: 1.45;
  --art-nudge: 8.14%;
  --art-shift-y: -1.02%;
}

.art--details-top {
  --canva-zoom: 1.78;
  --art-nudge: -1.78%;
  --art-shift-y: -4.83%;
}

.art--details-bottom {
  --canva-zoom: 1.78;
  --art-nudge: 0.25%;
  --art-shift-y: -1.78%;
}

.art__img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* Hotspots ---------------------------------------------------------- */
.hotspot {
  position: absolute;
  left: var(--x);
  top: var(--y);
  width: var(--w);
  height: var(--h);
  /* The hotspots are sized as a percentage of the collage, so on a narrow
     phone the smallest of them (21% x 8.4%) falls to 67x40 — under the 44px a
     fingertip needs. The floor only ever grows the tiny ones. */
  min-width: 44px;
  min-height: 44px;
  padding: 0;
  border: 0;
  border-radius: 12px;
  background: transparent;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background-color 0.25s var(--ease);
}

/*
  Hover tints only where a pointer can actually hover. On a touch screen the
  :hover state sticks after a tap, so opening the envelope used to leave a pale
  rectangle sitting on "אישורי הגעה" — the finger's last position, highlighted
  as though the guest were pointing at it.
*/
@media (hover: hover) and (pointer: fine) {
  .hotspot:hover {
    background: rgba(255, 255, 255, 0.16);
  }
}

.hotspot:focus-visible {
  background: rgba(255, 255, 255, 0.16);
  outline: none;
  box-shadow: 0 0 0 2px var(--olive);
}

/* REVEALS ----------------------------------------------------------- */
/*
  Canva fades each element up as it scrolls into view. Same idea: .reveal is
  the resting (hidden) state, .is-in is applied by the IntersectionObserver in
  app.js. data-delay staggers siblings so a screen assembles rather than
  appearing all at once.
*/
/*
  The hidden state is gated on .js-reveal, which app.js sets only once it has
  confirmed IntersectionObserver exists. If the script fails to run, or the
  observer never fires, the invitation is simply visible — an animation that
  does not play is a small loss, an invitation that never appears is a total
  one.
*/
.js-reveal .reveal {
  opacity: 0;
  transform: translateY(var(--rise));
  transition:
    opacity var(--reveal-dur) var(--ease),
    transform var(--reveal-dur) var(--ease);
}

.js-reveal .reveal.is-in {
  opacity: 1;
  transform: none;
}

/*
  The invitation itself arrives on a pure fade once the envelope is open — no
  travel, so it reads as the artwork emerging from the envelope rather than
  sliding in from somewhere. Slower than the scroll reveals, and held back
  until the envelope has finished getting out of the way.
*/
.js-reveal .reveal--fade {
  transform: none;
  transition-duration: 1.5s;
  transition-delay: 0.35s;
}

.reveal[data-delay="1"] { transition-delay: var(--stagger); }
.reveal[data-delay="2"] { transition-delay: calc(var(--stagger) * 2); }
.reveal[data-delay="3"] { transition-delay: calc(var(--stagger) * 3); }

/* ENVELOPE ---------------------------------------------------------- */
.envelope {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: grid;
  place-items: center;
  background: var(--cream);
  border: 0;
  /* No inset: --envelope-zoom is meant to read as a multiple of the screen
     width, which only holds if 100% is the screen. */
  padding: 0;
  cursor: pointer;
  overflow: hidden;
}

.envelope__stage {
  /* The envelope artwork carries a wide cream border of its own, so zooming
     past the screen edge crops that border rather than the flowers. */
  /*
    Percentages, not pixels. These offsets were tuned on a 393px-wide phone; in
    pixels they stayed put while the artwork around them grew with the screen,
    so the composition drifted off-centre by roughly 9px between a 320px and a
    430px phone. As a share of the container they hold on every device. Both
    axes: a percentage margin resolves against the container width, which is
    exactly what the artwork scales with.
  */
  --envelope-nudge: 5.34%;
  --envelope-shift-y: -0.51%;

  width: calc(100% * var(--envelope-zoom));
  margin-top: var(--envelope-shift-y);
  margin-left: calc((100% - 100% * var(--envelope-zoom)) / 2 - var(--envelope-nudge));
  margin-right: calc((100% - 100% * var(--envelope-zoom)) / 2 + var(--envelope-nudge));
  /* The flap tilts back from the top edge, so that is the hinge. */
  transform-origin: 50% 12%;
}

.envelope__img {
  width: 100%;
  animation: breathe 3.6s ease-in-out infinite;
}

.envelope__hint {
  position: absolute;
  bottom: max(6vh, env(safe-area-inset-bottom));
  margin: 0;
  font-size: 0.95rem;
  letter-spacing: 0.08em;
  color: var(--olive-soft);
  opacity: 0.75;
  animation: fade 2.4s ease-in-out infinite;
}

/*
  Arrival: the envelope and its caption slide up into place while fading in,
  the way the Canva page introduces itself. The caption follows a beat later so
  the two read as one movement rather than a single block appearing.
*/
.envelope__stage { animation: slide-in 1.15s var(--ease) both; }
.envelope__hint  {
  animation:
    slide-in 1.15s var(--ease) 0.25s both,
    fade 2.4s ease-in-out 1.6s infinite;
}

@keyframes slide-in {
  from { opacity: 0; transform: translateY(46px); }
  to   { opacity: 1; transform: none; }
}

/*
  Opening. The envelope is a single flat photograph, so there is no separate
  flap layer to hinge — the "opening" is the whole card tipping back and
  lifting toward the reader while the invitation rises up behind it. It reads
  as going *into* the envelope, which is the intent; a true hinged flap would
  need the flap exported as its own transparent layer.
*/
.envelope.is-opening {
  animation: envelope-out 0.95s var(--ease) forwards;
  pointer-events: none;
}

.envelope.is-opening .envelope__stage {
  animation: flap 0.95s var(--ease) forwards;
}

.envelope.is-opening .envelope__hint {
  animation: none;
  opacity: 0;
  transition: opacity 0.25s linear;
}

@keyframes flap {
  0%   { transform: perspective(1200px) rotateX(0deg) scale(1); }
  35%  { transform: perspective(1200px) rotateX(-18deg) scale(1.02); }
  100% { transform: perspective(1200px) rotateX(-52deg) scale(1.5); opacity: 0; }
}

@keyframes envelope-out {
  0%, 45% { opacity: 1; }
  100%    { opacity: 0; }
}

@keyframes breathe {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.018); }
}

@keyframes fade {
  0%, 100% { opacity: 0.35; }
  50% { opacity: 0.9; }
}

/* INVITATION -------------------------------------------------------- */
.invitation {
  opacity: 0;
  transform: scale(0.985);
  transition: opacity 1s var(--ease), transform 1s var(--ease);
}

.invitation.is-visible {
  opacity: 1;
  transform: none;
}

/* COUNTDOWN --------------------------------------------------------- */
.countdown__clock {
  display: flex;
  align-items: flex-start;
  justify-content: center;
  gap: clamp(0.4rem, 2.2vw, 1rem);
  font-family: "Cormorant Garamond", Georgia, serif;
  color: var(--olive);
}

.unit {
  display: flex;
  flex-direction: column;
  align-items: center;
  min-width: clamp(3.9rem, 17vw, 6rem);
}

.unit__num {
  /*
    Measured off the Canva screenshot: the digits stand 35 CSS px tall on a
    393px-wide phone. Cormorant's lining figures are about 0.66 of the font
    size, which puts the size near 13.5vw.
  */
  font-size: clamp(3rem, 13.5vw, 4.6rem);
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
  font-weight: 400;
}

.unit__label {
  margin-top: 0.4rem;
  font-family: "Heebo", sans-serif;
  font-size: clamp(0.75rem, 3.3vw, 1.05rem);
  letter-spacing: 0.16em;
  color: var(--olive-soft);
}

.colon {
  font-size: clamp(2.5rem, 10.5vw, 3.8rem);
  line-height: 1.25;
  color: var(--olive-soft);
}

.countdown__title {
  margin: 0;
  font-size: clamp(1.5rem, 7.1vw, 2.1rem);
  font-weight: 500;
  color: var(--olive);
}

/* BUTTONS ----------------------------------------------------------- */
.linkish {
  /* 44px is the smallest target a fingertip hits reliably; the text alone is
     36px tall, so the height comes from min-height rather than more padding —
     padding would push the underline away from the text. */
  display: inline-flex;
  align-items: center;
  min-height: 48px;
  border: 0;
  background: none;
  font-family: inherit;
  font-size: clamp(1.25rem, 5.9vw, 1.6rem);
  color: var(--olive-soft);
  cursor: pointer;
  padding: 0.5rem 0.7rem;
  border-bottom: 1px solid transparent;
  -webkit-tap-highlight-color: transparent;
}

.actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  justify-content: center;
}

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 48px;
  /*
    Sized so ניווט ב-Waze and Google Maps sit side by side on a phone, the way
    they did before the countdown was enlarged. At the larger size the pair
    wrapped onto two rows.
  */
  padding: 0.7rem 1.05rem;
  border-radius: 999px;
  background: var(--olive);
  color: var(--cream);
  text-decoration: none;
  font-size: clamp(0.92rem, 3.7vw, 1.1rem);
  letter-spacing: 0.02em;
  white-space: nowrap;
  transition: transform 0.2s var(--ease), background-color 0.2s var(--ease);
}

.btn--ghost {
  background: transparent;
  color: var(--olive);
  border: 1px solid var(--olive);
}

@media (hover: hover) and (pointer: fine) {
  .linkish:hover { border-bottom-color: var(--olive-soft); }
  .btn:hover { background: var(--olive-soft); transform: translateY(-1px); }
  .btn--ghost:hover { background: rgba(74, 83, 64, 0.08); }
}

.footer p {
  margin: 0;
  font-size: clamp(1.25rem, 5.9vw, 1.6rem);
  color: var(--olive-soft);
}

/* MUSIC ------------------------------------------------------------- */
.music {
  position: fixed;
  z-index: 40;
  inset-inline-start: max(1rem, env(safe-area-inset-left));
  bottom: max(1rem, env(safe-area-inset-bottom));
  width: 48px;
  height: 48px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: 50%;
  background: rgba(74, 83, 64, 0.92);
  color: var(--cream);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.music__bars {
  display: flex;
  align-items: flex-end;
  gap: 3px;
  height: 16px;
}

.music__bars i {
  width: 3px;
  height: 100%;
  background: currentColor;
  border-radius: 2px;
  transform-origin: bottom;
  animation: eq 0.9s ease-in-out infinite;
}

.music__bars i:nth-child(2) { animation-delay: 0.18s; }
.music__bars i:nth-child(3) { animation-delay: 0.36s; }

@keyframes eq {
  0%, 100% { transform: scaleY(0.35); }
  50% { transform: scaleY(1); }
}

.music__note {
  display: none;
  font-size: 1.15rem;
  line-height: 1;
}

/* Paused: swap the equaliser for a static note. */
.music[aria-pressed="false"] .music__bars { display: none; }
.music[aria-pressed="false"] .music__note { display: block; }
.music[aria-pressed="false"] { background: rgba(74, 83, 64, 0.62); }

/* ------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
    /* Snapping still helps here — it is the jumping that is unwelcome, not
       landing on a whole screen. */
  }

  .envelope__img,
  .envelope__hint,
  .envelope__stage,
  .music__bars i {
    animation: none;
  }

  .js-reveal .reveal {
    opacity: 1;
    transform: none;
    transition: none;
  }

  .invitation,
  .envelope {
    transition: none;
  }

  .envelope.is-opening,
  .envelope.is-opening .envelope__stage {
    animation: none;
    opacity: 0;
  }
}
