/* ═══════════════════════════════════════════
   the-invitation — "The wedding of" (pages.md, `the-invitation`)
   One 100dvh page over the garden: hanging decor above, the couple below,
   the formal sentence between them.

   Lifecycle (screens.md, "Card contract"; written by app.js unless noted):
     data-sv-state  pending -> entering -> done   (entrance only under
                    entering + data-sv-ready; done = end state, no animation)
     data-sv-ready  written by js/the-invitation.js (decode, or 1500 ms after settle)
     data-sv-view   on | near | far              (loops run only under on)
     --d            page top - viewport top, unitless px (parallax)
   Card attributes read here: #sv-card[data-sv-order], #sv-card[data-sv-emblem].
   Refit vars written by js/the-invitation.js: --fit, --the-invitation-name-scale
   (on the text block), --the-invitation-drop and --the-invitation-decor-raise (on the page).
   Animates transform, opacity and clip-path only; no canvas, no particles.
   ═══════════════════════════════════════════ */

/* ───── The page (snap area) ───── */
.the-invitation-container {
    position: relative;
    height: 100dvh;
    overflow: clip;
    isolation: isolate;
    scroll-snap-align: start;
    scroll-snap-stop: always;
    background: var(--wash-garden);
    color: var(--ink);
}

.the-invitation-container[data-sv-view="far"] {
    content-visibility: auto;
    contain-intrinsic-size: auto 100dvh;
}

/* ───── Art layers (back -> front) ───── */
.the-invitation-art,
.the-invitation-loops {
    position: absolute;
    inset: 0;
    pointer-events: none;
}
.the-invitation-art { z-index: 0; }
.the-invitation-loops { z-index: 1; }

/* 1. wash + garden, rate 0.22 (lags). The gap a lagging layer opens always
      sits outside the viewport, so the layer needs no overscan. */
.the-invitation-backdrop {
    position: absolute;
    inset: 0;
    background: var(--wash-garden);
    transform: translate3d(0, calc(var(--d, 0) * var(--px-backdrop) * -1px), 0);
}
.the-invitation-garden {
    position: absolute;
    inset: 0;
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: 50% 100%;
    transform-origin: 50% 60%;
}

/* 2. cream lift behind the text, rate 0 */
.the-invitation-lift {
    position: absolute;
    inset: 0;
    background: var(--lift-cream);
}

/* 3. hanging decor, two halves, rate -0.06 (leads a touch). --the-invitation-decor-raise
      (refit, <= 0) hangs it higher on wider-aspect pages so the bells clear the kicker. */
.the-invitation-decor {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    aspect-ratio: 1080 / 463;
    transform: translate3d(0, calc(var(--the-invitation-decor-raise, 0px) + var(--d, 0) * var(--px-hang) * -1px), 0);
}
.the-invitation-half,
.the-invitation-sway {
    position: absolute;
    inset: 0;
}
.the-invitation-half--left,
.the-invitation-sway--left { transform-origin: 25% 0; }
.the-invitation-half--right,
.the-invitation-sway--right { transform-origin: 75% 0; }

.the-invitation-decor-img {
    display: block;
    width: 100%;
    height: 100%;
}
.the-invitation-decor-img--left { clip-path: inset(0 50% 0 0); }
.the-invitation-decor-img--right { clip-path: inset(0 0 0 50%); }

/* 4. the couple, rate -0.04, plus the refit's drop (up to 8% of the page) */
.the-invitation-figure {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 59%;
    display: flex;
    justify-content: center;
    align-items: flex-end;
    transform: translate3d(0, calc(var(--the-invitation-drop, 0px) + var(--d, 0) * var(--px-figure) * -1px), 0);
}
.the-invitation-couple {
    display: block;
    flex: none;
    height: 100%;
    width: auto;
    aspect-ratio: 667 / 975;      /* bottom: 0; the art's own margin puts the feet ~2% above the edge */
}
@media (max-height: 679.98px) {
    .the-invitation-figure { height: 52%; }
}

/* Promote the moving layers only while the page is on screen. */
.the-invitation-container[data-sv-view="on"] :is(.the-invitation-backdrop, .the-invitation-decor, .the-invitation-figure) {
    will-change: transform;
}

/* ───── Text block (column --col-scene, 14% -> <= 46%, rate 0) ─────
   --fit is stepped 1 -> --fit-floor by the JS refit. */
.the-invitation-text {
    --fit: 1;
    position: absolute;
    z-index: 2;
    top: 14%;
    left: 0;
    right: 0;
    width: var(--col-scene);
    margin-inline: auto;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: calc(5px * var(--fit));
    text-align: center;
    color: var(--ink);
}
.the-invitation-text p { margin: 0; }
.the-invitation-text .the-invitation-script { margin: -0.16em 0; }

.the-invitation-emblem {
    order: 1;
    display: block;
    width: calc(28px * var(--fit));
    height: calc(28px * var(--fit));
    object-fit: contain;
    flex: none;
}
#sv-card[data-sv-emblem="none"] .the-invitation-emblem { display: none; }

.the-invitation-kicker {
    order: 2;
    width: 100%;
    font-family: var(--font-display);
    font-weight: 600;
    font-size: calc(var(--type-kicker) * var(--fit));
    line-height: 1.28;
    letter-spacing: var(--tracking-kicker);
    text-transform: uppercase;
    text-wrap: balance;
    color: var(--ink);
}

/* The script's swashes overhang its line box: pad the box (so the write-on
   clip never cuts a stroke) and take the padding back with a negative margin. */
.the-invitation-script {
    order: 3;
    padding: 0.16em 0.3em;
    font-family: var(--font-script);
    font-weight: 400;
    font-size: calc(var(--type-script-hero) * var(--fit));
    line-height: 1.05;
    white-space: nowrap;
    color: var(--ink-script);
}

.the-invitation-connector {
    font-family: var(--font-body);
    font-weight: 600;
    font-style: italic;
    font-size: calc(var(--type-connector) * var(--fit));
    line-height: 1.15;
    color: var(--ink);
}
.the-invitation-of { order: 4; }
.the-invitation-and { order: 6; }

/* Pairs: name + family travel together. Groom first unless the card says
   bride-first; "of" and "and" never move. --the-invitation-pair-off shifts
   the pair's stagger so the pair on top always arrives first. */
.the-invitation-pair {
    width: 100%;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: calc(2px * var(--fit));
}
.the-invitation-pair[data-sv-pair="groom"] { order: 5; --the-invitation-pair-off: 0ms; }
.the-invitation-pair[data-sv-pair="bride"] { order: 7; --the-invitation-pair-off: 450ms; }
#sv-card[data-sv-order="bride-first"] .the-invitation-pair[data-sv-pair="bride"] { order: 5; --the-invitation-pair-off: 0ms; }
#sv-card[data-sv-order="bride-first"] .the-invitation-pair[data-sv-pair="groom"] { order: 7; --the-invitation-pair-off: 450ms; }

.the-invitation-name {
    font-family: var(--font-display);
    font-weight: 700;
    font-size: max(1.375rem, calc(var(--type-name) * var(--fit) * var(--the-invitation-name-scale, 1)));
    line-height: 1.12;
    letter-spacing: var(--tracking-caps);
    white-space: nowrap;
    color: var(--ink-title);
}

.the-invitation-family {
    width: 100%;
    font-family: var(--font-body);
    font-weight: 600;
    font-size: calc(var(--type-detail) * var(--fit));
    line-height: var(--leading-detail);
    text-wrap: balance;
    color: var(--ink);
}
.the-invitation-family:empty { display: none; }

/* No line clamp anywhere: the refit keeps the kicker and each family line to 2 lines
   by fitting, so the buyer's words are never cut. Its squeeze closes the gaps. */
.the-invitation-text[data-the-invitation-squeeze] { gap: 0; }
.the-invitation-text[data-the-invitation-squeeze] .the-invitation-pair { gap: 0; }

/* ═══════════════════════════════════════════
   Entrance (pages.md steps 1-7). Pre-entrance state while pending, or while
   entering but not yet ready; plays once under entering + ready; done shows
   the end state because no rule here applies to it.
   ═══════════════════════════════════════════ */
.the-invitation-container:is([data-sv-state="pending"], [data-sv-state="entering"]:not([data-sv-ready])) .the-invitation-garden {
    transform: scale(1.04);
}
.the-invitation-container:is([data-sv-state="pending"], [data-sv-state="entering"]:not([data-sv-ready])) .the-invitation-half {
    transform: translateY(-104%);
}
.the-invitation-container:is([data-sv-state="pending"], [data-sv-state="entering"]:not([data-sv-ready])) :is(
    .the-invitation-emblem,
    .the-invitation-kicker,
    .the-invitation-script,
    .the-invitation-connector,
    .the-invitation-name,
    .the-invitation-family,
    .the-invitation-couple) {
    opacity: 0;
}

.the-invitation-container[data-sv-state="entering"][data-sv-ready] .the-invitation-garden {
    will-change: transform;
    animation: the-invitation-settle-in 1400ms var(--ease-expo-out) 0ms both;
}
.the-invitation-container[data-sv-state="entering"][data-sv-ready] .the-invitation-half {
    animation: the-invitation-drop-settle var(--dur-drop) var(--ease-expo-out) var(--the-invitation-at, 0ms) both;
}
.the-invitation-half--left { --the-invitation-at: 0ms; }
.the-invitation-half--right { --the-invitation-at: 120ms; }

.the-invitation-container[data-sv-state="entering"][data-sv-ready] .the-invitation-emblem {
    animation: the-invitation-pop 400ms var(--ease-expo-out) 200ms both;
}
.the-invitation-container[data-sv-state="entering"][data-sv-ready] .the-invitation-kicker {
    animation: the-invitation-fade-up 500ms var(--ease-expo-out) 350ms both;
}
.the-invitation-container[data-sv-state="entering"][data-sv-ready] .the-invitation-script {
    animation: the-invitation-write 1000ms var(--ease-silk) 700ms both;
}
.the-invitation-container[data-sv-state="entering"][data-sv-ready] .the-invitation-couple {
    animation: the-invitation-couple-in 1000ms var(--ease-expo-out) 1200ms both;
}
/* "of", first name, first family, "and", second name, second family: 150 ms apart from 1500 ms */
.the-invitation-container[data-sv-state="entering"][data-sv-ready] .the-invitation-of {
    animation: the-invitation-fade-up 450ms var(--ease-expo-out) 1500ms both;
}
.the-invitation-container[data-sv-state="entering"][data-sv-ready] .the-invitation-and {
    animation: the-invitation-fade-up 450ms var(--ease-expo-out) 1950ms both;
}
.the-invitation-container[data-sv-state="entering"][data-sv-ready] .the-invitation-name {
    animation: the-invitation-fade-up 450ms var(--ease-expo-out) calc(1650ms + var(--the-invitation-pair-off, 0ms)) both;
}
.the-invitation-container[data-sv-state="entering"][data-sv-ready] .the-invitation-family {
    animation: the-invitation-fade-up 450ms var(--ease-expo-out) calc(1800ms + var(--the-invitation-pair-off, 0ms)) both;
}

@keyframes the-invitation-settle-in {
    from { transform: scale(1.04); }
    to { transform: scale(1); }
}

/* sv-drop-settle (concept.md): expo-out to the hook, then a damped swing on `settle`. */
@keyframes the-invitation-drop-settle {
    0% { transform: translateY(-104%) rotate(0deg); animation-timing-function: var(--ease-expo-out); }
    52% { transform: translateY(0) rotate(1.6deg); animation-timing-function: var(--ease-settle); }
    70% { transform: translateY(0) rotate(-0.9deg); animation-timing-function: var(--ease-settle); }
    84% { transform: translateY(0) rotate(0.4deg); animation-timing-function: var(--ease-settle); }
    100% { transform: translateY(0) rotate(0deg); }
}

@keyframes the-invitation-pop {
    from { opacity: 0; transform: scale(0.9); }
    to { opacity: 1; transform: scale(1); }
}

@keyframes the-invitation-fade-up {
    from { opacity: 0; transform: translateY(10px); }
    to { opacity: 1; transform: translateY(0); }
}

/* Write-on: the wipe runs the full duration; opacity lands in the first 150 ms. */
@keyframes the-invitation-write {
    0% { opacity: 0; clip-path: inset(0 100% 0 0); }
    15% { opacity: 1; }
    100% { opacity: 1; clip-path: inset(0 0 0 0); }
}

@keyframes the-invitation-couple-in {
    from { opacity: 0; transform: translateY(4%); }
    to { opacity: 1; transform: translateY(0); }
}

@keyframes the-invitation-fade {
    from { opacity: 0; }
    to { opacity: 1; }
}

/* ═══════════════════════════════════════════
   Idle loops: declared paused, running only while the page is on screen.
   ═══════════════════════════════════════════ */

/* Decor halves breathe out of step: ±0.7°, full periods 6.0 s / 7.2 s. */
.the-invitation-sway--left {
    animation: the-invitation-sway 3000ms var(--ease-breath) 0ms infinite alternate both;
    animation-play-state: paused;
}
.the-invitation-sway--right {
    animation: the-invitation-sway 3600ms var(--ease-breath) -1200ms infinite alternate both;
    animation-play-state: paused;
}

/* Butterflies (blue, coral): a waypoint flight on the wrapper, a wing beat on the image. */
.the-invitation-flight {
    position: absolute;
    animation-play-state: paused;
}
.the-invitation-flight--a {
    left: 6%;
    top: 71%;
    width: 30px;
    animation: the-invitation-fly-a 12s var(--ease-drift) -3s infinite both;
    animation-play-state: paused;
}
.the-invitation-flight--b {
    left: 80%;
    top: 76%;
    width: 26px;
    animation: the-invitation-fly-b 13.5s var(--ease-drift) -7s infinite both;
    animation-play-state: paused;
}
.the-invitation-wing {
    display: block;
    width: 100%;
    height: auto;
    aspect-ratio: 128 / 95;
}
.the-invitation-wing--a {
    animation: the-invitation-wing 300ms var(--ease-breath) 0ms infinite both;
    animation-play-state: paused;
}
.the-invitation-wing--b {
    animation: the-invitation-wing 320ms var(--ease-breath) -140ms infinite both;
    animation-play-state: paused;
}

/* Petals (bougainvillea, blush): 2-stop radial ellipses falling in the outer bands. */
.the-invitation-loops {
    --the-invitation-bloom: rgba(200, 50, 122, 1);
    --the-invitation-bloom-hi: rgba(226, 104, 162, 1);
    --the-invitation-blush: rgba(242, 184, 181, 1);
    --the-invitation-blush-hi: rgba(252, 222, 218, 1);
}
.the-invitation-petal {
    position: absolute;
    top: -4%;
    width: 10px;
    height: 13px;
    border-radius: 50% 50% 50% 50% / 60% 60% 40% 40%;
    background: radial-gradient(circle at 35% 30%, var(--the-invitation-bloom-hi) 0%, var(--the-invitation-bloom) 72%);
    opacity: 0;
}
.the-invitation-petal--1 {
    left: 5%;
    animation: the-invitation-petal-fall-a 10s var(--ease-drift) -2s infinite both;
    animation-play-state: paused;
}
.the-invitation-petal--2 {
    left: 89%;
    width: 8px;
    height: 11px;
    background: radial-gradient(circle at 35% 30%, var(--the-invitation-blush-hi) 0%, var(--the-invitation-blush) 72%);
    animation: the-invitation-petal-fall-b 12s var(--ease-drift) -6s infinite both;
    animation-play-state: paused;
}
.the-invitation-petal--3 {
    left: 13%;
    width: 8px;
    height: 11px;
    background: radial-gradient(circle at 35% 30%, var(--the-invitation-blush-hi) 0%, var(--the-invitation-blush) 72%);
    animation: the-invitation-petal-fall-b 13.5s var(--ease-drift) -9s infinite both;
    animation-play-state: paused;
}
.the-invitation-petal--4 {
    left: 93%;
    width: 11px;
    height: 14px;
    animation: the-invitation-petal-fall-a 9s var(--ease-drift) -4.5s infinite both;
    animation-play-state: paused;
}
.the-invitation-petal--5 {
    left: 84%;
    animation: the-invitation-petal-fall-a 14s var(--ease-drift) -11s infinite both;
    animation-play-state: paused;
}

/* Sparkles: _shared/effects/sparkles.css (light variant) owns the look and the
   keyframes; this only sets the durations (3-4 s) and the pause. */
.the-invitation-sparkles {
    position: absolute;
    inset: 0;
}
#the-invitation-page .the-invitation-spark { animation-play-state: paused; }
#the-invitation-page .the-invitation-spark--1 { animation-duration: 3s; }
#the-invitation-page .the-invitation-spark--2 { animation-duration: 3.4s; }
#the-invitation-page .the-invitation-spark--3 { animation-duration: 3.8s; }
#the-invitation-page .the-invitation-spark--4 { animation-duration: 3.2s; }
#the-invitation-page .the-invitation-spark--5 { animation-duration: 4s; }
#the-invitation-page .the-invitation-spark--6 { animation-duration: 3.6s; }

/* On screen: run every loop. */
#the-invitation-page[data-sv-view="on"] :is(
    .the-invitation-sway,
    .the-invitation-flight,
    .the-invitation-wing,
    .the-invitation-petal,
    .the-invitation-spark) {
    animation-play-state: running;
}

/* Far: loop layers leave the render tree. */
.the-invitation-container[data-sv-view="far"] .the-invitation-loops { display: none; }

@keyframes the-invitation-sway {
    from { transform: rotate(-0.7deg); }
    to { transform: rotate(0.7deg); }
}

@keyframes the-invitation-wing {
    0%, 100% { transform: scaleX(1); }
    50% { transform: scaleX(0.32); }
}

/* Flight paths stay in y 66-96% and off the text column (decor rule, concept.md). */
@keyframes the-invitation-fly-a {
    0% { transform: translate(0, 0) rotate(-8deg); }
    20% { transform: translate(14cqi, -4dvh) rotate(12deg); }
    42% { transform: translate(22cqi, 5dvh) rotate(24deg); }
    63% { transform: translate(10cqi, 14dvh) rotate(-14deg); }
    82% { transform: translate(2cqi, 7dvh) rotate(-20deg); }
    100% { transform: translate(0, 0) rotate(-8deg); }
}

@keyframes the-invitation-fly-b {
    0% { transform: translate(0, 0) rotate(10deg); }
    22% { transform: translate(-13cqi, 5dvh) rotate(-16deg); }
    45% { transform: translate(-5cqi, 15dvh) rotate(-6deg); }
    68% { transform: translate(8cqi, 9dvh) rotate(18deg); }
    86% { transform: translate(4cqi, -6dvh) rotate(8deg); }
    100% { transform: translate(0, 0) rotate(10deg); }
}

/* ±4% sway while falling the full page. Mirrored pair so neighbours never move in step. */
@keyframes the-invitation-petal-fall-a {
    0% { opacity: 0; transform: translate(0, 0) rotate(0deg); }
    8% { opacity: 0.95; }
    25% { transform: translate(4cqi, 27dvh) rotate(80deg); }
    50% { transform: translate(0, 54dvh) rotate(170deg); }
    75% { transform: translate(-4cqi, 81dvh) rotate(250deg); }
    92% { opacity: 0.95; }
    100% { opacity: 0; transform: translate(0, 108dvh) rotate(330deg); }
}

@keyframes the-invitation-petal-fall-b {
    0% { opacity: 0; transform: translate(0, 0) rotate(0deg); }
    8% { opacity: 0.95; }
    25% { transform: translate(-4cqi, 27dvh) rotate(-70deg); }
    50% { transform: translate(0, 54dvh) rotate(-160deg); }
    75% { transform: translate(4cqi, 81dvh) rotate(-240deg); }
    92% { opacity: 0.95; }
    100% { opacity: 0; transform: translate(0, 108dvh) rotate(-320deg); }
}

/* ═══════════════════════════════════════════
   Reduced motion (pages.md): garden and decor in place; every text line and
   the couple fade in, 400 ms, 150 ms stagger; no loops. (tokens.css already
   zeroes the parallax rates.)
   ═══════════════════════════════════════════ */
@media (prefers-reduced-motion: reduce) {
    .the-invitation-container:is([data-sv-state="pending"], [data-sv-state="entering"]:not([data-sv-ready])) .the-invitation-garden,
    .the-invitation-container:is([data-sv-state="pending"], [data-sv-state="entering"]:not([data-sv-ready])) .the-invitation-half {
        transform: none;
    }
    .the-invitation-container[data-sv-state="entering"][data-sv-ready] .the-invitation-garden,
    .the-invitation-container[data-sv-state="entering"][data-sv-ready] .the-invitation-half {
        animation: none;
        will-change: auto;
    }

    .the-invitation-emblem { --the-invitation-rm-at: 0ms; }
    .the-invitation-kicker { --the-invitation-rm-at: 150ms; }
    .the-invitation-script { --the-invitation-rm-at: 300ms; }
    .the-invitation-couple { --the-invitation-rm-at: 450ms; }
    .the-invitation-of { --the-invitation-rm-at: 600ms; }
    .the-invitation-and { --the-invitation-rm-at: 1050ms; }
    .the-invitation-pair .the-invitation-name { --the-invitation-rm-at: calc(750ms + var(--the-invitation-pair-off, 0ms)); }
    .the-invitation-pair .the-invitation-family { --the-invitation-rm-at: calc(900ms + var(--the-invitation-pair-off, 0ms)); }

    .the-invitation-container[data-sv-state="entering"][data-sv-ready] :is(
        .the-invitation-emblem,
        .the-invitation-kicker,
        .the-invitation-script,
        .the-invitation-connector,
        .the-invitation-name,
        .the-invitation-family,
        .the-invitation-couple) {
        animation: the-invitation-fade 400ms var(--ease-silk) var(--the-invitation-rm-at, 0ms) both;
    }

    .the-invitation-sway { animation: none; }
    .the-invitation-container .the-invitation-loops { display: none; }
    .the-invitation-container[data-sv-view="on"] :is(.the-invitation-backdrop, .the-invitation-decor, .the-invitation-figure) {
        will-change: auto;
    }
}
