/* Wedding RSVP — full-screen pages.

   Every section of the invitation owns exactly one screen. The browser settles
   on one page at a time (CSS scroll snap), each page carries its own
   photograph sized to fill a phone screen rather than a short content-height
   strip, and the content transitions in as a page arrives and back out as it
   leaves. Loaded last, so it wins over style.css and sections.css. */

/* ── The scroll container ──
   Snap positions attach to the nearest ancestor scroll container. `overflow-x`
   on <body> would make body one, and every page below would attach to it
   instead of the viewport — nothing would snap at all. Only <html> carries the
   horizontal clip, which propagates to the viewport, and the snap type
   propagates with it. */
html {
    scroll-snap-type: y proximity;
}
/* On a handset held upright a page really is a page: one swipe, one screen.
   Only where a screen is tall enough to hold one — a phone on its side has no
   room, and forcing snap positions there would fight the guest. */
@media (max-width: 900px) and (min-height: 560px) and (orientation: portrait) {
    html { scroll-snap-type: y mandatory; }
}

/* ── A page ── */

.page {
    position: relative;
    min-height: 100svh;
    min-height: 100dvh;
    display: flex;
    flex-direction: column;
    justify-content: center;
    padding-top: max(30px, env(safe-area-inset-top, 0px));
    padding-bottom: max(30px, env(safe-area-inset-bottom, 0px));
    scroll-snap-align: start;
    scroll-snap-stop: always;
}

/* display:flex above would otherwise beat the UA sheet's display:none, and
   every empty section would appear as a blank screen. */
.page[hidden] { display: none; }

/* Content is centred in whatever room is left above the footer. `margin: auto`
   (not `auto 0`) keeps the horizontal centring the bands already rely on. */
.page > .page-inner {
    position: relative;
    z-index: 2;
    width: 100%;
    margin: auto;
}

/* A page whose content genuinely does not fit — a long gallery, a long story —
   keeps its snap position so mandatory snapping still allows free scrolling
   inside it, but stops demanding a separate swipe to pass. Set from script,
   which measures; CSS cannot. */
.page.page-tall {
    justify-content: flex-start;
    scroll-snap-stop: normal;
}

/* Bands and the RSVP step carry their own padding shorthands, which outrank
   .page — restate the vertical padding at a specificity that wins. */
.band.page,
.rsvp-step.page,
.rsvp-step.page.has-bg {
    padding-top: max(30px, env(safe-area-inset-top, 0px));
    padding-bottom: max(30px, env(safe-area-inset-bottom, 0px));
}
.band.page.page-tall,
.rsvp-step.page.page-tall { padding-top: 44px; padding-bottom: 44px; }

/* ── The photograph ──
   Its own layer, so it can settle into place as the page arrives without
   moving the text. `cover` against a full screen is what makes a landscape
   photo read properly on a portrait handset; `fixed` attachment is deliberately
   avoided — it is janky or ignored on iOS. */
.page.has-bg { overflow: hidden; }

.page-bg {
    position: absolute;
    inset: 0;
    z-index: 0;
    background-size: cover;
    background-position: center center;
    background-repeat: no-repeat;
    background-attachment: scroll;
    background-color: #17150F;
}

/* Scrim: enough to read white text on any photograph, light enough to still
   see the picture. Sits above the photo, below the content. */
.band.has-bg::before,
.rsvp-step.has-bg::before {
    z-index: 1;
    background: linear-gradient(180deg,
        rgba(12, 10, 7, .58) 0%,
        rgba(12, 10, 7, .40) 38%,
        rgba(12, 10, 7, .66) 100%);
}

/* Text that sits directly on a photograph rather than on a white card. */
.band.has-bg .band-title,
.band.has-bg .closing-title,
.band.has-bg .closing-names,
.band.has-bg .verse-text,
.band.has-bg h2 { color: #FFFFFF; }
.band.has-bg .band-sub,
.band.has-bg .gift-address,
.band.has-bg .details-intro { color: rgba(255, 255, 255, .86); }
.band.has-bg .verse-ref,
.band.has-bg .step-tag,
.band.has-bg .closing-names .amp { color: #E7DFC9; }
.band.has-bg .invite-footer,
.rsvp-step.has-bg .invite-footer { color: rgba(255, 255, 255, .72); }

/* ── Page transition ──
   The reveal classes in sections.css do the animating; here they are driven by
   the page rather than by each element, so a whole screen arrives together and
   leaves again behind you. Nothing is hidden unless script has confirmed it can
   show it again (html.js-reveal).

   The photograph itself does not move: it held a slow zoom as each page
   arrived, and that reads as the picture being restless. The gallery keeps its
   own zoom — tiles growing in, and the viewer — which is a different thing. */

/* A light stagger so a title does not arrive at the same instant as the cards
   under it. */
html.js-reveal .page.page-in .page-inner > *:nth-child(2) { transition-delay: .09s; }
html.js-reveal .page.page-in .page-inner > *:nth-child(3) { transition-delay: .18s; }
html.js-reveal .page:not(.page-in) .page-inner > * { transition-delay: 0s; }

/* ── Fitting the existing sections into the page system ── */

.invite { padding-bottom: 0; }
.details-section { margin-top: 0; }
.details-head { margin-bottom: 0; }
#rsvp, #details-section { scroll-margin-top: 0; }

/* The hero is a fixed cover panel once script takes over; only when it is a
   plain section (no JavaScript) is it part of the scroll. */
html:not(.js-cover) .hero-full { scroll-snap-align: start; }

/* The footer belongs to whichever page is currently last: the RSVP screen
   before anything is unlocked, the closing screen afterwards. */
.page > .invite-footer {
    position: relative;
    z-index: 2;
    flex: 0 0 auto;
    padding: 16px 0 2px;
}
html.details-open .rsvp-step > .invite-footer { display: none; }

/* Timeline and dress-code screens read better narrow. */
.page-narrow .page-inner { max-width: 560px; }

/* Celebration day: guests are already at the venue, so photo sharing is the
   first screen after the header rather than the eighth. */
.event-day .details-section { display: flex; flex-direction: column; }
.event-day .photos-band { order: -1; }

/* ── Gallery: three drifting rows on a handset ──
   Twenty-one thumbnails in a column is four screens tall, and a single swipe
   strip only ever shows one picture. Three rows that drift on their own, in a
   different random order every visit, show the whole set inside one screen and
   still open the full viewer on a tap. */

@media (max-width: 700px) {
    :root { --gal-row-h: clamp(112px, 19.5vh, 178px); }

    #gallery-grid.gal-marquee {
        display: flex;
        flex-direction: column;
        gap: 10px;
        overflow: hidden;
        /* Run the rows to both screen edges: a picture cut by the edge is what
           tells the eye there are more of them. */
        margin-left: calc(var(--gutter) * -1);
        margin-right: calc(var(--gutter) * -1);
        padding: 0;
    }
    .gal-row { overflow: hidden; }
    .gal-track {
        display: flex;
        width: max-content;
        animation: gal-drift 40s linear infinite;
        will-change: transform;
    }
    /* The middle row travels the other way, which reads as depth rather than
       three copies of the same movement. */
    .gal-row-rev .gal-track { animation-direction: reverse; }
    /* Hold still while a finger is down, so a tap lands on the photo it aimed at. */
    .gal-row:active .gal-track,
    .gal-row:hover .gal-track { animation-play-state: paused; }

    .gal-marquee .gal-item {
        flex: 0 0 auto;
        height: var(--gal-row-h);
        width: calc(var(--gal-row-h) * .76);
        aspect-ratio: auto;
        /* margin, not gap: the track slides exactly half its width, and a gap
           between the two copies would leave a visible jump at the loop. */
        margin-right: 10px;
        border-radius: 10px;
    }
}

/* The track holds two identical copies, so sliding one copy's width and
   starting over has no seam. */
@keyframes gal-drift {
    from { transform: translateX(0); }
    to   { transform: translateX(-50%); }
}

@media (prefers-reduced-motion: reduce) {
    /* Standing still, the rows become ordinary swipe strips — and the second
       copy of each row would just show every picture twice. */
    .gal-track { animation: none; }
    .gal-track .gal-item[aria-hidden="true"] { display: none; }
    .gal-row { overflow-x: auto; scrollbar-width: none; }
    .gal-row::-webkit-scrollbar { display: none; }
}

.gallery-hint { display: none; }
@media (max-width: 700px) {
    .gallery-hint {
        display: block;
        text-align: center;
        margin-top: 16px;
        font-size: 11px;
        letter-spacing: .2em;
        text-transform: uppercase;
        color: var(--muted);
    }
    .band.has-bg .gallery-hint { color: rgba(255, 255, 255, .7); }
}

/* Tablets keep the grid, but a four-column grid of twenty-one pictures is a
   screen and a half — tighten it so the gallery is still one screen. */
@media (min-width: 701px) and (max-width: 900px) {
    .gallery-grid { grid-template-columns: repeat(auto-fill, minmax(104px, 1fr)); gap: 8px; }
}

/* ── Mobile: undo the two-screen layout the page system now owns ── */

@media (max-width: 640px) {
    .screen-two { display: block; min-height: 0; }
    html.details-open .details-section,
    .details-section { min-height: 0; margin-top: 0; padding-top: 0; }

    /* One card per screen: give the venue screen room by keeping the map short. */
    .map-block, .map-embed { min-height: 190px; }
    .details-grid { gap: 14px; }
    .detail-block { padding: 20px 18px; }
    .detail-block h3 { font-size: 24px; }
    .band-title { font-size: 28px; }
    .verse-text { font-size: 19px; }
    .swatches { gap: 12px 16px; }
    .swatches li { width: 62px; }
    .story-card { padding: 18px 20px; }
    .closing-title { font-size: 30px; }
}

/* Short handsets: trim again so a screen stays a screen. The couple screen is
   the tight one — two stacked cards, each with a portrait. */
@media (max-width: 640px) and (max-height: 760px) {
    .map-block, .map-embed { min-height: 150px; }
    .band-title { font-size: 25px; }
    .verse-text { font-size: 17.5px; }
    .detail-block { padding: 16px 15px; }
    .gal-item { flex-basis: 68%; }
    .couple-grid { gap: 12px; }
    .couple-card { padding: 16px 14px; }
    .couple-photo { width: 84px; height: 84px; margin-bottom: 10px; }
    .couple-name { font-size: 24px; }
    .couple-parents { margin-top: 10px; font-size: 13px; }
}

/* Older 568px-tall handsets. Anything still over one screen after this keeps
   its page-tall fallback and simply scrolls. */
@media (max-width: 640px) and (max-height: 620px) {
    .page, .band.page, .rsvp-step.page, .rsvp-step.page.has-bg {
        padding-top: max(18px, env(safe-area-inset-top, 0px));
        padding-bottom: max(18px, env(safe-area-inset-bottom, 0px));
    }
    .band-title { font-size: 22px; }
    .couple-photo { width: 68px; height: 68px; margin-bottom: 8px; }
    .couple-card { padding: 12px; }
    .couple-name { font-size: 21px; }
    .couple-parents { font-size: 12px; margin-top: 8px; }
    .map-block, .map-embed { min-height: 120px; }
    .venue-facts { margin: 10px 0 12px; }
}

/* ════════ Glass cards over a photograph ════════
   On a screen that carries a picture the cards stop being white boxes and
   become glass, so the photograph reads through them. Everything inside has to
   flip to light text at the same time — a card that goes dark while its text
   stays black is worse than no card at all, so this list is deliberately
   exhaustive rather than clever. Screens with no picture keep white cards. */

.has-bg .card {
    background: rgba(16, 13, 9, .26);
    border-color: rgba(255, 255, 255, .3);
    box-shadow: 0 16px 44px rgba(0, 0, 0, .28);
    /* No blur at all: the picture behind the card stays sharp. What keeps the
       text readable is the tint and the shadow below, not a frosted pane. */
    backdrop-filter: saturate(110%);
    -webkit-backdrop-filter: saturate(110%);
    color: #FFFFFF;
    text-shadow: 0 1px 10px rgba(0, 0, 0, .55);
}

/* Headings and the values people actually read */
.has-bg .card h2,
.has-bg .card h3,
.has-bg .card h4,
.has-bg .venue-name,
.has-bg .venue-facts dd,
.has-bg .couple-name,
.has-bg .gift-number { color: #FFFFFF; }

/* Supporting copy */
.has-bg .card p,
.has-bg .venue-addr,
.has-bg .couple-desc,
.has-bg .couple-parents,
.has-bg .timeline-list li,
.has-bg .dress-code,
.has-bg .photos-intro,
.has-bg .swatch-name,
.has-bg .gift-holder,
.has-bg .story-card p { color: rgba(255, 255, 255, .84); }

/* Gold accents keep their role, one shade lighter so they hold on a photo */
.has-bg .venue-facts dt,
.has-bg .story-date,
.has-bg .gift-bank,
.has-bg .timeline-list strong,
.has-bg .couple-parents span { color: #E7DFC9; }

.has-bg .card .icon,
.has-bg .icon-gold,
.has-bg .icon-slate,
.has-bg .icon-ink {
    background: rgba(255, 255, 255, .16);
    color: #FFFFFF;
}
.has-bg .timeline-list li + li { border-top-color: rgba(255, 255, 255, .22); }
.has-bg .timeline-list li::before { background: #E7DFC9; }
.has-bg .couple-photo { border-color: rgba(255, 255, 255, .45); }
.has-bg .swatch-dot { border-color: rgba(255, 255, 255, .55); box-shadow: 0 2px 10px rgba(0, 0, 0, .3); }
.has-bg .map-block { background: rgba(255, 255, 255, .1); }

.has-bg .btn-outline {
    background: rgba(255, 255, 255, .14);
    border-color: rgba(255, 255, 255, .5);
    color: #FFFFFF;
}
.has-bg .btn-outline:hover { background: rgba(255, 255, 255, .26); border-color: #FFFFFF; color: #FFFFFF; }

/* ── The RSVP card ──
   Same treatment, but this one has to be filled in, so it gets a heavier pane
   and its fields are restyled rather than left as white boxes on glass. */

.rsvp-step.has-bg .rsvp-panel {
    background: rgba(14, 12, 8, .32);
    border-color: rgba(255, 255, 255, .28);
    backdrop-filter: saturate(110%);
    -webkit-backdrop-filter: saturate(110%);
    color: #FFFFFF;
    text-shadow: 0 1px 10px rgba(0, 0, 0, .55);
}
.rsvp-step.has-bg .rsvp-panel h2,
.rsvp-step.has-bg .closed-panel h2,
.rsvp-step.has-bg .success-panel h2,
.rsvp-step.has-bg label { color: #FFFFFF; }
.rsvp-step.has-bg .step-tag { color: rgba(255, 255, 255, .8); }
.rsvp-step.has-bg .step-tag span { background: rgba(255, 255, 255, .92); color: #14110C; }
.rsvp-step.has-bg label .opt { color: rgba(255, 255, 255, .55); }
.rsvp-step.has-bg .deadline-note,
.rsvp-step.has-bg .success-panel p,
.rsvp-step.has-bg .closed-panel p { color: rgba(255, 255, 255, .78); }
.rsvp-step.has-bg .success-ring { background: rgba(255, 255, 255, .18); color: #FFFFFF; }

.rsvp-step.has-bg input[type=text],
.rsvp-step.has-bg input[type=email],
.rsvp-step.has-bg input[type=tel],
.rsvp-step.has-bg input[type=number],
.rsvp-step.has-bg textarea {
    background: rgba(255, 255, 255, .13);
    border-color: rgba(255, 255, 255, .34);
    color: #FFFFFF;
}
.rsvp-step.has-bg input::placeholder,
.rsvp-step.has-bg textarea::placeholder { color: rgba(255, 255, 255, .5); }
.rsvp-step.has-bg input:focus,
.rsvp-step.has-bg textarea:focus {
    border-color: #E7DFC9;
    box-shadow: 0 0 0 3px rgba(231, 223, 201, .22);
}

/* ════════ The accept / decline choice ════════
   Guests were reading these two as text rather than as buttons. They now carry
   a real outline and a radio mark, and the pair breathes gently until one of
   them is chosen. */

.attend-toggle { gap: 12px; }
.attend-option {
    position: relative;
    border: 2px solid var(--gold);
    background: #FFFFFF;
    box-shadow: 0 4px 14px rgba(30, 26, 12, .10);
    padding: 16px 12px 14px;
}
.attend-option::before {
    content: '';
    position: absolute;
    top: 9px;
    right: 9px;
    width: 16px;
    height: 16px;
    border-radius: 50%;
    border: 2px solid rgba(0, 0, 0, .25);
    transition: all .15s ease;
}
.attend-toggle input:checked + .attend-option::before {
    border-color: #FFFFFF;
    background: #FFFFFF;
    box-shadow: inset 0 0 0 3px var(--gold);
}
.attend-toggle input:checked + .attend-option.decline::before {
    border-color: var(--slate);
    background: #FFFFFF;
    box-shadow: inset 0 0 0 3px var(--slate);
}
.attend-option.decline { border-color: var(--slate); }
.attend-toggle input:checked + .attend-option.decline { background: #EDEDEA; }

/* Until a choice is made, the pair pulses just enough to be read as a control. */
@keyframes attend-breathe {
    0%, 100% { box-shadow: 0 4px 14px rgba(30, 26, 12, .10), 0 0 0 0 rgba(143, 129, 89, .38); }
    50%      { box-shadow: 0 4px 14px rgba(30, 26, 12, .10), 0 0 0 7px rgba(143, 129, 89, 0); }
}
.attend-toggle:not(.chosen) .attend-option { animation: attend-breathe 2.4s ease-in-out infinite; }
.attend-toggle:not(.chosen) .attend-option.decline { animation-delay: .4s; }

.rsvp-step.has-bg .attend-option {
    background: rgba(255, 255, 255, .13);
    border-color: rgba(255, 255, 255, .62);
    color: #FFFFFF;
}
.rsvp-step.has-bg .attend-option strong { color: #FFFFFF; }
.rsvp-step.has-bg .attend-option span { color: rgba(255, 255, 255, .76); }
.rsvp-step.has-bg .attend-option::before { border-color: rgba(255, 255, 255, .7); }
.rsvp-step.has-bg .attend-toggle input:checked + .attend-option.accept {
    background: var(--gold);
    border-color: #FFFFFF;
}
.rsvp-step.has-bg .attend-toggle input:checked + .attend-option.decline {
    background: rgba(255, 255, 255, .3);
    border-color: #FFFFFF;
}
.rsvp-step.has-bg .attend-toggle input:checked + .attend-option.decline span { color: rgba(255, 255, 255, .9); }

@media (prefers-reduced-motion: reduce) {
    .attend-toggle:not(.chosen) .attend-option { animation: none; }
}

/* ════════ Mobile typography ════════
   Now that a photograph fills each screen the text has to hold its own against
   it: bigger titles, a rule under each one, and real space between the title
   and what follows. */

@media (max-width: 700px) {
    .band-title {
        font-size: 31px;
        letter-spacing: .01em;
        margin-bottom: 6px;
    }
    /* A short rule under the title, centred, in place of a heavier heading. */
    .band-title::after {
        content: '';
        display: block;
        width: 46px;
        height: 1px;
        margin: 13px auto 0;
        background: var(--gold);
    }
    .band.has-bg .band-title::after { background: rgba(255, 255, 255, .65); }
    .band-sub { font-size: 14.5px; margin-bottom: 20px; line-height: 1.5; }
    .band-title + .band-sub { margin-top: 14px; }

    .details-head h2 { font-size: 36px; margin: 10px 0 12px; }
    .details-intro { font-size: 15.5px; max-width: 20em; margin: 0 auto; }
    .step-tag { margin-bottom: 10px; }

    .verse-text { font-size: 20px; line-height: 1.75; }
    .verse-ref { margin-top: 20px; font-size: 11.5px; }

    .detail-block h3 { font-size: 25px; margin-bottom: 12px; }
    .detail-block .icon { width: 44px; height: 44px; margin-bottom: 13px; }
    .venue-name { font-size: 16.5px; }
    .venue-addr { font-size: 14.5px; line-height: 1.5; }

    .couple-name { line-height: 1.15; }
    .couple-parents { line-height: 1.55; }

    .story-card h4 { font-size: 22px; }
    .story-card p { font-size: 14.5px; }

    .closing-title { font-size: 34px; }
    .closing-names { font-size: 30px; margin-top: 6px; }
    .closing-band .band-inner { padding-bottom: 4px; }

    .rsvp-panel h2 { font-size: 27px; }
    .attend-option strong { font-size: 14.5px; }
    .attend-option span { font-size: 11.5px; line-height: 1.3; }
}

/* ════════ Letting the photograph show ════════
   With a picture on every screen the scrim can be much lighter at the top —
   where the subject of a photograph usually is — as long as the text carries
   its own shadow and the screens that are text alone sit low, over the part of
   the frame that is normally sky, sand or blur. */

.band.has-bg .band-title,
.band.has-bg .closing-title,
.band.has-bg .closing-names,
.band.has-bg .verse-text,
.band.has-bg .verse-ref,
.band.has-bg .band-sub,
.band.has-bg .gift-address,
.band.has-bg h2,
.band.has-bg .details-intro {
    text-shadow: 0 2px 18px rgba(0, 0, 0, .5);
}

/* Screens whose content is a card: the card supplies its own contrast, so the
   wash over the photo only has to keep the title readable. */
.couple-band.has-bg::before,
.story-band.has-bg::before,
.photos-band.has-bg::before,
.gift-band.has-bg::before,
.details-band.has-bg::before {
    background: linear-gradient(180deg,
        rgba(12, 10, 7, .30) 0%,
        rgba(12, 10, 7, .40) 45%,
        rgba(12, 10, 7, .60) 100%);
}

/* The RSVP screen has to be filled in, so it keeps a little more cover. */
.rsvp-step.has-bg::before {
    background: linear-gradient(180deg,
        rgba(12, 10, 7, .40) 0%,
        rgba(12, 10, 7, .46) 45%,
        rgba(12, 10, 7, .64) 100%);
}

/* Text-only screens: light at the top, heavy where the words are. */
.verse-band.has-bg::before,
.closing-band.has-bg::before,
.intro-band.has-bg::before {
    background: linear-gradient(180deg,
        rgba(12, 10, 7, .20) 0%,
        rgba(12, 10, 7, .26) 34%,
        rgba(12, 10, 7, .84) 100%);
}

@media (max-width: 700px) {
    /* …and the words themselves drop to the lower third, off the face of the
       photograph rather than across it. */
    .verse-band.has-bg > .page-inner,
    .closing-band.has-bg > .page-inner,
    .intro-band.has-bg > .page-inner {
        margin-top: auto;
        margin-bottom: 7vh;
    }
}
