/* animations.css — reveal + ambient motion for the pukhraj-poorva invite.
 *
 * All motion is GPU-only: transform, opacity, and small blur() (<=4px) on text /
 * name reveals. Tokens live in css/motion.css.
 *
 * Gating (so content is NEVER stuck invisible):
 *   - Every initial-hidden state is behind `html.js`  -> a no-JS visitor sees
 *     everything static (the `js` class is set inline in <head>).
 *   - Body reveals are ADDITIONALLY behind `html.anim-ready`, a class js/animations.js
 *     adds only once it has successfully built its IntersectionObserver. If the script
 *     never runs, throws, or half-loads, `.anim-ready` is absent -> body content stays
 *     fully visible.
 *   - Hard fail-safe (animationend-independent): js/animations.js reveals every target
 *     (adds `.is-in`) on window `error` and on `pagehide`, and inside a try/catch around
 *     setup. So even a mid-setup throw cannot leave anything hidden. (See animations.js.)
 *   - The inner <img> of an animated cutout is `.ly-in`; the reveal animates the WRAPPER
 *     (or, for parrots, the inner img's glide) while the ambient loop lives on the inner
 *     img — a reveal transition and an infinite transform loop never share one element.
 *
 * prefers-reduced-motion: reduce -> all reveals show their final state instantly, every
 * loop (sway/bob) and the parrot glide are disabled; parrots rest at their PSD frame.
 */

/* ===========================================================================
 * Inner-image wrapper fill (shared by hero flowers + body flower/parrot cutouts)
 * ======================================================================== */
.ly-in {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  pointer-events: none;
  -webkit-user-drag: none;
  user-select: none;
}

/* ===========================================================================
 * HERO ENTRANCE — pure CSS, keyed off the preloader handing off AND the envelope
 * intro clearing (html.js:not(.is-preloading):not(.intro-active)). When the intro
 * overlay is present it holds `intro-active` until the clip finishes, so the entrance
 * plays for the guest as the overlay dissolves — not unseen behind it; with no intro
 * present the class is never set, so it fires the instant the preloader hands off.
 * One-shot: keyframes use `both` fill and their end state matches the resting CSS, so
 * a day/night toggle never replays them.
 * ======================================================================== */

/* armed hidden (covered by the preloader while it holds) */
html.js .hero-text .mantra,
html.js .hero-text .para,
html.js .hero-text .cname {
  opacity: 0;
}
html.js .hero-flowers .ly--flower {
  opacity: 0;
}

@keyframes pp-rise {
  from { opacity: 0; transform: translate(-50%, -50%) translateY(14px); }
  to   { opacity: 1; transform: translate(-50%, -50%) translateY(0); }
}
/* paragraphs are anchored translateX(-50%) only (top-anchored, not y-centred) */
@keyframes pp-rise-x {
  from { opacity: 0; transform: translateX(-50%) translateY(16px); }
  to   { opacity: 1; transform: translateX(-50%) translateY(0); }
}
/* couple names settle in with a soft blur (morning: no static filter) */
@keyframes pp-name {
  from { opacity: 0; transform: translate(-50%, -50%) translateY(18px); filter: blur(4px); }
  60%  { opacity: 1; filter: blur(1px); }
  to   { opacity: 1; transform: translate(-50%, -50%) translateY(0); filter: blur(0); }
}
/* night names carry the luminous drop-shadow glow — keep it through the blur settle,
   and the `to` filter matches hero.css so the `both` fill leaves it identical */
@keyframes pp-name-glow {
  from {
    opacity: 0;
    transform: translate(-50%, -50%) translateY(18px);
    filter: blur(4px) drop-shadow(0 1px 1px rgba(4, 7, 30, 0.55))
      drop-shadow(0 0 7px rgba(232, 214, 184, 0.14));
  }
  60% { opacity: 1; }
  to {
    opacity: 1;
    transform: translate(-50%, -50%) translateY(0);
    filter: blur(0) drop-shadow(0 1px 1px rgba(4, 7, 30, 0.55))
      drop-shadow(0 0 7px rgba(232, 214, 184, 0.14));
  }
}
@keyframes pp-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* staggered sequence once the preloader releases (<=3s total) */
html.js:not(.is-preloading):not(.intro-active) .hero-text .mantra:nth-child(1) {
  animation: pp-rise var(--reveal-dur) var(--reveal-ease) both;
  will-change: transform, opacity;
}
html.js:not(.is-preloading):not(.intro-active) .hero-text .mantra:nth-child(2) {
  animation: pp-rise var(--reveal-dur) var(--reveal-ease) 120ms both;
  will-change: transform, opacity;
}
html.js:not(.is-preloading):not(.intro-active) .hero-text .mantra:nth-child(3) {
  animation: pp-rise var(--reveal-dur) var(--reveal-ease) 240ms both;
  will-change: transform, opacity;
}
html.js:not(.is-preloading):not(.intro-active) .hero-text .para {
  animation: pp-rise-x var(--reveal-dur) var(--reveal-ease) 400ms both;
  will-change: transform, opacity;
}
html.js:not(.is-preloading):not(.intro-active) .hero-text--morning .cname--1 {
  animation: pp-name var(--reveal-dur) var(--reveal-ease) 700ms both;
  will-change: transform, opacity, filter;
}
html.js:not(.is-preloading):not(.intro-active) .hero-text--morning .cname--2 {
  animation: pp-name var(--reveal-dur) var(--reveal-ease) 950ms both;
  will-change: transform, opacity, filter;
}
html.js:not(.is-preloading):not(.intro-active) .hero-text--night .cname--1 {
  animation: pp-name-glow var(--reveal-dur) var(--reveal-ease) 700ms both;
  will-change: transform, opacity, filter;
}
html.js:not(.is-preloading):not(.intro-active) .hero-text--night .cname--2 {
  animation: pp-name-glow var(--reveal-dur) var(--reveal-ease) 950ms both;
  will-change: transform, opacity, filter;
}
html.js:not(.is-preloading):not(.intro-active) .hero-flowers .ly--flower {
  animation: pp-fade var(--reveal-dur) var(--reveal-ease) 300ms both;
}

/* ===========================================================================
 * AMBIENT SWAY — the inner img of every flower rocks gently around its stem.
 * Loops keep will-change (small bitmaps). Desynced via per-instance duration +
 * negative delay so no two flowers move in lockstep.
 * ======================================================================== */
@keyframes pp-sway {
  from { transform: rotate(-1.4deg); }
  to   { transform: rotate(1.4deg); }
}

.hero-flowers .ly--flower .ly-in,
.ly--cut[data-layer^="flower-"] .ly-in {
  animation: pp-sway var(--sway-dur) ease-in-out infinite alternate;
  will-change: transform;
}

/* origin toward the stem/edge: left flowers hinge bottom-left, right flowers bottom-right */
.ly--cut[data-layer$="-left"] .ly-in  { transform-origin: 0% 30%; }
.ly--cut[data-layer$="-right"] .ly-in { transform-origin: 100% 30%; }

/* hero corner flowers hinge toward their corner */
.hero-flowers .ly--flower[data-layer="flower-1"] .ly-in { transform-origin: 0% 0%; }
.hero-flowers .ly--flower[data-layer="flower-2"] .ly-in { transform-origin: 0% 0%; }
.hero-flowers .ly--flower[data-layer="flower-3"] .ly-in { transform-origin: 100% 30%; }

/* per-instance desync (duration * factor + negative start offset) */
.hero-flowers .ly--flower[data-layer="flower-1"] .ly-in { animation-duration: calc(var(--sway-dur) * 0.9);  animation-delay: -0.6s; }
.hero-flowers .ly--flower[data-layer="flower-2"] .ly-in { animation-duration: calc(var(--sway-dur) * 1.15); animation-delay: -3.1s; }
.hero-flowers .ly--flower[data-layer="flower-3"] .ly-in { animation-duration: calc(var(--sway-dur) * 1.0);  animation-delay: -1.7s; }

.ly--cut[data-layer="flower-dl-left"] .ly-in   { animation-duration: calc(var(--sway-dur) * 0.9);  animation-delay: -0.5s; }
.ly--cut[data-layer="flower-dl-right"] .ly-in  { animation-duration: calc(var(--sway-dur) * 1.1);  animation-delay: -2.3s; }
.ly--cut[data-layer="flower-it-left"] .ly-in   { animation-duration: calc(var(--sway-dur) * 1.0);  animation-delay: -4.1s; }
.ly--cut[data-layer="flower-it-right"] .ly-in  { animation-duration: calc(var(--sway-dur) * 0.85); animation-delay: -1.4s; }
.ly--cut[data-layer="flower-w-left"] .ly-in    { animation-duration: calc(var(--sway-dur) * 1.15); animation-delay: -3.2s; }
.ly--cut[data-layer="flower-w-right"] .ly-in   { animation-duration: calc(var(--sway-dur) * 0.95); animation-delay: -0.9s; }
.ly--cut[data-layer="flower-last-left"] .ly-in { animation-duration: calc(var(--sway-dur) * 1.05); animation-delay: -2.8s; }
.ly--cut[data-layer="flower-last-right"] .ly-in{ animation-duration: calc(var(--sway-dur) * 0.8);  animation-delay: -1.9s; }

/* ===========================================================================
 * BODY REVEAL — armed only when js/animations.js sets html.anim-ready.
 * IntersectionObserver adds `.is-in`; a per-batch `--d` (set inline by JS) staggers
 * siblings. Every hidden transform keeps the element's centre-anchor.
 * ======================================================================== */

/* IMPORTANT: transitions live on the `.is-in` (reveal) state ONLY, never on the armed
   base. `.anim-ready` is added by JS after first paint, so a transition on the base
   would make elements visibly FADE OUT (1 -> 0) while arming. Arming must be instant;
   only the reveal animates. */

/* single-line primitives (own opacity + rise). Scratch coin/plate excluded: they sit
   under the foil canvas and must never depend on a scroll reveal to become readable. */
html.js.anim-ready .body-art .bline:not(.is-in):not(.pp-coin):not(.pp-plate) {
  opacity: 0;
  transform: translate(-50%, -50%) translateY(14px);
}
html.js.anim-ready .body-art .ev-name:not(.is-in) {
  opacity: 0;
  transform: translateY(14px); /* .ev-name has no centre-anchor (top-left positioned) */
}
/* script titles: longer, softer reveal with a small blur-out */
html.js.anim-ready .body-art .btitle:not(.is-in) {
  opacity: 0;
  transform: translate(-50%, -50%) translateY(14px);
  filter: blur(2px);
}
html.js.anim-ready .body-art .bline.is-in:not(.pp-coin):not(.pp-plate),
html.js.anim-ready .body-art .ev-name.is-in,
html.js.anim-ready .body-art .btitle.is-in {
  transition: opacity var(--reveal-dur) var(--reveal-ease) var(--d, 0ms),
    transform var(--reveal-dur) var(--reveal-ease) var(--d, 0ms),
    filter var(--reveal-dur) var(--reveal-ease) var(--d, 0ms);
}
html.js.anim-ready .body-art .btitle.is-in {
  transition-duration: 900ms;
}

/* multi-line blocks: the block fades as a unit while its .ln children rise in cascade */
html.js.anim-ready .body-art .bpara:not(.is-in),
html.js.anim-ready .body-art .ev-lines:not(.is-in) {
  opacity: 0;
}
html.js.anim-ready .body-art .bpara.is-in,
html.js.anim-ready .body-art .ev-lines.is-in {
  transition: opacity var(--reveal-dur) var(--reveal-ease) var(--d, 0ms);
}
html.js.anim-ready .body-art .bpara:not(.is-in) .ln,
html.js.anim-ready .body-art .ev-lines:not(.is-in) .ln {
  transform: translateY(12px);
}
html.js.anim-ready .body-art .bpara.is-in .ln,
html.js.anim-ready .body-art .ev-lines.is-in .ln {
  transform: translateY(0);
  transition: transform var(--reveal-dur) var(--reveal-ease);
}
/* per-line cascade (~60ms), stacked on the block's own --d, capped so long blocks
   (e.g. the family list) don't drift far behind the block fade */
html.js.anim-ready .body-art .is-in .ln:nth-child(1) { transition-delay: calc(var(--d, 0ms) + 0ms); }
html.js.anim-ready .body-art .is-in .ln:nth-child(2) { transition-delay: calc(var(--d, 0ms) + 60ms); }
html.js.anim-ready .body-art .is-in .ln:nth-child(3) { transition-delay: calc(var(--d, 0ms) + 120ms); }
html.js.anim-ready .body-art .is-in .ln:nth-child(4) { transition-delay: calc(var(--d, 0ms) + 180ms); }
html.js.anim-ready .body-art .is-in .ln:nth-child(5) { transition-delay: calc(var(--d, 0ms) + 240ms); }
html.js.anim-ready .body-art .is-in .ln:nth-child(6) { transition-delay: calc(var(--d, 0ms) + 300ms); }
html.js.anim-ready .body-art .is-in .ln:nth-child(7) { transition-delay: calc(var(--d, 0ms) + 360ms); }
html.js.anim-ready .body-art .is-in .ln:nth-child(8) { transition-delay: calc(var(--d, 0ms) + 420ms); }
html.js.anim-ready .body-art .is-in .ln:nth-child(n + 9) { transition-delay: calc(var(--d, 0ms) + 480ms); }

/* ---- flower wrappers: fade + settle in from their edge --------------------- */
@keyframes pp-flower-in-left {
  from { opacity: 0; transform: translateX(-12px) scale(0.96); }
  to   { opacity: 1; transform: translateX(0) scale(1); }
}
@keyframes pp-flower-in-right {
  from { opacity: 0; transform: translateX(12px) scale(0.96); }
  to   { opacity: 1; transform: translateX(0) scale(1); }
}
html.js.anim-ready .body-art .ly--cut[data-layer^="flower-"]:not(.is-in) {
  opacity: 0;
}
html.js.anim-ready .body-art .ly--cut[data-layer^="flower-"][data-layer$="-left"].is-in {
  animation: pp-flower-in-left var(--reveal-dur) var(--reveal-ease) both;
}
html.js.anim-ready .body-art .ly--cut[data-layer^="flower-"][data-layer$="-right"].is-in {
  animation: pp-flower-in-right var(--reveal-dur) var(--reveal-ease) both;
}

/* ===========================================================================
 * PARROTS — the signature moment. On `.is-in` the inner img settles in from a
 * slight scale-down with a small rotate, then chains an ambient bob around
 * identity. The settle ends at identity and the bob starts at identity, so
 * the handoff is seamless.
 *
 * In-box settle by design: any entrance transform that pushes the img's
 * painted pixels outside its own resting box causes a stale-compositor-raster
 * artifact in some renderers (Safari/Retina, throttled tabs) — the layer is
 * rasterized at resting bounds, so the portion outside those bounds paints as
 * blank, sheared at a clean line right at the box edge. A raised/translated
 * pose also risks overlapping neighboring art mid-flight. So the parrots
 * settle in place via scale + rotate only: they start slightly shrunk
 * (scale(0.9), fully inside the resting box) and grow to identity, with a
 * transform-origin near the bird's body so wing tips travel minimally. One
 * shared keyframe covers all 4 parrots since their motion is now uniform (a
 * bird materializing into place, not swooping in).
 * ======================================================================== */
@keyframes pp-glide-in {
  0%   { opacity: 0; transform: scale(0.9) rotate(-3deg); }
  30%  { opacity: 1; }
  65%  { transform: scale(1) rotate(1deg); }
  100% { opacity: 1; transform: none; }
}
/* symmetric bob around identity; 0% == 100% == identity so it loops (and hands off
   from the glide) seamlessly. `infinite` (not alternate) to get a true +/- oscillation. */
@keyframes pp-bob {
  0%   { transform: translateY(0) rotate(0deg); }
  25%  { transform: translateY(-3px) rotate(0.8deg); }
  50%  { transform: translateY(0) rotate(0deg); }
  75%  { transform: translateY(3px) rotate(-0.8deg); }
  100% { transform: translateY(0) rotate(0deg); }
}

/* inner parrot img hidden until the wrapper is revealed */
html.js.anim-ready .body-art .ly--cut[data-layer^="parrot-"]:not(.is-in) .ly-in {
  opacity: 0;
}
.ly--cut[data-layer^="parrot-"] .ly-in {
  will-change: transform, opacity; /* keeps: the bob loops after the glide */
  transform-origin: 50% 60%; /* pivot near the bird's body so wing tips travel minimally */
}
html.js.anim-ready .body-art .ly--cut[data-layer^="parrot-"].is-in .ly-in {
  animation: pp-glide-in var(--glide-dur) var(--reveal-ease) both,
    pp-bob var(--bob-dur) ease-in-out var(--glide-dur) infinite;
}

/* ===========================================================================
 * REDUCED MOTION — final states, no loops, no glide. Content fully readable.
 * ======================================================================== */
@media (prefers-reduced-motion: reduce) {
  /* hero: show at rest, no entrance animation */
  html.js .hero-text .mantra,
  html.js .hero-text .para,
  html.js .hero-text .cname,
  html.js .hero-flowers .ly--flower {
    opacity: 1 !important;
    animation: none !important;
    filter: none;
    will-change: auto;
  }
  /* night names keep their static glow (hero.css supplies the filter) */
  html.js .hero-text--night .cname {
    filter: drop-shadow(0 1px 1px rgba(4, 7, 30, 0.55))
      drop-shadow(0 0 7px rgba(232, 214, 184, 0.14));
  }

  /* all ambient loops off */
  .hero-flowers .ly--flower .ly-in,
  .ly--cut[data-layer^="flower-"] .ly-in,
  .ly--cut[data-layer^="parrot-"] .ly-in {
    animation: none !important;
    transform: none !important;
    will-change: auto;
  }

  /* body: reveals snap to rest (js/animations.js also adds .is-in to all targets) */
  html.js.anim-ready .body-art .bline:not(.pp-coin):not(.pp-plate),
  html.js.anim-ready .body-art .btitle,
  html.js.anim-ready .body-art .ev-name,
  html.js.anim-ready .body-art .bpara,
  html.js.anim-ready .body-art .ev-lines,
  html.js.anim-ready .body-art .bpara .ln,
  html.js.anim-ready .body-art .ev-lines .ln {
    filter: none;
    will-change: auto;
  }
  html.js.anim-ready .body-art .ly--cut[data-layer^="flower-"],
  html.js.anim-ready .body-art .ly--cut[data-layer^="parrot-"] {
    opacity: 1 !important;
    animation: none !important;
  }
  html.js.anim-ready .body-art .ly--cut[data-layer^="parrot-"] .ly-in {
    opacity: 1 !important;
  }
}
