/* ===========================
   Wedding Website Styles
   Adina & Andrew
   =========================== */

/* ===========================
   @property — Register color variables for smooth dark mode transitions
   =========================== */

@property --color-dark-green {
    syntax: "<color>";
    inherits: true;
    initial-value: #1a3a2e;
}

@property --color-soft-white {
    syntax: "<color>";
    inherits: true;
    initial-value: #F1EDEA;
}

@property --color-dark-bg {
    syntax: "<color>";
    inherits: true;
    initial-value: #122a20;
}

@property --color-accent {
    syntax: "<color>";
    inherits: true;
    initial-value: #2d5a4a;
}

/* CSS Reset & Base Styles */
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

/* Custom Font Loading */

@font-face {
    font-family: 'PP Playground Medium';
    src: url('../fonts/PPPlayground-Medium.woff2') format('woff2'),
        url('../fonts/PPPlayground-Medium.woff') format('woff');
    font-weight: 500;
    font-style: normal;
}

@font-face {
    font-family: 'PP Watch';
    src: url('../fonts/PPWatch-Bold.woff2') format('woff2'),
        url('../fonts/PPWatch-Bold.woff') format('woff');
    font-weight: 700;
    font-style: normal;
}

@font-face {
    font-family: 'PP Watch';
    src: url('../fonts/PPWatch-Medium.woff2') format('woff2'),
        url('../fonts/PPWatch-Medium.woff') format('woff');
    font-weight: 500;
    font-style: normal;
}

@font-face {
    font-family: 'PP Watch';
    src: url('../fonts/PPWatch-Extralight.woff2') format('woff2'),
        url('../fonts/PPWatch-Extralight.woff') format('woff');
    font-weight: 200;
    font-style: normal;
}

@font-face {
    font-family: 'Sentient';
    src: url('../fonts/Sentient-Regular.woff2') format('woff2'),
        url('../fonts/Sentient-Regular.woff') format('woff');
    font-weight: 400;
    font-style: normal;
}

:root {
    /* Colors */
    --color-dark-green: #1a3a2e;
    --color-dark-bg: #122a20;
    --color-soft-white: #F1EDEA;
    --color-accent: #2d5a4a;

    /* Fonts */
    --font-title: 'PP Playground Medium', 'Brush Script MT', cursive;
    --font-heading: 'PP Watch', 'Helvetica Neue', Arial, sans-serif;
    --font-body: 'Sentient', 'Baskerville', Georgia, serif;

    /* Borders */
    --border-hairline: 1px solid rgba(26, 58, 46, 0.1);
    --border-hairline-dark: 1px solid rgba(241, 237, 234, 0.12);

    /* Physical depth — shadow scale */
    --shadow-raised: inset 0 1px 3px rgba(0, 0, 0, 0.15), inset 0 0 0 1px rgba(0, 0, 0, 0.05), inset 0 -1px 0 rgba(255, 255, 255, 0.1), inset 0 1px 0 rgba(0, 0, 0, 0.12);
    --shadow-lifted: inset 0 1.6px 4.2px rgba(0, 0, 0, 0.20), inset 0 0 0 1px rgba(0, 0, 0, 0.06), inset 0 -1px 0 rgba(255, 255, 255, 0.09), inset 0 1px 0 rgba(0, 0, 0, 0.14);
    --shadow-pressed: inset 0 3px 7px rgba(0, 0, 0, 0.32), inset 0 1px 3px rgba(0, 0, 0, 0.15), inset 0 1px 0 rgba(0, 0, 0, 0.2);

    /* Nav text — letterpress states */
    --emboss-rest: 0 1px 0 rgba(255, 255, 255, 0.6), 0 -0.5px 0 rgba(26, 58, 46, 0.06);
    --emboss-hover: 0 -1px 1px rgba(26, 58, 46, 0.15), 0 1px 0 rgba(255, 255, 255, 0.3);

    --story-closing-size: 3.6rem;
    --story-closing-size-mobile: 2.2rem;

    /* Smooth dark mode color transitions */
    transition:
        --color-dark-green 400ms ease,
        --color-soft-white 400ms ease,
        --color-dark-bg 400ms ease,
        --color-accent 400ms ease;
}

/* ===========================
   Global horizontal-scroll guard
   =========================== */

/* Wide-screen clipping fix (July 2026): the RSVP card flow's off-screen exit/
   enter transforms need a clean, unconditional horizontal clip at the true
   document edges. `clip` (not `hidden`) so this never creates its own scroll
   container — vertical scrolling and any fixed-position elements (nav,
   footer, the RSVP grain backdrop) are unaffected. Site-wide rather than
   RSVP-scoped since it's a pure safety net (nothing else on the site
   intentionally bleeds past the viewport). The RSVP page's own overflow
   rules below (body.page-rsvp and its .intro-complete/.rsvp-flow-active
   variants) now only need to manage the vertical axis. */
html, body {
    overflow-x: clip;
}

/* ===========================
   Body & Dark Mode
   =========================== */

body {
    font-family: var(--font-body);
    background-color: #F1EDEA;
    background-image: url('../assets/textures/combined-light.png');
    background-size: 200px 200px;
    background-repeat: repeat;
    color: var(--color-dark-green);
    line-height: 1.7;
    font-size: 18px;
    letter-spacing: 0.01em;
    text-shadow: 0 2px 3px rgba(255, 255, 255, 0.9), 0 -1px 1px rgba(26, 58, 46, 0.1);
    min-height: 100vh;
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
    transition: color 400ms ease, background-color 400ms ease, text-shadow 400ms ease;
}

body.dark-mode {
    background-color: #122a20;
    background-image: url('../assets/textures/combined-dark.png');
    background-size: 200px 200px;
    background-repeat: repeat;
    color: var(--color-soft-white);
    text-shadow: 0 1px 2px rgba(241, 237, 234, 0.1), 0 2px 5px rgba(0, 0, 0, 0.5);
    --shadow-raised: inset 0 1px 2px rgba(0, 0, 0, 0.12), inset 0 0 0 1px rgba(0, 0, 0, 0.06);
    --shadow-lifted: inset 0 1.6px 3.2px rgba(0, 0, 0, 0.17), inset 0 0 0 1px rgba(0, 0, 0, 0.08);
    --shadow-pressed: inset 0 3px 6px rgba(0, 0, 0, 0.3), inset 0 1px 2px rgba(0, 0, 0, 0.15);
    --emboss-rest: 0 1px 0 rgba(241, 237, 234, 0.08), 0 -0.5px 0 rgba(0, 0, 0, 0.2);
    --emboss-hover: 0 -1px 1px rgba(0, 0, 0, 0.4), 0 1px 0 rgba(241, 237, 234, 0.06);
}

/* Prevent Sentient from ever rendering italic */
em,
i,
.page-intro {
    font-style: normal;
}


/* ===========================
   Password Overlay — Shared
   =========================== */

body.locked {
    overflow: hidden;
}

#password-overlay {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-color: var(--color-soft-white);
    z-index: 9999;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    padding: 2rem;
    transition: background-color 400ms ease;
}

#password-overlay.hidden {
    display: none;
}

/* Grain on password overlay (solid bg blocks body grain) */
#password-overlay::before {
    content: '';
    position: fixed;
    inset: 0;
    z-index: 1;
    pointer-events: none;
    background-color: #F1EDEA;
    background-image: url('../assets/textures/combined-light.png');
    background-size: 200px 200px;
    background-repeat: repeat;
}

body.dark-mode #password-overlay::before {
    background-color: #122a20;
    background-image: url('../assets/textures/combined-dark.png');
    background-size: 200px 200px;
    background-repeat: repeat;
}

body.dark-mode #password-overlay {
    background-color: var(--color-dark-bg);
}

.password-container {
    max-width: 400px;
    width: 100%;
    text-align: center;
    position: relative;
    z-index: 2;
}

.password-container h2 {
    font-family: var(--font-title);
    font-size: 5.2rem;
    font-weight: 400;
    margin-bottom: 0.5rem;
    color: var(--color-dark-green);
    transition: color 400ms ease;
}

.password-container p {
    font-family: var(--font-body);
    font-size: 1rem;
    margin-bottom: 0.5rem;
    color: var(--color-dark-green);
    transition: color 400ms ease;
}

body.dark-mode .password-container h2,
body.dark-mode .password-container p {
    color: var(--color-soft-white);
}

#password-form {
    display: flex;
    gap: 0.5rem;
    justify-content: center;
    align-items: stretch;
    max-width: 600px;
    margin: 0 auto;
    flex-wrap: wrap;
}

.password-input {
    width: 200px;
    min-width: 150px;
    padding: 0.5rem 1.2rem;
    font-family: var(--font-body);
    font-size: 0.7rem;
    border: 2px solid var(--color-dark-green);
    background: linear-gradient(135deg, var(--color-dark-green) 0%, var(--color-accent) 100%);
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    background-clip: text;
    text-align: center;
    border-radius: 25px;
    transition: all 0.3s ease;
}

.password-input:focus {
    outline: none;
    border-color: var(--color-dark-green);
}

body.dark-mode .password-input {
    border-color: var(--color-soft-white);
    background: linear-gradient(135deg, var(--color-soft-white) 0%, #d4cfc8 100%);
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    background-clip: text;
}

.password-submit {
    padding: 0.5rem 1.2rem;
    font-family: var(--font-heading);
    font-weight: 700;
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--color-soft-white);
    background-color: var(--color-dark-green);
    box-shadow: var(--shadow-raised);
    border: 2px solid rgba(0, 0, 0, 0.12);
    border-radius: 25px;
    cursor: pointer;
    position: relative;
    overflow: hidden;
    transition: box-shadow 0.2s ease, transform 0.2s ease, background-color 400ms ease, color 400ms ease, border-color 400ms ease;
    text-shadow: none;
    white-space: nowrap;
}

.password-submit:hover {
    box-shadow: var(--shadow-lifted);
    background-color: #152f25;
}

.password-submit:active {
    box-shadow: var(--shadow-pressed);
    background-color: #112a20;
    transform: translateY(0.5px);
}

body.dark-mode .password-submit {
    background-color: var(--color-soft-white);
    color: var(--color-dark-green);
    border: 2px solid rgba(0, 0, 0, 0.15);
}

body.dark-mode .password-submit:hover {
    box-shadow: var(--shadow-lifted);
    background-color: #e8e3df;
}

body.dark-mode .password-submit:active {
    box-shadow: var(--shadow-pressed);
    background-color: #dfd9d4;
    transform: translateY(0.5px);
}

.password-error {
    color: inherit;
    font-size: 0.9rem;
    margin-top: 0.5rem;
    display: none;
    width: 100%;
    flex-basis: 100%;
    text-align: center;
}

.password-error.show {
    display: block;
}

/* Protected content hidden by default */
#protected-content {
    display: none;
}

/* Hide nav when page is locked (nav lives outside #protected-content) */
body.locked .main-nav {
    display: none;
}

/* The RSVP "Coming Soon!" gate is a dead-end for guests (they can't unlock it),
   so keep the nav visible ABOVE the password overlay (z-index 9999) so they can
   still navigate away. Overrides both the locked-page hide and the Metro intro's
   opacity:0/pointer-events:none hold (which resumes normally once unlocked).
   Other page gates stay covered as usual. */
body.page-rsvp.locked .main-nav {
    display: block;
    z-index: 10000;
    opacity: 1;
    pointer-events: auto;
}

/* Default unlocked: flex column (nav + footer layout) */
#protected-content.unlocked {
    display: flex;
    flex-direction: column;
    min-height: 100vh;
}

/* Save the Date: simple block (no nav/footer) */
body.page-savethedate #protected-content.unlocked {
    display: block;
}

/* ===========================
   Navigation — Floating Marquise (Desktop) / Corner Layout (Mobile)
   =========================== */

.main-nav {
    position: fixed;
    top: 1rem;
    left: 50%;
    transform: translateX(-50%);
    z-index: 1000;
    padding: 0;
}

.nav-bar {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 1.8rem;
    position: relative;
    width: 750px;
    height: 56px;
}

/* Diamond PNG — positioned behind nav content */
.nav-diamond {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 100%;
    height: auto;
    pointer-events: none;
    z-index: 0;
    filter: drop-shadow(0 4px 14px rgba(0, 0, 0, 0.22));
}

body.dark-mode .nav-diamond {
    filter: drop-shadow(0 4px 14px rgba(0, 0, 0, 0.3));
}

/* Monogram link */
.site-title {
    display: flex;
    align-items: center;
    text-decoration: none;
    flex-shrink: 0;
    z-index: 1;
}

.nav-monogram {
    height: 36px;
    width: auto;
    display: block;
    object-fit: contain;
    filter: drop-shadow(0px 2px 2px rgba(255, 255, 255, 1)) drop-shadow(0px -1px 1px rgba(0, 0, 0, 0.15));
    transition: filter 0.25s ease;
}

.site-title:hover .nav-monogram {
    filter: drop-shadow(0 0 12px rgba(45, 90, 74, 0.35));
}

body.dark-mode .nav-monogram {
    filter: drop-shadow(0px 2px 2px rgba(0, 0, 0, 0.5)) drop-shadow(0px -1px 1px rgba(255, 255, 255, 0.1));
}

body.dark-mode .site-title:hover .nav-monogram {
    filter: drop-shadow(0 0 12px rgba(241, 237, 234, 0.3));
}

/* Desktop inline nav links */
.nav-link-inline {
    font-family: var(--font-heading);
    font-weight: 700;
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--color-dark-green);
    text-decoration: none;
    text-shadow: var(--emboss-rest);
    transition: text-shadow 0.25s ease, color 0.2s ease;
    white-space: nowrap;
    position: relative;
    z-index: 1;
}

.nav-link-inline:hover {
    color: var(--color-accent);
    text-shadow: 0 0 8px rgba(45, 90, 74, 0.375), 0 0 20px rgba(45, 90, 74, 0.26), 0 0 40px rgba(45, 90, 74, 0.135);
}

.nav-link-inline:active {
    color: var(--color-accent);
    text-shadow: 0 0 8px rgba(45, 90, 74, 0.375), 0 0 20px rgba(45, 90, 74, 0.26), 0 0 40px rgba(45, 90, 74, 0.135);
    transform: translateY(0.5px);
}

body.dark-mode .nav-link-inline {
    color: var(--color-soft-white);
    text-shadow: var(--emboss-rest);
}

body.dark-mode .nav-link-inline:hover {
    color: var(--color-soft-white);
    text-shadow: 0 0 8px rgba(241, 237, 234, 0.34), 0 0 20px rgba(241, 237, 234, 0.225), 0 0 40px rgba(241, 237, 234, 0.11);
}

body.dark-mode .nav-link-inline:active {
    color: var(--color-soft-white);
    text-shadow: 0 0 8px rgba(241, 237, 234, 0.34), 0 0 20px rgba(241, 237, 234, 0.225), 0 0 40px rgba(241, 237, 234, 0.11);
    transform: translateY(0.5px);
}

/* RSVP nav link */
.nav-rsvp {
    position: relative;
}

.nav-rsvp:active {
    transform: translateY(0.5px);
}

/* Nav link groups */
.nav-links-left,
.nav-links-right {
    display: flex;
    align-items: center;
    gap: 1.8rem;
    z-index: 1;
}

/* Mobile menu — hidden on desktop */
.mobile-menu-btn {
    display: none;
}

.mobile-menu-panel {
    display: none;
}

/* Content fades when menu is open */
main.menu-open,
.site-footer.menu-open {
    opacity: 0.4;
    pointer-events: none;
    transition: opacity 0.3s ease;
}

/* ===========================
   Footer — Floating, No Border
   =========================== */

.site-footer {
    margin-top: auto;
    background: transparent;
    overflow: visible;
}

.footer-content {
    display: flex;
    justify-content: flex-end;
    align-items: center;
    padding: 0.5rem 1.5rem 2rem;
    max-width: 900px;
    margin: 0 auto;
    overflow: visible;
}

.footer-info {
    font-family: var(--font-heading);
    font-weight: 700;
    font-size: 0.55rem;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    color: var(--color-dark-green);
    opacity: 0.4;
    margin: 0;
    white-space: nowrap;
    text-shadow: none;
    transition: color 400ms ease, opacity 400ms ease;
}

.footer-line {
    display: inline;
}

body.dark-mode .footer-info {
    color: var(--color-soft-white);
    opacity: 0.35;
}

/* Dark/light mode toggle */
.dark-mode-toggle {
    padding: 0;
}

.platform-light-toggle {
    display: flex;
    align-items: center;
    justify-content: center;
    background: none;
    border: none;
    cursor: pointer;
    padding: 0;
    width: 48px;
    height: 48px;
    -webkit-user-select: none;
    user-select: none;
}

.toggle-sym {
    font-size: 28px;
    line-height: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 48px;
    height: 48px;
    background: rgba(48, 78, 62, 0.75);
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
    filter: drop-shadow(0 1px 0 rgba(255, 255, 255, 0.75)) drop-shadow(0 -1px 1px rgba(26, 58, 46, 0.16));
    transition: filter 0.3s ease;
    position: relative;
}

body.dark-mode .toggle-sym {
    font-size: 36px;
    background: rgba(45, 90, 74, 0.5);
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
    filter: drop-shadow(0 1px 0 rgba(241, 237, 234, 0.08)) drop-shadow(0 -1px 1px rgba(0, 0, 0, 0.35));
}

.toggle-aura {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 100px;
    height: 100px;
    border-radius: 50%;
    pointer-events: none;
    z-index: -1;
    animation: breathe 4s ease-in-out infinite;
    background: radial-gradient(circle, rgba(48, 78, 62, 0.08) 0%, rgba(48, 78, 62, 0.04) 25%, rgba(48, 78, 62, 0.015) 50%, transparent 75%);
    filter: blur(8px);
}

body.dark-mode .toggle-aura {
    background: radial-gradient(circle, rgba(241, 237, 234, 0.08) 0%, rgba(241, 237, 234, 0.035) 25%, rgba(241, 237, 234, 0.012) 50%, transparent 75%);
}

@keyframes breathe {
    0% {
        opacity: 0.7;
        transform: translate(-50%, -50%) scale(0.95);
    }

    50% {
        opacity: 1;
        transform: translate(-50%, -50%) scale(1.05);
    }

    100% {
        opacity: 0.7;
        transform: translate(-50%, -50%) scale(0.95);
    }
}

/* ===========================
   Buttons
   =========================== */

/* Normal button — background-colored debossed button */
.btn-normal {
    display: inline-block;
    font-family: var(--font-heading);
    font-weight: 700;
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--color-dark-green);
    background-color: var(--color-soft-white);
    padding: 0.5rem 1.2rem;
    border-radius: 25px;
    border: 2px solid rgba(26, 58, 46, 0.2);
    text-decoration: none;
    text-shadow: none;
    cursor: pointer;
    box-shadow: var(--shadow-raised);
    position: relative;
    overflow: hidden;
    transition: box-shadow 0.2s ease, transform 0.2s ease, background-color 0.2s ease, color 400ms ease, border-color 400ms ease;
}

.btn-normal:hover {
    box-shadow: var(--shadow-lifted);
    background-color: #ece8e4;
}

.btn-normal:active {
    box-shadow: var(--shadow-pressed);
    background-color: #e1dcd7;
    transform: translateY(0.5px);
}

body.dark-mode .btn-normal {
    color: var(--color-soft-white);
    background-color: var(--color-dark-bg);
    border: 2px solid rgba(241, 237, 234, 0.12);
}

body.dark-mode .btn-normal:hover {
    background-color: #10261c;
}

body.dark-mode .btn-normal:active {
    background-color: #0a1e14;
}

/* Priority button — filled by default, lift/press on hover/active */
.btn-priority {
    display: inline-block;
    font-family: var(--font-heading);
    font-weight: 700;
    font-size: 0.8rem;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    padding: 0.7rem 2rem;
    border-radius: 25px;
    border: 2px solid rgba(0, 0, 0, 0.12);
    text-decoration: none;
    text-shadow: none;
    cursor: pointer;
    background-color: var(--color-dark-green);
    color: var(--color-soft-white);
    box-shadow: var(--shadow-raised);
    position: relative;
    overflow: hidden;
    transition: box-shadow 0.2s ease, transform 0.2s ease, background-color 400ms ease, color 400ms ease, border-color 400ms ease;
}

.btn-priority::before,
.btn-normal::before,
.password-submit::before {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: inherit;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='300'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='1'/%3E%3C/svg%3E");
    mix-blend-mode: soft-light;
    opacity: 0.18;
    pointer-events: none;
}

.btn-priority:hover {
    box-shadow: var(--shadow-lifted);
    background-color: #17352a;
}

.btn-priority:active {
    box-shadow: var(--shadow-pressed);
    background-color: #112a20;
    transform: translateY(0.5px);
}

body.dark-mode .btn-priority {
    border: 2px solid rgba(0, 0, 0, 0.15);
    background-color: var(--color-soft-white);
    color: var(--color-dark-green);
}

body.dark-mode .btn-priority:hover {
    background-color: #ece7e3;
}

body.dark-mode .btn-priority:active {
    background-color: #dfd9d4;
}

/* ===========================
   Content Sections
   =========================== */

.content-wrapper {
    max-width: 680px;
    margin: 0 auto;
    padding: 0 2rem;
}

/* Small uppercase label heading (PP Watch) — shared by FAQ questions,
   Travel hotel/transit labels, and DC Guide day labels */
.label-heading {
    font-family: var(--font-heading);
    font-weight: 700;
    font-size: 0.82rem;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    margin-bottom: 0.75rem;
}

/* Hotel List */
.hotel-list {
    margin-top: 2rem;
}

/* ===========================
   Homepage
   =========================== */

body.page-home #protected-content.unlocked {
    display: flex;
    flex-direction: column;
    min-height: 100vh;
}

body.page-home .registry-page {
    padding-top: 120px;
    flex: 1;
    display: flex;
    flex-direction: column;
}

body.page-home .registry-section {
    flex: 1;
    display: flex;
    flex-direction: column;
}

/* Homepage hero — vertically centered landing page */
.homepage-hero {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    flex: 1;
}

.homepage-hero .std-names-image {
    max-width: 680px;
    width: 100%;
    margin: 0 auto 1.5rem;
}

.homepage-date {
    font-family: var(--font-heading);
    font-size: 1.2rem;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    font-weight: 700;
    margin-bottom: 0.4rem;
}

.homepage-location {
    font-family: var(--font-body);
    font-size: 1rem;
    margin-bottom: 0;
}

/* ===========================
   Paper Card System — shared "sheet of paper" component
   Three layers: CSS-tiled grain (::before), baked ink or frame PNG
   (.paper-card__ink / .paper-card__frame), and a sheet box-shadow. Follows the
   floating nav's physical logic (flat fill + shadow = separation), extended
   with CSS grain because these are opaque rectangles, not the nav's
   alpha-shaped diamond — the "no CSS grain" rule applies to alpha-shaped
   transparent PNGs, not opaque rectangles (the body proves grain-on-rectangle
   is safe). Used by the RSVP invitation end-state (.invitation-card) and,
   going forward, the RSVP form cards (.paper-card--page).
   =========================== */
.paper-card {
    position: relative;
    aspect-ratio: 1500 / 2100;
    background-color: #F1EDEA;
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.22);
}

.paper-card::before {
    content: '';
    position: absolute;
    inset: 0;
    background-image: url('../assets/textures/combined-light.png');
    background-size: 200px 200px;
    background-repeat: repeat;
    /* Custom property so a settle transition (e.g. the RSVP hard cut) can fade
       grain in rather than having it present from the first paint. */
    opacity: var(--card-grain-opacity, 1);
    pointer-events: none;
    transition: opacity var(--settle-ms, 450ms) ease;
}

body.dark-mode .paper-card {
    /* Tile-load-failure fallback only — the tile is fully opaque, so it alone
       determines the rendered colour whenever it's loaded; this hex is what
       renders approximately if the tile 404s. Approximate by nature and will
       drift if --card-dark-tint below is retuned — that's acceptable. */
    background-color: #132C22;
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.3);
}

body.dark-mode .paper-card::before {
    /* The card now shares the BODY's grain tile (combined-dark.png) rather than
       its own darker combined-card-dark.png, which rendered at luminance 29.6 —
       ~11 below the body surface and ~4 below the nav, reading as too dark.
       These tiles are fully opaque, so the tile alone determines the surface
       colour; .paper-card's background-color above is a load-failure fallback,
       not a contributor.
       --card-dark-tint drops the card a couple of luminance points under the
       body so it still reads as a distinct sheet. 1.0 = identical to the body;
       useful range ~0.90–1.00. Tunable in DevTools; once the value is settled it
       should be baked into a re-exported tile (see decisions.md) so the surface
       goes back to being purely a baked asset. combined-card-dark.png is now
       unreferenced anywhere — kept in the repo as the starting point if the
       darker treatment is ever wanted back. */
    background-image: url('../assets/textures/combined-dark.png');
    filter: brightness(var(--card-dark-tint, 0.94));
}

.paper-card__ink,
.paper-card__frame {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
}

.paper-card__content {
    position: relative;
    padding: 8% 10%;
}

/* Form-card scaffold (shell only — no form/typography built here). Each step
   of the RSVP flow is intended to render as .paper-card.paper-card--page:
   the same paper/grain/shadow, a shared resting width so the invitation and
   the form cards can be aligned in one place later (the post-cut resize
   settle itself is separate, unconfirmed work), a .paper-card__frame overlay,
   and ink-scoped text/controls so content reads as the card's surface rather
   than the page's. */
.paper-card--page {
    width: var(--card-resting-width, min(90vw, 560px));
    color: #1a3a2e;
}

body.dark-mode .paper-card--page {
    color: #F1EDEA;
}

/* ===========================
   RSVP two-stage landing (rsvp.html)
   Stage 1 (hard cut): #invitation-endstate in its Rive-matched state — the baked
   last-motion end-frame (green field + tunnel + train, #183A2B, no card) behind
   the live card, no chrome. Stage 2 (settle): js/rive-intro.js adds
   `intro-complete` to <body> shortly after the cut; the rules below transition the
   page into a normal brand page over var(--settle-ms) (set from SETTLE_MS in JS;
   bail paths set it to 0ms so return visits land settled instantly).
   =========================== */

/* Fixed one-viewport composition through the cut — nothing scrolls (the card is
   fixed). Also load-bearing for registration: no scrollbars means the layout width
   always equals window.innerWidth, so the canvas box (sized from innerWidth/Height)
   and .invitation-field (sized from 100vw/100dvh) are the SAME box in every browser,
   including classic-scrollbar ones. Scroll unlocks only once settled — see
   body.page-rsvp.intro-complete below — so this registration guarantee holds for
   the entire pre-settle chain (password gate, intro, and the flat cut frame).
   Horizontal axis only needs overflow-y here — html,body's own overflow-x: clip
   (Global horizontal-scroll guard, above) covers X unconditionally at every stage. */
body.page-rsvp {
    overflow-y: hidden;
}

/* .invitation-endstate's containing block for its post-settle position:absolute
   (see below) — established explicitly rather than relying on the initial
   containing block, so the scroll behavior is unambiguous. Doesn't affect layout
   pre-settle (nothing inside is absolutely positioned against it until then). */
body.page-rsvp #protected-content {
    position: relative;
}

/* Settle unlocks vertical scroll — the fixed one-viewport composition above is
   only a pre-settle/registration constraint; X stays covered by the global
   rule above. Mirrors the existing body.page-rsvp.rsvp-flow-active rule in
   rsvp-styles.css (same value); that rule stays for when the flow is entered
   directly off a fast settle. */
body.page-rsvp.intro-complete {
    overflow-y: auto;
}

/* Matte-green base until the settle, so the unlock -> first-frame gap and the cut
   both read as tunnel green, never a cream flash. The texture tile is opaque, so
   background-image must be cleared for the color to show. Once .intro-complete is
   present this rule stops matching and the GLOBAL body rules take over — standard
   textured cream, and dark mode via the existing body.dark-mode mechanism. Body's
   own matte->cream swap is always hidden behind an opaque layer (password overlay,
   then canvas, then the opaque #invitation-endstate) — the visible fade the viewer
   sees is #invitation-endstate's own alpha fade below. addIntroComplete() in
   js/rive-intro.js bypasses the global 400ms body transition for this one flip so
   it snaps in sync rather than riding that unrelated, purposeless transition (see
   its comment — a masked-but-real green flash if a paint ever lands mid-transition). */
body.page-rsvp:not(.intro-complete) {
    background-image: none;
    background-color: #183a2c;
}

/* RSVP invitation end-state — the static page the Metro intro hard-cuts to.
   Reproduces the animation's last-motion frame: a baked green+tunnel+train PNG
   (assets/rive/metro-endframe.png, no card — see .endstate-endframe below) behind
   the live invitation card, matching #rive-container's own background exactly
   (both are the same scene green, #183A2B, for the whole intro — see its comment).
   Managed by js/rive-intro.js, which reveals #invitation-endstate at the hard cut
   (see .pre-reveal below). */
body.page-rsvp .invitation-endstate {
    position: fixed;
    inset: 0;
    z-index: 1500;
    /* Scene green — matches #rive-container and the baked end-frame PNG's own
       background exactly, so the cut is invisible at any window shape. The settle
       fades this to transparent WHILE .endstate-endframe (below) fades its baked
       image to transparent on the same clock — green field and drawn scene dissolve
       into the textured tile together, as one motion (grain is intentionally NOT in
       the .riv — it arrives with the settle). */
    background-color: #183A2B;
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    /* Nothing inside is interactive — let clicks reach the nav/footer/replay beneath. */
    pointer-events: none;
    transition: background-color var(--settle-ms, 450ms) ease;
}

/* Baked last-motion frame (green field + tunnel + train, no card) sitting behind
   the live invitation card. #invitation-endstate is full-viewport (fixed inset:0
   pre-settle, absolute + 100dvh tall post-settle) — the same box #rive-container's
   canvas filled during playback — so background-size: contain + center here
   reproduces the canvas's own Fit.Contain placement exactly, with no separate math
   to keep in sync. z-index: 0, and it precedes .invitation-field in the DOM, so
   .invitation-field's own z-index: 0 (its stacking-context sibling, later in
   source order) wins the tie and paints on top — this sits behind the card, above
   the green fill. Fades out on the settle; see the intro-complete pair below. */
body.page-rsvp .endstate-endframe {
    position: absolute;
    inset: 0;
    /* Fills the pillarbox/letterbox regions outside the contained 16:9 art (any
       viewport that isn't exactly 16:9 has some) with the SAME green as the art's
       own field, so the whole layer is one uniform green surface with the scene
       drawn in its center — not two different stacks (art-over-green in the
       center, bare green at the sides) fading on either side of a seam. Without
       this, the center fades art+green together while the sides fade only the
       green beneath (this element itself was transparent there), which read as
       faint vertical bars on wide viewports during the dissolve (horizontal bars
       on tall/portrait ones). The .invitation-endstate green fill one layer down
       becomes redundant for this specific seam but stays — it's still the
       letterbox-match safety for the moment before this layer has painted. */
    background-color: #183A2B;
    background-image: url('../assets/rive/metro-endframe.png');
    background-size: contain;
    background-position: center;
    background-repeat: no-repeat;
    opacity: 1;
    pointer-events: none;
    z-index: 0;
    transition: opacity var(--settle-ms, 450ms) ease;
}

/* Settle: the flat green fades out (in lockstep with .endstate-endframe's own
   image-opacity fade, right below), revealing the textured brand page beneath.
   The layer drops below the nav/footer (z 1000+) so the chrome floats over the
   card like on any other page. Explicit rgba (not `transparent`) keeps the hue.
   position also switches fixed -> absolute here, pinned to the same top-left
   corner (identical rect at scrollY:0 — .invitation-field and .invitation-card's
   own positioning, sized/centered relative to THIS box, are completely untouched)
   but now scrolling with the document instead of staying pinned to the viewport,
   so the card (and the arrow, positioned relative to the same field) travel with
   the page over .invitation-scroll-spacer below. inset:0 is replaced with an
   explicit height rather than a bottom edge, so growing the document (the
   spacer) doesn't stretch this box or re-center the field within it. */
body.page-rsvp.intro-complete .invitation-endstate {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: auto;
    height: 100dvh;
    background-color: rgba(24, 58, 43, 0);
    z-index: 1;
}

/* .endstate-endframe fades out on the same --settle-ms clock as the green fill
   above (and the card's own grain/shadow, and — on mobile portrait — the card's
   settle-grow) — green field, tunnel, and train all dissolve into the textured
   paper together, as one motion. */
body.page-rsvp.intro-complete .endstate-endframe {
    opacity: 0;
}

/* Fixed grain backdrop (July 2026 — "Settled RSVP page: fixed paper field,
   card scrolls over it"). #invitation-endstate above is already fully
   transparent post-settle and already scrolls normally with the document
   (position: absolute inside the normally-flowing #protected-content) — it
   was built that way so body's own grain would show through as the page
   scrolled. That's also exactly the "card scrolls over a backdrop" shape
   this wants; the only thing that needed to change is that the backdrop
   itself must NOT scroll. This div is a genuinely position: fixed element
   (NOT background-attachment: fixed, which iOS Safari disables) carrying the
   same grain tile as body's own background, painted behind everything else.
   As #invitation-endstate's own green fill fades out at the settle, it
   reveals this stationary layer instead of body's (formerly scrolling) one —
   the fade logic itself is completely untouched. Hidden until intro-complete
   so it can never appear during the matte-green pre-settle phase (would
   otherwise be a green flash during the brief unlock -> canvas-reveal gap on
   a first-time playback, since #rive-container sits at opacity: 0 for a
   moment before revealCanvas() runs). */
body.page-rsvp .rsvp-fixed-backdrop {
    display: none;
}

body.page-rsvp.intro-complete .rsvp-fixed-backdrop {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 0;
    background-color: #F1EDEA;
    background-image: url('../assets/textures/combined-light.png');
    background-size: 200px 200px;
    background-repeat: repeat;
}

body.dark-mode.page-rsvp.intro-complete .rsvp-fixed-backdrop {
    background-color: #122a20;
    background-image: url('../assets/textures/combined-dark.png');
}

/* Scroll room below the settled card — the fixed backdrop above shows
   through (no background of its own here), no new content. Collapsed to
   nothing pre-settle so it can't affect the registration-critical pre-cut
   layout, and again once the RSVP card flow is entered (rsvp-styles.css) so
   it doesn't push the flow's first panel down. Height is 0 on the settled
   page too (July 2026 — was 100vh, before that `100vh + 60vh`): the settled
   page is meant to read as exactly one viewport, like the homepage, with
   nothing to scroll past. With this at 0, the footer is the ONLY in-flow
   content inside #protected-content; #protected-content.unlocked's own
   min-height:100vh floor holds the box open, and body.page-rsvp
   #footer-placeholder { margin-top:auto } pins the footer to the bottom of
   that exact one-viewport box — no magic `calc(100vh - footerHeight)`
   needed, the auto margin already does it. Document height lands at exactly
   100vh: no scroll target, so a trackpad/rubber-band pull snaps back with
   nowhere to go. On mobile, js/rsvp-flow.js grows this further to fit
   whatever the current reply-card-stack card needs (see
   ensureMobileScrollRoom) — unaffected, since the flow-active state below
   already collapses this rule regardless of its settled-page value. */
.invitation-scroll-spacer {
    display: none;
}

body.page-rsvp.intro-complete .invitation-scroll-spacer {
    display: block;
    height: 0;
}

/* Hidden until the intro tears down (hard cut / skip / bail); prevents the
   invitation from flashing before the tunnel animation plays. */
body.page-rsvp .invitation-endstate.pre-reveal {
    display: none;
}

/* 16:9 field = a Contain replica of the canvas box: the 1920x1080 artboard as
   rendered in the viewport (width-limited on portrait, height-limited on wide),
   centered both axes exactly like the fullscreen canvas. It is purely a
   REGISTRATION box for the card — no background of its own — and must not clip:
   the card intentionally bleeds ~20px past its top and bottom edges. */
body.page-rsvp .invitation-field {
    position: relative;
    width: min(100vw, calc(100vh * 16 / 9));
    width: min(100vw, calc(100dvh * 16 / 9));
    aspect-ratio: 16 / 9;
    overflow: visible;
}

/* The invitation card — a .paper-card (see the Paper Card System block above),
   placed by CHOSEN artboard fractions of 1920x1080 (registration policy
   inversion, July 2026: this CSS is now the source of truth and the .riv's
   final keyframes get nudged to match it via the ?debug-registration overlay
   in js/rive-intro.js — these values are no longer measured off the .riv).
   Target artboard rect: x=650, w=620, y=145 -> left = 650/1920 = 33.854%,
   width = 620/1920 = 32.292%, top = 145/1080 = 13.426%. Centered horizontally,
   parked high with margin above and below (card height ≈ 80% of field height
   at this aspect ratio) — no bleed past the field's top or bottom edges. */
body.page-rsvp .invitation-card {
    position: absolute;
    left: 33.854%;
    width: 32.292%;
    top: 13.426%;
    height: auto;
    aspect-ratio: 1500 / 2100;
    display: block;
    /* Flat at the hard cut — must be pixel-identical to the .riv's final frame,
       which has no grain and no shadow. Both arrive with the settle below. */
    --card-grain-opacity: 0;
    box-shadow: none;
    transition: box-shadow var(--settle-ms, 450ms) ease;
}

/* Settle: grain and the sheet shadow fade in together on the wrapper... */
body.page-rsvp.intro-complete .invitation-card {
    --card-grain-opacity: 1;
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.22);
}

body.dark-mode.page-rsvp.intro-complete .invitation-card {
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.3);
}

/* ...while the illustration emboss (same drop-shadow pair as
   .registry-illustration, incl. the dark variant) applies to the ink layer
   itself, not the wrapper — text and frame press into the paper while the
   sheet shadow lifts the whole card off the page. Filters don't reflow: this
   cannot move the card. In dark mode the ink asset INVERTS to cream via the
   data-light/data-dark swap. */
body.page-rsvp .invitation-card .paper-card__ink {
    transition: filter var(--settle-ms, 450ms) ease;
}

body.page-rsvp.intro-complete .invitation-card .paper-card__ink {
    filter: drop-shadow(0px 2px 2px rgba(255, 255, 255, 1)) drop-shadow(0px -1px 1px rgba(0, 0, 0, 0.15));
}

body.dark-mode.page-rsvp.intro-complete .invitation-card .paper-card__ink {
    filter: drop-shadow(0px 2px 2px rgba(0, 0, 0, 0.5)) drop-shadow(0px -1px 1px rgba(255, 255, 255, 0.1));
}

/* ===========================
   RSVP paper suite — the reply-card stack
   Shares .invitation-field's coordinate system so every stack card (lookup,
   event cards, review, thanks) renders in the EXACT same on-screen rect as
   .invitation-card above — same field, same three fractions, restated here
   rather than factored into a shared variable so .invitation-card's own rule
   (registration-critical, ?debug-registration-verified) is never touched by
   this feature. If the target artboard rect above ever changes, update both.
   z-index: 0 gives .invitation-field its own stacking context so the stack's
   depth-based z-indices (see js/rsvp-flow.js) can't collide with anything
   else on the page.
   =========================== */
body.page-rsvp .invitation-field {
    z-index: 0;
}

.rsvp-stack {
    position: absolute;
    inset: 0;
    pointer-events: none;
}

body.page-rsvp .rsvp-stack .paper-card--page {
    position: absolute;
    left: 33.854%;
    width: 32.292%;
    top: 13.426%;
    height: auto;
    /* Reply cards are variable-height (content defines it) — this overrides
       the base .paper-card rule's fixed aspect-ratio, which stays untouched
       for .invitation-card (5:7, registration-critical, unaffected since
       this selector only ever matches .rsvp-stack children). min-height
       keeps short cards (lookup) from reading as a slip of paper. */
    aspect-ratio: auto;
    min-height: 340px;
    display: block;
    pointer-events: auto;
}

/* .invitation-card only gains a resting transform/z-index once it becomes a
   stack participant (the moment the peek appears) — js/rsvp-flow.js adds this
   class then, never before, so the cut/settle frame is untouched by it. */
body.page-rsvp .invitation-card.rsvp-stack-member {
    z-index: 500;
}

/* Nav + footer: present but invisible during the intro (so they can fade in with the
   settle), and non-interactive until visible. */
body.page-rsvp .main-nav,
body.page-rsvp .site-footer {
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--settle-ms, 450ms) ease;
}

body.page-rsvp.intro-complete .main-nav,
body.page-rsvp.intro-complete .site-footer {
    opacity: 1;
    pointer-events: auto;
}

/* min-height: 100vh (unchanged) is what makes the settled page have its
   ~60-80px of residual scroll on iOS in the first place — #protected-content
   is sized to the LAYOUT viewport (min-height: 100vh) while #invitation-
   endstate, the fixed backdrop the guest actually sees, is sized to the
   VISUAL viewport (height: 100dvh); the gap between the two is the browser
   toolbar. The scroll extent must stay exactly what it is today — do not
   expand or remove this. It no longer needs to reach the footer via flex/
   margin-auto, though: both .site-footer and .replay-intro are
   position: fixed below, pinned to the viewport rather than riding that
   scroll with the document. #footer-placeholder's own margin-top: auto
   (previously load-bearing here, pinning the in-flow footer to the bottom
   of this flex column) is removed as dead weight now that the footer it
   wrapped is out of flow — it does nothing. */
body.page-rsvp #protected-content.unlocked {
    min-height: 100vh;
}

/* Both the train and the footer's dark-mode toggle are pinned to the
   VIEWPORT (not the document) so they hold still against the settled page's
   residual scroll instead of traveling with the card through it — the same
   reference frame the fixed nav already uses. bottom:0/right:0 match
   .footer-content's own padding for the corner inset; z-index: 0 (July 2026)
   puts the toggle BELOW #invitation-endstate's own z-index: 1, so the reply
   cards paint over it the same way they paint over .rsvp-arrow /
   .rsvp-stack-back at z-index 400 — the toggle reads as pressed into the
   base paper surface, not floating on top of the sheets. It was 1600, which
   put it in a layer above the entire card stack and landed the moon inside
   the Saturday card's frame. Scrolling a tall card up reveals the toggle
   from underneath; scrolling back down covers it again. That is intended:
   being behind the card matters more than being permanently visible. */
body.page-rsvp .site-footer {
    position: fixed;
    bottom: 0;
    right: 0;
    left: auto;
    width: auto;
    margin: 0;
    z-index: 0;
}

/* Replay icon — bottom-left, icon-only. Hidden until the settle; fades in with it.
   Asset is the Metro car line illustration (train-light.svg/train-dark.svg) with the
   standard data-light/data-dark swap. z-index above the (settled) content, below the
   Rive canvas (2000) while the intro plays.
   position: fixed (July 2026 — reverted from a brief absolute stint the same
   month). #protected-content is min-height: 100vh while the fixed backdrop the
   guest actually sees (#invitation-endstate) is height: 100dvh — on iOS those
   differ by the browser toolbar, so the document runs ~60-80px taller than the
   visible viewport and the settled page has that much real scroll. Anchoring
   this to the document (position: absolute, briefly) meant it traveled through
   that scroll with the card while the footer's toggle — fixed to the viewport —
   held still, so a rubber-band pull visibly separated the two again, just with
   the roles reversed from the original complaint. Both this and the footer
   (see body.page-rsvp .site-footer above) now pin to the viewport instead: the
   card scrolls the ~60-80px underneath them, they don't move. bottom/left:20px
   land in the same screen corner either way. No .rsvp-flow-active override is
   needed here (unlike the footer's own in-flow/fixed switch) — see the
   flow-entered rule further down, which hides this control for good rather
   than repositioning it.
   Box is landscape (70x48, ~1.79:1) rather than the old flag's 44x44 square — the
   train's native ~165x92 ratio would render small and thin inside a square built
   for the flag's tall ~0.61:1 portrait shape. 70x48 is the box first sized to the
   train's own proportions (52x36, see prior revision), then scaled up ~35% at
   Andrew's request so it reads as a clearly bigger button next to the footer
   toggle, still flex-centered with a little breathing room rather than a
   hard-cropped fit. bottom/left stay at 20px — same corner, box grows up/right
   from that anchor. */
.replay-intro {
    position: fixed;
    bottom: 20px;
    left: 20px;
    /* z-index: 0 — see body.page-rsvp .site-footer's comment above. */
    z-index: 0;
    width: 70px;
    height: 48px;
    padding: 0;
    background: none;
    border: none;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--settle-ms, 450ms) ease;
}

.replay-intro img {
    width: 100%;
    height: 100%;
    object-fit: contain;
    display: block;
}

body.intro-complete .replay-intro {
    opacity: 0.7;
    pointer-events: auto;
}

body.intro-complete .replay-intro:hover,
body.intro-complete .replay-intro:focus-visible {
    opacity: 1;
}

/* Once inside the RSVP card flow, replay is a one-way control: entering the
   flow hides it for the rest of the page load, full stop — including after
   Back navigates all the way out to the invitation card again. There is no
   answer persistence (the page's only localStorage write is `siteUnlocked`),
   and replay clears sessionStorage's `intro-seen` and reloads — so pressing
   it mid-form, or after filling out cards and backing out, would silently
   throw away everything the guest entered. An earlier version restored the
   icon whenever the invitation card was the visible top card again (tracked
   via `rsvp-invitation-top`, toggled on every stack-nav update — see
   updateStackNav in js/rsvp-flow.js), on the theory that "back at the
   invitation" meant "safe to replay." It wasn't: a guest can back out to the
   invitation with a full set of answers already entered on cards further
   in. `rsvp-flow-active` itself is already a one-way flag (set once, never
   unset — see advanceFrom in js/rsvp-flow.js), so a plain hide here is
   permanent for the rest of the load without needing that finer-grained
   class at all; a guest who entered by accident can still re-enter the flow
   with the RSVP button. `visibility: hidden`, added on a delay equal to the
   opacity fade, takes the button out of the tab order once it's fully
   invisible — opacity/pointer-events alone still leave a hidden button
   focusable, so a keyboard user tabbing through the form could land on it
   and press Enter, which is exactly the data loss this closes. */
body.page-rsvp.rsvp-flow-active .replay-intro {
    opacity: 0;
    pointer-events: none;
    visibility: hidden;
    transition: opacity var(--settle-ms, 450ms) ease,
                visibility 0s linear var(--settle-ms, 450ms);
}

/* Hover/focus jiggle — the train rocks in place, as if idling on its track. Small
   rotation + horizontal shimmy, pivoting near the base (where the wheels would
   meet the rail) rather than the center. Lives on the img, not the button, so it's
   a pure compositor transform with no effect on the button's own box or anything
   around it. No glow — RSVP's breathing glow is the site's sole glow exception. */
@keyframes replay-train-jiggle {
    0%, 100% { transform: rotate(0deg) translateX(0); }
    25% { transform: rotate(-2deg) translateX(-1px); }
    50% { transform: rotate(0deg) translateX(0); }
    75% { transform: rotate(2deg) translateX(1px); }
}

body.intro-complete .replay-intro:hover img,
body.intro-complete .replay-intro:focus-visible img {
    transform-origin: 50% 100%;
    animation: replay-train-jiggle 0.6s ease-in-out infinite;
}

/* Their setting says no animation — and the bail path skips the intro anyway,
   so replay would no-op. Hide it entirely (which alone already keeps the jiggle
   above unreachable; the explicit override just makes that self-contained here
   too, independent of the display:none). */
@media (prefers-reduced-motion: reduce) {
    .replay-intro {
        display: none;
    }

    body.intro-complete .replay-intro:hover img,
    body.intro-complete .replay-intro:focus-visible img {
        animation: none;
    }
}

/* RSVP internal staging page — standard content-page top padding to clear the fixed nav */
body.page-rsvp-internal .registry-page {
    padding-top: 120px;
    flex: 1;
}

/* ===========================
   Rive intro (rsvp.html)
   Fullscreen Metro animation layer that plays after password unlock,
   then fades out to reveal the RSVP page beneath. Managed by js/rive-intro.js.
   z-index sits above the nav (1000/1001) but below the password overlay (9999).
   =========================== */
#rive-container {
    position: fixed;
    inset: 0;
    z-index: 2000;
    background-color: #183A2B;
    /* Scene green — the exact background hex of the baked end-frame PNG
       (assets/rive/metro-endframe.png), sampled from the .riv's own tunnel scene.
       The animation uses Fit.Contain, so on any viewport that isn't 16:9 this fill
       shows as the letterbox bars (top/bottom on tall/mobile viewports, sides on
       wide ones) — matching the tunnel scene's own background for the ENTIRE intro.
       It never changes color: the cut swaps straight to the baked end-frame layer
       beneath #invitation-endstate, which starts at this same green and dissolves
       to transparent on the settle — one dissolve, one clock, no separate
       letterbox fade to keep in sync. */
    opacity: 1;
    pointer-events: auto;
}

#rive-container.hidden {
    opacity: 0;
    pointer-events: none;
}

#rive-container canvas {
    width: 100%;
    height: 100%;
    display: block;
}

#skip-intro {
    position: fixed;
    bottom: 24px;
    right: 24px;
    z-index: 2001;
    color: rgba(241, 237, 234, 0.4);
    /* #F1EDEA at 40% */
    font-family: var(--font-heading);
    font-weight: 700;
    font-size: 15px;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    background: none;
    border: none;
    cursor: pointer;
    transition: color 0.2s ease;
    padding: 8px;
}

#skip-intro:hover,
#skip-intro:focus-visible {
    color: rgba(241, 237, 234, 0.8);
    outline: none;
}

#skip-intro.hidden {
    display: none;
}

.homepage-illustration {
    margin-top: 2rem;
}

.homepage-illustration .dupont-icon {
    max-height: 140px;
    width: auto;
    opacity: 0.85;
}

/* ===========================
   Save the Date Page
   =========================== */

.save-the-date-content {
    padding: 6rem 0 5rem;
    text-align: center;
}

.std-announcement {
    max-width: 700px;
    margin: 0 auto 4rem;
}

.std-heading {
    font-family: var(--font-heading);
    font-size: 1.5rem;
    text-transform: uppercase;
    letter-spacing: 0.15em;
    margin-bottom: 2rem;
    font-weight: 400;
}

.std-intro {
    font-family: var(--font-body);
    font-size: 1.1rem;
    line-height: 1.7;
    margin-bottom: 1.5rem;
}

.std-names {
    font-family: var(--font-title);
    font-size: 3.5rem;
    margin-bottom: 1.5rem;
    font-weight: 400;
}

.std-names-image {
    max-width: 100%;
    height: auto;
    margin: 1.5rem auto;
    display: block;
    filter: drop-shadow(0px 2px 2px rgba(255, 255, 255, 1)) drop-shadow(0px -1px 1px rgba(0, 0, 0, 0.15));
}

.std-details {
    font-family: var(--font-body);
    font-size: 1.1rem;
    line-height: 1.7;
}

.std-travel {
    max-width: 700px;
    margin: 0 auto;
    padding-top: 3rem;
    border-top: var(--border-hairline);
}

.std-section-title {
    font-family: var(--font-title);
    font-size: 4.5rem;
    margin-bottom: 1rem;
    font-weight: 400;
}

.std-travel-intro {
    font-family: var(--font-body);
    font-size: 1rem;
    line-height: 1.7;
    margin-bottom: 3.5rem;
    text-align: left;
}

.std-hotel {
    margin-bottom: 2.5rem;
    text-align: left;
}

.std-hotel:last-child {
    margin-bottom: 0;
}

.std-hotel-name {
    font-family: var(--font-heading);
    font-weight: 700;
    font-size: 1.1rem;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    margin-bottom: 0.75rem;
}

.std-hotel-description {
    font-family: var(--font-body);
    font-size: 1rem;
    line-height: 1.7;
    margin-bottom: 0.6rem;
}

.dupont-icon {
    filter: drop-shadow(0px 2px 2px rgba(255, 255, 255, 1)) drop-shadow(0px -1px 1px rgba(0, 0, 0, 0.15));
}

body.dark-mode .std-travel {
    border-top-color: rgba(241, 237, 234, 0.2);
}

body.dark-mode .std-names-image {
    filter: drop-shadow(0px 2px 2px rgba(0, 0, 0, 0.5)) drop-shadow(0px -1px 1px rgba(255, 255, 255, 0.1));
}

body.dark-mode .dupont-icon {
    filter: drop-shadow(0px 2px 2px rgba(0, 0, 0, 0.5)) drop-shadow(0px -1px 1px rgba(255, 255, 255, 0.1));
}

/* Announcement icon (Save the Date) */
.announcement-icon {
    display: block;
    text-align: center;
    margin-top: 2rem;
}

.announcement-icon img {
    width: 250px;
    height: auto;
    opacity: 0.9;
}

/* Line break control (Save the Date) */
.mobile-break {
    display: none;
}

.desktop-break {
    display: inline;
}

/* ===========================
   Registry Page
   =========================== */

body.page-registry .registry-page {
    padding-top: 120px;
    flex: 1;
}

body.page-registry .registry-page .content-wrapper {
    max-width: 700px;
}


.registry-title {
    font-family: var(--font-title);
    font-size: clamp(4rem, 8vw, 5.5rem);
    font-weight: 400;
    color: var(--color-dark-green);
    margin-bottom: 1.5rem;
    line-height: 1;
    text-align: center;
}

.registry-illustration {
    display: block;
    margin: 0 auto 1.2rem;
    width: auto;
    max-width: 200px;
    max-height: none;
    height: 200px;
    object-fit: contain;
    opacity: 0.9;
    filter: drop-shadow(0px 2px 2px rgba(255, 255, 255, 1)) drop-shadow(0px -1px 1px rgba(0, 0, 0, 0.15));
}

body.dark-mode .registry-illustration {
    filter: drop-shadow(0px 2px 2px rgba(0, 0, 0, 0.5)) drop-shadow(0px -1px 1px rgba(255, 255, 255, 0.1));
}

/* Our Story intro photo. Deliberately NOT .registry-illustration: that class
   carries opacity: 0.9 and a two-layer drop-shadow tuned for transparent-PNG
   line art, which on a photograph reads as a washed-out image with a white
   halo. Same footprint as the illustrations (200px / 150px mobile) so the
   page openings stay consistent.

   The hairline ring is load-bearing, not decoration: without it a light-toned
   photo edge dissolves into the cream page and the circle stops reading as a
   circle. Same hairline language as the reply-card frames. */
.story-intro-photo {
    display: block;
    margin: 0 auto 1.2rem;
    width: 200px;
    height: 200px;
    border-radius: 50%;
    object-fit: cover;
    box-shadow: 0 0 0 1px rgba(26, 58, 46, 0.2);
}

body.dark-mode .story-intro-photo {
    box-shadow: 0 0 0 1px rgba(241, 237, 234, 0.2);
}

.page-intro {
    font-family: var(--font-body);
    font-style: normal;
    font-size: 1.05rem;
    line-height: 1.7;
    text-align: left;
    color: var(--color-dark-green);
    max-width: 600px;
    margin: 0 auto 1.5rem;
    opacity: 0.85;
}

.registry-cta {
    text-align: center;
}

body.dark-mode .registry-title,
body.dark-mode .page-intro {
    color: var(--color-soft-white);
}

/* ===========================
   FAQ Page
   =========================== */

/* Joan of Arc illustration size — FAQ only.
   Overrides the shared .registry-illustration size so other pages are untouched.
   Tweak both numbers together (height is the binding constraint for the tall figure;
   max-width caps the width so neither clamps it). */
#faq-illustration {
    max-width: 240px;
    height: 240px;
    /* Shared .registry-illustration sets margin-bottom: 1.2rem;
       nudge it up slightly for breathing room above the first text — FAQ only. */
    margin-bottom: 1.8rem;
}

body.page-faq .password-container {
    max-width: 600px;
}

body.page-faq .password-container h2 {
    white-space: nowrap;
}

body.page-faq .registry-page {
    padding-top: 120px;
    flex: 1;
}

.faq-item {
    max-width: 600px;
    margin: 0 auto 2.5rem;
    text-align: left;
    /* Clears the fixed nav when a deep-link (e.g. #faq-black-tie from the RSVP
       dress tag) scrolls an item to the top. */
    scroll-margin-top: 110px;
}

.faq-item:last-child {
    margin-bottom: 1.5rem;
}

.faq-answer {
    font-family: var(--font-body);
    font-size: 1rem;
    line-height: 1.7;
    text-align: left;
    opacity: 0.85;
    margin-bottom: 0.6rem;
}

.faq-answer:last-child {
    margin-bottom: 0;
}

/* ===========================
   Schedule Page
   =========================== */

body.page-schedule .registry-page {
    padding-top: 120px;
    flex: 1;
}

body.page-schedule .registry-cta {
    margin-top: 2rem;
}

.schedule-day {
    max-width: 600px;
    margin: 0 auto 0.5rem;
    text-align: center;
}

.schedule-date {
    font-family: var(--font-heading);
    font-size: 0.85rem;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    font-weight: 700;
    margin-top: 2rem;
}

.schedule-day:first-child .schedule-date {
    margin-top: 0;
}

.schedule-event {
    max-width: 600px;
    margin: 0 auto 2rem;
    text-align: left;
}

.schedule-event-name {
    font-family: var(--font-title);
    font-size: 2.8rem;
    font-weight: 400;
    margin-bottom: 0.5rem;
    line-height: 1.2;
}

.schedule-event-detail {
    font-family: var(--font-heading);
    font-weight: 500;
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    opacity: 0.7;
    margin-bottom: 0.4rem;
}

.schedule-event-description {
    font-family: var(--font-body);
    font-size: 1.05rem;
    line-height: 1.7;
    text-align: left;
    opacity: 0.85;
    margin-bottom: 0.5rem;
}

/* Manual underline (July 2026). text-decoration is not painted at all by iOS
   Safari on this site — confirmed by device screenshot, and NOT fixed by
   prefixed longhands, explicit text-decoration-thickness, or removing the
   transition on text-decoration-color (all tried first). Root cause never
   identified.

   The line is drawn as a background gradient instead: a 1px band stretched to
   the element width and pinned to the bottom of the padding box. Different
   rendering path, so it is immune to whatever suppresses the decoration.

   currentColor makes the rule follow the text, which means dark mode needs no
   override — but it also means the line is full-strength rather than the 55%
   alpha the original design used. That is deliberate: the whole defect was
   that the underline could not be seen.

   Do not "restore" text-decoration here. It does not work. */
.schedule-link {
    color: inherit;
    text-decoration: none;
    background-image: linear-gradient(currentColor, currentColor);
    background-repeat: no-repeat;
    background-size: 100% 1px;
    background-position: 0 100%;
    padding-bottom: 3px;
    transition: background-size 0.2s ease;
}

.schedule-link:hover {
    background-size: 100% 2px;
}

/* ===========================
   Our Story Page
   =========================== */

body.page-our-story .registry-page {
    padding-top: 120px;
    flex: 1;
}

body.page-dc-guide .registry-page {
    padding-top: 120px;
    flex: 1;
}

.story-timeline {
    max-width: 600px;
    margin: 0 auto;
    padding-top: 0.5rem;
}

.story-moment {
    margin-bottom: 3.5rem;
}

.story-moment:last-child {
    margin-bottom: 1.5rem;
}

.story-year {
    font-family: var(--font-heading);
    font-weight: 700;
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.15em;
    color: var(--color-dark-green);
    text-align: center;
    margin-bottom: 1.2rem;
    opacity: 0.5;
    text-shadow: none;
}

body.dark-mode .story-year {
    color: var(--color-soft-white);
}

.story-photo-single {
    display: block;
    margin: 0 auto;
    max-width: 380px;
}

.story-photo-pair {
    display: flex;
    gap: 14px;
    justify-content: center;
    max-width: 500px;
    margin: 0 auto;
}

.story-photo-pair .story-photo-frame {
    flex: 1;
    max-width: 243px;
}

.story-photo-frame {
    overflow: hidden;
    border-radius: 4px;
}

.story-photo-frame img {
    width: 100%;
    height: auto;
    display: block;
    object-fit: cover;
}

.story-photo-single .story-photo-frame img.photo-portrait {
    aspect-ratio: 3 / 4;
    object-fit: cover;
}

.story-photo-single .story-photo-frame img.photo-landscape {
    aspect-ratio: 4 / 3;
    object-fit: cover;
}

.story-photo-pair .story-photo-frame img {
    aspect-ratio: 3 / 4;
    object-fit: cover;
}

.story-caption {
    font-family: var(--font-body);
    font-style: normal;
    font-size: 1rem;
    line-height: 1.7;
    text-align: left;
    color: var(--color-dark-green);
    max-width: 480px;
    margin: 1rem auto 0;
    opacity: 0.85;
}

body.dark-mode .story-caption {
    color: var(--color-soft-white);
}

.story-caption-label {
    font-family: var(--font-heading);
    font-weight: 700;
    font-size: 0.65rem;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    display: block;
    margin-bottom: 0.3rem;
    opacity: 0.6;
    text-shadow: none;
}

/* Per-photo caption — Our Story */
.story-photo-caption {
    font-family: var(--font-body);
    font-style: normal;
    font-size: 0.95rem;
    line-height: 1.5;
    text-align: center;
    color: var(--color-dark-green);
    max-width: 480px;
    margin: 1rem auto 0;
    opacity: 0.85;
    transition: opacity 180ms ease;
    min-height: 1.5em;
}

body.dark-mode .story-photo-caption {
    color: var(--color-soft-white);
}

.story-photo-caption.is-fading {
    opacity: 0;
}

.story-divider {
    border: none;
    border-top: var(--border-hairline);
    max-width: 200px;
    margin: 3rem auto;
}

body.dark-mode .story-divider {
    border-top-color: rgba(241, 237, 234, 0.2);
}

.story-closing {
    text-align: center;
    font-family: var(--font-title);
    font-size: var(--story-closing-size);
    color: var(--color-dark-green);
    margin-top: 1rem;
    line-height: 1.4;
}

body.dark-mode .story-closing {
    color: var(--color-soft-white);
}

/* Narrative prose blocks */
.story-prose {
    font-family: var(--font-body);
    font-style: normal;
    font-size: 1rem;
    line-height: 1.7;
    text-align: left;
    color: var(--color-dark-green);
    max-width: 480px;
    margin: 0 auto;
    opacity: 0.85;
}

.story-prose p {
    margin: 0 0 1rem 0;
}

.story-prose p:last-child {
    margin-bottom: 0;
}

body.dark-mode .story-prose {
    color: var(--color-soft-white);
}

/* Carousel */
.story-carousel {
    position: relative;
    max-width: 480px;
    margin: 1.5rem auto 0;
}

.story-carousel-track {
    display: flex;
    height: var(--story-carousel-h, 360px); /* carousel box height — tunable */
    overflow-x: auto;
    scroll-snap-type: x mandatory;          /* one photo at a time */
    scroll-behavior: smooth;
    scrollbar-width: none;
    -ms-overflow-style: none;
    border-radius: 4px;
    outline: none;
}

.story-carousel-track::-webkit-scrollbar {
    display: none;
}

.story-carousel-track:focus-visible {
    box-shadow: 0 0 0 2px rgba(26, 58, 46, 0.3);
}

body.dark-mode .story-carousel-track:focus-visible {
    box-shadow: 0 0 0 2px rgba(241, 237, 234, 0.3);
}

.story-carousel-slide {
    flex: 0 0 100%;             /* each slide fills the frame — one photo at a time */
    scroll-snap-align: center;
    display: flex;
    align-items: center;
    justify-content: center;    /* photo centered; blank space on the sides if narrower */
}

.story-carousel-slide img {
    max-width: 100%;
    max-height: 100%;
    width: auto;
    height: auto;
    object-fit: contain;
    display: block;
    border-radius: 4px;
}

/* Any story photo can be clicked to enlarge */
.story-carousel-slide img,
.story-photo-single img,
.story-photo-pair img {
    cursor: zoom-in;
}

/* Lightbox — enlarged photo over a dimmed page */
.story-lightbox {
    position: fixed;
    inset: 0;
    z-index: 2000;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 1rem;
    padding: 5vmin;
    background: rgba(10, 22, 16, 0.94);   /* dims the whole page behind the photo */
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.25s ease, visibility 0.25s ease;
    cursor: zoom-out;
}

.story-lightbox.open {
    opacity: 1;
    visibility: visible;
}

.story-lightbox-figure {
    margin: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 1rem;
    max-width: 94vw;
}

.story-lightbox-img {
    max-width: 94vw;
    max-height: 82vh;
    width: auto;
    height: auto;
    object-fit: contain;
    border-radius: 4px;
    box-shadow: 0 12px 44px rgba(0, 0, 0, 0.55);
}

.story-lightbox-caption {
    color: var(--color-soft-white);
    font-family: var(--font-heading);
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    font-size: 0.8rem;
    line-height: 1.5;
    text-align: center;
    opacity: 0.9;
    max-width: 60ch;
}

.story-lightbox-close {
    position: fixed;
    top: 1rem;
    right: 1.2rem;
    width: 44px;
    height: 44px;
    padding: 0;
    border: none;
    background: none;
    color: var(--color-soft-white);
    font-size: 2rem;
    line-height: 1;
    cursor: pointer;
    opacity: 0.8;
    transition: opacity 0.2s ease;
}

.story-lightbox-close:hover {
    opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
    .story-lightbox {
        transition: none;
    }
}

.story-carousel-arrow {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    width: 38px;
    height: 38px;
    border-radius: 50%;
    background-color: var(--color-soft-white);
    color: var(--color-dark-green);
    border: 2px solid rgba(26, 58, 46, 0.2);
    font-family: var(--font-heading);
    font-weight: 700;
    font-size: 1.1rem;
    line-height: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    text-shadow: none;
    box-shadow: var(--shadow-raised);
    overflow: hidden;
    z-index: 2;
    padding: 0;
    transition: box-shadow 0.2s ease, transform 0.2s ease, background-color 0.2s ease, opacity 0.2s ease, color 400ms ease, border-color 400ms ease;
}

.story-carousel-arrow::before {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: inherit;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='300'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='1'/%3E%3C/svg%3E");
    mix-blend-mode: soft-light;
    opacity: 0.18;
    pointer-events: none;
}

.story-carousel-arrow span {
    position: relative;
    z-index: 1;
    transform: translateY(-1px);
}

.story-carousel-arrow-prev {
    left: -50px;
}

.story-carousel-arrow-next {
    right: -50px;
}

.story-carousel-arrow:hover {
    box-shadow: var(--shadow-lifted);
    background-color: #ece8e4;
}

.story-carousel-arrow:active {
    box-shadow: var(--shadow-pressed);
    background-color: #e1dcd7;
    transform: translateY(calc(-50% + 0.5px));
}

.story-carousel-arrow.disabled {
    opacity: 0.35;
    cursor: default;
    box-shadow: var(--shadow-raised);
}

.story-carousel-arrow.disabled:hover,
.story-carousel-arrow.disabled:active {
    background-color: var(--color-soft-white);
    box-shadow: var(--shadow-raised);
    transform: translateY(-50%);
}

body.dark-mode .story-carousel-arrow {
    color: var(--color-soft-white);
    background-color: var(--color-dark-bg);
    border: 2px solid rgba(241, 237, 234, 0.12);
}

body.dark-mode .story-carousel-arrow:hover {
    background-color: #10261c;
}

body.dark-mode .story-carousel-arrow:active {
    background-color: #0a1e14;
}

body.dark-mode .story-carousel-arrow.disabled:hover,
body.dark-mode .story-carousel-arrow.disabled:active {
    background-color: var(--color-dark-bg);
}

.story-carousel-dots {
    display: flex;
    justify-content: center;
    gap: 8px;
    margin-top: 12px;
}

.story-carousel-dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background-color: rgba(26, 58, 46, 0.25);
    border: none;
    padding: 0;
    cursor: pointer;
    text-shadow: none;
    transition: background-color 0.2s ease;
}

.story-carousel-dot.active {
    background-color: var(--color-dark-green);
}

body.dark-mode .story-carousel-dot {
    background-color: rgba(241, 237, 234, 0.25);
}

body.dark-mode .story-carousel-dot.active {
    background-color: var(--color-soft-white);
}

/* Carousel — section spacing inside .story-moment */
.story-moment .story-prose + .story-carousel {
    margin-top: 2rem;
}

.story-moment .story-carousel + .story-caption-label {
    display: block;
    text-align: center;
    margin: 1rem auto 0;
    max-width: 480px;
}

.story-moment .story-photo-single + .story-caption-label,
.story-moment .story-photo-pair + .story-caption-label {
    display: block;
    text-align: center;
    margin: 1rem auto 0;
    max-width: 480px;
}

/* ===========================
   Travel Page
   =========================== */

body.page-travel .registry-page {
    padding-top: 120px;
    flex: 1;
}

.travel-section-title {
    font-family: var(--font-title);
    font-size: 3.2rem;
    font-weight: 400;
    text-align: left;
    margin: 2.5rem auto 1.5rem;
    max-width: 600px;
    line-height: 1.2;
}

/* DC Guide labels sit inline between prose blocks — more space above, tighter below */
body.page-dc-guide .label-heading {
    margin: 1.4rem 0 0.4rem;
}

/* Body prose under section labels — DC Guide */
.dc-prose {
    font-family: var(--font-body);
    font-size: 1rem;
    line-height: 1.7;
    margin: 0;
}

.section-divider {
    border: none;
    border-top: var(--border-hairline);
    max-width: 600px;
    margin: 3rem auto;
}

body.dark-mode .section-divider {
    border-top-color: rgba(241, 237, 234, 0.2);
}

.travel-hotel {
    max-width: 600px;
    margin: 0 auto 2.5rem;
    text-align: left;
}

.travel-hotel-description {
    font-family: var(--font-body);
    font-size: 1rem;
    line-height: 1.7;
    text-align: left;
    opacity: 0.85;
    margin-bottom: 0.6rem;
}

/* See .schedule-link for the rationale. */
.travel-link {
    color: inherit;
    text-decoration: none;
    background-image: linear-gradient(currentColor, currentColor);
    background-repeat: no-repeat;
    background-size: 100% 1px;
    background-position: 0 100%;
    padding-bottom: 3px;
    transition: background-size 0.2s ease;
}

.travel-link:hover {
    background-size: 100% 2px;
}

.travel-directions {
    max-width: 600px;
    margin: 0 auto;
    text-align: center;
}

.travel-direction-item {
    text-align: left;
    margin-bottom: 1.5rem;
}

/* ===========================
   Responsive — Mobile (900px)
   =========================== */

@media (max-width: 900px) {

    /* Body */
    body {
        font-size: 16px;
    }

    /* Mobile nav — menu button replaces inline links */
    .nav-bar {
        display: none;
    }

    .mobile-menu-btn {
        display: inline-block;
        font-family: var(--font-heading);
        font-weight: 700;
        font-size: 0.7rem;
        text-transform: uppercase;
        letter-spacing: 0.1em;
        color: var(--color-dark-green);
        background-color: var(--color-soft-white);
        padding: 0.5rem 1.5rem;
        border-radius: 25px;
        border: 1px solid rgba(26, 58, 46, 0.15);
        text-shadow: none;
        cursor: pointer;
        box-shadow: var(--shadow-raised);
        position: relative;
        overflow: hidden;
        transition: box-shadow 0.2s ease, transform 0.2s ease, background-color 0.2s ease, color 400ms ease, border-color 400ms ease;
    }

    .mobile-menu-btn::before {
        content: '';
        position: absolute;
        inset: 0;
        border-radius: inherit;
        background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='300'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='1'/%3E%3C/svg%3E");
        mix-blend-mode: soft-light;
        opacity: 0.18;
        pointer-events: none;
    }

    .mobile-menu-btn:active {
        box-shadow: var(--shadow-pressed);
        transform: translateY(0.5px);
    }

    body.dark-mode .mobile-menu-btn {
        color: var(--color-soft-white);
        background-color: var(--color-dark-green);
        border: 1px solid rgba(241, 237, 234, 0.1);
    }

    /* Mobile menu panel */
    .mobile-menu-panel {
        display: none;
        position: absolute;
        top: calc(100% + 0.5rem);
        right: 0;
        background-color: var(--color-soft-white);
        border: 2px solid rgba(26, 58, 46, 0.15);
        border-radius: 12px;
        padding: 0.75rem 0;
        min-width: 160px;
        box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
        z-index: 1001;
        transition: background-color 400ms ease, border-color 400ms ease;
    }

    .mobile-menu-panel::before {
        content: '';
        position: absolute;
        inset: 0;
        border-radius: inherit;
        background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='300'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='1'/%3E%3C/svg%3E");
        mix-blend-mode: soft-light;
        opacity: 0.12;
        pointer-events: none;
    }

    .mobile-menu-panel.open {
        display: block;
    }

    body.dark-mode .mobile-menu-panel {
        background-color: var(--color-dark-green);
        border-color: rgba(241, 237, 234, 0.1);
        box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
    }

    .mobile-menu-link {
        display: block;
        text-align: center;
        font-family: var(--font-heading);
        font-weight: 700;
        font-size: 0.7rem;
        text-transform: uppercase;
        letter-spacing: 0.1em;
        color: var(--color-dark-green);
        text-decoration: none;
        padding: 0.6rem 1.5rem;
        text-shadow: none;
        transition: text-shadow 0.25s ease, color 0.2s ease;
        position: relative;
    }

    .mobile-menu-link:hover {
        color: var(--color-accent);
        text-shadow: 0 0 8px rgba(45, 90, 74, 0.375), 0 0 20px rgba(45, 90, 74, 0.26), 0 0 40px rgba(45, 90, 74, 0.135);
    }

    .mobile-menu-link:active {
        transform: translateY(0.5px);
    }

    body.dark-mode .mobile-menu-link {
        color: var(--color-soft-white);
        text-shadow: none;
    }

    body.dark-mode .mobile-menu-link:hover {
        color: var(--color-soft-white);
        text-shadow: 0 0 8px rgba(241, 237, 234, 0.34), 0 0 20px rgba(241, 237, 234, 0.225), 0 0 40px rgba(241, 237, 234, 0.11);
    }

    /* RSVP in mobile menu */
    .mobile-rsvp {
        margin-top: 0;
        padding-top: 0.6rem;
        border-top: none;
    }

    /* Monogram inside mobile dropdown */
    .mobile-monogram-link {
        display: flex;
        justify-content: center;
        padding: 0.5rem 1.5rem 0.6rem;
    }

    .mobile-monogram-link .nav-monogram {
        height: 48px;
        width: auto;
    }

    /* Main nav positioning on mobile */
    .main-nav {
        position: fixed;
        top: 1.5rem;
        right: 1.5rem;
        left: auto;
        transform: none;
        padding: 0;
        z-index: 1000;
    }

    /* Password overlay */
    .password-container h2 {
        font-size: 3.5rem;
    }

    #password-form {
        flex-direction: column;
        align-items: center;
    }

    .password-input {
        font-size: 16px;
    }

    /* Content wrapper */
    .content-wrapper {
        padding: 0 1.5rem;
    }

    /* Save the Date page */
    .std-names {
        font-size: 2.5rem;
    }

    .std-names-image {
        max-width: 90%;
    }

    .std-heading {
        font-size: 1.2rem;
    }

    .std-section-title {
        font-size: 2.75rem;
    }

    .std-intro,
    .std-details {
        font-size: 1rem;
    }

    .std-hotel-name {
        font-size: 1rem;
    }

    .std-hotel-description {
        font-size: 0.95rem;
    }

    .mobile-break {
        display: inline;
    }

    .desktop-break {
        display: none;
    }

    .announcement-icon img {
        width: 175px;
    }

    /* Registry page */
    body.page-registry .registry-page {
        flex: none;
        padding-top: 125px;
    }

    body.page-registry .registry-title {
        font-size: 3.8rem;
        margin-bottom: 1rem;
    }

    .page-intro {
        font-size: 1rem;
        line-height: 1.65;
        max-width: none;
        margin-bottom: 1.5rem;
    }

    .registry-illustration {
        max-width: 150px;
        height: 150px;
    }

    .story-intro-photo {
        width: 150px;
        height: 150px;
    }

    /* Joan of Arc illustration size — FAQ only, mobile.
       Needed because #faq-illustration (id) outweighs .registry-illustration (class),
       so without this the desktop 240px would persist on mobile. */
    #faq-illustration {
        max-width: 180px;
        height: 180px;
    }

    .story-photo-caption {
        min-height: 3em;
    }


    body.page-registry #protected-content.unlocked {
        min-height: auto;
    }

    body.page-registry .site-footer {
        margin-top: 2.4rem;
    }

    /* FAQ, Schedule, Travel, Our Story pages — enough clearance below mobile menu button */
    body.page-faq .registry-page,
    body.page-schedule .registry-page,
    body.page-travel .registry-page,
    body.page-our-story .registry-page,
    body.page-dc-guide .registry-page {
        flex: none;
        padding-top: 120px;
    }

    body.page-faq #protected-content.unlocked,
    body.page-schedule #protected-content.unlocked,
    body.page-travel #protected-content.unlocked,
    body.page-our-story #protected-content.unlocked,
    body.page-dc-guide #protected-content.unlocked {
        min-height: auto;
    }

    body.page-faq .site-footer,
    body.page-schedule .site-footer,
    body.page-travel .site-footer,
    body.page-our-story .site-footer,
    body.page-dc-guide .site-footer {
        margin-top: 2.4rem;
    }

    .schedule-event-name {
        font-size: 1.8rem;
    }

    /* Homepage */
    body.page-home .registry-page {
        padding-top: 125px;
    }

    .homepage-hero .std-names-image {
        max-width: 90%;
    }

    /* Travel section titles */
    .travel-section-title {
        font-size: 2.3rem;
    }

    /* Our Story mobile */
    .story-photo-pair {
        flex-direction: column;
        align-items: center;
        gap: 14px;
        max-width: 320px;
    }

    .story-photo-pair .story-photo-frame {
        max-width: 100%;
        width: 100%;
    }

    .story-photo-single {
        max-width: 320px;
    }

    .story-caption {
        font-size: 0.95rem;
    }

    .story-prose {
        font-size: 0.95rem;
    }

    .story-closing {
        font-size: var(--story-closing-size-mobile);
    }

    /* Carousel arrows overlay inside the photo on mobile */
    .story-carousel {
        max-width: 100%;
    }

    .story-carousel-track {
        --story-carousel-h: 260px;          /* smaller uniform height on phones — tunable */
    }

    .story-carousel-arrow {
        width: 34px;
        height: 34px;
        background-color: rgba(241, 237, 234, 0.85);
        backdrop-filter: blur(2px);
        -webkit-backdrop-filter: blur(2px);
    }

    .story-carousel-arrow-prev {
        left: 8px;
    }

    .story-carousel-arrow-next {
        right: 8px;
    }

    body.dark-mode .story-carousel-arrow {
        background-color: rgba(18, 42, 32, 0.85);
    }

    /* Footer */
    .footer-content {
        flex-direction: row;
        justify-content: flex-end;
        padding: 0.5rem 1.5rem;
    }

    .footer-info {
        white-space: normal;
        text-align: center;
        line-height: 1.8;
    }

    .footer-separator {
        display: none;
    }

    .footer-line {
        display: block;
    }

    /* Skip button — pad up to a 44x44 minimum tap target rather than growing the
       type further; bottom/right stay at 24px so the box's own corner (not the
       text) is what's anchored, same as before. */
    #skip-intro {
        padding: 14px;
    }
}
