/* ==================================================================
   Kerala — "Backwater Green" · T-KB
   A SKIN of Lantern Palace (T-LP), taken by way of Tamil Mandapam (T-TM) and
   Goa (T-GS). Same renderer, same class names, same asset keys — only the
   tokens, the type, the edge paths and the hero band numbers change.

   The room modifier classes keep their Lantern Palace names on purpose:
   main.js maps `room--teal → paper-teal` etc., so renaming them would be a
   change to the engine. `room--palm` is T-GS's one added class (with its
   matching line in main.js's paintPaper map) and is inherited unchanged — the
   seventh ground here is GOLD rather than palm green.

   WHICH TILE SITS IN WHICH SLOT IS NOT A FREE CHOICE. Each room multiplies its
   paper tile over its own ground at 24% with mix-blend-mode: multiply, so a
   ground that disagrees with its tile in HUE goes muddy. The source of truth is
   `../assets/manifest.json` → `skin.rooms`, which is DERIVED at build time by
   manifest.py: it samples each tile's median hue, compares it against the
   ground below, and refuses to write a manifest where any pair has drifted more
   than 40 degrees. Do not re-derive the mapping from key names, and do not
   "fix" a mismatch here — fix it where it is measured.

   Measured off the tiles on disk, agreeing with skin.rooms:

       paper-teal      = paper-kasavu.png  #EDDBBA  kasavu cream   hue  39
       paper-pink      = paper-leaf.png    #7E9739  banana leaf    hue  76
       paper-marigold  = paper-blue.png    #197C86  backwater      hue 186
       paper-indigo    = paper-deep.png    #294C2B  deep green     hue 123
       paper-cream     = paper-cream.png   #EDDBBA  kasavu cream   hue  39
       paper-palm      = paper-gold.png    #E0AA30  kasavu gold    hue  42

   So the slots are:

     .room--teal      cream       invite         The Tharavad
     .room--pink      leaf        functions      Four Mornings
     .room--marigold  backwater   route          Follow the backwaters
     .room--indigo    deep green  story          How It Happened
     .room--cream     pale cream  RSVP           Say You Will Come
     .room--palm      gold        things to know Things Worth Knowing
     .room--night     night green countdown      Counting to the Muhurtham
   ================================================================== */

:root {
  /* the brief's six, verbatim */
  --kasavu:    #F3EAD3;   /* kasavu cream */
  --gold-k:    #C9A24A;   /* kasavu gold */
  --leaf:      #3E7A42;   /* leaf green — an ACCENT; see --leaf-gr for the ground */
  --deep:      #1F4D33;   /* deep green */
  --backwater: #4E8C8A;
  --tile-red:  #A64B2A;   /* Mangalore tile */

  /* four derived values the six do not cover */
  --leaf-gr:   #4F6B2C;   /* the functions ground. NOT --leaf: paper-leaf.png measures
                             #7E9739 (hue 76) and --leaf is hue 124, a 48-degree
                             disagreement that goes grey-olive under the multiply.
                             #4F6B2C is hue 87 — the same young-banana-leaf green,
                             turned towards the tile. --leaf stays as an accent. */
  --gold-gr:   #DDBE72;   /* the `know` ground: --gold-k lifted two steps, so deep-green
                             body copy clears 4.5:1 on it. Same hue (42) as paper-gold. */
  --cream-lo:  #FBF6E9;   /* the RSVP ground — cream, one step paler than the invite's,
                             so two cream rooms read as two rooms */
  --night:     #12301F;   /* the backwater after dark — gate, footer, countdown */

  /* the rooms — LP slot names, Kerala values, each matched to its paper tile */
  --teal:      var(--kasavu);       /* paper-teal tile is kasavu cream */
  --teal-deep: #E4D6B4;
  --pink:      var(--leaf-gr);      /* paper-pink tile is banana leaf */
  --pink-deep: #3F5722;
  --gold:      var(--backwater);    /* paper-marigold tile is backwater blue */
  --gold-deep: #3C7371;
  --zari:      var(--gold-k);       /* the kasavu metal, where LP had gold */
  --zari-deep: #A07C31;
  --indigo:    var(--deep);         /* the story room */
  --indigo-lo: var(--night);
  --cream:     var(--cream-lo);
  --cream-deep:#EFE7D4;
  --rose:      var(--tile-red);

  /* ink that stays readable on paper */
  --ink:        #22301F;
  --ink-soft:   #4A5B46;
  --on-dark:    #F7F1E1;
  --on-dark-soft:#DDE5D5;

  /* Cormorant Garamond for the running display type; Marcellus for the caps —
     a Trajan-descended inscriptional face, which is what the gold lettering on
     a kerala wedding card is cut from. Marcellus has NO lowercase personality
     to speak of and only one weight, so it is used tracked and uppercase for
     labels and never for running text. Noto Serif Malayalam carries exactly one
     line (the household greeting) and is loaded for that alone. */
  --serif:  "Cormorant Garamond", Georgia, "Times New Roman", serif;
  --body:   "Cormorant Garamond", Georgia, serif;
  --caps:   "Marcellus", "Cormorant Garamond", Georgia, serif;
  --mal:    "Noto Serif Malayalam", "Cormorant Garamond", serif;

  --wrap: 1080px;
  --edge-h: clamp(46px, 6vw, 108px);
  --pad-y: clamp(72px, 11vh, 150px);
  --pad-x: clamp(20px, 5vw, 56px);

  /* Gate geometry — the box the two door-leaf plates are drawn into, as a % of
     the gate plate. Both leaves were cut from the real gate onto ONE shared
     540x713 canvas at (770,430) of the 2048x1143 plate, so they take the same
     rect and cannot drift apart, and each canvas edge IS its hinge.
     These are defaults: main.js overrides them from ../assets/manifest.json's
     `door_rig.rect_norm` when it is present, so a repainted gate needs no edit
     here. */
  --gate-door-left:  38.52%;
  --gate-door-top:   46.76%;
  --gate-door-w:     22.07%;
  --gate-door-h:     41.87%;
  --gate-hinge-l:    0%;
  --gate-hinge-r:    100%;
}

/* The phone gate is a different painting with a different door rect, so it gets
   its own defaults. main.js overwrites all four from assets/manifest.json's
   `door_rig.mobile` once that fetch lands; these keep the first painted frame
   correct in the meantime. From rect_norm x0 .3358 x1 .6270 y0 .4611 y1 .8672. */
@media (max-width: 759px) {
  :root {
    --gate-door-left: 33.58%;
    --gate-door-top:  46.11%;
    --gate-door-w:    29.12%;
    --gate-door-h:    40.61%;
  }
}

*, *::before, *::after { box-sizing: border-box; }

html { scroll-behavior: auto; }

body {
  margin: 0;
  background: var(--indigo-lo);
  color: var(--ink);
  font-family: var(--body);
  font-size: clamp(16px, 1.05vw + 12px, 19px);
  /* Josefin Sans has a large x-height and short descenders, so it takes less
     leading than a Tamil face did */
  line-height: 1.66;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

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

a { color: inherit; }

.skip {
  position: fixed; top: -100px; left: 16px; z-index: 90;
  background: var(--cream); color: var(--ink);
  padding: 10px 18px; border-radius: 999px; text-decoration: none;
  font-family: var(--caps); font-size: 14px;
}
.skip:focus { top: 16px; }

:focus-visible { outline: 3px solid var(--zari); outline-offset: 3px; }

/* T-GS: T-TM's bilingual block (.ta / .en / .bi and the two Tamil faces) is
   gone. This skin is monolingual English, so nothing has to cancel a
   text-transform or a tracking to stay legible. */

/* ------------------------------------------------------------------
   type
   ------------------------------------------------------------------ */
.eyebrow {
  font-family: var(--caps);
  font-size: clamp(11px, 1.1vw, 14px);
  letter-spacing: .26em;
  text-transform: uppercase;
  margin: 0 0 .9em;
  opacity: .78;
}
.room-title {
  font-family: var(--caps);
  font-weight: 400;
  font-size: clamp(30px, 5.4vw, 62px);
  letter-spacing: .1em;
  text-transform: uppercase;
  line-height: 1.12;
  margin: 0 0 .35em;
}
.room-sub {
  font-size: clamp(16px, 1.7vw, 21px);
  font-style: italic;
  opacity: .82;
  margin: 0 auto;
  max-width: 46ch;
}
.center { text-align: center; }
.wrap {
  position: relative; z-index: 3;
  width: min(var(--wrap), 100% - var(--pad-x) * 2);
  margin-inline: auto;
}
.wrap--narrow { width: min(720px, 100% - var(--pad-x) * 2); }
.room-head { margin-bottom: clamp(36px, 6vw, 72px); }

.rule {
  width: 130px; height: 1px; margin: clamp(22px,3vw,34px) auto;
  background: currentColor; opacity: .32;
}

/* ------------------------------------------------------------------
   buttons
   ------------------------------------------------------------------ */
.btn {
  display: inline-block;
  font-family: var(--caps);
  font-size: clamp(12px, 1.2vw, 15px);
  letter-spacing: .18em;
  text-transform: uppercase;
  text-decoration: none;
  padding: 14px 30px;
  border-radius: 999px;
  border: 1.5px solid currentColor;
  background: var(--ink);
  color: var(--cream);
  border-color: var(--ink);
  cursor: pointer;
  transition: transform .25s ease, background .25s ease, color .25s ease;
}
.btn:hover { transform: translateY(-2px); }
.btn--ghost { background: transparent; color: inherit; border-color: currentColor; }
.cta-row {
  display: flex; flex-wrap: wrap; gap: 14px;
  justify-content: center; align-items: center;
  margin-top: clamp(26px, 4vw, 42px);
}

.mobcta {
  position: fixed; left: 50%; bottom: 18px; transform: translateX(-50%) translateY(140%);
  z-index: 60;
  background: var(--rose); color: var(--cream);
  font-family: var(--caps); letter-spacing: .2em; text-transform: uppercase;
  font-size: 13px; padding: 14px 40px; border-radius: 999px; text-decoration: none;
  box-shadow: 0 10px 30px rgba(30,14,20,.35);
  transition: transform .5s cubic-bezier(.2,.8,.2,1);
}
.mobcta.is-in { transform: translateX(-50%) translateY(0); }
.mobcta.is-hid { transform: translateX(-50%) translateY(140%); } /* the real buttons are on screen — main.js */
@media (min-width: 760px) { .mobcta { display: none; } }

/* ==================================================================
   1 + 2. THE JOURNEY — sky, push-through, doors
   ================================================================== */
.journey {
  position: relative;
  height: 340vh;               /* JS shortens this when motion is reduced */
  background: #3A2430;   /* pre-dawn behind the mandapam */
}
.stage {
  position: sticky; top: 0;
  height: 100vh; height: 100svh;
  overflow: hidden;
  perspective: 1500px;
  transform-style: preserve-3d;
}

.sky, .courtyard {
  position: absolute; inset: -6% -6% -6% -6%;
  background-size: cover; background-position: center bottom;
  will-change: opacity, transform;
}
.sky--dusk  { background-color: #E8A46A; }   /* sunset over the Arabian Sea */
.sky--night { background-color: #0E2E36; opacity: 0; }
/* the painted night stars are large; holding the plate under full opacity keeps
   the dusk warmth showing through and stops them reading as confetti */
.courtyard  { opacity: 0; transform: scale(1.25); background-position: center; }

.lyr {
  position: absolute; left: 50%; bottom: -2%;
  width: 128%; transform: translateX(-50%);
  transform-origin: 50% 100%;
  will-change: transform;
}
.lyr img { width: 100%; }

/* the cloud plane: the slowest thing in the hero, slower than palace-back */
.lyr--cloud {
  bottom: auto; top: 0; height: 74%;
  width: 118%;
  transform-origin: 50% 60%;
}
/* T-KB: the monsoon cloud is a raining cumulus with visible drops, so it is
   kept small, low-contrast and off the centre line for T-GS's reason (a big
   pale mass behind the names reads as fog on the lens) and one of its own: at
   any size the drops are legible, and rain falling directly behind the couple's
   names says monsoon on a January morning that is explicitly dry. */
.cloud { position: absolute; width: auto; will-change: transform; }
.cloud--a { top: 3%;  left: -11%; width: 20%; opacity: .22; }
.cloud--b { top: 15%; right: -9%; left: auto; width: 15%; opacity: .18; }
/* These four numbers are the whole desktop hero, and they are not free.
   `.lyr` sets a percentage WIDTH and bottom-aligns; `.lyr img` is width:100%.
   So a band's drawn height is (lyr width in px) x band_over_width, and the
   plate's own HEIGHT never enters it (assets/manifest.json → hero_layers).

   Two things are different from T-GS and both are about this skin's SKY.
   sky-dusk.png here is not a sky: it is a whole painted backwater — sunset,
   flat paper-cut palms, two garlanded kettuvallam and water — so the hero has
   FOUR planes of landscape, not three, and any band whose own cut edge lands in
   open sky shows as a hard horizontal seam across the full width.
   palace-back is therefore pushed almost to the floor (bottom 2%, against
   T-GS's 14%): its plate bottom then sits behind palace-mid's compound wall
   instead of over the sky's water, which is where the seam was. Its misty
   treeline still reads in the gap above the wall.
   palace-mid is palace-mid-alt2, band_over_width 0.350 — the lowest of the six
   shipped bands, and the only one that leaves the back skyline visible (the
   default palace-mid measures 0.507 and buries it). Both stay past 100% so
   their own left and right cut edges fall off-screen. Composited at 1440x900
   with hero.py before a line of this was written. */
.lyr--back { bottom: 2%; width: 108%; opacity: .92; }
.lyr--mid  { bottom: -6%; width: 106%; }

/* the gate sits nearest the camera and is what you fly through. It is
   deliberately bottom-heavy: the names live in the open sky above it. */
.gate {
  position: absolute; left: 50%; bottom: -4%;
  /* 70vh / 46%: at 1440x900 this lands at 630px wide, which is what the hero
     was composited at. Bigger and the nalukettu's own roof eats the backwater
     band behind it; smaller and the teak doors — the thing you fly through —
     are under 140px on screen before the push starts. */
  width: min(70vh, 46%);
  aspect-ratio: 1672 / 941;
  transform: translateX(-50%);
  /* The camera flies into the DOORWAY, not the middle of the plate, so the
     opening stays put on screen while everything else expands past the edges.
     This is the centre of the door rect measured by doors.py and published in
     assets/manifest.json — 49.6% / 67.7%. The doors are RECTANGULAR here, under
     a flat carved lintel, and they stop at y1 = 0.8863 on the granite plinth.
     Scale is 10.4: the doorway is 22.1% of this plate's width, and push-through
     is 2.3 / that share — 12.4 at Goa's 18.5%, 9 at Tamil's 25.6%. Copying
     either number would over- or under-shoot. main.js reads it from the
     manifest; this is only the fallback origin. */
  transform-origin: var(--gate-origin, 49.6% 67.7%);
  will-change: transform, opacity;
}
.gate-plate { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; object-position: bottom; }
.gate-plate--open, .gate-plate--frame { opacity: 0; }

/* Lantern Palace's gate plate was keyed — transparent outside the gateway
   silhouette — so it sat on the sky with no edge. Both Tamil plates are OPAQUE
   and full-bleed (manifest: alpha false), so at the small end of the fly-through
   they read as a painted rectangle pasted onto the sky. Feather the edges: the
   mandapam genuinely continues past frame, so a soft fall-off reads as aerial
   distance where a hard cut reads as a mistake. The mask scales with the plate,
   so by the time the camera is through the arch the feather is off-screen.
   T-KB keeps T-GS's deep top fade for the same reason and then some: this
   plate's own sky is a pale overcast white-blue and it is laid over a warm
   sunset, so a shallow fade left a cold rectangle across the top third. 22%
   desktop / 18% phone dissolves it into the sky it sits on. */
.gate-plate {
  /* Every plate, not just --shut: main.js shows `.gate-plate--frame` whenever
     both door leaves exist (which they do), and although that plate has the
     arch punched transparent, its OUTER boundary is still the opaque full
     rectangle — so the cut shows on whichever plate is on screen.
     Both axes. The top edge could stay crisp while the gate sat against open
     sky, but palace-mid is a full-width band now, so the gate is an opaque
     painting laid over another opaque painting of the same building — a hard
     cut there reads as a panel pasted into the facade. Feathered on all three
     free edges it reads as depth instead. Bottom stays hard; it is the ground. */
  -webkit-mask-image:
    linear-gradient(90deg, transparent 0, #000 11%, #000 89%, transparent 100%),
    linear-gradient(180deg, transparent 0, #000 22%, #000 100%);
  -webkit-mask-composite: source-in;
          mask-image:
    linear-gradient(90deg, transparent 0, #000 11%, #000 89%, transparent 100%),
    linear-gradient(180deg, transparent 0, #000 22%, #000 100%);
          mask-composite: intersect;
}
@media (max-width: 759px) {
  .gate-plate {
    -webkit-mask-image:
      linear-gradient(90deg, transparent 0, #000 7%, #000 93%, transparent 100%),
      linear-gradient(180deg, transparent 0, #000 18%, #000 100%);
    -webkit-mask-composite: source-in;
            mask-image:
      linear-gradient(90deg, transparent 0, #000 7%, #000 93%, transparent 100%),
      linear-gradient(180deg, transparent 0, #000 18%, #000 100%);
            mask-composite: intersect;
  }
}

.arch-view, .doors {
  position: absolute;
  top: var(--gate-door-top);
  left: var(--gate-door-left);
  width: var(--gate-door-w);
  height: var(--gate-door-h);
  transform-style: preserve-3d;
  /* the .gate ancestor is scaled, which cuts off .stage's perspective — so the
     doors carry their own. Keep it generous or the swing reads as a shear. */
  perspective: 1100px;
}
.door {
  position: absolute; inset: 0;      /* both leaves share the box; each paints its half */
  backface-visibility: hidden;
  will-change: transform;
}
.arch-view {
  background-size: cover;
  background-position: center bottom;
  background-color: #2B1218;
  opacity: 0;
}
.door img { width: 100%; height: 100%; object-fit: fill; }
.door--l { transform-origin: var(--gate-hinge-l) center; }
.door--r { transform-origin: var(--gate-hinge-r) center; }

.drift {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  pointer-events: none;
  z-index: 4;
}

/* hero type */
.hero {
  position: absolute; inset: 0; z-index: 6;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  text-align: center;
  /* sits above the skyline, not on it */
  padding: 0 var(--pad-x) clamp(140px, 42vh, 420px);
  color: var(--on-dark);
  text-shadow: 0 2px 26px rgba(27,13,18,.55), 0 1px 3px rgba(27,13,18,.4);
  will-change: opacity, transform;
}
.hero-plate {
  position: relative;
  padding: clamp(26px, 5vh, 60px) clamp(24px, 5vw, 80px);
}
/* T-GS: an ELLIPSE, not `closest-side`. The inherited circle was sized off the
   box's short side, so on a 1440-wide hero it read as a distinct grey disc
   floating in a bright sunset sky — very visible, where over T-TM's dark
   mandapam it was invisible. Widened to the shape of the type block, dropped
   in peak alpha; T-KB tints it to the deep green rather than the night sea. */
.hero-plate::before {
  content: ""; position: absolute; inset: -8% -4%; z-index: -1;
  background: radial-gradient(ellipse 62% 76% at 50% 46%,
    rgba(14,40,26,.36), rgba(14,40,26,.19) 54%, rgba(14,40,26,0) 88%);
}
/* On a PHONE the ellipse is not enough, and the reason is this skin's own
   layout: .lyr--back sits at bottom 50% here (goa-shack had 40%), which puts a
   band of sunlit open water directly behind the date and venue lines — the two
   smallest, lightest lines on the page. Measured off the capture, the date sat
   on #BFD2D6 at 1.9:1. A second, LOWER scrim carries them: it is bottom-weighted
   so it darkens the water without touching the names above, which already have
   the sky behind them. */
@media (max-width: 759px) {
  .hero-plate::after {
    content: ""; position: absolute; inset: 46% -6% -10%; z-index: -1;
    background: linear-gradient(rgba(14,40,26,0), rgba(14,40,26,.42) 38%, rgba(14,40,26,.46));
    border-radius: 40% 40% 30% 30% / 26% 26% 20% 20%;
  }
}

.hero-eyebrow {
  text-shadow: 0 1px 14px rgba(10,40,48,.6);
  font-family: var(--caps);
  font-size: clamp(10px, 1.1vw, 13px);
  letter-spacing: .4em; text-transform: uppercase;
  margin: 0 0 clamp(14px, 2.4vw, 26px); opacity: .85;
}
.hero-names {
  font-family: var(--serif);
  font-weight: 300;
  font-size: clamp(46px, 10.5vw, 132px);
  line-height: .96;
  letter-spacing: .01em;
  margin: 0;
  /* carries most of the legibility now that the scrim behind is light — a
     tight, dark halo reads as evening air rather than as a panel */
  text-shadow: 0 2px 26px rgba(10,40,48,.55), 0 1px 3px rgba(10,40,48,.35);
  display: flex; flex-direction: column; align-items: center; gap: .04em;
}
.hero-weds {
  font-family: var(--caps);
  font-style: normal;
  font-size: clamp(13px, 1.9vw, 22px);
  letter-spacing: .5em; text-transform: lowercase;
  opacity: .82;
  padding-left: .5em;
}
.hero-date {
  font-family: var(--caps);
  font-size: clamp(13px, 1.6vw, 19px);
  letter-spacing: .22em; text-transform: uppercase;
  margin: clamp(20px, 3vw, 34px) 0 6px;
}
.hero-venue { margin: 0; font-style: italic; opacity: .9; font-size: clamp(15px,1.5vw,19px); }
/* both small lines carry their own shadow as well as the plate scrim: on the
   phone they land on open water, which is the brightest thing in the hero */
.hero-date, .hero-venue { text-shadow: 0 1px 12px rgba(14,40,26,.75); }
/* "Kumarakom Lake Resort · Kottayam, Kerala" wraps to two lines at 390px. The
   eyebrow directly above already carries KUMARAKOM · KERALA, so the phone drops
   the duplicate rather than shrinking the type to fit. */
@media (max-width: 759px) { .hero-city { display: none; } }
.hero-hint {
  position: absolute; bottom: clamp(24px, 5vh, 54px); left: 0; right: 0;
  margin: 0;
  font-family: var(--caps);
  font-size: clamp(10px, 1vw, 12px);
  letter-spacing: .3em; text-transform: uppercase;
  opacity: .95;
  display: flex; flex-direction: column; align-items: center; gap: 12px;
}
/* the hint lands over the gate, so it carries its own scrim */
.hint-pill {
  background: rgba(27,13,18,.42);
  border: 1px solid rgba(247,242,230,.22);
  border-radius: 999px;
  padding: 9px 20px;
  backdrop-filter: blur(3px);
}
/* ------------------------------------------------------------------
   9:16 — the phone hero
   ------------------------------------------------------------------
   `.lyr` sizes these bands by WIDTH and bottom-aligns them, so the only ratio
   that matters is band-height over plate-WIDTH; plate height never enters it.
   At 9:16 the same "bottom 40% of the frame" is 1.8x taller relative to width
   than at 16:9, which is how the inherited `.lyr--mid { width: 160% }` ended up
   filling the whole phone viewport with facade and burying the dawn sky.
   The 9:16 bands have since been repainted low (palace-mid-9x16 band/width went
   1.79 → 0.374), and these two pairs are the asset side's A/B at 390x844
   (manifest.json → hero_layers.recommended_css_mobile).
   ------------------------------------------------------------------ */
@media (max-width: 759px) {
  /* palace-mid-9x16's band_over_width is 1.197 — the band is TALLER than the
     plate is wide — so on a 390px phone it draws 532px of palms and wall. It is
     left on the floor and taken past the gate's own width instead: at 114% its
     palms flank the tharavad on both sides, which is the only place they can be
     seen at all, and its solid lower half covers the gate plate's feathered
     bottom corners.
     palace-back is lifted to 50%, NOT T-GS's 40%. The 9:16 back plate is a
     band of open backwater with one kettuvallam on it; at 40% the gate's gable
     cut straight through the boat. At 50% the whole crossing sits clear above
     the roofline and is the first thing read on the phone. Composited at
     390x844, three variants side by side, with hero.py. */
  .lyr--back { width: 120%; bottom: 50%; opacity: .88; }
  .lyr--mid  { width: 114%; bottom: 0; }

  /* The phone gate is a SEPARATE PORTRAIT PAINTING (palace-gate-9x16, 941x1672),
     not a crop of the 16:9 one — a 9:16 cover-crop of the wide nalukettu keeps
     the doors and loses both nilavilakku, both banana clumps and the carved
     gable, i.e. every Kerala recogniser except the door, at the exact moment
     the gate is the whole screen.
     Because the plate is portrait, the BOX has to be portrait too: the door
     rect is a percentage of this box, and a 16:9 box would letterbox the
     portrait plate inside it and slide the doors off the opening.
     74vw, not T-GS's 78vw: at 78vw the gable reached the backwater band and cut
     the kettuvallam in half. At 74vw it draws 513px on an 844px screen, the
     water clears the roof, and the doors are still 150px across. */
  .gate {
    width: 74vw;
    aspect-ratio: 941 / 1672;
    /* centre of door_rig.mobile's rect, not the desktop rect. main.js overwrites
       --gate-origin from the manifest once it loads; this is the default. */
    --gate-origin: 48.1% 66.4%;
  }
}

.hint-arrow {
  width: 1px; height: 34px; background: currentColor;
  transform-origin: top;
  animation: hint 2.1s ease-in-out infinite;
}
@keyframes hint {
  0%, 100% { transform: scaleY(.35); opacity: .35; }
  50%      { transform: scaleY(1);   opacity: .95; }
}

/* ==================================================================
   ROOMS — painted paper, curved edges between them
   ================================================================== */
.room {
  position: relative;
  padding: calc(var(--pad-y) + var(--edge-h) * .55) 0 var(--pad-y);
  isolation: isolate;
}
.room::before {                       /* the paper grain */
  content: ""; position: absolute; inset: 0; z-index: 0;
  background-image: var(--paper-tex, none);
  background-size: 300px 300px;
  opacity: .24;
  mix-blend-mode: multiply;
  pointer-events: none;
}
.room > .wrap, .room > .drive { position: relative; z-index: 3; }

/* T-KB: the ONE Malayalam line on the page — the household greeting, above its
   English twin in the invite room and inside the gate. Noto Serif Malayalam
   sits noticeably larger on the same em than a Latin serif, so it is stepped
   down to .82em and given extra line-height: Malayalam stacks conjuncts above
   and below the baseline and clips against a tight leading. */
.greet-mal {
  display: block;               /* a span by default, so it would sit ON the English line */
  font-family: var(--mal);
  font-size: .82em;
  line-height: 1.8;
  opacity: .8;
  margin: 0 0 .3em;
}
.gate-to .greet-mal { font-size: .92em; margin: 0 0 .25em; }

/* the curved wipe: the room's own paper bulges up over the section above it */
.edge {
  position: absolute; top: calc(-1 * var(--edge-h) + 1px); left: 0;
  width: 100%; height: var(--edge-h);
  z-index: 2; display: block;
  fill: var(--edge-fill, currentColor);
}

/* The four rooms of the BRIEF, plus RSVP and the countdown. Each ground is the
   colour of the paper tile main.js will multiply over it — see the map at the
   top of this file. Swapping a ground without swapping the tile makes mud. */
/* Contrast against the ground, measured with the ink each room actually uses:
     kasavu #F3EAD3 + ink #22301F              12.0:1
     leaf   #4F6B2C + on-dark #F7F1E1           6.0:1
     backwater #4E8C8A + on-dark                3.5:1 — headings and buttons only,
                                                never body copy (inherited rule)
     deep   #1F4D33 + on-dark                   9.3:1
     cream  #FBF6E9 + ink                      12.6:1
     gold   #DDBE72 + deep-green ink #22301F    6.7:1
   This is why the `know` ground is --gold-gr and not the brief's --gold-k: at
   #C9A24A the same body copy measures 4.3:1 and misses. The brief's gold stays
   as --gold-k and is what every rule, border and small-caps label is drawn in. */
.room--teal     { background: var(--kasavu);  color: var(--ink);     --edge-fill: var(--kasavu); }    /* The Tharavad */
.room--pink     { background: var(--leaf-gr); color: var(--on-dark); --edge-fill: var(--leaf-gr); }   /* Four Mornings */
.room--marigold { background: var(--backwater); color: var(--on-dark); --edge-fill: var(--backwater); } /* Follow the backwaters */
.room--indigo   { background: var(--deep);    color: var(--on-dark); --edge-fill: var(--deep); }      /* How It Happened */
.room--cream    { background: var(--cream-lo); color: var(--ink);    --edge-fill: var(--cream-lo); }
/* T-GS's added class, inherited. main.js has `room--palm → paper-palm` beside
   it; without that line the ground paints but the paper grain does not. Here
   the seventh ground is GOLD. */
.room--palm     { background: var(--gold-gr); color: var(--ink);     --edge-fill: var(--gold-gr); }   /* Things Worth Knowing */
.room--night    { background: var(--indigo-lo); color: var(--on-dark); --edge-fill: var(--indigo-lo); }

/* FOUR of the seven rooms are dark grounds here where T-GS had three — the
   functions room went from coral to leaf green and the story room from sand to
   deep green — so the light-button rule picks up two more selectors. `room--palm`
   drops OUT of it: gold is the one light ground that is not cream, and a white
   button on #DDBE72 measures 1.8:1 and disappears. */
.room--pink .btn, .room--indigo .btn, .room--night .btn, .room--marigold .btn {
  background: var(--kasavu); color: var(--ink); border-color: var(--kasavu);
}
.room--pink .btn--ghost, .room--indigo .btn--ghost,
.room--night .btn--ghost, .room--marigold .btn--ghost {
  background: transparent; color: var(--on-dark); border-color: rgba(247,241,225,.6);
}
/* the gold room takes the inverse: deep-green fill, deep-green hairline */
.room--palm .btn { background: var(--deep); color: var(--kasavu); border-color: var(--deep); }
.room--palm .btn--ghost { background: transparent; color: var(--deep); border-color: rgba(34,48,31,.5); }
/* hairline rules: kasavu gold on every ground that will hold it, cream on the
   two dark greens where gold goes muddy */
.room--marigold .rule, .room--indigo .rule { background: var(--kasavu); opacity: .5; }
.room--teal .rule, .room--cream .rule, .room--palm .rule { background: var(--gold-k); opacity: .7; }
/* hibiscus red is the accent on the two sand grounds, never a ground itself */
/* the joiner — the word "weds", not an ampersand (see index.html): kasavu gold
   on the cream rooms, a warm terracotta on the deep-green story room where gold
   sinks into the ground */
.room--teal .amp, .room--cream .amp { color: var(--gold-k); }
.room--indigo .amp { color: #D98A63; }

/* petals drift BEHIND the copy inside a paper room — in the sky stage they are
   in front of the palace, but type is never something you read through. */
.room > .drift { z-index: 1; }

/* painted stars for the night rooms */
.stars {
  position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background-repeat: repeat;
  background-size: 520px 520px;
  opacity: .85;
  animation: twinkle 7s ease-in-out infinite;
}
@keyframes twinkle {
  0%, 100% { opacity: .58; }
  50%      { opacity: .95; }
}

/* ------------------------------------------------------------------
   3. INVITE
   ------------------------------------------------------------------ */
.greet {
  font-style: italic; font-size: clamp(17px, 2vw, 23px);
  margin: 0 0 clamp(28px, 4vw, 46px); opacity: .9;
}
.hosts { margin: 0 0 clamp(30px, 4.5vw, 54px); display: grid; gap: 18px; }
.host-l { font-family: var(--caps); font-size: clamp(17px, 2.2vw, 25px); letter-spacing: .05em; margin: 0; }
.host-s { margin: 4px 0 0; font-style: italic; font-size: clamp(14px,1.4vw,16px); opacity: .72; }
.lede { font-style: italic; font-size: clamp(16px,1.7vw,20px); opacity: .85; margin: 0 0 .5em; }
.couple {
  font-weight: 300;
  font-size: clamp(48px, 11vw, 122px);
  line-height: 1; margin: .1em 0 0;
  display: flex; flex-direction: column; align-items: center;
}
/* T-KB: the joiner is the WORD "weds", so it is set at a fraction of the
   ampersand's size and tracked — an ampersand can carry 36px between two names,
   a four-letter word at 36px competes with them. Matches .hero-weds. */
.couple .amp {
  font-family: var(--caps); font-style: normal;
  font-size: clamp(11px, 1.5vw, 17px);
  letter-spacing: .42em; text-transform: lowercase;
  opacity: .78; margin: .38em 0 .3em; padding-left: .42em;
}
.couple-date {
  font-family: var(--caps); font-size: clamp(20px, 3.4vw, 40px);
  letter-spacing: .22em; margin: 0;
}
.couple-meta { margin: 10px 0 0; font-style: italic; opacity: .82; }

/* ------------------------------------------------------------------
   4. FUNCTIONS
   ------------------------------------------------------------------ */
.funcs { display: grid; gap: clamp(28px, 4.5vw, 56px); }
.func {
  display: grid;
  grid-template-columns: minmax(0, 270px) minmax(0, 1fr);
  gap: clamp(20px, 3.5vw, 48px);
  align-items: center;
  background: rgba(253,250,241,.94);
  /* T-KB: the card sets its OWN ink. It is a near-white PANEL, and the room it
     sits in went from T-GS's light coral to a dark banana-leaf green, so the
     room's `color: var(--on-dark)` now cascades into it — cream type on a cream
     card, which is exactly what the first capture showed: six blank cards with
     a sprite on each. Any light panel inside a dark room has to say this. */
  color: var(--ink);
  border: 1px solid rgba(34,48,31,.20);
  border-radius: clamp(30px, 5vw, 64px);
  box-shadow: 0 20px 44px rgba(18,48,31,.20);
  padding: clamp(24px, 3.2vw, 42px) clamp(26px, 3.6vw, 48px);
  will-change: transform, opacity;
}
.func:nth-child(even) { direction: rtl; }
.func:nth-child(even) > * { direction: ltr; }
.func-art { margin: 0; }
/* the pieces behind these cards are not one family — chappals on sand, a
   hibiscus, a hanging lantern, a shell frame, a boat, a light swag — so cap the
   height and let each one sit inside the box at its own aspect */
.func-art img { width: 100%; max-height: clamp(180px, 26vh, 300px); object-fit: contain; }
.func-title {
  font-family: var(--caps); font-weight: 400;
  font-size: clamp(26px, 4vw, 44px); letter-spacing: .06em;
  margin: 0 0 .18em;
}
.func-meta { font-family: var(--caps); letter-spacing: .12em; text-transform: uppercase;
  font-size: clamp(11px,1.2vw,13px); margin: 0 0 .55em; opacity: .8; }
.func-window { margin: 0 0 .5em; font-style: italic; opacity: .9; }
.func-venue { margin: 0 0 .35em; }
.func-venue a { text-decoration-thickness: 1px; text-underline-offset: 3px; }
.func-body { margin: 0 0 .5em; }
.func-dress, .func-avoid { margin: 0; font-size: .92em; opacity: .78; }
.func-acts { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 18px; }
.mini {
  font-family: var(--caps); font-size: 12px; letter-spacing: .14em; text-transform: uppercase;
  padding: 9px 18px; border-radius: 999px;
  border: 1px solid currentColor; background: none; color: inherit;
  cursor: pointer; text-decoration: none; opacity: .85;
}
.mini:hover { opacity: 1; }

@media (max-width: 720px) {
  .func { grid-template-columns: 1fr; text-align: center; border-radius: 34px; }
  .func:nth-child(even) { direction: ltr; }
  .func-art { max-width: 190px; margin-inline: auto; }
  .func-acts { justify-content: center; }
}

/* ------------------------------------------------------------------
   5. ROUTE — the drive
   ------------------------------------------------------------------ */
/* The ScrollTrigger runs `start: top top` to `end: bottom bottom`, so the whole
   crossing happens in (height - 100vh) of scroll — 20vh at T-TM's 120vh, which
   is 180px on a 900px screen. The boat crossed in about a fifth of one flick of
   the wheel and every screenshot stop missed it. 168vh gives it ~610px, which
   is roughly one scroll gesture. It cannot leave a blank field the way T-TM
   warned: `end: bottom bottom` keeps the stage pinned for the whole range. */
.drive { height: 168vh; margin-bottom: clamp(30px, 6vw, 80px); }
.drive-stage {
  position: sticky; top: 0;
  height: 100vh; height: 100svh;
  overflow: hidden;
  display: flex; align-items: center; justify-content: center;
}
/* The room ground is already the backwater, so a band painted
   `var(--backwater)` would be invisible — the same colour as the wall behind
   it. It is the DEEP channel instead: darker than the room, with the shallows
   reading as the far bank above it. */
.drive-band {
  position: absolute; left: 0; right: 0; bottom: 0; height: 34%;
  background: linear-gradient(#3C7371, #10333B);
}
/* the waterline along the top of the band, and the wake line the boat runs on,
   so the band carries something even at scroll positions between boat poses */
.drive-band::before {
  content: ""; position: absolute; left: 0; right: 0; top: 0; height: 5px;
  background: linear-gradient(rgba(247,241,225,.7), rgba(247,241,225,0));
  opacity: .85;
}
/* T-TM's version of this was the ROAD the car drove on — a long-dash centre
   line at a steady rhythm. On water that reads as tarmac, so it is broken into
   short, faint, widely spaced strokes: swell catching the last of the light. */
.drive-band::after {
  content: ""; position: absolute; left: 0; right: 0; top: 15%; height: 2px;
  background: repeating-linear-gradient(90deg,
    rgba(247,241,225,.3) 0 22px, rgba(247,241,225,0) 22px 104px);
}
/* the car and (optionally) the couple sitting on it travel as one rig */
.drive-rig {
  position: absolute;
  /* rides the waterline: the hull sits INSIDE the deep-water band rather than
     on top of it, which is what floating looks like */
  bottom: 23%;
  left: 0;
  width: clamp(280px, 44vw, 680px);
  will-change: transform;
  z-index: 2;
}
/* CONTENT SWAP: the `car` key holds couple-on-houseboat — the two of them
   already sitting in the kettuvallam, one plate.
   Bound by WIDTH here, which is the one place this skin goes BACK to Lantern
   Palace and away from T-GS. Goa's plate was portrait (477x647) because its
   couple stood above the hull, so a width bind made it taller than the stage.
   This plate is LANDSCAPE (810x479) — a long low boat with the two of them
   seated under the canopy — so width is the natural bind and the hull lands on
   the waterline without an object-position fix. Height-binding it instead would
   draw a 340px-tall boat 575px wide, which crosses the stage in three strides. */
.drive-car {
  width: 100%; height: auto;
  object-fit: contain;
  filter: drop-shadow(0 14px 20px rgba(16,51,59,.32));
}
.drive-couple {
  position: absolute; left: 0; bottom: 34%;
  width: 100%;
}
/* pinned to the top of the stage rather than centred: the car crosses the
   middle band, and a centred heading sits right under its wheels */
.drive-head { position: absolute; top: 11%; left: 0; right: 0; z-index: 3; margin: 0; padding: 0 var(--pad-x); }

/* ------------------------------------------------------------------
   5b. venue + travel
   ------------------------------------------------------------------ */
.venue-card {
  background: rgba(247,242,230,.10);
  border: 1px solid rgba(201,162,74,.55);
  border-radius: 999px / 140px;
  padding: clamp(34px,5vw,64px) clamp(24px,4vw,56px);
  margin-bottom: clamp(34px, 5vw, 66px);
}
.venue-name { font-family: var(--caps); font-size: clamp(24px,3.6vw,42px); margin: 0 0 .2em; letter-spacing: .04em; }
.venue-city { font-family: var(--caps); letter-spacing: .28em; font-size: clamp(11px,1.2vw,13px); margin: 0; opacity: .75; }

.rolls { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: clamp(18px,2.6vw,32px); }
.roll {
  background: rgba(247,242,230,.09);
  border: 1px solid rgba(201,162,74,.42);
  border-radius: 26px; padding: clamp(20px,2.6vw,30px);
  will-change: transform, opacity;
}
.roll-h { font-family: var(--caps); letter-spacing: .14em; text-transform: uppercase;
  font-size: clamp(12px,1.3vw,14px); margin: 0 0 .6em; }
.roll-b { margin: 0; font-size: .95em; }

.hotels { display: grid; gap: 16px; margin-top: clamp(22px,3vw,36px); }
.hotel {
  background: rgba(247,242,230,.09);
  border: 1px solid rgba(201,162,74,.42);
  border-radius: 26px; padding: clamp(20px,2.6vw,30px);
  display: grid; grid-template-columns: minmax(0,1.2fr) minmax(0,1fr); gap: 10px 26px;
  will-change: transform, opacity;
}
.hotel-n { font-family: var(--caps); font-size: clamp(17px,2vw,22px); margin: 0 0 .25em; letter-spacing: .04em; }
.hotel-m, .hotel-p { margin: 0; font-size: .93em; opacity: .85; }
.hotel-acts { display: flex; flex-wrap: wrap; gap: 10px; align-self: end; }
@media (max-width: 640px) { .hotel { grid-template-columns: 1fr; } }

.baraat {
  margin-top: clamp(34px,5vw,60px);
  display: grid; grid-template-columns: minmax(0, 240px) minmax(0,1fr);
  gap: clamp(18px,3vw,40px); align-items: center;
}
.baraat-art { width: 100%; }
.baraat-line { margin: 0; font-size: clamp(16px,1.7vw,20px); }
@media (max-width: 640px) { .baraat { grid-template-columns: 1fr; text-align: center; }
  .baraat-art { max-width: 200px; margin-inline: auto; } }

/* ------------------------------------------------------------------
   6. STORY
   ------------------------------------------------------------------ */
.beats { display: grid; gap: clamp(40px, 7vw, 96px); }
.beat {
  display: grid; grid-template-columns: minmax(0, 0.95fr) minmax(0, 1fr);
  gap: clamp(22px, 4vw, 54px); align-items: center;
  will-change: transform, opacity;
}
.beat:nth-child(even) .beat-fig { order: 2; }
.beat-fig {
  margin: 0; position: relative;
  border-radius: 50% 50% 14px 14px / 34% 34% 14px 14px;
  overflow: hidden;
  border: 1px solid rgba(247,242,230,.28);
  background: rgba(247,242,230,.08);
  aspect-ratio: 4 / 5;
  max-width: 380px;
  width: 100%;
  justify-self: center;
}
.beat-fig img { width: 100%; height: 100%; object-fit: cover; }
.beat-fig .miss {
  position: absolute; inset: 0; display: grid; place-items: center;
  font-family: var(--caps); letter-spacing: .2em; text-transform: uppercase;
  font-size: 12px; opacity: .6;
}
.beat-no { font-family: var(--caps); font-size: clamp(32px,5vw,58px); opacity: .35; margin: 0; line-height: 1; }
.beat-kicker { font-family: var(--caps); letter-spacing: .2em; text-transform: uppercase;
  font-size: clamp(11px,1.2vw,13px); margin: .8em 0 .5em; opacity: .8; }
.beat-title { font-weight: 300; font-size: clamp(24px,3.4vw,40px); line-height: 1.16; margin: 0 0 .4em; }
.beat-body { margin: 0; opacity: .86; }
@media (max-width: 720px) {
  .beat { grid-template-columns: 1fr; }
  .beat:nth-child(even) .beat-fig { order: 0; }
  .beat-fig { aspect-ratio: 3 / 2; border-radius: 44% 44% 12px 12px / 22% 22% 12px 12px; }
}

/* ------------------------------------------------------------------
   7. RSVP
   ------------------------------------------------------------------ */
/* T-KB: bound by HEIGHT, not width. The `ring` key here is the mullappoo
   strand (204x428, knot at the top) — at T-GS's width clamp it drew 168px wide
   and 352px tall and pushed the RSVP heading off the first screen. */
.ring-art {
  width: auto; height: clamp(120px, 17vw, 210px);
  display: block; margin: 0 auto clamp(10px,2vw,22px);
}
.form { display: grid; gap: clamp(18px, 2.6vw, 28px); }
.grp-h {
  display: block; font-family: var(--caps); letter-spacing: .2em; text-transform: uppercase;
  font-size: clamp(11px,1.2vw,13px); margin-bottom: 14px; opacity: .8;
}
.field { display: grid; gap: 7px; }
.field label { font-size: .88em; opacity: .8; }
input, select, textarea {
  font: inherit; font-size: 1em;
  color: var(--ink);
  background: rgba(255,255,255,.72);
  border: 1px solid rgba(34,32,58,.24);
  border-radius: 14px;
  padding: 13px 15px;
  width: 100%;
}
textarea { resize: vertical; }
.toggles { display: flex; flex-wrap: wrap; gap: 10px; }
.toggle { position: relative; }
.toggle input { position: absolute; opacity: 0; width: 1px; height: 1px; }
/* direct child only: the labels carry bilingual <span class="ta">/<span class="en">
   runs inside them, and a descendant selector drew a pill around each one */
.toggle > span {
  display: inline-block;
  font-family: var(--caps); font-size: clamp(12px,1.2vw,14px);
  letter-spacing: .1em;
  padding: 11px 22px; border-radius: 999px;
  border: 1px solid rgba(34,32,58,.3);
  cursor: pointer; transition: background .2s, color .2s, border-color .2s;
}
.toggle input:checked + span { background: var(--deep); color: var(--kasavu); border-color: var(--deep); }
.toggle input:focus-visible + span { outline: 3px solid var(--zari); outline-offset: 2px; }
.guests { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 14px; }
.guest {
  border: 1px solid rgba(34,32,58,.16);
  border-radius: 22px; padding: clamp(14px,1.8vw,20px);
  background: rgba(255,255,255,.34);
}
.guest-h { font-family: var(--caps); letter-spacing: .16em; text-transform: uppercase;
  font-size: 11px; margin: 0 0 12px; opacity: .75; }
.guest-row { display: grid; grid-template-columns: minmax(0,1.3fr) minmax(0,1fr); gap: 14px; }
@media (max-width: 560px) { .guest-row { grid-template-columns: 1fr; } }
.rsvp-note { font-size: .88em; opacity: .72; margin: 0; }
.rsvp-err { color: #8E1F19; opacity: 1; }
.rsvp-submit { justify-self: start; }
.rsvp-done { padding: clamp(30px,5vw,60px) 0; }
.rsvp-done-h { font-family: var(--caps); font-size: clamp(24px,3.4vw,38px); letter-spacing: .12em;
  text-transform: uppercase; margin: 0 0 .4em; }

/* ------------------------------------------------------------------
   8. THINGS TO KNOW
   ------------------------------------------------------------------ */
.knows { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: clamp(18px,2.6vw,30px); }
.know {
  border: 1px solid rgba(247,242,230,.3);
  border-radius: 28px; padding: clamp(22px,2.8vw,32px);
  background: rgba(247,242,230,.08);
  will-change: transform, opacity;
}
/* The default .know is a pale card for a DARK room, which is what the countdown
   is. T-KB moves Things-to-Know onto GOLD, so it flips to the ink card here —
   a cream wash over #DDBE72 is invisible. (T-GS had the opposite list, because
   its `know` room was palm green and its functions and story rooms were light.) */
.room--palm .know, .room--teal .know, .room--cream .know {
  border-color: rgba(34,48,31,.26); background: rgba(255,253,246,.44);
}
.know-h { font-family: var(--caps); letter-spacing: .16em; text-transform: uppercase;
  font-size: clamp(12px,1.3vw,14px); margin: 0 0 .6em; }
.know-b { margin: 0; font-size: .95em; opacity: .9; }

/* ------------------------------------------------------------------
   8b. COUNTDOWN
   ------------------------------------------------------------------ */
.count {
  display: grid; grid-template-columns: repeat(4, 1fr);
  gap: clamp(8px, 1.6vw, 20px);
  margin: clamp(26px,4vw,46px) 0 clamp(20px,3vw,30px);
}
.count-cell {
  border: 1px solid rgba(247,242,230,.28);
  border-radius: 20px; padding: clamp(12px,2vw,22px) 6px;
  background: rgba(247,242,230,.06);
}
.count-n { font-family: var(--caps); font-size: clamp(26px, 5.6vw, 54px); line-height: 1; }
.count-l { font-family: var(--caps); letter-spacing: .2em; text-transform: uppercase;
  font-size: clamp(9px,1vw,11px); opacity: .7; margin-top: 8px; }

/* ------------------------------------------------------------------
   footer
   ------------------------------------------------------------------ */
.site-footer {
  position: relative;
  color: var(--on-dark);
  background: var(--indigo-lo);
  padding: clamp(90px, 16vh, 200px) 0 clamp(50px, 8vh, 90px);
  overflow: hidden;
}
.footer-sky {
  position: absolute; inset: 0;
  background-size: cover; background-position: center bottom;
  opacity: .85;
}
/* The Goan night plate has a lit villa and its reflection sitting exactly where
   the hashtag and the footer line go, and white italic over lamplight is
   unreadable. One scrim, centre-weighted so the palms at the edges stay
   visible. */
.footer-sky::after {
  content: ""; position: absolute; inset: 0;
  background:
    radial-gradient(ellipse 74% 56% at 50% 46%, rgba(27,13,18,.74) 0%, rgba(27,13,18,.5) 60%, rgba(27,13,18,.12) 100%),
    linear-gradient(180deg, rgba(27,13,18,.5) 0%, rgba(27,13,18,.12) 38%, rgba(27,13,18,.55) 100%);
}
.site-footer .wrap { position: relative; z-index: 2; }
.hashtag {
  font-family: var(--caps); font-size: clamp(22px,4vw,46px);
  letter-spacing: .16em; margin: 0 0 .3em;
}
.footer-line { font-style: italic; opacity: .82; margin: 0 0 clamp(30px,5vw,54px); }
.fams { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px,1fr)); gap: clamp(18px,3vw,40px); }
.fam-s { font-family: var(--caps); letter-spacing: .2em; text-transform: uppercase;
  font-size: 11px; opacity: .68; margin: 0 0 .5em; }
.fam-n { font-family: var(--caps); font-size: clamp(16px,1.9vw,21px); margin: 0 0 .3em; }
.fam-l { margin: 0 0 .5em; font-style: italic; font-size: .9em; opacity: .72; }
.fam-t { margin: 0; }
.footer-fine { margin: clamp(34px,5vw,60px) 0 0; font-size: .84em; opacity: .6; }

/* ------------------------------------------------------------------
   no-JS twin
   ------------------------------------------------------------------ */
.fallback { display: none; background: var(--cream); color: var(--ink); padding: 40px 24px; }
.fallback h1 { font-weight: 300; font-size: 44px; margin: 0 0 10px; }
.fallback h2 { font-family: var(--caps); font-size: 20px; letter-spacing: .1em; text-transform: uppercase; }

/* ------------------------------------------------------------------
   reduced motion — a composed still, not a broken scroller
   ------------------------------------------------------------------ */
@media (prefers-reduced-motion: reduce) {
  .journey { height: auto; }
  .stage { position: relative; height: 92vh; }
  /* the courtyard only makes sense as the thing you arrive at — with no
     journey to travel, it would just black out the dusk sky. */
  .courtyard { display: none; }
  .gate { width: min(58vh, 44%); }
  .drive { height: auto; margin-bottom: 0; }
  .drive-stage { position: relative; height: 56vh; }
  .drive-car { transform: translateX(0); left: 8%; }
  .hint-arrow { animation: none; opacity: .6; }
  .stars { animation: none; opacity: .8; }
  .drift { display: none; }
  .mobcta { transition: none; }
  * { scroll-behavior: auto !important; }
}


/* ==================================================================
   0. THE OPEN MOMENT — the envelope
   ==================================================================
   Geometry is the manifest's `envelope_rig`, in canvas fractions:
     canvas 1200x1681 · pocket V at .229/.437/.224 of canvas height
     flap art in the top .350, hinge center top, opens rotateX(-170deg)
     card 0.80 of canvas width, closed top .30, travels up ~.70 to withdraw
   Everything below is those numbers and nothing else, so a re-cut rig only
   needs these constants changed.
   ================================================================== */
:root {
  /* unitless percentages of CANVAS height, so they can be composed in calc().
     A plain `top: calc(75% + 30%)` on the card resolves both against the clip
     box, not the canvas, which put the card below the clip and made it
     invisible — hence the explicit re-basing in .env-card below. */
  --env-clip-up: 75;        /* headroom above the canvas for the card to rise into */
  --env-card-top: 30;       /* manifest envelope_rig.card.closed_top */
  --env-card-travel: -87%;  /* of the CARD's own height, = .70 canvas height */
}

.env {
  position: fixed; inset: 0; z-index: 100;
  display: grid; place-items: center;
  background: radial-gradient(circle at 50% 44%, #FBF3E4, #E7D6B8 70%, #D8C29C);
  cursor: pointer;
  perspective: 1800px;
  transition: opacity .7s ease, transform .7s cubic-bezier(.4,0,.2,1);
}
body.env-out .env { opacity: 0; transform: translateY(6vh) scale(1.06); pointer-events: none; }
body.env-gone .env { display: none; }
/* the page does not scroll until the envelope is open */
body.pre-open { overflow: hidden; height: 100%; }

.env-stage {
  position: relative;
  width: min(430px, 74vw);
  aspect-ratio: 1200 / 1681;
  transform-style: preserve-3d;
}

/* every plate is the full canvas, so one rule places all of them */
.env-l {
  position: absolute; inset: 0;
  background-size: 100% 100%; background-repeat: no-repeat;
}
/* fallback tint if a plate is missing — a plain cream envelope, never a blank */
.env-l--back  { z-index: 1; background-color: #EFE0C2; border-radius: 8px; }
.env-l--front { z-index: 3; }

.env-cardclip {
  position: absolute; left: 0; right: 0;
  top: calc(-1% * var(--env-clip-up)); bottom: 0;
  overflow: hidden;
  z-index: 2;
}
.env-card {
  position: absolute; left: 10%; width: 80%;
  aspect-ratio: 1200 / 1694;
  /* the clip box is (100 + clip-up)% of canvas height, so a canvas-relative
     position has to be re-based onto it */
  top: calc((var(--env-clip-up) + var(--env-card-top)) / (100 + var(--env-clip-up)) * 100%);
  background: var(--cream);
  transition: transform .95s cubic-bezier(.3,0,.2,1) .2s;
}
body.is-opening .env-card { transform: translateY(var(--env-card-travel)); }
.env-card-art { border-radius: 4px; }
.env-card-txt {
  /* clear of the card's printed paisley border */
  position: absolute; left: 20%; right: 20%; top: 26%;
  text-align: center; color: var(--ink);
}
.env-note {
  font-family: var(--caps); letter-spacing: .26em; text-transform: uppercase;
  font-size: clamp(8px, 1.5vw, 12px); margin: 0 0 .8em; opacity: .7;
}
.env-names {
  font-size: clamp(19px, 4.4vw, 34px); font-weight: 300; margin: 0;
  display: flex; align-items: center; justify-content: center; gap: .28em;
  flex-wrap: wrap;
}
.env-names em { font-family: var(--caps); font-style: normal; font-size: .55em; opacity: .65; }
.env-date {
  font-family: var(--caps); letter-spacing: .18em;
  font-size: clamp(10px, 1.9vw, 14px); margin: .8em 0 0; opacity: .8;
}

/* the greeting is printed on the pocket panel, below the V */
.env-to {
  position: absolute; left: 8%; right: 8%; top: 74%; z-index: 4;
  margin: 0; text-align: center; font-style: italic;
  font-size: clamp(13px, 2.6vw, 19px);
  color: var(--ink); opacity: .85;
}

.env-flap {
  position: absolute; inset: 0; z-index: 5;
  transform-origin: center top;
  transform-style: preserve-3d;
  transition: transform .9s cubic-bezier(.5,0,.2,1);
}
/* the seal is composited into the flap plate, so it lifts with the flap —
   no separate seal element to keep in sync */
body.is-opening .env-flap { transform: rotateX(-170deg); }

.env-cta {
  position: absolute; bottom: clamp(24px, 7vh, 70px); left: 0; right: 0;
  margin: 0; text-align: center;
  font-family: var(--caps); letter-spacing: .3em; text-transform: uppercase;
  font-size: clamp(10px, 1.2vw, 13px); color: var(--ink); opacity: .62;
  animation: hint 2.4s ease-in-out infinite;
}

/* ==================================================================
   0. THE OPEN MOMENT — the gate (replaces the envelope, 2026-09-02)
   ==================================================================
   Full-bleed painted night palace, dimmed to indigo, gold monogram, two
   hairlines, the greeting, one outlined button. Lanterns drift behind the
   type. Reuses .env's fixed/transition/open-state rules above.
   ================================================================== */
:root { --gate-gold: #D9B45F; }   /* zari, lifted so it holds on the night plate */

.env.opengate {
  background: var(--indigo-lo);
  display: grid; place-items: center;
  overflow: hidden;
  cursor: pointer;
}
.gate-bg {
  position: absolute; inset: -4%;
  background: url("media/palace-night.jpg") center 62% / cover no-repeat;
  animation: gate-drift 26s ease-in-out infinite alternate;
  will-change: transform;
}
@media (max-width: 760px) {
  .gate-bg { background-image: url("media/palace-night-9x16.jpg"); background-position: center 58%; }
  /* the palace sits right behind the type on a phone — hold it darker there */
  .gate-shade {
    background:
      radial-gradient(ellipse 70% 46% at 50% 50%, rgba(27,13,18,.62) 0%, rgba(27,13,18,.7) 55%, rgba(27,13,18,.86) 100%),
      linear-gradient(180deg, rgba(27,13,18,.55) 0%, rgba(27,13,18,.25) 40%, rgba(27,13,18,.72) 100%);
  }
}
@keyframes gate-drift {
  from { transform: scale(1) translateY(0); }
  to   { transform: scale(1.07) translateY(-1.5%); }
}
.gate-shade {
  position: absolute; inset: 0;
  background:
    radial-gradient(ellipse at 50% 42%, rgba(27,13,18,.18) 0%, rgba(27,13,18,.62) 60%, rgba(27,13,18,.86) 100%),
    linear-gradient(180deg, rgba(27,13,18,.55) 0%, rgba(27,13,18,.18) 40%, rgba(27,13,18,.7) 100%);
}

.gate-lanterns { position: absolute; inset: 0; pointer-events: none; }
.gl {
  position: absolute; top: -20vh; width: clamp(34px, 4.2vw, 64px);
  opacity: 0; filter: drop-shadow(0 0 14px rgba(217,178,94,.5));
  animation: gl-rise linear infinite;
}
.gl-1 { left: 8%;  animation-duration: 21s; animation-delay: -3s;  }
.gl-2 { left: 22%; animation-duration: 26s; animation-delay: -11s; width: clamp(26px, 3vw, 46px); }
.gl-3 { left: 36%; animation-duration: 23s; animation-delay: -17s; }
.gl-4 { left: 64%; animation-duration: 25s; animation-delay: -7s;  width: clamp(28px, 3.4vw, 50px); }
.gl-5 { left: 78%; animation-duration: 20s; animation-delay: -14s; }
.gl-6 { left: 90%; animation-duration: 28s; animation-delay: -1s;  width: clamp(24px, 2.8vw, 42px); }
/* Rises again, the way Lantern Palace's did and T-TM's jasmine did not: these
   are fairy-light bulbs coming up off the shack, and a falling bulb reads as a
   dropped one. */
@keyframes gl-rise {
  0%   { transform: translate3d(0, 24vh, 0) rotate(-2deg); opacity: 0; }
  8%   { opacity: .9; }
  50%  { transform: translate3d(1.6vw, -34vh, 0) rotate(2deg); }
  92%  { opacity: .8; }
  100% { transform: translate3d(-1vw, -96vh, 0) rotate(-1deg); opacity: 0; }
}

.gate-body {
  position: relative; z-index: 2;
  display: flex; flex-direction: column; align-items: center;
  text-align: center; color: var(--on-dark);
  padding: 0 var(--pad-x);
  max-width: 34rem;
  animation: gate-in 1.4s cubic-bezier(.2,.7,.2,1) both;
}
@keyframes gate-in { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }

/* The monogram over the gate: R & A cut in white, the way the villa's own
   name is painted on its gatepost. T-TM set ஸ்ரீ here in the Tamil face; this
   skin has no equivalent opening word, so the initials carry the slot. */
.gate-sri {
  margin: 0 0 .55rem;
  font-family: var(--serif);
  font-size: clamp(34px, 4.6vw, 58px);
  font-weight: 300;
  line-height: 1;
  /* GOLD, not cream. The brief's gate is "nalukettu at dusk with lamps lit,
     gold monogram on cream" — and the plate behind it is a lit tharavad whose
     own windows are warm, so a cream monogram reads as one more window. */
  color: var(--gold-k);
  letter-spacing: .04em;
  text-shadow: 0 0 34px rgba(201,162,74,.45);
}
.gate-sri em { font-style: italic; font-size: .62em; opacity: .74; padding: 0 .12em; }
.gate-mono {
  margin: 0 0 .9rem;
  font-family: var(--caps); letter-spacing: .32em; color: var(--gate-gold);
  font-size: clamp(15px, 1.7vw, 20px);
  display: flex; align-items: center; gap: .55em;
}
.gate-mono i { font-style: normal; font-size: .5em; opacity: .85; transform: translateY(-.05em); }
.gate-rule {
  display: block; width: 3.4rem; height: 1px;
  background: linear-gradient(90deg, transparent, var(--gate-gold), transparent);
  margin: .2rem 0 1.3rem; opacity: .85;
}
.gate-to {
  margin: 0 0 1.6rem; font-family: var(--serif); font-style: italic;
  font-size: clamp(16px, 2vw, 21px); color: var(--on-dark-soft);
}
.gate-note {
  margin: 0 0 .5rem; font-family: var(--caps); letter-spacing: .3em; text-transform: uppercase;
  font-size: clamp(9px, 1vw, 11px); color: var(--gate-gold); opacity: .9;
}
.gate-names {
  margin: 0; font-family: var(--serif); font-weight: 300;
  /* 1.14, not T-TM's 1.02. Cormorant's descenders are long and the two names
     stack, so at 1.02 the y of "Ananya" ran into the date line under it on a
     390px screen. The shadow is for the same reason the hero has one: the lit
     facade of the night villa sits directly behind these two words. */
  line-height: 1.14;
  font-size: clamp(40px, 7.2vw, 92px); letter-spacing: .01em;
  text-shadow: 0 2px 24px rgba(6,20,26,.7), 0 1px 4px rgba(6,20,26,.5);
  display: flex; flex-direction: column; align-items: center;
}
.gate-names em {
  font-style: italic; font-weight: 300; color: var(--gate-gold);
  font-size: .42em; line-height: 1.2; margin: .1em 0;
}
.gate-date {
  margin: 1.5rem 0 0; font-family: var(--caps); letter-spacing: .34em;
  font-size: clamp(11px, 1.3vw, 15px); color: var(--on-dark);
}
.gate-date + .gate-rule { margin-top: 1.4rem; }
.gate-cta { margin: 0; }
.gate-cta span {
  display: inline-block; padding: .95em 2.4em;
  border: 1px solid var(--gate-gold); border-radius: 999px;
  font-family: var(--caps); letter-spacing: .28em; text-transform: uppercase;
  font-size: clamp(10px, 1.05vw, 12px); color: var(--gate-gold);
  background: rgba(27,13,18,.25); backdrop-filter: blur(2px);
  transition: background .35s ease, color .35s ease, transform .35s ease;
  animation: gate-pulse 3.2s ease-in-out infinite;
}
.env.opengate:hover .gate-cta span, .env.opengate:focus-visible .gate-cta span {
  background: var(--gate-gold); color: var(--indigo-lo); transform: translateY(-1px);
}
@keyframes gate-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(217,178,94,.0); }
  50%      { box-shadow: 0 0 0 9px rgba(217,178,94,.12); }
}
.gate-place {
  margin: 2rem 0 0; font-family: var(--caps); letter-spacing: .36em;
  font-size: clamp(9px, .95vw, 11px); color: var(--on-dark-soft); opacity: .7;
}
.env.opengate:focus-visible { outline: none; }

@media (prefers-reduced-motion: reduce) {
  .gate-bg, .gl, .gate-body, .gate-cta span { animation: none; }
  .gl { opacity: .8; top: 20%; }
}

/* ==================================================================
   THE MUSIC TOGGLE
   ================================================================== */
.audio-btn {
  position: fixed; top: 16px; right: 16px; z-index: 70;
  display: inline-flex; align-items: center; gap: 9px;
  padding: 9px 15px;
  border-radius: 999px;
  border: 1px solid rgba(247,242,230,.5);
  background: rgba(33,16,22,.4);
  backdrop-filter: blur(6px);
  color: var(--on-dark);
  font-family: var(--caps); letter-spacing: .16em; text-transform: uppercase;
  font-size: 11px; cursor: pointer;
  opacity: 0; transform: translateY(-10px);
  transition: opacity .5s ease .4s, transform .5s ease .4s, background .25s ease;
}
body.is-open .audio-btn { opacity: 1; transform: none; }
/* `display: inline-flex` above beats the UA rule for [hidden], so the toggle
   has to be told again — otherwise a site with no track shows a dead control */
.audio-btn[hidden] { display: none !important; }
.audio-btn:hover { background: rgba(33,16,22,.6); }
.audio-ico { display: inline-flex; align-items: flex-end; gap: 2px; height: 13px; }
.audio-ico i {
  width: 2px; background: currentColor; border-radius: 1px;
  height: 5px; transform-origin: bottom;
}
.audio-btn[aria-pressed="true"] .audio-ico i { animation: eq .9s ease-in-out infinite; }
.audio-btn[aria-pressed="true"] .audio-ico i:nth-child(2) { animation-delay: .18s; }
.audio-btn[aria-pressed="true"] .audio-ico i:nth-child(3) { animation-delay: .36s; }
.audio-btn[aria-pressed="false"] .audio-ico i { height: 4px; opacity: .55; }
@keyframes eq { 0%,100% { height: 4px; } 50% { height: 13px; } }

/* ==================================================================
   ORNAMENT LAYER — it frames, it does not shout
   ================================================================== */
.orn-edge {
  position: absolute; left: 0; right: 0; height: clamp(28px, 4vw, 56px);
  z-index: 2; pointer-events: none;
  background-repeat: repeat-x;
  background-size: auto 100%;
  opacity: .3;
}
.orn-edge--top { top: calc(var(--edge-h) * .5); }
.orn-edge--bot { bottom: clamp(10px, 2vw, 26px); transform: scaleY(-1); }
.room--cream .orn-edge, .room--pink .orn-edge,
.room--teal .orn-edge, .room--indigo .orn-edge { opacity: .5; }

/* The arch plate is 1400x1747 and its interior opening runs x 16.1-83.3%,
   y 16.2-90.4% (measured off the alpha channel). The cusps narrow it toward
   the top, so the type block is inset further than the raw opening. */
.name-arch {
  position: relative;
  width: min(560px, 100%);
  margin: clamp(18px, 3vw, 34px) auto 0;
  aspect-ratio: 1400 / 1747;
  display: grid; place-items: center;
}
.name-arch-frame {
  position: absolute; inset: 0;
  background-size: contain; background-repeat: no-repeat; background-position: center;
  pointer-events: none;
}
.name-arch-in {
  position: relative;
  /* the cusps make the top of the opening narrower than its 16-83% span, and
     the decorated head reads as "the frame" — so the block sits a little low
     and a little wide, which fills the plain lower half of the opening */
  width: 70%;
  margin-top: 9%;
}
/* no frame plate: the type keeps its own rhythm rather than a squashed box */
.name-arch:not(:has(.name-arch-frame[style*="url"])) { aspect-ratio: auto; }
.name-arch:not(:has(.name-arch-frame[style*="url"])) .name-arch-in { width: 100%; margin-top: 0; }

.name-arch .couple { font-size: clamp(36px, 8vw, 84px); }
.name-arch .couple-date { font-size: clamp(15px, 2.4vw, 28px); letter-spacing: .16em; }
.name-arch .couple-meta { font-size: clamp(12px, 1.4vw, 16px); }
.name-arch .rule { margin: clamp(12px,2vw,22px) auto; width: 84px; }

.orn-mandala {
  position: absolute; left: 50%; top: 46%;
  width: min(760px, 108%); aspect-ratio: 1;
  transform: translate(-50%, -50%);
  background-size: contain; background-repeat: no-repeat; background-position: center;
  opacity: .07; z-index: 1; pointer-events: none;
}

/* The garland spans the full width; only its lower swag is shown.
   Two earlier attempts were both wrong: sizing by width alone dropped the
   catenary through the heading, and capping the height shrank it to a small
   centred necklace. The fix is to draw it full width and clip the top off —
   the band is the window, --garland-hide is how much of the plate rides above
   it. Clipping happens on the band, NOT on the room: the room has to stay
   overflow-visible or its curved top edge gets cut off. */
.orn-garland-band {
  position: absolute; top: calc(var(--edge-h) * .3); left: 0; right: 0;
  height: clamp(88px, 12vh, 168px);
  overflow: hidden;
  z-index: 2; pointer-events: none;
}
/* T-TM ran its swag full-bleed and cropped it from the bottom, because its
   2048x796 thoranam was a dense curtain whose hanging depth varied between
   regenerations. This plate is the opposite object: one 488x189 light string
   with five bulbs and a knotted rope end at each corner. Stretched to 1440 and
   then cropped vertically, the only thing left inside the band was the two
   knots — two large brown posts floating over the coral with no string between
   them. So it is CENTRED and CONTAINED at its own aspect instead, which is how
   one string is actually hung over a doorway. */
.orn-garland {
  position: absolute; left: 50%;
  width: min(760px, 74%); height: auto;
  max-height: clamp(88px, 12vh, 168px);
  object-fit: contain; object-position: top center;
  transform: translateX(-50%);
  opacity: .95;
}
/* the swag hangs into the room, so its room needs headroom for the heading */
#functions { padding-top: calc(var(--pad-y) + var(--edge-h) * .55 + clamp(84px, 12vh, 168px)); }
#functions .orn-edge--top { display: none; }   /* the garland IS the top edge here */

/* orn-garland is the fairy-light swag (488x189): its rope leaves the plate at
   both TOP corners, so it takes a HEIGHT cap and hangs from the top rather than
   a width that would stretch it into the room.
   orn-toran here is the azulejo strip, which is a flat band and not a hang — so
   it takes its natural aspect, capped, and sits as a rule above the RSVP. */
.orn-toran {
  display: block; width: min(560px, 100%);
  max-height: clamp(26px, 4vh, 46px);
  object-fit: cover; object-position: center;
  margin: 0 auto clamp(14px, 2.4vw, 28px);
  opacity: .9;
}

/* four mirrored corners on a card, from one file */
.orn-corner {
  position: absolute; width: clamp(38px, 5vw, 68px); aspect-ratio: 1;
  background-size: contain; background-repeat: no-repeat;
  opacity: .34; pointer-events: none;
}
.orn-corner--tl { top: 10px; left: 10px; }
.orn-corner--tr { top: 10px; right: 10px; transform: scaleX(-1); }
.orn-corner--bl { bottom: 10px; left: 10px; transform: scaleY(-1); }
.orn-corner--br { bottom: 10px; right: 10px; transform: scale(-1); }
.func, .know, .roll, .venue-card { position: relative; }

/* ==================================================================
   COUPLE SLOTS — a real photo, a commissioned illustration, or a
   deliberately abstract paper silhouette. Never a generated face.
   ================================================================== */
.portrait {
  position: relative;
  width: min(340px, 76%);
  margin: clamp(30px, 5vw, 56px) auto 0;
  aspect-ratio: 620 / 780;
}
.portrait--wide { width: min(380px, 82%); margin-top: clamp(40px, 6vw, 76px); }
.portrait-img {
  /* the opening of this skin's frame plate, measured from its alpha (top,
     right, bottom, left, plus half a percent inside the ring). The 7%/10%
     inset this rule shipped with drew the picture larger than the ring, so
     it showed outside the frame. */
  position: absolute; inset: 12.5% 30.5% 21.5% 27.5%;
  border-radius: 50%;
  overflow: hidden;
  background: rgba(247,242,230,.14);
  background-size: cover; background-position: center;
}
.room--cream .portrait-img { background-color: rgba(34,32,58,.05); }
.portrait-frame { position: absolute; inset: 0; width: 100%; height: 100%; }
.portrait[data-empty="1"] .portrait-img {
  background-size: contain; background-repeat: no-repeat; background-position: bottom center;
  opacity: .9;
}
/* the caption lives here, not baked into the SVG, so it takes the room's ink
   and stays legible on teal, indigo or cream alike */
.portrait[data-empty="1"]::after {
  content: "A photo of the two of you goes here";
  position: absolute; left: 0; right: 0; bottom: -1.9em;
  text-align: center; font-style: italic;
  font-size: clamp(12px, 1.2vw, 14px); opacity: .5;
}

@media (prefers-reduced-motion: reduce) {
  .env, .env-flap, .env-card { transition: none; }
  .env-cta { animation: none; }
  .audio-btn { transition: none; }
  .audio-btn[aria-pressed="true"] .audio-ico i { animation: none; height: 9px; }
}

/* ==================================================================
   ROOM ATMOSPHERE — the round-4 pieces
   ==================================================================
   Each one is dressing: low in the stack, never over live text, and every
   key is `optional` so a room ends up plainer rather than wrong.
   ================================================================== */
.piece { position: absolute; z-index: 1; pointer-events: none; }

/* the coconut palm leans left, so it stands on the right and leans into the copy */
.piece--peacock {
  right: -4%; bottom: clamp(12px, 3vw, 40px);
  width: clamp(140px, 19vw, 300px);
  opacity: .85;
}

/* T-TM hung an oonjal swing from the story room's top edge. There is nothing
   in this skin that hangs, so that element is removed from index.html and its
   rules with it — a palm frond stands in the same room instead. */

.piece--jharokha {
  left: -6%; top: clamp(40px, 8vw, 140px);
  width: clamp(130px, 17vw, 270px);
  opacity: .5;
}

/* Sits on the floor of the night room. Capped by HEIGHT, not width: the plate
   is 2048x260 but the diyas are large within it, so a full-width draw made them
   a quarter of the viewport. Height-capped and centred, it reads as a row of
   lamps on the floor. */
.piece--diyas {
  left: 50%; bottom: 0;
  transform: translateX(-50%);
  height: clamp(44px, 7vh, 92px); width: auto; max-width: 118%;
  opacity: .95;
  animation: diya-flicker 4.5s ease-in-out infinite;
}
@keyframes diya-flicker {
  0%, 100% { opacity: .82; }
  38%      { opacity: 1; }
  61%      { opacity: .88; }
}

/* The lake strip. The boat was first put on the car's band travelling the other
   way, and the two crossed in the middle of the screen with the boat behind the
   car — so it gets its own field instead. */
.lake {
  position: relative;
  /* TALLER than T-GS's 130px, and the reason is the plate, not the taste. The
     strip has `overflow: hidden`, so it is a window: goa-shack's fishing boat
     was 485x420 and fitted; a kettuvallam is 441x357 and at the old 170px width
     drew 138px into a 130px window with a 23px bottom offset, so the capture
     came back with the canopy roof sliced off and, at small viewports, with an
     empty pill of water. Sized so the whole boat clears the window. */
  height: clamp(104px, 13vw, 168px);
  margin: clamp(26px, 4vw, 52px) 0 clamp(6px, 1vw, 14px);
  overflow: hidden;
  border-radius: 999px;
  /* the open water past the jetty. It sits ON the backwater ground, so it is
     only a shade or two off it — a strip of deeper channel read against the
     shallows behind it, not a separate blue object. */
  background: linear-gradient(#3C7371, #10333B);
  opacity: .62;
}
/* piece-boat is the kettuvallam, and it FLOATS — so it sits in the middle of
   the strip rather than standing on the bank. */
.piece--boat {
  left: 32%; bottom: 12%;
  width: clamp(96px, 11vw, 150px);
  opacity: .95;
}

@media (max-width: 759px) {
  .piece--jharokha { opacity: .34; width: clamp(110px, 30vw, 160px); }
  .piece--peacock  { right: -8%; width: clamp(120px, 34vw, 200px); }
}
@media (prefers-reduced-motion: reduce) {
  .piece--diyas { animation: none; opacity: .92; }
}

/* ==================================================================
   ?plain=1 — the painted scene with none of the site's furniture,
   so the video pipeline can shoot the live hero cleanly.
   ================================================================== */
body.is-plain .hero,
body.is-plain .hero-hint,
body.is-plain .audio-btn,
body.is-plain .mobcta,
body.is-plain .skip,
body.is-plain .env { display: none !important; }
body.is-plain { overflow: hidden; }

/* ==================================================================
   0b. THE OPENING FILM
   ==================================================================
   Full-bleed, plays once, and hands to the interactive hero on `ended`.
   The type is DOM rather than burnt into the clip — same reason the RSVP is
   a form and not a picture of one.
   ================================================================== */
.intro {
  position: fixed; inset: 0; z-index: 95;
  background: #0D1030;
  opacity: 0;
  transition: opacity .6s ease;
}
.intro[hidden] { display: none !important; }
.intro.is-in { opacity: 1; }
/* on handoff the film fades and the live hero is already behind it, seeked to
   the progress the last frame was rendered at */
.intro.is-out { opacity: 0; transition: opacity .65s ease; pointer-events: none; }

.intro-vid {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  background: #0D1030;
}
/* keeps cream type legible over whatever the clip is doing underneath */
.intro-scrim {
  position: absolute; inset: 0;
  background:
    /* T-GS: the pool is pushed DOWN to 62% and the bottom stop is deepened from
       .46 to .62. This clip is a bright daylit beach, and the couple stand full
       height in the 16:9 cut, so the title lands on a white dress in white
       serif — at T-TM's values "Rohan" was barely there. Tinted to the night
       sea, not to maroon. */
    radial-gradient(ellipse 66% 40% at 50% 62%, rgba(10,40,48,.46), rgba(10,40,48,0) 72%),
    linear-gradient(rgba(10,40,48,.26), rgba(10,40,48,0) 26%, rgba(10,40,48,0) 46%, rgba(10,40,48,.62));
}

.intro-type {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  text-align: center;
  padding: 0 var(--pad-x);
  color: var(--on-dark);
  text-shadow: 0 2px 26px rgba(27,13,18,.6), 0 1px 3px rgba(27,13,18,.45);
}
.intro-line {
  grid-area: 1 / 1;              /* every line stacks on the same cell */
  /* A grid item centred with place-items sizes to max-content, so a long line
     runs straight off the viewport instead of wrapping — the bilingual date +
     venue did exactly that on a 390px phone. */
  max-width: 100%;
  opacity: 0;
  will-change: transform, opacity;
}
.intro-line--greeting { font-style: italic; font-size: clamp(17px, 2.6vw, 30px); }
.intro-line--title {
  font-weight: 300; font-size: clamp(40px, 8.4vw, 118px); line-height: 1.02;
  display: flex; flex-direction: column; align-items: center; gap: .04em;
}
/* the joiner is a plain ampersand — set in the display italic, small, so the
   two names stay the loud things on the line */
.intro-line--title em {
  font-family: var(--serif); font-style: italic;
  font-size: clamp(15px, 2vw, 30px); letter-spacing: normal; text-transform: none;
  opacity: .8; padding-left: 0;
}
/* Josefin Sans caps want the tracking back that T-TM had to cut for Tamil. */
.intro-line--detail {
  font-family: var(--caps); letter-spacing: .17em; text-transform: uppercase;
  font-size: clamp(16px, 2.1vw, 28px); line-height: 1.9;
}
.intro-line--tag {
  font-family: var(--caps); letter-spacing: .18em;
  font-size: clamp(22px, 4.4vw, 52px);
}

/* ==================================================================
   0c. THE INVITE SCENE — config.intro.mode "scene"
   ==================================================================
   Ganesha, then the couple, then the words. Everything here is positioned in
   percentages of the scene box and sized in vmin, so the same choreography
   plays at 1440x900 and at 390x844 without a second set of timings — only the
   backdrop swaps to its 9:16 twin. GSAP owns opacity/transform; this file owns
   placement and the two ambient loops (flame flicker, heart pulse).
   ================================================================== */
/* CENTRING NOTE for the four elements GSAP animates (.isc-kolam, .isc-ganesha,
   .isc-diyas, .isc-heart): they carry NO transform here. GSAP writes the whole
   transform property when it animates scale or y, which silently drops a
   translate(-50%,-50%) written in CSS and shunts the element half its own size
   down and right. Their centring is set once in playIntroScene() with GSAP's
   own xPercent/yPercent so it survives every tween. .isc-couple is not animated
   directly, so it keeps its CSS transform. */
.iscene { position: absolute; inset: 0; overflow: hidden; }
.iscene[hidden] { display: none !important; }

.isc-backdrop {
  position: absolute; inset: 0;
  background: var(--night) center / cover no-repeat;
  opacity: 0;
}
/* The gold kolam grid is baked into the plate, so it cannot be dimmed on its
   own — a flat wash of the plate's own ground colour pulls the pattern ~30%
   back toward it without shifting the hue or darkening the figures, which sit
   above this layer. */
.isc-backdrop::after {
  content: ""; position: absolute; inset: 0;
  background: rgba(110,27,43,.30);
}

/* The kolam draws itself left-to-right: a clip-path wipe, which works on a PNG
   as well as on an SVG and needs no path length to be known.
   The delivered plate is a square MEDALLION, not the wide strip this was first
   built against, so it sits BEHIND Ganesha as the floor he arrives onto rather
   than as a band under him — at strip proportions it overflowed the viewport
   bottom by 70-odd pixels. */
/* SIZING NOTE. These three are sized by HEIGHT, not width, because the brief
   specifies them in vh and a width in vmin only yields the intended vh on a
   landscape viewport — on a phone vmin is the WIDTH, so the same rule shrank
   Ganesha to 28vh where 44 was asked for. Heights are set directly; widths
   follow the plate's own aspect. */
.isc-kolam {
  position: absolute; left: 50%; top: 42%;
  height: 65vh; width: auto;       /* 1.25x Ganesha, behind him */
  z-index: 0; opacity: .5;
  clip-path: inset(0 100% 0 0);
}
.isc-kolam img { height: 100%; width: auto; display: block; }

.isc-diyas {
  /* bottom-anchored, not centred on a percentage: at 90% the pair hung 5px
     past the viewport floor */
  position: absolute; left: 50%; bottom: 3%; top: auto;
  width: min(30vmin, 300px);       /* centred by GSAP — see CENTRING NOTE */
  opacity: 0;
}

.isc-ganesha {
  /* Sized from the delivered sprites, not guessed: murti 1204x1604 drawn at
     78% of this width, lotus 1200x758 overlapped by 14%, so the stack is about
     1.53x whatever width is set here. 300px therefore composes ~460px tall and
     leaves the blessing a clear band beneath it. */
  position: absolute; left: 50%; top: 42%;
  height: 52vh; width: auto;       /* centred by GSAP — see CENTRING NOTE */
  z-index: 2;
  opacity: 0;
}
.isc-murti { height: 100%; width: auto; display: block; position: relative; z-index: 2; }

.isc-sparks { position: absolute; inset: 0; pointer-events: none; }
.isc-sparks img {
  position: absolute; width: min(5.5vmin, 54px);
  opacity: 0; will-change: transform, opacity;
}
/* a ring around where Ganesha lands */
/* a ring around Ganesha's landing point (50%, 38%) — scattered across the whole
   frame they read as background sparkle rather than as his arrival */
.isc-sparks img:nth-child(1) { left: 33%; top: 26%; }
.isc-sparks img:nth-child(2) { left: 65%; top: 25%; }
.isc-sparks img:nth-child(3) { left: 28%; top: 44%; }
.isc-sparks img:nth-child(4) { left: 70%; top: 43%; }
.isc-sparks img:nth-child(5) { left: 42%; top: 17%; }
.isc-sparks img:nth-child(6) { left: 58%; top: 18%; }
.isc-sparks img:nth-child(7) { left: 36%; top: 56%; }
.isc-sparks img:nth-child(8) { left: 64%; top: 55%; }

.isc-couple {
  /* Sits in the TOP two thirds — the words own the bottom third. Width is set
     from the delivered sprite (1427x1688, so 1.18x as tall as it is wide): at
     380px the pair composes ~450px and clears the title's cap height. */
  position: absolute; left: 50%; top: 42%;
  height: 58vh; width: auto;
  transform: translate(-50%, -50%);
}
/* the cream pool the bride stands against — see the note in index.html */
.isc-lift {
  position: absolute; left: 50%; top: 52%;
  height: 74vh; aspect-ratio: 1 / 1; width: auto;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  background: radial-gradient(closest-side,
    rgba(247,242,230,.38), rgba(247,242,230,.17) 46%, rgba(247,242,230,0) 74%);
  pointer-events: none; opacity: 0;
}
.isc-heart {
  position: absolute; left: 50%; top: 50%;
  height: 48vh; width: auto;       /* centred by GSAP — see CENTRING NOTE */
  opacity: 0;
}
.isc-pair { position: relative; height: 100%; width: auto; display: block; opacity: 0; }

.isc-petals { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }

.isc-blessing {
  position: absolute; left: 0; right: 0; top: 74%;
  z-index: 4;                      /* over the lotus, never behind it */
  margin: 0; padding: 0 var(--pad-x);
  text-align: center; color: var(--zari);
  font-family: var(--serif);
  font-size: clamp(17px, 2.6vw, 30px);
  opacity: 0;
  text-shadow: 0 2px 20px rgba(27,13,18,.7);
}

/* the scene lays the type lower than the video path did — the couple occupies
   the middle of the frame where the film had sky */
/* The words sit in a band above the bottom edge, not against it. On a phone the
   layout viewport is taller than the captured/visible frame, and a line resting
   on the last few percent of the height falls off the bottom — the bilingual
   date line, which wraps to two rows, did exactly that. */
/* Anchored to a fixed line rather than to the bottom edge: every line's TOP
   sits at 74vh, ~4vh under the figures centred at 42%, so the text reads as
   belonging to them instead of stranded at the foot of the frame. */
.intro.is-scene .intro-type {
  align-items: start; justify-items: center;
  padding-top: 74vh; padding-bottom: 0;
}
/* Video mode too: the film composes its figures in the upper ~65% and leaves
   the lower third clear, so the live lines sit there instead of dead centre
   (2026-09-02: figures were being kept tiny to dodge centred text). */
.intro:not(.is-scene):not(.is-still) .intro-type {
  align-items: start; justify-items: center;
  padding-top: 68vh; padding-bottom: 0;
}
.intro:not(.is-scene):not(.is-still) .intro-line--title { font-size: min(9vh, 8.4vw, 110px); }
@media (max-width: 760px) {
  .intro:not(.is-scene):not(.is-still) .intro-type { padding-top: 66vh; }
}
/* The title is the only line tall enough to fall off the bottom from 74vh: two
   118px rows plus the joiner is 30vh, which needs 104. Capped against the
   HEIGHT as well as the width so it always fits the 26vh the band leaves it. */
.intro.is-scene .intro-line--title { font-size: min(9.4vh, 8.4vw, 118px); }
.intro.is-scene .intro-line--tag   { font-size: min(5.2vh, 4.4vw, 52px); }
.intro.is-scene .intro-scrim {
  background:
    radial-gradient(ellipse 70% 40% at 50% 78%, rgba(27,13,18,.62), rgba(27,13,18,0) 72%),
    linear-gradient(rgba(27,13,18,.28), rgba(27,13,18,0) 30%, rgba(27,13,18,0) 54%, rgba(27,13,18,.5));
}

@media (max-width: 759px) {
  /* Heights, not widths — see the SIZING NOTE above. The numbers are ~13%
     larger than the vh targets they hit: on a phone `vh` resolves against the
     DEVICE height while the layout viewport is taller, so a literal 44vh
     rendered as 39% of the frame the guest actually sees. These are calibrated
     against the measured result, not the nominal unit. */
  .isc-ganesha { height: 50vh; width: auto; top: 42%; }   /* ≈ 44% of frame */
  .isc-couple  { height: 54vh; width: auto; top: 42%; }   /* ≈ 48% of frame */
  .isc-kolam   { height: 62vh; width: auto; top: 42%; }   /* 1.25x Ganesha   */
  .isc-heart   { height: 45vh; width: auto; }
  .isc-lift    { height: 68vh; }
  .isc-diyas   { width: min(44vmin, 175px); bottom: 4%; }
  .isc-blessing { top: 74%; }
  /* calibrated the same way as the heights above: a literal 74vh lands at 66%
     of the visible frame, which is exactly where the couple's feet end */
  .intro.is-scene .intro-type { padding-top: 79vh; }
}

/* prefers-reduced-motion: the scene is a held still, not a sequence */
@media (prefers-reduced-motion: reduce) {
  .iscene .isc-diyas, .iscene .isc-sparks { display: none; }
  .isc-kolam { clip-path: none; }
}

.intro-skip {
  position: absolute; right: clamp(14px, 2.4vw, 30px); bottom: clamp(14px, 2.4vw, 30px);
  z-index: 3;
  padding: 9px 20px; border-radius: 999px;
  border: 1px solid rgba(247,242,230,.45);
  background: rgba(33,16,22,.4);
  backdrop-filter: blur(6px);
  color: var(--on-dark);
  font-family: var(--caps); letter-spacing: .2em; text-transform: uppercase;
  font-size: 11px; cursor: pointer;
}
.intro-skip:hover { background: rgba(33,16,22,.62); }

/* reduced motion gets the last frame and the lines stacked, no video */
.intro.is-still {
  background-size: cover; background-position: center; background-repeat: no-repeat;
}
.intro.is-still .intro-vid,
.intro.is-still .intro-skip { display: none; }
.intro.is-still .intro-type {
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  gap: clamp(12px, 2.6vh, 26px);
}
.intro.is-still .intro-line { grid-area: auto; opacity: 1; transform: none; }

/* ==================================================================
   T-KB ENGINE CONVERSION — everything below this line is new: the
   tagline, real function photos, the gallery, the scratch card, the
   evening/gifts/wishes cards, and the Devanagari fallback face. Ported
   from templates/goa-shack/site/styles.css's own T-GP addition, in
   Kerala's own tokens (--ink/--kasavu/--night/--zari/--caps/--serif —
   see the :root block at the top of this file) rather than Goa's.
   ================================================================== */

/* the tagline under the names */
.tagline {
  margin: clamp(12px,1.8vw,20px) 0 0;
  font-family: var(--caps); letter-spacing: .16em; text-transform: uppercase;
  font-size: clamp(11px,1.2vw,14px); opacity: .72;
}
.name-arch .tagline { font-size: clamp(10px, 1.1vw, 13px); }

/* a real photograph, when the record has one (see renderEvents() in
   main.js) — a photo needs cover + a fixed box, not the illustrated
   piece's contain-and-shrink. */
.func-art img.func-photo {
  /* Capped like the illustrated piece it stands in for. `max-height: none`
     let a photograph fill the whole column at 4:3, so a card that had been a
     300px lantern became a 450px photograph and the page went from a set of
     cards to a stack of posters — the "giant image" a couple sees the moment
     they upload one. The shape stays 4:3; only the size is disciplined, and
     `cover` means a portrait photo from a phone crops rather than stretches. */
  max-height: clamp(200px, 30vh, 340px); aspect-ratio: 4 / 3; object-fit: cover;
  width: 100%; margin-inline: auto; border-radius: 22px;
}

/* ------------------------------------------------------------------
   GALLERY — a mosaic of the record's own photographs (C.gallery),
   portrait-friendly by default (a landscape shot still reads fine at
   3:4, cropped by its own `focal`). See renderGallery() in main.js.
   ------------------------------------------------------------------ */
.gallery { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: clamp(10px, 1.6vw, 18px); }
.gal-shot {
  position: relative; margin: 0; overflow: hidden;
  border-radius: 22px; aspect-ratio: 3 / 4;
  border: 1px solid rgba(34,48,31,.18);
  background: rgba(243,234,211,.4);
  will-change: transform, opacity;
}
.gal-shot img { display: block; width: 100%; height: 100%; object-fit: cover; }
.gal-cap {
  position: absolute; left: 0; right: 0; bottom: 0; margin: 0;
  padding: .9em .9em .6em; font-size: .78em; color: var(--kasavu);
  background: linear-gradient(to top, rgba(18,48,31,.75), transparent);
}

/* ------------------------------------------------------------------
   THE SCRATCH CARD — still backwater water over the date, wiped clear
   rather than scratched. See the comment over #scratch in index.html
   for why this material, and wireScratchCard() in main.js for the
   procedural paint (a deep backwater gradient, ripple glints, mist
   grain — no image file).
   ------------------------------------------------------------------ */
.scratch-card {
  position: relative; margin: 0 auto; width: 100%; max-width: 460px;
  aspect-ratio: 4 / 3; border-radius: 22px; overflow: hidden;
  background: linear-gradient(180deg, var(--kasavu), var(--gold-gr));
  box-shadow: 0 24px 50px rgba(8, 20, 14, .38);
}
.scratch-card .under {
  position: absolute; inset: 0; display: grid; align-content: center;
  gap: .5rem; padding: 1.4rem; color: var(--ink); text-align: center;
}
.scr-lbl {
  font-family: var(--caps); font-size: .58rem; letter-spacing: .28em;
  text-transform: uppercase; color: var(--zari-deep); margin: 0;
}
.scr-date {
  font-family: var(--serif); font-size: clamp(2rem, 10vw, 3rem);
  line-height: 1; margin: .32em 0 0;
}
.scr-date i { font-style: normal; font-size: .42em; color: var(--zari); margin: 0 .16em; }
.scr-sub { margin: .3em 0 0; font-size: 1.02rem; }
.scr-place { margin: 0; font-size: .9rem; opacity: .78; }
#waterFilm {
  position: absolute; inset: 0; width: 100%; height: 100%; display: block;
  touch-action: none; cursor: grab; transition: opacity .8s ease;
}
#waterFilm.is-gone { opacity: 0; pointer-events: none; }
.scr-prompt {
  position: absolute; left: 0; right: 0; bottom: 12%; pointer-events: none;
  font-family: var(--caps); font-size: .6rem; letter-spacing: .26em;
  text-transform: uppercase; color: #E8F1E6; text-align: center;
  transition: opacity .4s ease;
}
.scr-prompt .shell { color: var(--zari); }
.scratch-card.is-scratched .scr-prompt { opacity: 0; }
.scr-reveal { margin-top: 1.3rem; }
.scr-reveal.is-gone { display: none; }
@media (prefers-reduced-motion: reduce) {
  #waterFilm, .scr-prompt { transition: none; }
}

/* the wishes wall reuses the .know card (see renderWishes() in main.js)
   — "How you know them", inline after the guest's name. */
.wish-rel { font-style: italic; text-transform: none; letter-spacing: 0; opacity: .75; font-size: .92em; }
.wish-empty { text-align: center; opacity: .78; font-style: italic; margin: 0 0 clamp(24px,3.5vw,40px); }

/* Reused by #evening (the timeline) and #gifts (shagun + registry) — same
   card, new content, so nothing else needed to add those two rooms. */
.know code { font-size: .9em; opacity: .85; }
.gift-qr { display: block; width: 140px; height: 140px; margin: 0 auto .8em; border-radius: 12px; }
.copy {
  font: inherit; font-size: .85em; letter-spacing: .04em; text-transform: uppercase;
  border: 1px solid currentColor; border-radius: 999px; padding: .2em .8em;
  background: transparent; color: inherit; cursor: pointer;
}

/* Words the record supplies in Hindi or Marathi. This skin's own faces have
   no Devanagari (Marcellus and Cormorant Garamond are Latin-only, and Noto
   Serif Malayalam covers only the Malayalam block), so those glyphs would
   fall back to whatever the phone has. main.js marks any leaf whose text is
   Devanagari with .dv after render — same mechanism as Goa's own T-GS
   engine conversion, and independent of this skin's own .greet-mal /
   Malayalam handling above, which is a different script entirely. */
.dv{font-family:"Tiro Devanagari Hindi",Georgia,serif!important;letter-spacing:0!important;text-transform:none!important}
html[lang="mr"] .dv{font-family:"Tiro Devanagari Marathi",Georgia,serif!important}

/* A form told to hide must hide: `display` here outranks the browser's own
   [hidden] rule, so the submitted form used to sit beside its own thank-you. */
.form[hidden],form[hidden]{display:none!important}


/* Couple-uploaded photographs — the venue card and the hotel list. Both are
   optional and both are hidden until a file exists, so a wedding with no
   photographs looks exactly as this design did before they were possible. */
.venue-photo{display:block;width:100%;max-width:520px;aspect-ratio:16/9;object-fit:cover;
  margin:0 auto 18px;border-radius:6px}
/* `display:block` above beats the UA rule for [hidden] (same trap as .audio-btn),
   so an empty slot painted a blank 16:9 box above "The venue". */
.venue-photo[hidden]{display:none!important}
.hotel-photo{width:84px;height:84px;object-fit:cover;border-radius:6px;flex:none;margin-right:14px}
@media(max-width:560px){.hotel-photo{width:62px;height:62px;margin-right:10px}}

/* Scroll reveals — a class and a transition (see the reveals in main.js).
   Elements get .rv from JS only, so without JS nothing is ever hidden. */
.rv { opacity: 0; transform: translateY(46px); transition: opacity .9s cubic-bezier(.2,.7,.2,1), transform .9s cubic-bezier(.2,.7,.2,1); }
.rv-l { transform: translateX(-70px); }
.rv-r { transform: translateX(70px); }
.rv.is-shown { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) { .rv { opacity: 1; transform: none; transition: none; } }

/* The corner piece of the invite room (data-img="piece-peacock") sits in the
   room's bottom-right; on a phone that corner is where the RSVP row ends up,
   and the piece was drawn over "Add to calendar". A clear band under the
   buttons keeps them apart. (Measured at 430px on every skin.) */
@media (max-width: 759px) { #invite .wrap { padding-bottom: clamp(150px, 40vw, 200px); } }
