/* ==================================================================
   Ram Sita — "Ayodhya" · T-RS
   A SKIN of Lantern Palace (T-LP), taken by way of Goa Shack (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. ONE class is added — `room--palm`, with a matching line
   in main.js's paintPaper map — because this skin has a seventh ground (palm
   saffron, for "before you come") that the six inherited slots could not carry.

   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.
   (T-TM inverted this same mapping four times while it lived only in prose.)

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

       paper-teal      = paper-ivory.png      #F8EAD1  ivory
       paper-pink      = paper-rosegold.png   #DF9F8E  rose blush
       paper-marigold  = paper-maroon.png     #863842  deep maroon
       paper-indigo    = paper-peach.png      #FBC596  peach
       paper-cream     = paper-ivory-alt.png  #FFF8DE  ivory, lightened
       paper-palm      = paper-saffron.png    #FA9D2B  saffron

   NOTE none of those FILES is named after an engine key it is not. A key
   resolves under its OWN NAME FIRST and only then its `as:` aliases, so a file
   literally called paper-teal.png would be claimed by the `paper-teal` KEY —
   which is the invite room's ivory. Goa lost a round to that rule and Shiv
   Parvati lost a second one; this skin's five delivered papers are named for
   their COLOUR, which is why it did not happen a third time.

   So the slots are:

     .room--teal      ivory       invite      The Gate of Ayodhya
     .room--pink      rose blush  functions   Six Days in Ayodhya
     .room--marigold  deep maroon route       The road to Ayodhya
     .room--indigo    peach       story       How It Began
     .room--cream     ivory-lit   RSVP        Say You Will Come
     .room--palm      saffron     know        Before You Come
     .room--night     night       countdown   The Muhurat, Counted
   ================================================================== */

:root {
  /* the brief's six, verbatim */
  --rose-gold: #C9A24A;   /* the METAL — rules, monogram, strips. Never a ground. */
  --saffron:   #E39A2C;
  --ivory:     #F3EAD3;
  --peach:     #F0C9A8;
  --maroon:    #6E1B2B;   /* deep maroon */
  --lotus:     #E8A9B8;   /* lotus pink */
  /* three derived values the six do not cover */
  --rose-blush:#E0A793;   /* the FUNCTIONS ground. NOT --rose-gold: paper-rosegold.png
                             measures #DE9F8D (hue 13) and --rose-gold is hue 42, a
                             28-degree disagreement that goes brass-brown under the
                             multiply. The metal stays an accent. */
  --ivory-lit: #FFFBF2;   /* the RSVP ground: ivory, one step up, so invite and RSVP
                             read as two rooms and not one long one */
  --night:     #2A0F1A;   /* deep maroon after dark — gate, footer, countdown. NOT the
                             night sky's own violet #24113E: the countdown room takes a
                             peach tile at 24% multiply, and a warm ground keeps the
                             little that lands inside the skin's own family. */

  /* the rooms — LP slot names, Ayodhya values, each matched to its paper tile */
  --teal:      var(--ivory);        /* paper-teal tile is ivory */
  --teal-deep: #E7DCC0;
  --pink:      var(--rose-blush);   /* paper-pink tile is rose blush */
  --pink-deep: #CE9280;
  --gold:      var(--maroon);       /* paper-marigold tile is deep maroon */
  --gold-deep: #58121F;
  --zari:      var(--rose-gold);    /* the warm metal, where LP had gold */
  --zari-deep: #A07E31;
  --indigo:    var(--peach);        /* the story room */
  --indigo-lo: var(--night);
  --cream:     var(--ivory-lit);
  --cream-deep:#F1E8D6;
  --rose:      var(--lotus);

  /* Ink measured, not assumed: maroon ink is 12.5:1 on ivory, 9.7:1 on peach,
     7.2:1 on rose blush, 14.5:1 on ivory-lit and 6.4:1 on saffron. */
  --ink:        #4A1220;
  --ink-soft:   #7A4450;
  --on-dark:    #F3EAD3;
  --on-dark-soft:#E4D3B8;

  /* Cormorant Garamond for display. Marcellus for caps and body: a
     Trajan-derived Roman with real stone-carved proportion — the same
     letterform the gateway's own reliefs are cut in, which is why it replaces
     Goa's Josefin Sans. Its lowercase carries body copy but never a label. */
  --serif:  "Cormorant Garamond", Georgia, "Times New Roman", serif;
  --body:   "Marcellus", Georgia, "Times New Roman", serif;
  --caps:   "Marcellus", Georgia, 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 onto ONE shared 438x517 canvas at
     (1062,685) of the 2560x1440 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. From rect_norm x0 .4148 x1 .5859 y0 .4757 y1 .8347. */
  --gate-door-left:  41.48%;
  --gate-door-top:   47.57%;
  --gate-door-w:     17.11%;
  --gate-door-h:     35.90%;
  --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 .3049 x1 .6937 y0 .4066 y1 .8523. */
@media (max-width: 759px) {
  :root {
    --gate-door-left: 30.49%;
    --gate-door-top:  40.66%;
    --gate-door-w:    38.88%;
    --gate-door-h:    44.57%;
  }
}

*, *::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);
  /* Marcellus is a Roman with a modest x-height and long extenders, so it takes
     a touch more leading than Josefin Sans did */
  line-height: 1.72;
  -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-GS: half T-TM's size and two thirds its opacity, and both pushed off the
   centre line. The Goan cloud plate is a single big cumulus rather than a thin
   wisp, so at 30% width it sat directly behind the names as a pale blob that
   read as fog on the lens — it is the thing that looked like a broken scrim in
   the first pass, not the scrim. */
.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).
   palace-mid measures 0.425 here against tamil-mandapam's 0.287 — its palms run
   most of the plate — so at T-TM's `bottom: -3%` it stood 636px tall on a
   900px viewport and buried palace-back completely. Pushed down to -16% and
   with back lifted to 14%, the sea horizon and the church land in the gap
   between mid's compound wall and its palm canopy. Both stay past 100% so their
   own left and right cut edges fall off-screen. Composited at 1440x900. */
/* T-RS: both bands arrived as REAL RGBA with a feathered top and painted
   ground running to the plate's own bottom edge, so neither needed the haze
   ramp or the bottom crop Shiv Parvati's chroma-keyed bands did. Measured,
   palace-mid is 0.490 of its plate width and palace-back is 0.383; back lifts
   to 16% so the Ayodhya skyline and the flat Sarayu clear mid's mandap arches
   and its row of diyas. Both stay past 100% so their own left and right cut
   edges fall off-screen. Composited at 1440x900 with hero.py. */
.lyr--back { bottom: 16%; width: 106%; opacity: .9; }
.lyr--mid  { bottom: -14%; width: 104%; }

/* 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%;
  /* 50%, not Goa's 46%: palace-mid's arches and pillars are big near-camera
     masses here, and at 46% the gateway was smaller than the band flanking it,
     which inverts the depth.
     82vh and not Goa's 70vh, and this was caught by shooting the real page
     rather than by the composite: at 1440x900 `min(70vh, 50%)` resolves to the
     70vh arm (630px) and NOT to 50% (720px), so the live gate came out 90px
     smaller than the frame hero.py had signed off — hero.py models the
     percentage arm only. 82vh moves the clamp onto the 50% arm at this
     viewport, which is what was actually composited. */
  width: min(82vh, 50%);
  aspect-ratio: 2560 / 1440;
  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 — 50.0% / 65.5%. The doors are RECTANGULAR here, under
     a flat carved lintel, and they stop at y1 = 0.8347 because they stand on
     the marble stair. Scale is 13.4, NOT Goa's 12.4: the doorway is 17.1% of
     this plate's width against 18.5% there, so the camera has further to travel
     before it is through. main.js reads the number from the manifest. */
  transform-origin: var(--gate-origin, 50.0% 65.5%);
  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-RS keeps Goa's DEEP top fade: this plate's own sky is painted the same
   peach sunset as sky-dusk, so the step is gentler than Goa's orange-on-teal
   was, but the plate is a hard rectangle either way. 22% desktop / 18% phone. */
.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, and tinted to the night sea rather than to maroon; the names
   pick the rest of the contrast up from their own shadow below. */
.hero-plate::before {
  content: ""; position: absolute; inset: -8% -4%; z-index: -1;
  background: radial-gradient(ellipse 62% 74% at 50% 48%,
    rgba(10,40,48,.34), rgba(10,40,48,.18) 54%, rgba(10,40,48,0) 88%);
}

.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: .86; font-size: clamp(15px,1.5vw,19px); }
/* "Sri Meenakshi Kalyana Mandapam · Mylapore, Chennai" is 50 characters and
   wraps to two lines at 390px. The eyebrow directly above already carries
   மயிலாப்பூர் · CHENNAI, 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.053 — the band is TALLER than the
     plate is wide — so on a 390px phone it draws 460px of palms and shack. It
     is left at the bottom and taken past the gate's own width instead: at 112%
     its palms flank the villa on both sides, which is the only place they can
     be seen at all. palace-back is lifted to 40% so the church and the sea
     horizon clear the gate's gable. Composited at 390x844. */
  /* palace-mid-9x16 measures 1.236 — TALLER THAN ITS OWN PLATE IS WIDE — so at
     Goa's `bottom: 0` it stood about 570px on an 844px screen and buried the
     back band and the gate together. Dropped to -4% and taken to 118% so its
     arches flank the gateway instead of stacking above it; back lifted to 34%
     so the Ayodhya skyline clears the gate's dome. Composited at 390x844. */
  .lyr--back { width: 124%; bottom: 34%; opacity: .85; }
  .lyr--mid  { width: 118%; bottom: -4%; }

  /* 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 gateway keeps the
     doors and loses both lotus-capital pillars, both side niches, the marigold
     torans and the whole marble stair, i.e. every Ayodhya 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: this plate is taller for its width (0.563) than Goa's was, and the
     mid band is taller again, so anything above ~76vw hid the back band
     outright. */
  .gate {
    width: 74vw;
    aspect-ratio: 1440 / 2560;
    /* 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: 49.9% 62.9%;
  }
}

.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; }

/* 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. */
/* Two of these rooms are LIGHT where T-TM's were dark, so their `color` flips
   to ink. Contrast against the ground, measured: sand #EFE3CC + ink #26383A is
   9.6:1; coral #F08A5D + a deep brown ink is 5.6:1; sea teal #2A9D9F + white is
   3.4:1 at the large sizes it carries and is only used for headings and buttons
   there, never for body copy. */
.room--teal     { background: var(--ivory);      color: var(--ink);     --edge-fill: var(--ivory); }      /* The Gate of Ayodhya */
.room--pink     { background: var(--rose-blush); color: var(--ink);     --edge-fill: var(--rose-blush); } /* Six Days in Ayodhya */
.room--marigold { background: var(--maroon);     color: var(--on-dark); --edge-fill: var(--maroon); }     /* The road to Ayodhya */
.room--indigo   { background: var(--peach);      color: var(--ink);     --edge-fill: var(--peach); }      /* How It Began */
.room--cream    { background: var(--ivory-lit);  color: var(--ink);     --edge-fill: var(--ivory-lit); }
/* the one class this skin ADDS. main.js gains `room--palm → paper-palm` beside
   it; without that line the ground paints but the paper grain does not.
   THE CONTRAST REPAIR: Goa's room--palm was palm green, a DARK ground taking
   white ink and light buttons. Here it is saffron #E39A2C, which is a LIGHT
   one — ivory on it measures 2.0:1 and fails outright, exactly the trap Shiv
   Parvati hit. It takes the maroon ink (6.4:1) and it is OUT of both dark-room
   selectors below. This is the one rule that could not be copied from Goa. */
.room--palm     { background: var(--saffron);    color: var(--ink);     --edge-fill: var(--saffron); }    /* Before You Come */
.room--night    { background: var(--indigo-lo);  color: var(--on-dark); --edge-fill: var(--indigo-lo); }

/* TWO of the seven rooms are dark grounds, so they take light buttons — the
   maroon route room and the night countdown. `room--palm` is deliberately NOT
   in this list; see the contrast repair above. */
.room--night .btn, .room--marigold .btn {
  background: var(--ivory); color: var(--ink); border-color: var(--ivory);
}
.room--night .btn--ghost, .room--marigold .btn--ghost {
  background: transparent; color: var(--on-dark); border-color: rgba(243,234,211,.6);
}
/* hairline rules: ivory on the maroon, the rose-gold metal on the light rooms */
.room--marigold .rule { background: var(--ivory); opacity: .55; }
.room--teal .rule, .room--indigo .rule { background: var(--rose-gold); opacity: .7; }
/* lotus pink is too pale to be an accent on a light ground; the deep maroon
   reads on all five light rooms and is already the ink */
.room--indigo .amp, .room--teal .amp { color: var(--maroon); }

/* 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;
}
.couple .amp {
  font-family: var(--caps); font-style: normal;
  font-size: clamp(20px, 3vw, 36px); opacity: .7; margin: .12em 0;
}
.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; }
/* T-GP addition: 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;
}

/* ------------------------------------------------------------------
   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(255,252,246,.92);
  border: 1px solid rgba(74,44,6,.22);
  border-radius: clamp(30px, 5vw, 64px);
  box-shadow: 0 18px 40px rgba(74,44,6,.12);
  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; }
/* T-GP addition: 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;
}
.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 band is the GOLDEN GHAT PATH, and it is the one bright mass in the room.
   Goa's problem was a teal band on a teal room; here the room ground is deep
   maroon, so the band goes the other way — the marble road to Ayodhya lit by
   the same sunset the plates are painted in. It is what makes the section read
   as a road at all. */
.drive-band {
  position: absolute; left: 0; right: 0; bottom: 0; height: 34%;
  background: linear-gradient(#F0CE87, #C4893A);
}
/* the lip along the top of the path, so the band carries something even at
   scroll positions between deer poses */
.drive-band::before {
  content: ""; position: absolute; left: 0; right: 0; top: 0; height: 5px;
  background: linear-gradient(rgba(255,246,222,.85), rgba(255,246,222,0));
  opacity: .9;
}
/* Goa broke this into short faint strokes because a steady dash on water reads
   as tarmac. This IS a road — marble ghat paving — so the rhythm comes back,
   but as the JOINTS between slabs: a maroon hairline, wide-set, not a painted
   centre line. */
.drive-band::after {
  content: ""; position: absolute; left: 0; right: 0; top: 15%; height: 2px;
  background: repeating-linear-gradient(90deg,
    rgba(110,27,43,.26) 0 46px, rgba(110,27,43,0) 46px 132px);
}
/* the car and (optionally) the couple sitting on it travel as one rig */
.drive-rig {
  position: absolute;
  /* the deer STANDS on the path — its hooves sit low inside the band rather
     than on the lip of it. The band is 34% of the stage and this plate draws
     about 220px on a 900px one; at anything above ~12% the path's top edge cuts
     across its shoulder and it reads as half-buried. Checked at both viewports. */
  bottom: 8%;
  left: 0;
  width: clamp(280px, 44vw, 680px);
  will-change: transform;
  z-index: 2;
}
/* CONTENT SWAP: the `car` key holds the GOLDEN DEER, alone — the one creature
   the Ramayana puts on a road. `couple.onCar` stays null: a couple suspended
   above a small walking animal reads as a compositing mistake, not a ride.
   Bound by HEIGHT, like Goa's and unlike Lantern Palace's car. 22vh, not Goa's
   34vh: that plate was portrait (a couple standing above a hull) and this one
   is a small fawn — at 34vh it filled a third of the screen and read as a deer
   in a room rather than one on a road. object-position is BOTTOM, so the hooves
   land on the path. */
.drive-car {
  width: auto; max-width: 100%;
  height: clamp(120px, 22vh, 300px);
  object-fit: contain; object-position: bottom center;
  filter: drop-shadow(0 14px 18px rgba(42,15,26,.34));
}
.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; }
}

/* ------------------------------------------------------------------
   6b. GALLERY — T-GP addition. 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(38,56,58,.18);
  background: rgba(255,253,249,.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(--on-dark);
  background: linear-gradient(to top, rgba(42,15,26,.78), transparent);
}

/* ------------------------------------------------------------------
   6c. THE SCRATCH CARD — wet sand over the date. T-GP addition: the same
   reveal moment as palace's own #scratch (a gold foil canvas, rubbed away
   with destination-out — see templates/palace-journey/site/site.js), in
   this skin's own material. Painted procedurally in wireScratchCard() in
   main.js, 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, #FBF9F4, #F0E6D2);
  box-shadow: 0 24px 50px rgba(10, 26, 20, .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; }
#scratchFoil {
  position: absolute; inset: 0; width: 100%; height: 100%; display: block;
  touch-action: none; cursor: grab; transition: opacity .8s ease;
}
#scratchFoil.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: #5A4526; 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) {
  #scratchFoil, .scr-prompt { transition: none; }
}

/* ------------------------------------------------------------------
   7. RSVP
   ------------------------------------------------------------------ */
.ring-art { width: clamp(96px, 13vw, 168px); 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(--maroon); color: var(--on-dark); border-color: var(--maroon); }
.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;
}
/* Things-to-Know sits on palm green, which is dark, so it keeps the pale card
   the countdown wants. The light rooms are the ones that need ink edges. */
.room--pink .know, .room--teal .know, .room--cream .know, .room--indigo .know {
  border-color: rgba(38,56,58,.24); background: rgba(255,253,249,.52);
}
.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; }
/* T-GP addition: the wishes wall reuses this same 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;
}

/* ------------------------------------------------------------------
   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;
  color: var(--on-dark);
  letter-spacing: .04em;
  text-shadow: 0 0 30px rgba(243,234,211,.28);
}
.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 .tagline { font-size: clamp(10px, 1.1vw, 13px); }
.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 {
  /* SMALLER AND FAINTER than Goa's. Goa left `orn-corner` unpainted, so these
     numbers were only ever tuned against Lantern Palace's flat paisley. This
     skin's corner is a lotus-and-peacock cluster with dark green leaves in it,
     and at 68px / .34 on the saffron `know` cards it sat ON the body copy —
     four of them per card, one at each corner. Caught by looking at the room,
     not by a contrast number: it never failed a ratio, it just made the text
     hard to read. */
  position: absolute; width: clamp(26px, 3.2vw, 44px); aspect-ratio: 1;
  background-size: contain; background-repeat: no-repeat;
  opacity: .20; 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: 19.5% 23.5% 30.5% 28.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;
  height: clamp(70px, 11vw, 130px);
  margin: clamp(26px, 4vw, 52px) 0 clamp(6px, 1vw, 14px);
  overflow: hidden;
  border-radius: 999px;
  /* the water past the end of the beach road. It sits ON the sea-teal ground,
     so it is only a shade or two off it — a strip of deeper water read against
     the shallows behind it, not a separate blue object. */
  /* a shade or two deeper than this room's own ground (--maroon #6E1B2B), never the Goa sea-teal this rule was copied with */
  background: linear-gradient(#4F131E, #370D15);
  opacity: .6;
}
/* piece-boat is the lateen-sail fishing boat, and unlike T-TM's banana plant it
   FLOATS — so it goes back to Lantern Palace's reading and sits in the middle
   of the strip rather than standing on the bank. */
.piece--boat {
  left: 0; right: 0; margin: 0 auto; bottom: 18%; /* centred, still — see the note in main.js */
  width: clamp(90px, 12vw, 170px);
  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; }

/* Words the record supplies in Hindi or Marathi. This skin's own faces have
   no Devanagari, so those glyphs would fall back to whatever the phone has.
   main.js marks any leaf whose text is Devanagari with .dv after render. */
.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); } }
