/*
  მარიამი & გიორგი, all styling for index.html.

  Mobile-first: every base rule targets the narrowest phone and the only media
  queries are min-width. 769px hands the screen over to the desktop notice, so
  nothing above it needs a real layout.

  Two palettes: the day tokens on :root and the night tokens on html.is-night,
  both lifted off the hero paintings (golden-hour cream and honey by day,
  indigo and lamplight gold by night). Everything that is coloured reads a
  token, so the toggle re-tints the whole page through one class.

  Blocks in order: fonts -> tokens -> reset/scrollbar -> corner toggles ->
  desktop notice -> loader -> entry -> hero scene -> hero copy -> scroll cue ->
  sheets -> invitation + countdown -> timetable -> RSVP envelope -> find your table ->
  footer -> breakpoints -> reduced motion.
*/

/* ==========================================================================
   1. FONTS
   font-display: block is only safe because the loader covers the block period
   and gates on document.fonts, see script.js. swap would re-flow the whole page
   under the guest.

   DM Medea   the names and headings: a tall calligraphic hand whose ascenders
              and descenders run far outside the em box, so every block set in
              it carries generous line-height. It has no ampersand; the stacks
              fall through to DM Constitution for that glyph.
   BPG Ucnobi body copy.
   DM Constitution  eyebrows, times and figures: a capital-height face that
              gives small labels a lettered, engraved feel without any
              text-transform.
   ========================================================================== */
@font-face {
    font-family: 'DM Medea';
    src: url('dm_medea.ttf') format('truetype');
    font-weight: normal;
    font-style: normal;
    font-display: block;
}

@font-face {
    font-family: 'BPG Ucnobi';
    src: url('bpg_ucnobi.otf') format('opentype');
    font-weight: normal;
    font-style: normal;
    font-display: block;
}

@font-face {
    font-family: 'DM Constitution';
    src: url('dm_constitution.ttf') format('truetype');
    font-weight: normal;
    font-style: normal;
    font-display: block;
}

/* ==========================================================================
   2. TOKENS
   ========================================================================== */
:root {
    --vh: 1vh;

    --paper:   #fbf6ec;
    --paper-2: #f5ede0;
    --paper-3: #eee3d1;
    --cream:   #fffdf8;

    --deep:      #4b4237;
    --ink:       #5d5245;
    --ink-soft:  #8b7e6c;
    --gold:      #b48a4e;
    --gold-lt:   #dcc59c;
    --line:      #e4d7c0;
    --shadow:    rgba(110, 82, 44, 0.12);

    /* The rounded top lip that makes each section read as a sheet of paper laid
       over the one before it. Used as both radius and negative margin. */
    --lip: 30px;

    /* Every element that declares its own `transition` must repeat this list,
       because a local transition replaces the global one below rather than
       merging with it, and that element would then snap between palettes. */
    --tint: background-color 1.4s ease, color 1.4s ease, border-color 1.4s ease, box-shadow 1.4s ease;
}

html.is-night {
    --paper:   #121a34;
    --paper-2: #0f1730;
    --paper-3: #0b1227;
    --cream:   #18213f;

    --deep:      #f3ead7;
    --ink:       #d8cfbd;
    --ink-soft:  #a39c8e;
    --gold:      #d9b774;
    --gold-lt:   #8b7650;
    --line:      #2b3559;
    --shadow:    rgba(0, 0, 0, 0.32);
}

/* ==========================================================================
   3. RESET + SCROLLBAR
   ========================================================================== */
*,
*::before,
*::after {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
    transition: var(--tint);
}

html {
    scrollbar-width: thin;
    scrollbar-color: transparent transparent;
    transition: scrollbar-color 1.2s ease, var(--tint);
}

/* The thumb fades in only once the entry is done, so nothing rides the edge of
   the screen while the opening art is still on it. */
html.is-open {
    scrollbar-color: var(--gold) var(--paper-2);
}

html::-webkit-scrollbar {
    width: 7px;
}

html::-webkit-scrollbar-track {
    background-color: var(--paper-2);
}

html::-webkit-scrollbar-thumb {
    border-radius: 7px;
    background-color: transparent;
}

html.is-open::-webkit-scrollbar-thumb {
    background-color: var(--gold);
}

html,
body {
    overscroll-behavior-y: none;
}

body {
    background-color: var(--paper);
    color: var(--ink);
    font-family: 'BPG Ucnobi', Georgia, serif;
    font-size: 16px;
    line-height: 1.65;
    -webkit-font-smoothing: antialiased;
    -webkit-tap-highlight-color: transparent;
}

/* Entry/loader scroll lock. NEVER position: fixed or overflow: hidden, that
   freezes Chrome iOS's boot viewport ~110px short and leaves a band of page
   background under the overlay. The gesture is eaten in JS instead. */
html.is-locked,
body.is-locked {
    overscroll-behavior: none;
    touch-action: none;
}

img {
    display: block;
    max-width: 100%;
}

button,
input,
textarea {
    font: inherit;
    color: inherit;
}

/* ==========================================================================
   4. CORNER TOGGLES
   Music top-left, sky top-right. Both hidden and inert until body.is-open, so
   they appear with the hero rather than over the envelope.
   ========================================================================== */
.music-toggle,
.sky-toggle {
    position: fixed;
    top: 14px;
    z-index: 50;
    padding: 0;
    border: 1px solid rgba(255, 248, 232, 0.55);
    background-color: rgba(255, 251, 242, 0.32);
    -webkit-backdrop-filter: blur(8px);
    backdrop-filter: blur(8px);
    box-shadow: 0 4px 18px rgba(40, 50, 90, 0.18);
    cursor: pointer;
    opacity: 0;
    transform: translateY(-8px);
    pointer-events: none;
    transition: opacity 1s ease, transform 1s ease, var(--tint);
    -webkit-tap-highlight-color: transparent;
}

body.is-open .music-toggle,
body.is-open .sky-toggle {
    opacity: 1;
    transform: none;
    pointer-events: auto;
}

/* Once the guest has scrolled onto paper, the glass over a pale sheet would
   vanish; it firms up into the page's own cream. */
body.is-scrolled .music-toggle,
body.is-scrolled .sky-toggle {
    border-color: var(--line);
    background-color: var(--cream);
    box-shadow: 0 3px 14px var(--shadow);
}

.music-toggle:focus-visible,
.sky-toggle:focus-visible {
    outline: 2px solid var(--gold);
    outline-offset: 3px;
}

.music-toggle {
    left: 14px;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    border-radius: 50%;
}

/* Four bars that equalise while playing and rest short and even while paused,
   so the state reads without an icon swap. */
.music-toggle__bars {
    display: flex;
    align-items: flex-end;
    gap: 2px;
    height: 15px;
}

.music-toggle__bars span {
    width: 2.5px;
    height: 100%;
    border-radius: 1px;
    background-color: #fffaf0;
    transform-origin: bottom center;
    transform: scaleY(0.32);
    transition: transform 0.3s ease, var(--tint);
}

body.is-scrolled .music-toggle__bars span {
    background-color: var(--deep);
}

.music-toggle.playing .music-toggle__bars span {
    animation: eq 1.15s ease-in-out infinite;
}

/* Negative delays so the four bars start mid-cycle and never move in unison. */
.music-toggle.playing .music-toggle__bars span:nth-child(2) { animation-delay: -0.76s; }
.music-toggle.playing .music-toggle__bars span:nth-child(3) { animation-delay: -0.38s; }
.music-toggle.playing .music-toggle__bars span:nth-child(4) { animation-delay: -0.95s; }

@keyframes eq {
    0%, 100% { transform: scaleY(0.3); }
    50%      { transform: scaleY(1); }
}

/* The sky switch: a pill whose thumb carries the hero's own painted sun and
   moon, so the control and the scene it drives are visibly one thing. */
.sky-toggle {
    right: 14px;
    width: 62px;
    height: 34px;
    border-radius: 999px;
}

.sky-toggle__thumb {
    position: absolute;
    top: 3px;
    left: 3px;
    width: 26px;
    height: 26px;
    border-radius: 50%;
    background-color: rgba(255, 249, 235, 0.92);
    box-shadow: 0 2px 8px rgba(120, 80, 20, 0.25);
    transition: transform 0.9s cubic-bezier(0.65, 0, 0.25, 1), var(--tint);
}

html.is-night .sky-toggle__thumb {
    transform: translateX(28px);
    background-color: rgba(30, 42, 82, 0.95);
    box-shadow: 0 0 12px rgba(220, 210, 160, 0.35);
}

.sky-toggle__icon {
    position: absolute;
    inset: 3px;
    width: 20px;
    height: 20px;
    object-fit: contain;
    transition: opacity 0.6s ease, transform 0.9s cubic-bezier(0.65, 0, 0.25, 1);
}

.sky-toggle__icon--moon {
    opacity: 0;
    transform: rotate(-90deg) scale(0.6);
}

html.is-night .sky-toggle__icon--sun {
    opacity: 0;
    transform: rotate(90deg) scale(0.6);
}

html.is-night .sky-toggle__icon--moon {
    opacity: 1;
    transform: none;
}

/* The "tap me" label under the sky switch. Centred on the 62px pill (right
   14px + 31px), arrives after the hero has settled, bobs gently toward the
   switch, and leaves for good once the guest has used it. A small cream
   plate, so it reads over both the sky and the paper sheets. */
.sky-hint {
    position: fixed;
    top: 56px;
    right: 45px;
    z-index: 50;
    padding: 2px 9px 1px;
    border-radius: 999px;
    background-color: rgba(255, 252, 244, 0.85);
    -webkit-backdrop-filter: blur(6px);
    backdrop-filter: blur(6px);
    box-shadow: 0 2px 10px rgba(40, 30, 10, 0.16);
    transform: translateX(50%);
    white-space: nowrap;
    font-size: 10.5px;
    letter-spacing: 0.06em;
    color: #4b4237;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.8s ease;
}

body.is-open .sky-hint {
    opacity: 1;
    transition-delay: 3.4s;
    animation: sky-hint-bob 2.4s ease-in-out 3.4s infinite;
}

html.sky-tried .sky-hint {
    opacity: 0;
    transition-delay: 0s;
    animation: none;
}

/* A little upward caret pointing at the switch. */
.sky-hint::before {
    content: '';
    position: absolute;
    top: -3px;
    left: 50%;
    width: 6px;
    height: 6px;
    margin-left: -3px;
    background-color: rgba(255, 252, 244, 0.85);
    transform: rotate(45deg);
}

@keyframes sky-hint-bob {
    0%, 100% { transform: translateX(50%) translateY(0); }
    50%      { transform: translateX(50%) translateY(-3px); }
}

/* ==========================================================================
   5. DESKTOP NOTICE
   ========================================================================== */
.mobile-prompt {
    display: none;
}

/* ==========================================================================
   6. LOADER
   Out-ranks the entry overlay (z 10000).
   ========================================================================== */
.loader {
    position: fixed;
    inset: 0;
    z-index: 10050;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 26px;
    background-color: var(--paper);
    transition: opacity 0.9s ease, var(--tint);
}

.loader.is-hidden {
    opacity: 0;
    pointer-events: none;
}

.loader.is-removed {
    display: none;
}

.loader__mark {
    display: flex;
    align-items: center;
    gap: 12px;
    font-family: 'DM Medea', 'BPG Ucnobi', serif;
    font-size: 38px;
    line-height: 1.6;
    color: var(--deep);
    animation: mark-breathe 3.2s ease-in-out infinite;
}

.loader__amp {
    font-family: 'DM Constitution', Georgia, serif;
    font-size: 20px;
    color: var(--gold);
}

@keyframes mark-breathe {
    0%, 100% { opacity: 0.45; }
    50%      { opacity: 1; }
}

.loader__track {
    position: relative;
    width: 150px;
    height: 1px;
    overflow: hidden;
    background-color: var(--line);
}

.loader__bar {
    position: absolute;
    inset: 0;
    transform: scaleX(0);
    transform-origin: left center;
    background-color: var(--gold);
}

/* ==========================================================================
   7. ENTRY

   Layers, back to front: the two halves of the opening painting, the seam of
   light between them, the envelope over their lower third, the hint and the
   tap ring.

   The halves are cropped TOWARD each other (object-position right / left),
   which is what one cover-cropped copy of the whole painting would do, so the
   seam stays invisible on a phone narrower than the art's 9:16.
   ========================================================================== */
.entry {
    position: fixed;
    /* inset: 0 reaches the true bottom on its own once the document is
       JS-locked. Forcing a height with --vh / svh here oversizes it under the
       Chrome tab bar. */
    inset: 0;
    z-index: 10000;
    overflow: hidden;
    background-color: #e9dcc4;
    cursor: pointer;
    transition: opacity 1.2s ease;
}

/* Dropped on the tap: from here on anything the overlay does not paint must be
   the hero showing through, not the overlay's own fill. */
.entry.is-down {
    background-color: transparent;
}

.entry.is-gone {
    opacity: 0;
    pointer-events: none;
}

.entry.is-removed {
    display: none;
}

.entry__panel {
    position: absolute;
    top: 0;
    z-index: 1;
    width: 50%;
    height: 100%;
    overflow: hidden;
    /* Long and heavily eased: two halves of a heavy painting swinging aside,
       not a slide. */
    transition: transform 4s cubic-bezier(0.72, 0.02, 0.24, 1);
    will-change: transform;
}

.entry__panel--left  { left: 0; }
.entry__panel--right { right: 0; }

.entry__panel img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.entry__panel--left  img { object-position: right center; }
.entry__panel--right img { object-position: left center; }

/* 101%: a hairline of panel left on screen after a sub-pixel transform reads
   as a bright seam against the hero. */
.entry.is-apart .entry__panel--left  { transform: translateX(-101%); }
.entry.is-apart .entry__panel--right { transform: translateX(101%); }

/* Closed state: a hairline down the middle with a soft shadow either side,
   so the opening reads as two leaves of one envelope-door, not one picture.
   Each leaf is also shaded toward its inner edge. Both go once the light
   seam takes over. */
.entry__split {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 50%;
    z-index: 2;
    width: 1px;
    margin-left: -0.5px;
    background-color: rgba(255, 246, 224, 0.85);
    box-shadow: -1px 0 4px rgba(80, 55, 20, 0.35), 1px 0 4px rgba(80, 55, 20, 0.35);
    transition: opacity 0.8s ease;
}

.entry__panel::after {
    content: '';
    position: absolute;
    top: 0;
    bottom: 0;
    width: 14px;
    pointer-events: none;
    transition: opacity 0.8s ease;
}

.entry__panel--left::after {
    right: 0;
    background-image: linear-gradient(to left, rgba(70, 48, 18, 0.22), rgba(70, 48, 18, 0));
}

.entry__panel--right::after {
    left: 0;
    background-image: linear-gradient(to right, rgba(70, 48, 18, 0.22), rgba(70, 48, 18, 0));
}

.entry.is-glow .entry__split,
.entry.is-glow .entry__panel::after {
    opacity: 0;
}

/* A thread of warm light down the seam that swells just before the halves
   part, as if the morning were already on the other side. Static gradient,
   so nothing here needs the palette transition. */
.entry__seam {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 50%;
    z-index: 2;
    width: 4px;
    margin-left: -2px;
    background-image: linear-gradient(
        to bottom,
        rgba(255, 244, 214, 0) 0%,
        rgba(255, 244, 214, 0.95) 30%,
        rgba(255, 250, 235, 1) 55%,
        rgba(255, 244, 214, 0.9) 78%,
        rgba(255, 244, 214, 0) 100%
    );
    box-shadow: 0 0 22px 8px rgba(255, 232, 180, 0.55);
    opacity: 0;
    transform: scaleX(0.4);
    transition: opacity 1.1s ease, transform 1.6s cubic-bezier(0.22, 1, 0.36, 1);
}

.entry.is-glow .entry__seam {
    opacity: 1;
    transform: scaleX(1.6);
}

/* Spreads into the widening gap and burns off as the hero takes over. */
.entry.is-apart .entry__seam {
    opacity: 0;
    transform: scaleX(30);
    transition: opacity 1.8s ease 0.3s, transform 2.4s cubic-bezier(0.22, 1, 0.36, 1);
}

/* Bottom-anchored at its own aspect ratio (916 x 648) and 8% wider than the
   screen so the flap tips bleed past both edges. Its height is therefore
   108vw * 0.707 = 76vw, which the hint and tap ring below are placed from. */
.entry__env {
    position: absolute;
    left: -4%;
    bottom: -8%;
    z-index: 3;
    width: 108%;
    max-width: none;
    transition: transform 2.2s cubic-bezier(0.55, 0, 0.35, 1),
                opacity 0.8s ease 1.4s;
    will-change: transform;
}

.entry.is-down .entry__env {
    transform: translateY(112%);
    opacity: 0;
}

/* Parked just above the envelope's flap tips, on the painting's balustrade,
   with a frosted plate so it holds on busy art. */
.entry__hint {
    position: absolute;
    left: 50%;
    /* Tracks the envelope's -8% bottom offset, so it stays just above the
       flap tips however tall the phone is. */
    bottom: calc(76vw + 18px - 8%);
    z-index: 4;
    display: flex;
    align-items: center;
    gap: 11px;
    padding: 7px 18px;
    border-radius: 999px;
    background-color: rgba(255, 252, 244, 0.82);
    -webkit-backdrop-filter: blur(7px);
    backdrop-filter: blur(7px);
    box-shadow: 0 2px 18px rgba(110, 82, 44, 0.18);
    transform: translateX(-50%);
    white-space: nowrap;
    font-size: 13.5px;
    letter-spacing: 0.14em;
    color: #4b4237;
    animation: hint-pulse 3.6s ease-in-out infinite;
    transition: opacity 0.4s ease;
}

.entry__hint-line {
    width: 24px;
    height: 1px;
    background-color: #b48a4e;
}

@keyframes hint-pulse {
    0%, 100% { opacity: 0.8; }
    50%      { opacity: 1; }
}

/* A ring breathing out of the bow's knot (62% down the art), which is where a
   hand would go to untie it. */
.entry__tap {
    position: absolute;
    left: 50%;
    bottom: calc(29vw - 8%);
    z-index: 4;
    width: 64px;
    height: 64px;
    margin: 0 0 -32px -32px;
    border: 1px solid rgba(180, 138, 78, 0.9);
    border-radius: 50%;
    animation: tap-ring 3.6s ease-out infinite;
}

@keyframes tap-ring {
    0%   { opacity: 0; transform: scale(0.5); }
    25%  { opacity: 0.8; }
    100% { opacity: 0; transform: scale(1.9); }
}

.entry.is-down .entry__hint,
.entry.is-down .entry__tap {
    opacity: 0;
    animation: none;
}

/* ==========================================================================
   8. HERO SCENE

   .scene is the painting's own 941 x 1671 box, sized like object-fit: cover
   against the pinned hero height, and every layer inside it is placed in
   percent of that box. That is what keeps the sun registered behind the
   mountain overlay on every phone shape.

   Stack: day base / night base -> stars -> sky (sun, moon, their light) ->
   day ridge overlay / night ridge overlay. The overlays are the top 50.03%
   (836 / 1671) of the paintings with the sky cut away.
   ========================================================================== */
.page {
    position: relative;
    overflow: hidden;
}

.hero {
    position: relative;
    display: flex;
    justify-content: center;
    /* One lip taller than the screen: the next sheet rides up over the hero
       by exactly --lip, so this puts its rounded top edge right on the bottom
       of the glass. The guest meets a full-bleed painting and only sees the
       paper once they start to scroll. Floors first, then the JS pin, order
       matters, the pin must win. */
    min-height: calc(100vh + var(--lip));
    min-height: calc(100svh + var(--lip));
    overflow: hidden;
    background-color: #6f9ad8;
}

.vh-lock .hero {
    min-height: calc(var(--vh) * 100 + var(--lip));
}

html.is-night .hero {
    background-color: #0d1736;
}

/* Held a touch oversized behind the closed halves and settling to 1 as they
   part, so the uncover has a slow breath in it. 0.5632 = 941 / 1671. */
.scene {
    position: absolute;
    left: 50%;
    top: 50%;
    z-index: 1;
    width: max(100%, calc((var(--vh) * 100 + var(--lip)) * 0.5632));
    aspect-ratio: 941 / 1671;
    transform: translate(-50%, -50%) scale(1.08);
    transition: transform 7.5s cubic-bezier(0.16, 0.8, 0.3, 1);
}

.is-parting .scene,
.is-open .scene {
    transform: translate(-50%, -50%);
}

.scene__base,
.scene__top {
    position: absolute;
    left: 0;
    top: 0;
    width: 100%;
    max-width: none;
}

.scene__base {
    height: 100%;
    object-fit: cover;
}

.scene__top {
    z-index: 4;
    height: 50.03%;
    /* The overlay's bottom edge never matches the base pixel for pixel (both
       are compressed separately), and a hard edge shows as a faint line across
       the hills. Feathered over its last ~45px of art (rows 790-836) instead;
       the sky clip below stops at row 790, above the feather, so a body that
       has set is still behind fully solid overlay. */
    -webkit-mask-image: linear-gradient(to bottom, #000 94.5%, transparent 100%);
    mask-image: linear-gradient(to bottom, #000 94.5%, transparent 100%);
}

/* The night set cross-fades in a beat after the toggle, while the sun is
   already sinking, and out the same way, so the sky change always sits between
   one body setting and the other rising. */
.scene__base--night,
.scene__top--night {
    opacity: 0;
    transition: opacity 2.2s ease 0.6s;
}

.scene__base--night { z-index: 1; }
.scene__top--night  { z-index: 5; }

html.is-night .scene__base--night,
html.is-night .scene__top--night {
    opacity: 1;
}

/* A few brighter stars over the painted ones, breathing on their own clocks.
   They sit in the open sky band only (top 40%). */
.stars {
    position: absolute;
    inset: 0 0 60% 0;
    z-index: 2;
    opacity: 0;
    transition: opacity 2s ease 1.2s;
}

html.is-night .stars {
    opacity: 1;
}

.stars span {
    position: absolute;
    width: 3px;
    height: 3px;
    border-radius: 50%;
    background-color: #fff8e6;
    box-shadow: 0 0 6px 2px rgba(255, 238, 200, 0.6);
    animation: star 5s ease-in-out infinite;
}

.stars span:nth-child(1) { left: 12%; top: 14%; animation-delay: -1.2s; }
.stars span:nth-child(2) { left: 28%; top: 38%; animation-delay: -3.4s; width: 2px; height: 2px; }
.stars span:nth-child(3) { left: 46%; top: 9%;  animation-delay: -0.6s; }
.stars span:nth-child(4) { left: 63%; top: 29%; animation-delay: -2.2s; width: 2px; height: 2px; }
.stars span:nth-child(5) { left: 81%; top: 12%; animation-delay: -4.1s; }
.stars span:nth-child(6) { left: 90%; top: 47%; animation-delay: -1.8s; width: 2px; height: 2px; }
.stars span:nth-child(7) { left: 7%;  top: 58%; animation-delay: -2.9s; width: 2px; height: 2px; }
.stars span:nth-child(8) { left: 38%; top: 70%; animation-delay: -0.2s; width: 2px; height: 2px; }
.stars span:nth-child(9) { left: 72%; top: 64%; animation-delay: -3.8s; }

@keyframes star {
    0%, 100% { opacity: 0.15; transform: scale(0.6); }
    50%      { opacity: 1;    transform: scale(1.15); }
}

/* The sky band the sun and moon travel in, clipped at the bottom edge of the
   ridge overlay: under that line the overlay is solid, above it the clip is,
   so a body that has set is hidden either way. */
.sky {
    position: absolute;
    left: 0;
    top: 0;
    z-index: 3;
    width: 100%;
    height: 47.3%;
    overflow: hidden;
}

/* The light each body throws across the horizon. Static gradients, faded by
   opacity only, so they are free to transition. */
.sky__warmth,
.sky__cool {
    position: absolute;
    inset: 0;
    opacity: 0;
    pointer-events: none;
}

.sky__warmth {
    background-image: radial-gradient(
        ellipse 70% 42% at 20% 86%,
        rgba(255, 214, 140, 0.55) 0%,
        rgba(255, 196, 120, 0.22) 45%,
        rgba(255, 196, 120, 0) 75%
    );
    transition: opacity 3s ease;
}

.sky__cool {
    background-image: radial-gradient(
        ellipse 60% 40% at 82% 74%,
        rgba(200, 216, 255, 0.32) 0%,
        rgba(170, 190, 245, 0.12) 50%,
        rgba(170, 190, 245, 0) 78%
    );
    transition: opacity 3s ease;
}

html.is-risen .sky__warmth {
    opacity: 1;
    transition: opacity 3.6s ease 2s;
}

html.is-risen.is-night .sky__warmth {
    opacity: 0;
    transition: opacity 1.4s ease;
}

html.is-risen.is-night .sky__cool {
    opacity: 1;
    transition: opacity 3.6s ease 2s;
}

/* SUN. Parked below the ridge (translateY in its own height) until html gets
   .is-risen at the end of the opening, then eased up over 3.4s. Setting is an
   ease-in, rising an ease-out with a 1.6s delay, so on a toggle the sky has
   always emptied before the other body appears. Two inner layers carry the
   ambient motion so it never fights the rise over `transform`. */
.sun {
    position: absolute;
    left: 5%;
    top: 66.6%;
    width: 25%;
    transform: translateY(140%);
    transition: transform 1.7s cubic-bezier(0.55, 0, 0.8, 0.45);
}

html.is-risen .sun {
    transform: none;
    transition: transform 3.6s cubic-bezier(0.2, 0.7, 0.25, 1) 1.6s;
}

html.is-risen.is-night .sun {
    transform: translateY(140%);
    transition: transform 1.7s cubic-bezier(0.55, 0, 0.8, 0.45);
}

.sun__img {
    position: relative;
    z-index: 1;
    width: 100%;
    animation: spin 160s linear infinite;
}

.sun__glow {
    position: absolute;
    inset: -45%;
    border-radius: 50%;
    background-image: radial-gradient(
        circle,
        rgba(255, 228, 160, 0.75) 0%,
        rgba(255, 210, 130, 0.3) 35%,
        rgba(255, 210, 130, 0) 68%
    );
    animation: glow 7s ease-in-out infinite;
}

@keyframes spin {
    to { transform: rotate(360deg); }
}

@keyframes glow {
    0%, 100% { opacity: 0.75; transform: scale(0.94); }
    50%      { opacity: 1;    transform: scale(1.06); }
}

/* MOON. The mirror of the sun, on the viewer's right. */
.moon {
    position: absolute;
    right: 14%;
    top: 64%;
    width: 15%;
    transform: translateY(160%);
    transition: transform 1.7s cubic-bezier(0.55, 0, 0.8, 0.45);
}

html.is-risen.is-night .moon {
    transform: none;
    transition: transform 3.6s cubic-bezier(0.2, 0.7, 0.25, 1) 1.6s;
}

.moon__img {
    position: relative;
    z-index: 1;
    width: 100%;
    animation: sway 9s ease-in-out infinite;
}

.moon__glow {
    position: absolute;
    inset: -55%;
    border-radius: 50%;
    background-image: radial-gradient(
        circle,
        rgba(236, 232, 210, 0.42) 0%,
        rgba(200, 214, 250, 0.16) 38%,
        rgba(200, 214, 250, 0) 66%
    );
    animation: glow 8s ease-in-out infinite;
}

@keyframes sway {
    0%, 100% { transform: rotate(-4deg); }
    50%      { transform: rotate(3deg); }
}

/* ==========================================================================
   9. HERO COPY
   Set in warm white over the sky in both palettes: the day sky at the top of
   the painting is a saturated blue and the night one an indigo, and cream
   holds on both. A faint navy wash behind the block does the rest.
   ========================================================================== */
.hero__veil {
    position: absolute;
    inset: 0;
    z-index: 2;
    pointer-events: none;
    background-image: radial-gradient(
        ellipse 90% 30% at 50% 22%,
        rgba(24, 40, 86, 0.2) 0%,
        rgba(24, 40, 86, 0.08) 55%,
        rgba(24, 40, 86, 0) 85%
    );
}

.hero__content {
    position: relative;
    z-index: 3;
    width: 100%;
    /* vh so the block keeps to the open sky on short and tall phones alike;
       --vh resolves to 1vh with JS off, so this needs no .vh-lock twin. */
    padding: calc(var(--vh) * 6.5) 24px 0;
    text-align: center;
    color: #fffaf0;
}

/* Held at the first frame until .is-open; the entrance then runs THROUGH the
   halves parting, so the reveal and the names arriving are one movement. */
.hero__eyebrow,
.hero__names,
.hero__rule,
.hero__when {
    opacity: 0;
}

.is-open .hero__eyebrow { animation: rise 1.6s cubic-bezier(0.22, 1, 0.36, 1) 0.15s both; }
.is-open .hero__names   { animation: names-in 2.2s cubic-bezier(0.22, 1, 0.36, 1) 0.45s both; }
.is-open .hero__rule    { animation: rule-in 1.6s cubic-bezier(0.22, 1, 0.36, 1) 1.5s both; }
.is-open .hero__when    { animation: rise 1.6s cubic-bezier(0.22, 1, 0.36, 1) 1.8s both; }

@keyframes rise {
    from { opacity: 0; transform: translateY(16px); }
    to   { opacity: 1; transform: none; }
}

/* The names come out of a soft blur rather than just fading, which is what a
   calligraphic hand looks like resolving on paper. */
@keyframes names-in {
    from { opacity: 0; transform: translateY(14px) scale(0.97); filter: blur(8px); }
    to   { opacity: 1; transform: none; filter: blur(0); }
}

@keyframes rule-in {
    from { opacity: 0; transform: scaleX(0); }
    to   { opacity: 1; transform: scaleX(1); }
}

.hero__eyebrow {
    font-family: 'DM Constitution', 'BPG Ucnobi', serif;
    font-size: 12.5px;
    letter-spacing: 0.3em;
    text-shadow: 0 1px 10px rgba(24, 40, 86, 0.45);
}

.hero__names {
    margin-top: 14px;
    font-family: 'DM Medea', 'DM Constitution', serif;
    font-size: 34px;
    font-weight: normal;
    line-height: 1.65;
    /* Medea's descenders on გიორგი hang almost a full em below the baseline;
       this keeps them clear of the rule and date. */
    padding-bottom: 16px;
    text-shadow: 0 2px 22px rgba(24, 40, 86, 0.4);
}

.hero__name {
    display: block;
}

.hero__amp {
    display: block;
    margin: -2px 0 6px;
    font-family: 'DM Constitution', Georgia, serif;
    font-size: 20px;
    line-height: 1.4;
    color: #f6dfae;
}

.hero__rule {
    display: block;
    width: 64px;
    height: 1px;
    margin: 12px auto 0;
    background-color: rgba(255, 240, 210, 0.85);
}

/* No plate: just a soft navy haze behind the two lines that fades out to
   nothing at its edges, enough to lift cream type off the peach horizon
   without reading as a box. Static gradient on a pseudo-element, faded in
   with the block itself. */
.hero__when {
    position: relative;
    margin-top: 14px;
    padding: 6px 0;
}

.hero__when::before {
    content: '';
    position: absolute;
    top: -14px;
    bottom: -14px;
    left: 50%;
    z-index: -1;
    width: 300px;
    margin-left: -150px;
    background-image: radial-gradient(
        ellipse 50% 50% at 50% 50%,
        rgba(22, 36, 78, 0.34) 0%,
        rgba(22, 36, 78, 0.18) 50%,
        rgba(22, 36, 78, 0) 100%
    );
    pointer-events: none;
}

.hero__date {
    font-size: 18px;
    line-height: 1.5;
    letter-spacing: 0.12em;
    text-shadow: 0 1px 6px rgba(16, 26, 60, 0.45);
}

.hero__place {
    margin-top: 1px;
    font-family: 'DM Constitution', 'BPG Ucnobi', serif;
    font-size: 13px;
    letter-spacing: 0.32em;
    color: #ffe9bf;
    text-shadow: 0 1px 6px rgba(16, 26, 60, 0.45);
}

/* ==========================================================================
   10. SCROLL CUE
   Entrance is a transition, not an animation, because an animation with
   `both` would hold opacity 1 and fight the .is-scrolled fade-out.
   ========================================================================== */
.cue {
    position: absolute;
    left: 50%;
    /* Measured from the bottom of the glass, not of the taller hero. */
    bottom: calc(50px + var(--lip));
    z-index: 5;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 9px;
    opacity: 0;
    transform: translateX(-50%);
    pointer-events: none;
    transition: opacity 1.1s ease;
}

.is-open .cue { opacity: 1; transition-delay: 3s; }
.is-open.has-scrolled .cue { opacity: 0; transition-delay: 0s; }

.cue__label,
.cue__ring {
    background-color: rgba(255, 252, 244, 0.82);
    -webkit-backdrop-filter: blur(7px);
    backdrop-filter: blur(7px);
    box-shadow: 0 2px 18px rgba(40, 30, 10, 0.2);
}

.cue__label {
    padding: 5px 15px;
    border-radius: 999px;
    font-size: 11.5px;
    letter-spacing: 0.16em;
    color: #4b4237;
}

.cue__ring {
    position: relative;
    display: block;
    width: 38px;
    height: 38px;
    border-radius: 50%;
    animation: cue-bob 2.6s ease-in-out infinite;
}

.cue__ring::after {
    content: '';
    position: absolute;
    inset: 0;
    border: 1px solid rgba(255, 250, 238, 0.9);
    border-radius: 50%;
    animation: cue-ping 2.6s ease-out infinite;
}

.cue__chev {
    position: absolute;
    left: 50%;
    top: 12px;
    width: 10px;
    height: 10px;
    margin-left: -5px;
    border-right: 2px solid #4b4237;
    border-bottom: 2px solid #4b4237;
    transform: rotate(45deg);
    animation: chev-fade 2.6s ease-in-out infinite;
}

.cue__chev--2 {
    top: 19px;
    opacity: 0.45;
    animation-delay: 0.32s;
}

@keyframes cue-bob {
    0%, 100% { transform: translateY(0); }
    50%      { transform: translateY(7px); }
}

@keyframes cue-ping {
    0%   { opacity: 0.6; transform: scale(1); }
    70%  { opacity: 0; transform: scale(1.85); }
    100% { opacity: 0; transform: scale(1.85); }
}

@keyframes chev-fade {
    0%, 100% { opacity: 0.35; }
    45%      { opacity: 1; }
}

/* ==========================================================================
   11. SHEETS
   Every section below the hero is a sheet of paper laid over the one before:
   a rounded lip, an upward shadow and a negative margin. DOM order paints the
   later sheet on top, which is exactly what the overlap needs.
   ========================================================================== */
.sheet {
    position: relative;
    z-index: 2;
    margin-top: calc(var(--lip) * -1);
    padding: 56px 24px 50px;
    border-top-left-radius: var(--lip);
    border-top-right-radius: var(--lip);
    background-color: var(--paper);
    box-shadow: 0 -14px 32px var(--shadow);
}

.sheet--alt  { background-color: var(--paper-2); }
.sheet--rsvp { background-color: var(--paper); }

.sheet--foot {
    padding-top: 42px;
    padding-bottom: 36px;
    background-color: var(--paper-3);
    text-align: center;
}

.sheet__inner {
    width: 100%;
    max-width: 360px;
    margin: 0 auto;
    text-align: center;
}

.sheet__eyebrow {
    font-family: 'DM Constitution', 'BPG Ucnobi', serif;
    font-size: 12.5px;
    letter-spacing: 0.26em;
    color: var(--gold);
}

.sheet__title {
    margin-top: 2px;
    /* Medea's ascenders and descenders reach far past its line box; the padding
       keeps them off the eyebrow above and the rule below. */
    padding: 10px 0 14px;
    font-family: 'DM Medea', 'BPG Ucnobi', serif;
    font-size: 34px;
    font-weight: normal;
    line-height: 1.5;
    color: var(--deep);
}

/* Grows out of the centre once the heading lands, so it reads as belonging to
   the title rather than as a divider that was already there. A lozenge rides
   its middle, the page's one recurring ornament. */
.sheet__rule {
    position: relative;
    display: block;
    width: 84px;
    height: 1px;
    margin: 6px auto 0;
    background-color: var(--gold-lt);
}

.sheet__rule::after {
    content: '';
    position: absolute;
    top: -3px;
    left: 50%;
    width: 7px;
    height: 7px;
    margin-left: -4px;
    border: 1px solid var(--gold);
    background-color: var(--paper);
    transform: rotate(45deg);
}

.sheet--alt .sheet__rule::after {
    background-color: var(--paper-2);
}

/* ===== Scroll reveal ===== */
.reveal {
    opacity: 0;
    transform: translateY(20px);
    transition: opacity 1.3s ease, transform 1.3s cubic-bezier(0.22, 1, 0.36, 1), var(--tint);
}

.reveal.is-in {
    opacity: 1;
    transform: none;
}

/* The rule animates scaleX instead of the translate; its in-state is re-stated
   at matching specificity or .reveal would pin it shut. */
.sheet__rule.reveal       { transform: scaleX(0); transition: transform 1.4s cubic-bezier(0.22, 1, 0.36, 1) 0.3s, opacity 1s ease 0.3s, var(--tint); }
.sheet__rule.reveal.is-in { transform: scaleX(1); }

/* ==========================================================================
   12. INVITATION + COUNTDOWN
   ========================================================================== */

/* The sun / moon medallion over the invitation. It follows the sky toggle, so
   the page below the hero answers the switch too. */
.orb {
    position: relative;
    display: block;
    width: 62px;
    height: 62px;
    margin: 0 auto;
}

.orb__sun,
.orb__moon {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: contain;
    transition: opacity 1.2s ease, transform 1.6s cubic-bezier(0.22, 1, 0.36, 1);
}

.orb__sun {
    animation: spin 90s linear infinite;
}

.orb__moon {
    padding: 10px;
    opacity: 0;
    transform: translateY(14px) scale(0.8);
}

html.is-night .orb__sun {
    opacity: 0;
}

html.is-night .orb__moon {
    opacity: 1;
    transform: none;
}

.invite__text {
    margin-top: 22px;
    font-size: 19px;
    line-height: 1.85;
    color: var(--deep);
}

.invite__heart {
    display: inline-block;
    margin-left: 2px;
    font-size: 16px;
    animation: heartbeat 2.8s ease-in-out 1.5s infinite;
}

@keyframes heartbeat {
    0%, 100%   { transform: scale(1); }
    12%        { transform: scale(1.2); }
    24%        { transform: scale(0.98); }
    36%        { transform: scale(1.1); }
    50%        { transform: scale(1); }
}

/* Keeps a hyphenated word like ერთ-ერთი on one line. */
.nowrap {
    white-space: nowrap;
}

.sheet--invite .sheet__rule {
    margin-top: 26px;
}

.sheet__eyebrow--count {
    margin-top: 30px;
}

.cd {
    display: flex;
    gap: 8px;
    margin-top: 16px;
}

.cd__unit {
    position: relative;
    flex: 1 1 0;
    min-width: 0;
    padding: 15px 4px 12px;
    border: 1px solid var(--line);
    border-radius: 999px 999px 16px 16px;
    background-color: var(--cream);
    box-shadow: 0 3px 14px var(--shadow);
}

/* Each unit is a little arched window, the same arch as the opening painting's
   balcony, with a hairline ruled inside it. */
.cd__unit::before {
    content: '';
    position: absolute;
    inset: 4px;
    border: 1px solid var(--line);
    border-radius: 999px 999px 12px 12px;
    pointer-events: none;
}

.cd__num {
    display: block;
    padding-top: 6px;
    font-family: 'DM Constitution', 'BPG Ucnobi', serif;
    font-size: 27px;
    line-height: 1;
    color: var(--deep);
    /* Tabular figures: without them the seconds jitter the row every tick. */
    font-variant-numeric: tabular-nums;
}

.cd__label {
    display: block;
    margin-top: 7px;
    font-size: 11px;
    letter-spacing: 0.06em;
    color: var(--ink-soft);
}

/* ==========================================================================
   13. TIMETABLE
   One rail down the left with a gold fill that follows the guest, and a
   numbered medallion per stop. The numerals carry the order, which is what
   lets the two stops without a clock time sit in the list naturally.
   ========================================================================== */
.tl {
    position: relative;
    margin-top: 34px;
    padding-left: 46px;
    text-align: left;
}

.tl__rail,
.tl__rail-fill {
    position: absolute;
    top: 16px;
    left: 15px;
    width: 2px;
    border-radius: 2px;
}

.tl__rail {
    bottom: 30px;
    background-image: linear-gradient(
        to bottom,
        var(--line) 0 6px,
        transparent 6px 12px
    );
    background-size: 2px 12px;
}

.tl__rail-fill {
    height: 0;
    background-color: var(--gold);
    transition: height 0.4s linear, var(--tint);
}

/* On iOS the fill steps from stop to stop instead of tracking every scroll
   frame (see startRail in script.js), so it needs a longer, eased glide. */
.tl--stepped .tl__rail-fill {
    transition: height 1.4s cubic-bezier(0.22, 1, 0.36, 1), var(--tint);
}

.tl__stop {
    position: relative;
    padding-bottom: 28px;
    opacity: 0;
    /* Alternating tilt: the cards settle out of a slight lean rather than
       sliding straight up, so a five-card list does not read as a template. */
    transform: translateY(22px) rotate(-1.4deg);
    transition: opacity 1.15s ease, transform 1.3s cubic-bezier(0.22, 1, 0.36, 1), var(--tint);
}

.tl__stop:nth-child(even) {
    transform: translateY(22px) rotate(1.4deg);
}

.tl__stop:last-child {
    padding-bottom: 0;
}

.tl__stop.is-lit,
.tl__stop:nth-child(even).is-lit {
    opacity: 1;
    transform: none;
}

/* The medallion straddles the rail, so the numeral reads as a marker ON the
   line rather than a bullet beside it. */
.tl__dot {
    position: absolute;
    top: 16px;
    left: -46px;
    z-index: 2;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    border: 1px solid var(--line);
    border-radius: 50%;
    background-color: var(--cream);
    font-size: 11px;
    letter-spacing: 0.04em;
    color: var(--ink-soft);
}

.tl__dot i {
    font-style: normal;
}

.tl__stop.is-lit .tl__dot {
    border-color: var(--gold);
    background-color: var(--gold);
    color: var(--cream);
}

/* Fires as the stop lights, so the eye is pulled to the medallion the moment
   the card arrives, then left alone. */
.tl__dot::after {
    content: '';
    position: absolute;
    inset: -1px;
    border: 1px solid var(--gold);
    border-radius: 50%;
    opacity: 0;
}

.tl__stop.is-lit .tl__dot::after {
    animation: dot-ping 2s ease-out 0.35s 2;
}

@keyframes dot-ping {
    0%   { opacity: 0.7; transform: scale(1); }
    100% { opacity: 0; transform: scale(2.1); }
}

.tl__card {
    position: relative;
    padding: 20px 18px 18px;
    /* Asymmetric and mirrored per stop: the corner that points back at the rail
       is the soft one, so the cards lean into the line down the page. */
    border-radius: 26px 14px 14px 14px;
    background-color: var(--cream);
    box-shadow: 0 4px 18px var(--shadow);
}

.tl__stop:nth-child(even) .tl__card {
    border-radius: 14px 26px 14px 14px;
}

/* A hairline ruled just inside the card, the way a mount is ruled. */
.tl__card::before {
    content: '';
    position: absolute;
    inset: 5px;
    border: 1px solid var(--line);
    border-radius: 22px 10px 10px 10px;
    pointer-events: none;
}

.tl__stop:nth-child(even) .tl__card::before {
    border-radius: 10px 22px 10px 10px;
}

/* A four-point sparkle in the corner the radius leaves open. */
.tl__card::after {
    content: '';
    position: absolute;
    top: 13px;
    right: 15px;
    width: 13px;
    height: 13px;
    background-color: var(--gold);
    clip-path: polygon(50% 0%, 59% 41%, 100% 50%, 59% 59%, 50% 100%, 41% 59%, 0% 50%, 41% 41%);
    opacity: 0;
    pointer-events: none;
}

.tl__stop.is-lit .tl__card::after {
    animation: twinkle 7.5s ease-in-out 1.6s infinite;
}

@keyframes twinkle {
    0%, 70%, 100% { opacity: 0; transform: scale(0.5); }
    82%           { opacity: 0.8; transform: scale(1); }
}

.tl__time {
    display: inline-block;
    padding: 4px 13px 3px;
    border: 1px solid var(--gold-lt);
    border-radius: 999px;
    background-color: var(--paper);
    font-family: 'DM Constitution', 'BPG Ucnobi', serif;
    font-size: 15px;
    line-height: 1.4;
    letter-spacing: 0.06em;
    color: var(--deep);
}

/* A relative marker ("after the ceremony") rather than a clock time: lighter
   plate, body face, so it never reads as a fixed hour. */
.tl__time--soft {
    border-style: dashed;
    background-color: transparent;
    font-family: 'BPG Ucnobi', Georgia, serif;
    font-size: 12.5px;
    letter-spacing: 0.04em;
    color: var(--ink-soft);
}

.tl__title {
    /* Room above and below for Medea's long strokes, which otherwise reach
       into the time plate and the note. */
    padding: 10px 0 12px;
    font-family: 'DM Medea', 'BPG Ucnobi', serif;
    font-size: 22px;
    font-weight: normal;
    /* Generous, so a title that wraps (ხელის მოწერის ცერემონია) keeps its
       descenders off the second line's ascenders. */
    line-height: 1.9;
    color: var(--deep);
}

.tl__note {
    font-size: 14px;
    line-height: 1.6;
    color: var(--ink-soft);
}

/* flex with a top-aligned pin: venue names wrap on a narrow phone, and a
   baseline-aligned pin would drift down to the last line. */
.tl__place {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    margin-top: 8px;
    font-size: 14px;
    line-height: 1.45;
    color: var(--ink-soft);
}

/* A map pin drawn in the palette instead of an emoji, whose primary red would
   be the loudest colour on the page. */
.tl__pin {
    position: relative;
    flex: 0 0 auto;
    width: 11px;
    height: 11px;
    margin-top: 3px;
    border: 1.5px solid var(--gold);
    border-radius: 50% 50% 50% 0;
    transform: rotate(-45deg);
}

.tl__pin::after {
    content: '';
    position: absolute;
    left: 50%;
    top: 50%;
    width: 3px;
    height: 3px;
    margin: -1.5px 0 0 -1.5px;
    border-radius: 50%;
    background-color: var(--gold);
}

/* Underlined in gold rather than coloured: the guest still sees it is
   tappable, and the venue name keeps the card's quiet tone. */
.tl__link {
    color: var(--ink);
    text-decoration: underline;
    text-decoration-color: var(--gold-lt);
    text-underline-offset: 3px;
    text-decoration-thickness: 1px;
}

.tl__link:hover,
.tl__link:focus-visible {
    color: var(--deep);
    text-decoration-color: var(--gold);
}

/* The illustrations close each card, so a stop reads what -> where -> picture.
   They enter with the card and only then start drifting: the float is delayed
   past the entrance so the animation and the transition never fight over
   `transform`. */
.tl__art {
    position: relative;
    margin-top: 15px;
    border-radius: 16px;
    overflow: hidden;
    box-shadow: 0 4px 16px var(--shadow);
    opacity: 0;
    transform: translateY(16px) scale(0.95);
    transition: opacity 1.6s ease 0.45s,
                transform 1.7s cubic-bezier(0.22, 0.61, 0.36, 1) 0.45s,
                var(--tint);
}

.tl__art img {
    width: 100%;
    height: auto;
    aspect-ratio: 1 / 1;
    object-fit: cover;
}

/* The night painting sits exactly over the day one and fades in with the
   palette, a beat after the toggle like the hero sky. */
.tl__art .tl__art-night {
    position: absolute;
    left: 0;
    top: 0;
    height: 100%;
    opacity: 0;
    transition: opacity 1.6s ease 0.4s;
}

html.is-night .tl__art .tl__art-night {
    opacity: 1;
}

/* The church gets the arch; the rest stay plain plates. */
.tl__art--arch,
.tl__art--arch img {
    border-radius: 130px 130px 16px 16px;
}

.tl__art-line {
    position: absolute;
    inset: 7px;
    border: 1px solid rgba(255, 252, 244, 0.6);
    border-radius: 10px;
    pointer-events: none;
}

.tl__art--arch .tl__art-line {
    border-radius: 122px 122px 9px 9px;
}

.tl__stop.is-lit .tl__art {
    opacity: 1;
    transform: none;
    animation: floaty 10.5s ease-in-out 2.2s infinite;
}

@keyframes floaty {
    0%, 100% { transform: translateY(0); }
    50%      { transform: translateY(-6px); }
}

/* ==========================================================================
   14. RSVP ENVELOPE

   .post is [slot][spacer], with the envelope pinned to its bottom edge at the
   art's own 916 x 648 aspect ratio (height E = 70.74% of the width):

     - .post__env   the envelope box. No z-index, so its children stack with
                    the slot: back (1) and flap (1) behind the letter (2), the
                    painted pocket (3) in front of it.
     - .post__slot  the letter's window. overflow: hidden, and its bottom edge
                    sits 0.45E below the envelope's mouth, behind the pocket's
                    plate and bow, so whatever of the letter is below that line
                    is genuinely inside the envelope.
     - spacer       padding-bottom: 0.55E = 38.9% (percent padding resolves
                    against WIDTH, which is what lets this track E with no JS).

   script.js arms it (.is-armed) by shrinking the slot to a peek, so only the
   letter's head shows in the pocket's V; once the envelope is on screen it
   adds .is-open (the back flap unfolds) and grows the slot to the letter's
   full height, which reads as the letter rising out and the envelope settling
   below it. Without JS the slot is simply auto height: the letter stands open.

   The envelope and the letter are objects, not page surfaces, so they keep
   their own fixed paper colours in both palettes.
   ========================================================================== */
.post {
    position: relative;
    /* Headroom for the opened flap (34% of E = 24% of the width, percent
       margins resolve against width too), so it never rises into the title
       in the beat before the letter covers it. */
    margin: calc(24% + 8px) -6px 0;
    padding-bottom: 38.9%;
}

.post__env {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    aspect-ratio: 916 / 648;
}

/* The inside of the envelope, seen above the pocket's V. Its top edge lines up
   with the pocket's flap tips. */
.post__back {
    position: absolute;
    left: 2.6%;
    right: 3.4%;
    top: 1.5%;
    bottom: 12%;
    z-index: 1;
    border: 1px solid #d9bf8c;
    border-radius: 4px;
    background-color: #efe4cf;
    box-shadow: inset 0 18px 30px rgba(120, 90, 40, 0.18);
}

/* The open back flap, standing up behind the letter. Folded edge-on until
   .is-open, then hinged up from its base. A gold triangle with a cream one
   inset 2px reads as the art's gilded edge. */
.post__flap {
    position: absolute;
    left: 2.6%;
    right: 3.4%;
    bottom: 98.5%;
    z-index: 1;
    height: 34%;
    background-color: #d4b77f;
    clip-path: polygon(0 100%, 50% 0, 100% 100%);
    transform-origin: bottom center;
    transform: perspective(700px) rotateX(-90deg);
    transition: transform 1.1s cubic-bezier(0.34, 1.3, 0.5, 1);
}

.post__flap::before {
    content: '';
    position: absolute;
    inset: 0;
    background-color: #f3e9d6;
    clip-path: polygon(2.4% 99%, 50% 3%, 97.6% 99%);
}

.post.is-open .post__flap {
    transform: perspective(700px) rotateX(0deg);
}

.post__front {
    position: absolute;
    left: 0;
    top: 0;
    z-index: 3;
    width: 100%;
    height: 100%;
    max-width: none;
    pointer-events: none;
}

html.is-night .post__front,
html.is-night .post__back,
html.is-night .post__flap {
    filter: brightness(0.92);
}

.post__slot {
    position: relative;
    z-index: 2;
    margin: 0 9%;
    overflow: hidden;
}

/* Rising: a long ease that starts slow, as if drawn out by hand. */
.post.is-armed .post__slot {
    transition: height 2.1s cubic-bezier(0.6, 0, 0.2, 1);
}

/* The flap's headroom is only needed while the letter is still in the pocket.
   It closes on the same curve and beat as the rise (0.65s is the flap-first
   delay in startPost), and since the slot grows far faster than the margin
   shrinks, the flap tip only ever moves down, away from the title. */
.post.is-armed {
    transition: margin-top 2.1s cubic-bezier(0.6, 0, 0.2, 1) 0.65s, var(--tint);
}

.post.is-armed.is-open {
    margin-top: 30px;
}

/* The letter. Its bottom padding (0.45E, 36% of its own width + room) is the
   part that stays tucked inside the pocket even when it stands fully open. */
.letter {
    --l-paper: #fffcf5;
    --l-ink: #4b4237;
    --l-soft: #8b7e6c;
    --l-gold: #b48a4e;
    --l-line: #e6d8bd;

    position: relative;
    padding: 22px 20px calc(36% + 14px);
    border-radius: 6px 6px 0 0;
    background-color: var(--l-paper);
    box-shadow: 0 -4px 20px rgba(110, 82, 44, 0.14);
    color: var(--l-ink);
    text-align: left;
}

/* A ruled border inset from the edge, like an engraved card. */
.letter::before {
    content: '';
    position: absolute;
    inset: 8px 8px 0;
    border: 1px solid var(--l-line);
    border-bottom: 0;
    border-radius: 3px 3px 0 0;
    pointer-events: none;
}

html.is-night .letter {
    box-shadow: 0 -4px 26px rgba(0, 0, 0, 0.35);
}

/* The monogram at the head of the letter: the bit the guest sees peeking out
   of the pocket before it rises. */
.letter__seal {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 56px;
    height: 56px;
    margin: 0 auto 10px;
    border: 1px solid var(--l-gold);
    border-radius: 50%;
    font-family: 'DM Medea', 'DM Constitution', serif;
    font-size: 20px;
    line-height: 1;
    color: var(--l-gold);
    box-shadow: inset 0 0 0 3px var(--l-paper), inset 0 0 0 4px var(--l-line);
}

.rsvp {
    position: relative;
}

.field {
    margin-bottom: 16px;
}

.field__label {
    display: block;
    margin-bottom: 4px;
    font-family: 'DM Constitution', 'BPG Ucnobi', serif;
    font-size: 11.5px;
    letter-spacing: 0.2em;
    color: var(--l-gold);
}

/* Written-on-the-letter inputs: a single ruled line, no box. */
.field__input {
    width: 100%;
    padding: 8px 2px;
    border: 0;
    border-bottom: 1px solid var(--l-line);
    border-radius: 0;
    background-color: transparent;
    font-size: 16px;
    color: var(--l-ink);
    -webkit-appearance: none;
    appearance: none;
}

.field__input::placeholder {
    color: #b9ad9a;
}

.field__input:focus {
    outline: none;
    border-bottom-color: var(--l-gold);
}

/* Ruled writing paper: the lines are the textarea's own background, attached
   to its content so they scroll with the text. Line-height and the rule pitch
   must stay equal. */
.field__input--area {
    display: block;
    min-height: 92px;
    padding: 2px 2px 0;
    border-bottom: 0;
    line-height: 30px;
    background-image: repeating-linear-gradient(
        to bottom,
        transparent 0 29px,
        #e6d8bd 29px 30px
    );
    background-attachment: local;
    resize: vertical;
}

.choice {
    margin-bottom: 18px;
    border: 0;
}

.choice__row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 9px;
    margin-top: 6px;
}

.choice__opt input {
    position: absolute;
    opacity: 0;
    pointer-events: none;
}

.choice__box {
    display: block;
    padding: 11px 6px;
    border: 1px solid var(--l-line);
    border-radius: 999px;
    background-color: #fbf5ea;
    font-size: 14px;
    text-align: center;
    color: var(--l-ink);
    cursor: pointer;
    transition: border-color 0.3s ease, background-color 0.3s ease, color 0.3s ease, transform 0.3s ease;
}

.choice__opt input:checked + .choice__box {
    border-color: var(--l-gold);
    background-color: var(--l-gold);
    color: #fffcf5;
    transform: scale(1.03);
}

.choice__opt input:focus-visible + .choice__box {
    outline: 2px solid var(--l-gold);
    outline-offset: 2px;
}

.rsvp__submit {
    position: relative;
    display: block;
    width: 100%;
    margin-top: 6px;
    padding: 13px;
    overflow: hidden;
    border: 0;
    border-radius: 999px;
    background-color: #8f6a3a;
    color: #fffcf5;
    font-size: 16px;
    letter-spacing: 0.08em;
    cursor: pointer;
    transition: background-color 0.3s ease, opacity 0.3s ease;
}

/* A slow sheen across the button: the one moving thing on the letter, so the
   eye finds the send button without it shouting. */
.rsvp__submit::after {
    content: '';
    position: absolute;
    top: 0;
    bottom: 0;
    left: -40%;
    width: 30%;
    background-image: linear-gradient(
        100deg,
        rgba(255, 255, 255, 0) 0%,
        rgba(255, 248, 230, 0.35) 50%,
        rgba(255, 255, 255, 0) 100%
    );
    transform: skewX(-18deg);
    animation: sheen 5.5s ease-in-out 2s infinite;
}

@keyframes sheen {
    0%, 70% { left: -40%; }
    100%    { left: 120%; }
}

.rsvp__submit:active {
    background-color: #7a5a30;
}

.rsvp__submit:disabled {
    opacity: 0.55;
    cursor: default;
}

.rsvp__status {
    min-height: 20px;
    margin-top: 10px;
    font-size: 13.5px;
    color: var(--l-soft);
    text-align: center;
}

.rsvp__thanks {
    padding: 4px 0 8px;
    text-align: center;
    animation: rise 1.2s cubic-bezier(0.22, 1, 0.36, 1) both;
}

.rsvp__thanks-title {
    font-family: 'DM Medea', 'BPG Ucnobi', serif;
    font-size: 30px;
    line-height: 2.6;
    color: var(--l-gold);
}

.rsvp__thanks-body {
    font-size: 15px;
    color: var(--l-soft);
}

/* ==========================================================================
   14b. FIND YOUR TABLE
   Stays [hidden] until find-table-link.js finds a published, non-empty seating
   plan; .sheet sets display, so [hidden] has to be restated to win.
   ========================================================================== */
.sheet--table[hidden] {
    display: none;
}

.table__lead {
    margin-top: 16px;
    font-size: 15.5px;
    line-height: 1.75;
    color: var(--ink-soft);
}

/* Same bronze as the RSVP send button by day; lamplight gold with indigo ink at
   night, since bronze sinks into the night paper. */
.table__cta {
    display: inline-flex;
    align-items: center;
    gap: 12px;
    margin-top: 24px;
    padding: 13px 26px;
    border-radius: 999px;
    background-color: #8f6a3a;
    color: #fffcf5;
    font-size: 16px;
    letter-spacing: 0.06em;
    text-decoration: none;
    box-shadow: 0 6px 18px var(--shadow);
    transition: opacity 1.3s ease, transform 1.3s cubic-bezier(0.22, 1, 0.36, 1), var(--tint);
}

.table__cta:active {
    background-color: #7a5a30;
}

html.is-night .table__cta {
    background-color: var(--gold);
    color: var(--paper);
}

/* Chevron from borders, so the button costs no extra request */
.table__chev {
    width: 7px;
    height: 7px;
    margin-left: -2px;
    border-top: 1px solid currentColor;
    border-right: 1px solid currentColor;
    transform: rotate(45deg);
}

/* ==========================================================================
   15. FOOTER
   ========================================================================== */
.foot__names {
    font-family: 'DM Medea', 'BPG Ucnobi', serif;
    font-size: 28px;
    line-height: 2.7;
    color: var(--deep);
}

.foot__amp {
    font-family: 'DM Constitution', Georgia, serif;
    font-size: 16px;
    color: var(--gold);
}

.foot__date {
    font-size: 13px;
    letter-spacing: 0.2em;
    color: var(--ink-soft);
}

.credit {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    margin-top: 22px;
    font-size: 12px;
    color: var(--ink-soft);
    text-decoration: none;
    opacity: 0.3;
    transition: opacity 0.3s ease, var(--tint);
}

.credit:hover,
.credit:focus-visible {
    opacity: 0.75;
}

/* logo-vector.svg is a single-colour mark; the filter takes it from its stock
   blue to the page's bronze, and to lamplight gold at night. */
.credit__logo {
    width: 16px;
    height: auto;
    filter: brightness(0) saturate(100%) invert(48%) sepia(38%) saturate(620%) hue-rotate(359deg) brightness(92%);
}

html.is-night .credit__logo {
    filter: brightness(0) saturate(100%) invert(80%) sepia(30%) saturate(620%) hue-rotate(2deg) brightness(98%);
}

/* ==========================================================================
   16. BREAKPOINTS
   Mobile-first, min-width only. 481px is the large-phone step; 769px hands the
   screen over to the desktop notice entirely.
   ========================================================================== */
@media (min-width: 481px) {
    .hero__names  { font-size: 40px; }
    .hero__amp    { font-size: 22px; }
    .hero__date   { font-size: 19px; }
    .sheet__title { font-size: 38px; }
    .sheet__inner { max-width: 400px; }
    .cd__num      { font-size: 31px; }
    .cd__label    { font-size: 12px; }
    .invite__text { font-size: 20px; }
}

@media (min-width: 769px) {
    .mobile-prompt {
        position: fixed;
        inset: 0;
        z-index: 20000;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        gap: 18px;
        padding: 30px;
        background-color: var(--paper);
        text-align: center;
    }

    .mobile-prompt__icon {
        width: 52px;
        height: auto;
        opacity: 0.8;
    }

    .mobile-prompt p {
        font-size: 17px;
        letter-spacing: 0.06em;
        color: var(--ink-soft);
    }
}

/* ==========================================================================
   17. REDUCED MOTION
   Ambient loops go entirely. Reveals become instant rather than disabled, so
   nothing is left stuck at opacity 0; the sun and moon still change places,
   just quickly.
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
    .loader__mark,
    .music-toggle.playing .music-toggle__bars span,
    .entry__hint,
    .entry__tap,
    .stars span,
    .sun__img,
    .sun__glow,
    .moon__img,
    .moon__glow,
    .orb__sun,
    .invite__heart,
    .cue__ring,
    .cue__chev,
    .rsvp__submit::after,
    .tl__stop.is-lit .tl__art,
    .tl__stop.is-lit .tl__dot::after,
    .tl__stop.is-lit .tl__card::after,
    body.is-open .sky-hint {
        animation: none !important;
    }

    .music-toggle.playing .music-toggle__bars span { transform: scaleY(1); }
    .music-toggle.playing .music-toggle__bars span:nth-child(2) { transform: scaleY(0.5); }
    .music-toggle.playing .music-toggle__bars span:nth-child(3) { transform: scaleY(0.78); }
    .music-toggle.playing .music-toggle__bars span:nth-child(4) { transform: scaleY(0.42); }

    .entry__tap,
    .cue__ring::after,
    .rsvp__submit::after {
        display: none;
    }

    .is-open .hero__eyebrow,
    .is-open .hero__names,
    .is-open .hero__rule,
    .is-open .hero__when {
        animation: none !important;
        opacity: 1;
        transform: none;
    }

    .scene,
    .entry__panel,
    .entry__env,
    .sun,
    .moon,
    .post__flap,
    .post.is-armed .post__slot {
        transition-duration: 1s !important;
        transition-delay: 0s !important;
    }

    .reveal,
    .tl__stop,
    .tl__art,
    .sheet__rule {
        transition-duration: 0.01ms !important;
    }
}
