/* ============================================================
   Wedding reply card — bespoke, self-contained page styles.
   Identity: graphite pencil portrait on its own cream paper,
   a single deep-laurel accent, system serif + sans (no webfonts).
   Namespaced under .inv so it never leans on Bootstrap.
   ============================================================ */

/* ============================================================
   TOKENS
   ============================================================ */
:root {
    /* the palette is three colours; everything else is derived from them, so the
       triplets are kept alongside for the rgba() lines, washes and shadows */
    --paper-rgb:   255, 250, 238;  /* cream, sampled from the drawing's own paper */
    --ink-rgb:      44,  42,  38;  /* warm graphite-black */
    --laurel-rgb:   69,  96,  79;  /* deep laurel green */

    --paper:       rgb(var(--paper-rgb));
    --ink:         rgb(var(--ink-rgb));    /* body text */
    --pencil:      #6E6A62;                /* muted graphite — secondary text, matches the drawing */
    --laurel:      rgb(var(--laurel-rgb)); /* the green as a *field* — never inverted */
    --accent:      var(--laurel);          /* the green as a *mark* — re-pointed inside .topband */
    --accent-ink:  #354B3E;                /* darker green for text-on-paper contrast */
    --line:        rgba(var(--ink-rgb), 0.16);  /* hairline rules */
    --line-strong: rgba(var(--ink-rgb), 0.30);
    --flag:        #9B3B2E;                /* the one red — form errors only */

    --display: Georgia, "Times New Roman", Times, serif;
    --body:    -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;

    --measure: 1120px;
    --ease: cubic-bezier(0.22, 1, 0.36, 1);
    --transition: 0.3s var(--ease);  /* the default hover/focus timing */
}

/* ============================================================
   PAGE SHELL
   ============================================================ */
*, *::before, *::after { box-sizing: border-box; }

/* this page opts out of the Bootstrap entrypoint, so it carries its own reset —
   without it the UA's 8px body margin leaves a gutter around the paper */
html { background: var(--paper); }  /* also colours the overscroll area */
body { margin: 0; }

.inv {
    margin: 0;
    min-height: 100svh;
    background: var(--paper);
    color: var(--ink);
    font-family: var(--body);
    font-weight: 400;
    line-height: 1.6;
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
    overflow-x: hidden;
}

/* faint paper grain — sits above the background, below content */
.inv::before {
    content: "";
    position: fixed;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    opacity: 0.05;
    mix-blend-mode: multiply;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

.inv__wrap {
    position: relative;
    z-index: 1;
    max-width: var(--measure);
    margin: 0 auto;
    padding: 0 clamp(1.25rem, 4vw, 2.5rem);
}

/* ============================================================
   SHARED PRIMITIVES
   ============================================================ */

/* the small letterspaced cap — eyebrows, form labels, the copy confirmation */
.eyebrow,
.form-label,
.col-form-label,
.practical__event-name,
.gifts__accounts-label,
.gifts__copy-status {
    font-family: var(--body);
    font-weight: 600;
    font-size: 0.72rem;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--accent-ink);
    margin: 0;
}

/* the standfirst under a section title */
.reply__lede,
.notice__lede,
.dress__lede,
.gifts__lede {
    max-width: 30rem;
    margin: 1rem auto 0;
    font-size: 1.02rem;
    color: var(--pencil);
}

/* one focus treatment for everything interactive; the components below shift
   the offset where a ring at arm's length would collide with a neighbour */
.langbar__link:focus-visible,
.form-check-input:focus-visible + .form-check-label,
.submit:focus-visible,
.gifts__iban:focus-visible,
.gallery__track:focus-visible,
.gallery__btn:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 3px;
}

/* ============================================================
   TOP BAND — the first screen, full-bleed laurel
   The paper-context tokens are re-pointed here, so every rule
   inside inverts without being rewritten. Islands that stay on
   paper (the portrait's mat) reset them back.
   ============================================================ */
.topband {
    position: relative;
    background: var(--laurel);
    --ink:         var(--paper);
    --pencil:      rgba(var(--paper-rgb), 0.68);
    --accent-ink:  rgba(var(--paper-rgb), 0.84);
    --accent:      #B7CCBA;  /* light sage — the amp, focus rings */
    --line:        rgba(var(--paper-rgb), 0.20);
    --line-strong: rgba(var(--paper-rgb), 0.34);
}

/* the band holds exactly one screen: langbar on top, hero taking the rest */
.topband > .inv__wrap {
    display: flex;
    flex-direction: column;
    min-height: 100svh;
}

/* the active pill inverts too — paper chip, laurel type */
.topband .langbar__link.is-active {
    background: var(--paper);
    color: var(--laurel);
}

/* ============================================================
   LANGUAGE SWITCH — pinned to the top of the page
   ============================================================ */
.langbar {
    position: relative;
    z-index: 2;
    padding-top: clamp(1.1rem, 2.5vw, 1.6rem);
}

.langbar__list {
    /* wide enough that the active pill's fill clears the separator on both sides */
    --langbar-gap: 0.9rem;
    display: flex;
    justify-content: flex-end;
    align-items: center;
    gap: var(--langbar-gap);
    list-style: none;
    margin: 0;
    padding: 0;
}

.langbar__link {
    display: inline-block;
    padding: 0.4rem 0.7rem;
    font-family: var(--body);
    font-weight: 600;
    font-size: 0.7rem;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--pencil);
    text-decoration: none;
    border-radius: 2px;
    transition: color var(--transition), background-color var(--transition);
}
.langbar__link:hover { color: var(--accent-ink); }
.langbar__link:focus-visible { outline-offset: 2px; }
.langbar__link.is-active {
    color: var(--paper);
    background: var(--accent);
}

/* hairline separators between the language options */
.langbar__list li + li { position: relative; }
.langbar__list li + li::before {
    content: "";
    position: absolute;
    left: calc(var(--langbar-gap) / -2);  /* centred in the gap, so both neighbours clear it equally */
    top: 50%;
    transform: translateY(-50%);
    width: 1px;
    height: 0.9rem;
    background: var(--line);
}

@media (max-width: 767.98px) {
    .langbar__list { justify-content: center; }
}

/* ============================================================
   HERO — the opened invitation
   ============================================================ */
.hero {
    display: grid;
    /* the drawing carries the page — give its leaf the wider share */
    grid-template-columns: 1.15fr 1fr;
    align-items: stretch;
    gap: clamp(1.5rem, 4vw, 3.5rem);
    /* less air up top so the hero sits closer to the language switch */
    padding: clamp(0.25rem, 1vw, 0.75rem) 0 clamp(1.5rem, 3vw, 2.5rem);
}
/* inside the band the langbar already claims its slice, so the hero just
   fills what's left of the screen; min-height:0 lets it shrink to do so */
.topband .hero { flex: 1; min-height: 0; }

/* left leaf — the portrait, matted like a framed drawing */
.hero__plate {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
}

/* the fold — hairline between the two leaves */
.hero__plate::after {
    content: "";
    position: absolute;
    top: 8%;
    bottom: 8%;
    right: calc(clamp(1.5rem, 4vw, 3.5rem) / -2);
    width: 1px;
    background: linear-gradient(to bottom, transparent, var(--line-strong) 18%, var(--line-strong) 82%, transparent);
}

/* the mat: a passe-partout in the page's own paper, so the frame and the page
   below the band are the same ground — only the hairline border separates them */
.portrait {
    --mat: clamp(0.85rem, 2.2vw, 1.6rem);
    /* against the dark field the sheet reads as hung, not printed:
       a tight contact shadow under a wide, deep ambient one */
    --lift:     14px 30px -16px;
    --lift-far: 46px 80px -44px;
    /* a paper island on the laurel field — put the paper-context hairlines back
       so the mat keeps its graphite rules instead of the band's light ones */
    --line:        rgba(var(--ink-rgb), 0.16);
    --line-strong: rgba(var(--ink-rgb), 0.30);
    position: relative;
    /* the drawing is a tall 5:7 sheet — track the viewport height so it never
       outgrows the fold on short screens */
    width: min(100%, 34rem, 58svh);
    margin: 0;
    padding: var(--mat);
    background: var(--paper);
    border: 1px solid var(--line-strong);
    box-shadow:
        0 1px 2px rgba(20, 30, 24, 0.20),
        0 var(--lift) rgba(20, 30, 24, 0.45),
        0 var(--lift-far) rgba(15, 24, 19, 0.75);
}
/* second hairline, hugging the sheet itself */
.portrait::after {
    content: "";
    position: absolute;
    inset: calc(var(--mat) - 1px);
    border: 1px solid var(--line);
    pointer-events: none;
}
.portrait__img {
    display: block;
    width: 100%;
    height: auto;
    /* the signature move: multiply melts the scan's white into the paper */
    mix-blend-mode: multiply;
}

/* right leaf — the words */
.hero__card {
    display: flex;
    flex-direction: column;
    justify-content: center;
    padding: clamp(1rem, 3vw, 2rem) 0;
}
.hero__intro { margin: 0 0 1.6rem; }

.names {
    font-family: var(--display);
    font-weight: 400;
    font-optical-sizing: auto;
    line-height: 0.92;
    letter-spacing: -0.01em;
    margin: 0;
    color: var(--ink);
}
.names__line { display: block; font-size: clamp(3rem, 8.5vw, 5.75rem); }
.names__amp {
    display: block;
    font-style: italic;
    font-weight: 300;
    font-size: clamp(2.4rem, 6vw, 4rem);
    color: var(--accent);
    margin: 0.1em 0 0.05em;
    padding-left: 0.15em;
}

.meta {
    margin: 2rem 0 0;
    font-size: 1.02rem;
    color: var(--pencil);
    line-height: 1.7;
}
/* the date is marked in the drawing's own paper tone — a highlighter swipe.
   The negative margin cancels the padding so the text still lines up with the
   schedule below; the band is what overhangs, the way a marker would. */
.meta strong {
    display: inline-block;
    font-family: var(--display);
    font-weight: 500;
    font-size: 1.35rem;
    color: var(--laurel);
    background: var(--paper);
    padding: 0.08em 0.45em 0.14em;
    margin-left: -0.45em;
    letter-spacing: 0.005em;
}
.meta__schedule { display: grid; gap: 0.7rem; margin-top: 0.7rem; }
.meta__event { display: grid; gap: 0.1rem; }
.meta__event-when { color: var(--ink); font-weight: 500; }
.meta__event-where { font-size: 0.94rem; color: var(--pencil); }

.scroll-cue {
    display: inline-flex;
    align-items: center;
    gap: 0.6rem;
    margin-top: 2.5rem;
    font-size: 0.85rem;
    font-weight: 600;
    letter-spacing: 0.01em;
    color: var(--accent-ink);
    text-decoration: none;
}
.scroll-cue__arrow {
    position: relative;
    display: inline-block;
    width: 2.25rem;
    height: 1px;
    background: currentColor;
}
/* the head: top + right borders spun about the box centre, so the vertex points
   along the rule and only its tip meets the end — the arms stay clear of it */
.scroll-cue__arrow::after {
    content: "";
    position: absolute;
    right: 0;
    top: 50%;
    width: 6px;
    height: 6px;
    border-top: 1px solid currentColor;
    border-right: 1px solid currentColor;
    transform: translate(-1px, -50%) rotate(45deg);
    transition: transform 0.4s var(--ease);
}
.scroll-cue:hover .scroll-cue__arrow::after { transform: translate(2px, -50%) rotate(45deg); }

/* ---- confirmation: the same right leaf, with the receipt instead of the date ---- */

/* the eyebrow carries the tick, so it sets its own line */
.confirm__eyebrow {
    display: flex;
    align-items: center;
    gap: 0.6rem;
}

/* the seal — a small ring around the tick, the one "done" mark on the page.
   Fixed width, so the eyebrow's line-height doesn't pull the ring into an oval */
.confirm__seal {
    display: grid;
    place-items: center;
    flex: none;
    width: 1.5rem;
    height: 1.5rem;
    border: 1px solid var(--line-strong);
    border-radius: 50%;
    color: var(--accent);
}
.confirm__seal svg { width: 0.8rem; height: 0.8rem; }

.confirm__title {
    font-family: var(--display);
    font-weight: 400;
    font-size: clamp(2.2rem, 5vw, 3.4rem);
    line-height: 1.06;
    letter-spacing: -0.01em;
    margin: 0;
    color: var(--ink);
}
.confirm__lede {
    max-width: 34ch;
    margin: 1.6rem 0 0;
    font-size: 1.02rem;
    color: var(--pencil);
    line-height: 1.7;
}

/* the two numbers to call on the day: a small stacked pair, each name over its
   digits, so they stay tappable instead of running on inside the paragraph.
   It takes the ink of whatever it sits on — laurel on the confirmation, paper
   on the check-in — so only the placing changes between the two. */
.contacts {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem 1.6rem;
    margin: 0.9rem 0 0;
}
/* mid-page, under a centred head, rather than in the hero's left-aligned card */
.contacts--mid { justify-content: center; margin-top: 1.1rem; }
/* one of us: the name, then the two ways of reaching them stacked under it */
.contacts__host {
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
}
.contacts--mid .contacts__host { align-items: center; }
.contacts__link {
    color: var(--pencil);
    text-decoration: none;
    transition: color var(--transition);
}
.contacts__link:hover { color: var(--accent-ink); }
.contacts__link:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 2px; }
.contacts__name { font-family: var(--display); font-size: 1.15rem; line-height: 1.2; color: var(--ink); }
.contacts__num { font-size: 0.95rem; font-variant-numeric: tabular-nums; }
/* an address is longer than the column it sits in on a narrow screen */
.contacts__mail { font-size: 0.95rem; overflow-wrap: anywhere; }

/* what a checked-in guest settled on: one quiet line under the title, the parts
   strung on middots so it reads as a single string rather than three phrases —
   enough to read back, never enough to mistake for something still to fill in */
.settled {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 0.2rem 0.5rem;
    max-width: 30rem;
    /* it stands off from the words below it, so the answer reads as its own line */
    margin: 1.1rem auto 1.9rem;
    font-size: 0.84rem;
    color: var(--pencil);
}
.settled__item { display: inline-flex; align-items: baseline; gap: 0.35rem; }
.settled__item + .settled__item::before {
    content: '\00b7';
    padding-right: 0.5rem;
    opacity: 0.5;
}
/* every answer they gave carries the same weight — a no is an answer too */
.settled__yn { color: var(--accent-ink); font-weight: 600; }

/* the sign-off — the salutation in the body hand, the names in the display one,
   so the two lines read as a hand-written close rather than one more paragraph */
.confirm__signoff {
    margin: 1.7rem 0 0;
    font-size: 1.02rem;
    color: var(--pencil);
    line-height: 1.5;
}
.confirm__names {
    font-family: var(--display);
    font-size: 1.35rem;
    letter-spacing: 0.005em;
    color: var(--ink);
}

/* stack the leaves on narrow screens */
@media (max-width: 767.98px) {
    .hero {
        grid-template-columns: 1fr;
        gap: 1.5rem;
        padding-top: 3rem;
        text-align: center;
    }
    .hero__plate::after { display: none; }
    .portrait {
        --mat: 0.8rem;
        --lift:     10px 22px -14px;
        --lift-far: 32px 56px -36px;
        width: min(100%, 24rem);
    }
    .hero__card { padding-top: 0.5rem; align-items: center; }
    .hero__intro, .meta, .confirm__lede, .confirm__signoff { text-align: center; }
    .contacts { justify-content: center; }
    .confirm__eyebrow { justify-content: center; }
    /* centred here, so the swipe shouldn't be pulled left */
    .meta strong { margin-left: 0; }
    .scroll-cue { align-self: center; }
}

/* ============================================================
   PRACTICAL — how the day runs, one framed panel per event
   ============================================================ */
.practical {
    position: relative;
    padding: clamp(3rem, 6.5vw, 5rem) 0 clamp(0.5rem, 2vw, 1.5rem);
}
.practical__inner {
    max-width: 54rem;
    margin: 0 auto;
    padding: 0 clamp(0.25rem, 2vw, 1rem);
}
.practical__head { text-align: center; }
.practical__title {
    font-family: var(--display);
    font-weight: 400;
    font-size: clamp(1.8rem, 4.5vw, 2.6rem);
    line-height: 1.1;
    margin: 0.9rem 0 0;
    color: var(--ink);
}

/* the two panels sit side by side while there's room, and stack when there isn't */
.practical__events {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(19rem, 1fr));
    gap: clamp(1.25rem, 3vw, 1.75rem);
    margin-top: clamp(2rem, 5vw, 3rem);
}

/* a faint laurel wash rather than white: the form card downstream keeps the only
   white surface on the page, so these read as reference, not as the thing to do */
.practical__event {
    padding: clamp(1.5rem, 4vw, 2.1rem);
    background: rgba(var(--laurel-rgb), 0.045);
    border: 1px solid var(--line);
    border-radius: 3px;
}

.practical__event-head {
    display: flex;
    align-items: center;
    gap: 0.8rem;
    margin: 0;
    font: inherit;
}
.practical__icon {
    flex: 0 0 auto;
    display: grid;
    place-items: center;
    width: 2.5rem;
    height: 2.5rem;
    border-radius: 50%;
    background: rgba(var(--laurel-rgb), 0.10);
    color: var(--accent-ink);
}
.practical__icon svg { width: 1.3rem; height: 1.3rem; }

/* the hour is the headline of its panel, with a short rule to sit it on */
.practical__event-when {
    font-family: var(--display);
    font-weight: 400;
    font-size: clamp(1.3rem, 3vw, 1.55rem);
    line-height: 1.15;
    color: var(--accent-ink);
    margin: 1.25rem 0 0;
}
.practical__event-when::after {
    content: "";
    display: block;
    width: 2.5rem;
    height: 1px;
    margin-top: 0.85rem;
    background: var(--line-strong);
}
.practical__event-where {
    margin: 0.85rem 0 0;
    font-size: 0.88rem;
    color: var(--pencil);
}
.practical__event-lede { margin: 0.85rem 0 0; color: var(--ink); }

/* ---- the venue map ----
   Leaflet paints its own furniture; these rules re-point it at the page's tokens.
   The two pins are the only laurel on a pale basemap. */
.practical__map { margin: clamp(2rem, 5vw, 3rem) 0 0; }
/* Leaflet puts .leaflet-container on this very element, so its own furniture is
   restated here rather than on a descendant */
.practical__map-canvas {
    /* a shade wider than square, and centred in its column once the column outgrows it:
       the two venues sit almost due north-south of each other, so the height carries the
       run between them while the extra width keeps the labels off the frame */
    width: 100%;
    max-width: 32rem;
    margin-inline: auto;
    aspect-ratio: 5 / 4;
    border: 1px solid var(--line);
    border-radius: 3px;
    background: var(--paper);
    font-family: var(--body);
}

.practical__pin { color: var(--accent); }
.practical__pin svg {
    display: block;
    width: 100%;
    height: 100%;
    filter: drop-shadow(0 1px 2px rgba(var(--ink-rgb), 0.35));
}

/* Leaflet's own chrome, restated in the page's hand */
/* the basemap is tinted toward the laurel rather than swapped for a keyed provider.
   This is a filter, not a blend: Leaflet transforms its map pane, which isolates the
   tiles from the container's background, so there is nothing behind to multiply into.
   grayscale + sepia flattens the tiles to one tone, hue-rotate carries that tone from
   sepia's brown round to green, and saturate sets how much of it survives.
   The pins sit in panes above, untouched. */
.practical__map-canvas .leaflet-tile-pane {
    filter: grayscale(1) sepia(0.45) hue-rotate(100deg) saturate(0.3) brightness(1.02) contrast(0.94);
}

.practical__map-canvas .leaflet-tooltip {
    padding: 0.2rem 0.5rem;
    background: var(--paper);
    border: 1px solid var(--line);
    border-radius: 2px;
    box-shadow: none;
    font-weight: 600;
    font-size: 0.7rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--accent-ink);
}
.practical__map-canvas .leaflet-tooltip::before { display: none; }

/* the label carries the panel's own icon, at cap height beside the words, and the
   whole of it is the link to that venue — so the row is laid out on the anchor */
.practical__map-canvas .practical__pin-label { display: block; }
/* scoped through the canvas to outrank Leaflet's own `.leaflet-container a`, which
   would otherwise paint the label its link blue and rule a line under it */
.practical__map-canvas .practical__pin-label-link {
    display: flex;
    align-items: center;
    gap: 0.35rem;
    color: inherit;
    text-decoration: none;
    cursor: pointer;
}
.practical__map-canvas .practical__pin-label-link:hover { color: inherit; text-decoration: none; }
.practical__map-canvas .practical__pin-label-link:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}
.practical__pin-label-icon {
    display: block;
    width: 0.85rem;
    height: 0.85rem;
}
.practical__pin-label-icon svg { display: block; width: 100%; height: 100%; }

/* the hour rides along beside the name, out of the letterspaced caps */
.practical__pin-label-time {
    text-transform: none;
    letter-spacing: 0;
    font-weight: 400;
    color: var(--pencil);
}
.practical__map-canvas .leaflet-bar a {
    background: var(--paper);
    border-bottom-color: var(--line);
    color: var(--accent-ink);
}
/* mixed into paper rather than laid over it — a translucent tint here would let the
   tiles through the button */
.practical__map-canvas .leaflet-bar a:hover {
    background: color-mix(in srgb, var(--laurel) 8%, var(--paper));
}
.practical__map-canvas .leaflet-bar a.leaflet-disabled {
    background: var(--paper);
    color: var(--line-strong);
    cursor: default;
}
.practical__map-canvas .leaflet-control-attribution {
    background: rgba(var(--paper-rgb), 0.85);
    font-size: 0.66rem;
    color: var(--pencil);
}
.practical__map-canvas .leaflet-control-attribution a { color: var(--accent-ink); }

.practical__notes {
    display: grid;
    gap: 0.7rem;
    margin: 1.1rem 0 0;
    padding: 0;
    list-style: none;
}
/* each note carries a mark of its own subject rather than a bullet: the car, the
   camera, the popper — drawn in the template, sized here */
.practical__note {
    display: flex;
    gap: 0.55rem;
    font-size: 0.95rem;
    color: var(--pencil);
}
.practical__note-icon {
    flex: 0 0 auto;
    /* square on the note's own type size, then centred in the first line box:
       (line-height − icon) / 2, with the 1.6 inherited from .inv */
    width: 1em;
    height: 1em;
    margin-top: calc((1.6em - 1em) / 2);
    color: var(--accent);
    opacity: 0.8;
}
.practical__note-icon svg { display: block; width: 100%; height: 100%; }
.practical__note-text { min-width: 0; }
/* a place — the venue's address, or a car park named in a note — carries its own
   link to the map: it keeps the type colour it sits in, and only the rule marks it */
.practical__link {
    color: inherit;
    text-decoration: underline;
    text-decoration-color: var(--line-strong);
    text-underline-offset: 0.2em;
    transition: color var(--transition), text-decoration-color var(--transition);
}
.practical__link:hover { color: var(--accent-ink); text-decoration-color: currentColor; }

/* ============================================================
   REPLY CARD
   ============================================================ */
.reply,
.notice {
    position: relative;
    padding: clamp(3rem, 6.5vw, 5.75rem) 0 clamp(1.75rem, 4vw, 3rem);
}
/* the form — or, on the invitation, the word standing in for it — sits on its own
   white card, lifted off the cream page: the only white surface, so the one thing
   to read or to *do* stands out */
.reply__inner,
.notice__inner {
    position: relative;
    z-index: 1;
    /* the measure is the content's; the padding is added on top so the form
       keeps the same line length it had on the bare page */
    --reply-pad: clamp(1.5rem, 5vw, 3.25rem);
    max-width: calc(40rem + 2 * var(--reply-pad));
    margin: 0 auto;
    padding: var(--reply-pad);
    background: #FFFFFF;
    border: 1px solid var(--line);
    border-radius: 3px;
    box-shadow:
        0 1px 2px rgba(var(--ink-rgb), 0.05),
        0 18px 40px -24px rgba(var(--ink-rgb), 0.35);
}
.reply__head { text-align: center; margin-bottom: clamp(2rem, 5vw, 3rem); }
/* nothing follows the notice's head, so it carries no bottom margin of its own */
.notice__head { text-align: center; }
.reply__title,
.notice__title {
    font-family: var(--display);
    font-weight: 400;
    font-size: clamp(2rem, 5vw, 3rem);
    line-height: 1.05;
    margin: 0.9rem 0 0;
    color: var(--ink);
}

/* ---- the code form on the invitation ----
   The way through to a guest's own page. Nothing is posted: it is a GET that
   builds the check-in URL out of what they type. */
.notice__code-lede {
    max-width: 26rem;
    margin: clamp(1.75rem, 4vw, 2.5rem) auto 0;
    text-align: center;
    font-size: 0.95rem;
    color: var(--pencil);
}
.code {
    display: grid;
    max-width: 22rem;
    margin: 1.25rem auto 0;
    text-align: center;
}
.code .form-label { margin-bottom: 0.55rem; }
/* the four characters, spaced out and centred: a code to read back, not prose */
.code .form-control {
    font-size: 1.2rem;
    letter-spacing: 0.42em;
    /* the tracking hangs off the last letter; the indent puts the run back in the middle */
    text-indent: 0.42em;
    text-align: center;
    text-transform: uppercase;
}
.code .form-control::placeholder { letter-spacing: 0.1em; text-indent: 0; text-transform: none; }
/* a longer label than the reply card's: it wraps rather than pushing past the card */
.code .submit {
    max-width: 100%;
    padding-inline: clamp(1.25rem, 5vw, 2.5rem);
    text-align: center;
}

/* ---- form scaffolding ----
   The reply card is rendered by the Symfony form component through the Bootstrap 5
   theme. Bootstrap's stylesheet is never loaded — only its class names are, and
   they're dressed here as this page's own primitives:
     .mb-3 …………… one field row      .form-control ……… text input / textarea
     .form-label ……… its label        .form-text ……………… the "optional" hint
     .form-check …… one radio        .invalid-feedback … a field's error
*/
.form { display: grid; gap: 1.75rem; }

/* one field row. The theme prints label → control → hint → errors, but the hint
   belongs beside its label, so the first line is shared and everything else
   claims a full line of its own. */
.mb-3 {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.4rem;
}
.mb-3 > * { flex: 0 0 100%; }
.mb-3 > .form-label { flex: 0 1 auto; order: -2; }
.mb-3 > .form-text  { flex: 0 1 auto; order: -1; }

#respond_website,
[for="respond_website"] {
    position: absolute;
    left: -9999px;
    width: 1px;
    height: 1px;
    overflow: hidden;
}
.mb-3:has(> #respond_website) { position: absolute; }

/* fieldsets reset to plain boxes — the legend does the labelling */
fieldset.mb-3,
.events {
    border: 0;
    padding: 0;
    margin: 0;
    min-width: 0;
}
/* a <legend> can't take part in the fieldset's grid gap, so it needs an explicit
   bottom margin to match the label→input rhythm; padding drops the UA's inline nudge */
legend.col-form-label {
    padding: 0;
    margin-bottom: 0.4rem;
}
.form-text {
    font-size: 0.86rem;
    color: var(--pencil);
}

/* the real radio, kept operable but out of sight — the label beside it is the UI */
.form-check-input {
    position: absolute;
    opacity: 0;
    width: 1px;
    height: 1px;
    pointer-events: none;
}

/* the pressable face shared by both radio groups */
.form-check-label {
    font-family: var(--body);
    font-size: 0.76rem;
    font-weight: 600;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--pencil);
    cursor: pointer;
}

/* ---- text inputs ---- */
.form-control {
    width: 100%;
    font-family: var(--body);
    font-size: 1.02rem;
    color: var(--ink);
    background: transparent;
    border: 0;
    border-bottom: 1px solid var(--line-strong);
    padding: 0.6rem 0.1rem;
    transition: border-color var(--transition);
}
.form-control::placeholder { color: var(--pencil); opacity: 0.7; }
/* the underline is the focus signal here, so no ring */
.form-control:focus {
    outline: none;
    border-bottom-color: var(--accent);
}
textarea.form-control { resize: vertical; min-height: 3.2rem; line-height: 1.6; }

/* the theme flags a rejected value on the control itself */
.form-control.is-invalid { border-bottom-color: var(--flag); }
.invalid-feedback,
.alert-danger {
    font-size: 0.86rem;
    color: var(--flag);
}

/* ---- party size — one connected bar of choices, the whole answer visible at a
   glance. Two options of equal weight, so two equal halves. ----
   The theme wraps expanded choices in an unclassed <div>; inside a field row
   that div is the group, and party is the page's only such row. */
fieldset.mb-3 > div {
    display: grid;
    grid-template-columns: 1fr 1fr;
    border: 1px solid var(--line-strong);
    border-radius: 2px;
    overflow: hidden;
}
fieldset.mb-3 .form-check { position: relative; display: flex; }
fieldset.mb-3 .form-check-label {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 0.7rem 0.6rem;
    transition: background-color var(--transition), color var(--transition);
}
/* hairlines between segments, never on the outer edge */
fieldset.mb-3 .form-check + .form-check .form-check-label { border-left: 1px solid var(--line-strong); }
fieldset.mb-3 .form-check-label:hover { color: var(--accent-ink); background: rgba(var(--laurel-rgb), 0.07); }
fieldset.mb-3 .form-check-input:checked + .form-check-label {
    background: var(--accent);
    color: var(--paper);
}
/* inset, so the ring stays inside the bar's own frame */
fieldset.mb-3 .form-check-input:focus-visible + .form-check-label { outline-offset: -3px; }

/* ---- attendance — mark yes / no for each event on its own ---- */
.events { display: grid; gap: 0.9rem; }
.event {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding: 1rem 1.15rem;
    border: 1px solid var(--line-strong);
    border-radius: 2px;
}
.event__info { display: grid; gap: 0.2rem; }
.event__name {
    font-family: var(--display);
    font-size: 1.3rem;
    line-height: 1.1;
    color: var(--ink);
}
.event__time,
.event__where { font-size: 0.82rem; color: var(--pencil); }

.event > [role="radiogroup"] { display: inline-flex; gap: 0.4rem; flex-shrink: 0; }
.event .form-check { position: relative; }
.event .form-check-label {
    display: block;
    min-width: 3.5rem;
    text-align: center;
    padding: 0.5rem 0.9rem;
    border: 1px solid var(--line-strong);
    border-radius: 2px;
    transition: border-color var(--transition), background-color var(--transition), color var(--transition);
}
.event .form-check-label:hover { border-color: var(--accent); color: var(--accent-ink); }
.event .form-check-input:checked + .form-check-label {
    border-color: var(--accent);
    background: var(--accent);
    color: var(--paper);
}

/* ---- submit ---- */
.submit {
    justify-self: center;
    display: inline-flex;
    align-items: center;
    gap: 0.6rem;
    margin-top: 0.75rem;
    font-family: var(--body);
    font-weight: 600;
    font-size: 0.8rem;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--paper);
    background: var(--accent);
    border: 1px solid var(--accent);
    border-radius: 2px;
    padding: 1rem 2.75rem;
    cursor: pointer;
    transition: background-color var(--transition), transform 0.25s var(--ease);
}
.submit:hover { background: var(--accent-ink); }
.submit:active { transform: translateY(1px); }

@media (max-width: 575.98px) {
    .event { flex-direction: column; align-items: stretch; gap: 0.85rem; text-align: left; }
    .event > [role="radiogroup"] { align-self: stretch; }
    .event .form-check { flex: 1; }
    .event .form-check-label { width: 100%; }
    /* too narrow for two written-out options side by side — stack them */
    fieldset.mb-3 > div { grid-template-columns: 1fr; }
    fieldset.mb-3 .form-check + .form-check .form-check-label {
        border-left: 0;
        border-top: 1px solid var(--line-strong);
    }
}

/* ============================================================
   ASIDES — the quiet notes between the cards: presents, and what to wear
   ============================================================ */
/* the pair of them, side by side while the page is wide enough for two columns
   of prose. The air around them is the wrapper's, so the two frames sit flush
   against each other when they stack below. */
.asides {
    display: grid;
    grid-template-columns: 1fr 1fr;
    column-gap: clamp(1.5rem, 4vw, 3rem);
    padding: clamp(2rem, 5vw, 3.25rem) 0;
}
.dress,
.gifts {
    position: relative;
}
.dress__inner,
.gifts__inner {
    /* side by side they fill their column; stacked they fall back to the measure
       a single centred aside wants, which the narrow rules below restore */
    max-width: none;
    /* both frames are as tall as the taller of the two, so the rules line up */
    height: 100%;
    text-align: center;
    /* hairline rules top and bottom frame it as an aside between the two cards */
    border-top: 1px solid var(--line);
    border-bottom: 1px solid var(--line);
    padding: clamp(2.5rem, 6vw, 3.5rem) clamp(1.25rem, 4vw, 2rem);
}
.dress__title,
.gifts__title {
    font-family: var(--display);
    font-weight: 400;
    font-size: clamp(1.6rem, 4vw, 2.4rem);
    line-height: 1.12;
    margin: 0.9rem auto 0;
    max-width: 22ch;
    color: var(--ink);
}
/* too narrow for two columns: they stack, and give up the rule between them so
   the pair reads as one framed block, the way they did before there was room */
@media (max-width: 767.98px) {
    .asides { display: block; }
    .dress__inner,
    .gifts__inner {
        max-width: 34rem;
        margin: 0 auto;
        height: auto;
    }
    .gifts + .dress .dress__inner { border-top: 0; }
}

.gifts__accounts-label { margin: 1.9rem 0 0; }

/* two accounts side by side, stacking on narrow screens */
.gifts__accounts {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 0.9rem clamp(1rem, 4vw, 2rem);
    list-style: none;
    margin: 1rem 0 0;
    padding: 0;
}
.gifts__account {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.4rem;
}
.gifts__account-name {
    font-family: var(--display);
    font-size: 0.95rem;
    letter-spacing: 0.06em;
    color: var(--pencil);
}
/* the plate doubles as a copy button — same frame, now tappable */
.gifts__iban {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    font-family: var(--display);
    font-size: 1.02rem;
    letter-spacing: 0.08em;
    white-space: nowrap;
    color: var(--laurel);
    background: none;
    padding: 0.4rem 0.8rem;
    border: 1px solid var(--line-strong);
    border-radius: 2px;
    cursor: pointer;
    transition: color var(--transition), border-color var(--transition);
}
.gifts__iban:hover { color: var(--accent-ink); border-color: var(--accent); }
.gifts__iban:active { transform: translateY(1px); }
.gifts__iban.is-copied { color: var(--accent-ink); border-color: var(--accent); }

.gifts__copy-icon {
    width: 0.85em;
    height: 0.85em;
    flex-shrink: 0;
    opacity: 0.45;
    transition: opacity var(--transition);
}
.gifts__iban:hover .gifts__copy-icon { opacity: 0.75; }
.gifts__copy-icon--done,
.gifts__iban.is-copied .gifts__copy-icon--copy { display: none; }
.gifts__iban.is-copied .gifts__copy-icon--done { display: block; opacity: 1; }

/* live region: reserves its own line so the row doesn't jump when it fills */
.gifts__copy-status {
    min-height: 1.2em;
    margin: 0.7rem 0 0;
}

/* ============================================================
   CLOSING — footer and gallery share one padded block, so the
   names, note and filmstrip read as a single sign-off
   ============================================================ */
.closing {
    padding: clamp(2rem, 5vw, 3.25rem) 0 clamp(2rem, 5vw, 3rem);
}

/* ---- footer ---- */
.foot { text-align: center; }
.foot__heart {
    font-size: 1.75rem;
    line-height: 1;
}
.foot__names {
    font-family: var(--display);
    font-size: 1.15rem;
    letter-spacing: 0.02em;
    color: var(--ink);
    margin: 0.5rem 0 0.75rem;
}
.foot__amp { margin: 0 0.5rem; color: var(--accent); font-style: italic; }
.foot__note { font-size: 0.82rem; letter-spacing: 0.06em; color: var(--pencil); margin: 0; }

/* ============================================================
   GALLERY — a swipeable filmstrip carousel below the footer.
   Fixed height + auto width, so portrait and landscape frames
   sit side by side without cropping.
   ============================================================ */
.gallery {
    position: relative;
    /* sits tight under the footer — the two form the closing block */
    padding: clamp(1.25rem, 3vw, 1.75rem) 0 0;
}
.gallery__viewport { position: relative; }
.gallery__track {
    display: flex;
    gap: clamp(0.75rem, 2vw, 1.25rem);
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scroll-behavior: smooth;
    -webkit-overflow-scrolling: touch;
    list-style: none;
    margin: 0;
    padding: 0;
    scrollbar-width: none;  /* nav is by swipe + buttons */
}
.gallery__track::-webkit-scrollbar { display: none; }
.gallery__track:focus-visible { outline-offset: 4px; border-radius: 2px; }
.gallery__item { flex: 0 0 auto; scroll-snap-align: center; margin: 0; }
.gallery__img {
    display: block;
    height: clamp(8.5rem, 26vw, 13rem);
    width: auto;
    max-width: none;
    border: 1px solid var(--line-strong);
    border-radius: 2px;
    box-shadow: 0 14px 30px -20px rgba(var(--ink-rgb), 0.55);
}

.gallery__btn {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.25rem;
    height: 2.25rem;
    border-radius: 50%;
    /* translucent paper so the button reads over any photo without a hard plate */
    background: rgba(var(--paper-rgb), 0.6);
    -webkit-backdrop-filter: blur(4px);
    backdrop-filter: blur(4px);
    color: var(--accent-ink);
    border: 1px solid var(--line);
    cursor: pointer;
    /* kept out of the way until the viewer hovers or tabs to it */
    opacity: 0;
    transition: opacity 0.25s var(--ease), color var(--transition), border-color var(--transition);
    z-index: 2;
}
.gallery__btn--prev { left: clamp(0.25rem, 1.5vw, 1rem); }
.gallery__btn--next { right: clamp(0.25rem, 1.5vw, 1rem); }
.gallery__viewport:hover .gallery__btn,
.gallery__btn:focus-visible { opacity: 1; }
.gallery__btn:hover { color: var(--accent); border-color: var(--accent); }
.gallery__btn:active { transform: translateY(-50%) scale(0.96); }
.gallery__btn:focus-visible { outline-offset: 2px; }

/* ============================================================
   ENTRANCE MOTION
   ============================================================ */
.reveal { opacity: 0; transform: translateY(14px); }
.inv.is-ready .reveal {
    opacity: 1;
    transform: none;
    transition: opacity 0.9s var(--ease), transform 0.9s var(--ease);
}
.inv.is-ready .reveal[data-delay="1"] { transition-delay: 0.08s; }
.inv.is-ready .reveal[data-delay="2"] { transition-delay: 0.18s; }
.inv.is-ready .reveal[data-delay="3"] { transition-delay: 0.30s; }
.inv.is-ready .reveal[data-delay="4"] { transition-delay: 0.44s; }
.inv.is-ready .reveal[data-delay="5"] { transition-delay: 0.60s; }

/* the drawing settles rather than slides — slower, with a touch of scale */
.portrait.reveal { transform: translateY(14px) scale(0.985); }
.inv.is-ready .portrait.reveal { transform: none; transition: opacity 1.4s var(--ease), transform 1.4s var(--ease); }

@media (prefers-reduced-motion: reduce) {
    .reveal, .inv.is-ready .reveal { opacity: 1; transform: none; transition: none; }
    .scroll-cue__arrow::after { transition: none; }
    .gallery__track { scroll-behavior: auto; }
}