/* ==================================================================
   Shiv Parvati — "Kailash" · T-SP
   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. `room--palm` is Goa's added seventh class, inherited
   with its paintPaper line; here it carries the SAFFRON ground.

   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-snow.png     #F6F5F1  warm snow white
       paper-pink      = paper-lotus.png    #EDB4A9  lotus pink
       paper-marigold  = paper-night.png    #142F4D  deep indigo
       paper-indigo    = paper-ash.png      #B6B1A9  ash grey
       paper-cream     = paper-mist.png     #F7F1E6  warm mist white
       paper-palm      = paper-saffron.png  #DC6721  saffron

   So the slots are:

     .room--teal      snow      invite         The Gate of Kailash
     .room--pink      lotus     functions      Six Days on the Mountain
     .room--marigold  indigo    route          The Path to Kailash
     .room--indigo    ash       story          How It Began
     .room--cream     mist      RSVP           Say You Will Come
     .room--palm      saffron   before you climb
     .room--night     night     countdown      Counting to the Muhurtham

   The two DARK rooms — route (indigo) and countdown (night) — and the one
   SATURATED light room (saffron) each needed their ink checked rather than
   inherited; see the contrast block under the room classes.
   ================================================================== */

:root {
  /* the brief's six, verbatim */
  --snow:      #F7F7F9;
  --ash:       #C9C6C1;
  --indigo-b:  #2A2E5C;   /* the route room's ground */
  --saffron:   #E39A2C;
  --lotus:     #E8A9B8;
  --rudraksha: #6B4A2F;
  /* three derived values the six do not cover */
  --snow-warm: #F7F6F2;   /* the invite ground. NOT --snow: paper-snow.png measures
                             #F6F5F1 (hue 48) and --snow is hue 240. Both are
                             near-white so the multiply cannot go visibly wrong,
                             but a 192-degree pair should not be signed off just
                             because nobody can see it. --snow stays the ink. */
  --mist:      #FBF8F2;   /* the RSVP ground: warm white, one step off the invite */
  --night:     #1A1E3C;   /* Kailash after dark — gate, footer, countdown */

  /* the rooms — LP slot names, Kailash values, each matched to its paper tile */
  --teal:      var(--snow-warm);    /* paper-teal tile is warm snow */
  --teal-deep: #E9E7E0;
  --pink:      var(--lotus);        /* paper-pink tile is lotus pink */
  --pink-deep: #D98FA2;
  --gold:      var(--indigo-b);     /* paper-marigold tile is deep indigo */
  --gold-deep: #1D2145;
  --zari:      #C9962F;             /* the lamps' warm brass, where LP had gold */
  --zari-deep: #9A7222;
  --indigo:    var(--ash);          /* the story room */
  --indigo-lo: var(--night);
  --cream:     var(--mist);
  --cream-deep:#EFE8DA;
  --rose:      var(--lotus);
  --palm-gr:   var(--saffron);      /* the seventh ground, inherited class name */

  /* ink that stays readable on paper */
  --ink:        #2C2A33;
  --ink-soft:   #57545F;
  --on-dark:    #F7F7F9;
  --on-dark-soft:#C9CBDD;

  /* Cormorant Garamond for display: a high-contrast old-style that carries the
     painted-manuscript feel of the plates. Marcellus is the caps face only — a
     Trajan-derived Roman with real stone-carved proportion, which is the same
     letterform the gateway's own reliefs are cut in. Its lowercase carries body
     copy but never a label. */
  --serif:  "Cormorant Garamond", Georgia, "Times New Roman", serif;
  --body:   "Marcellus", Georgia, 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 from the real gate onto ONE shared
     225x428 canvas at (725,405) of the 1672x941 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:  43.36%;
  --gate-door-top:   43.04%;
  --gate-door-w:     13.46%;
  --gate-door-h:     45.48%;
  --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 .3688 x1 .6429 y0 .4815 y1 .7787. */
@media (max-width: 759px) {
  :root {
    --gate-door-left: 36.88%;
    --gate-door-top:  48.15%;
    --gate-door-w:    27.41%;
    --gate-door-h:    29.72%;
  }
}

*, *::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: #C8D2E0; }   /* the pale blue behind the Kailash dusk */
.sky--night { background-color: #1A1E3C; 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. */
.lyr--back { bottom: 14%; width: 106%; opacity: .9; }
.lyr--mid  { bottom: -16%; 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%;
  /* 70vh, not T-TM's 62vh: palace-mid is a much taller band here, and at 62vh
     the villa was small enough that the neighbour's palms read as the subject.
     At 1440x900 this lands at 630px, which is what the hero was composited at. */
  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.9% / 61.7%. The doors are RECTANGULAR here, with
     a flat lintel, and they stop at y1 = 0.8438 because they stand on the
     verandah step. Scale is 12.4, NOT T-TM's 9: the doorway is only 18.5% of
     this plate's width against 25.6% there, so the camera has much further to
     travel before it is through. main.js reads the number from the manifest. */
  transform-origin: var(--gate-origin, 49.9% 61.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-GS needs a DEEPER top fade than T-TM did: this plate's own sky is painted
   sunset orange and it is laid over a teal dusk sky, so a 9% fade left a
   visible colour step across the top third of the frame. 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, 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. */
  /* Both 9x16 bands were chroma-keyed off a flat green field, so both end in a
     CUT along the bottom rather than in painted ground. These numbers put each
     cut behind the layer in front of it: back's bottom edge lands at y 675 on
     an 844px screen and mid's opaque snowline at about y 577, which covers it.
     At the first pass (back 40%, mid 0) the back band's bottom ran as a ruled
     line straight across the phone. Verified at 390x844 with hero.py. */
  .lyr--back { width: 130%; bottom: 22%; opacity: .85; }
  .lyr--mid  { width: 135%; bottom: -10%; }

  /* 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 villa keeps the
     shutters and loses both palms, both wall lanterns, the azulejo dado and the
     hibiscus, i.e. every Goan 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.
     78vw, not T-TM's 88vw: this plate is taller for its width (0.563 against
     0.637), so at 88vw it stood 610px on an 844px screen and hid both hero
     bands outright. */
  .gate {
    width: 80vw;
    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.7% 56.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. */
/* Which rooms are LIGHT and which are DARK is NOT what it was in Goa — the two
   sets swapped. Contrast against the ground, measured:
     snow-warm #F7F6F2 + ink #2C2A33          14.8:1
     ash       #C9C6C1 + ink #2C2A33           8.9:1
     mist      #FBF8F2 + ink #2C2A33          15.4:1
     lotus     #E8A9B8 + plum ink #4A2230      6.9:1
     saffron   #E39A2C + brown ink #3A2409     6.4:1   <- NOT white: #F7F7F9 on
                                                          saffron is 2.1:1 and
                                                          fails outright, which
                                                          is the one place the
                                                          inherited Goa rule
                                                          could not be copied
     indigo    #2A2E5C + snow #F7F7F9         11.6:1
     night     #1A1E3C + snow #F7F7F9         15.1:1                            */
.room--teal     { background: var(--snow-warm); color: var(--ink);     --edge-fill: var(--snow-warm); }  /* The Gate of Kailash */
.room--pink     { background: var(--lotus);     color: #4A2230;        --edge-fill: var(--lotus); }      /* Six Days on the Mountain */
.room--marigold { background: var(--indigo-b);  color: var(--on-dark); --edge-fill: var(--indigo-b); }   /* The Path to Kailash */
.room--indigo   { background: var(--ash);       color: var(--ink);     --edge-fill: var(--ash); }        /* How It Began */
.room--cream    { background: var(--mist);      color: var(--ink);     --edge-fill: var(--mist); }
/* Goa's added seventh class, inherited with its paintPaper line. Here it is the
   SAFFRON ground, and unlike Goa's palm green it is a LIGHT one — see the ink
   note above. Without the main.js line the ground paints but the grain does not. */
.room--palm     { background: var(--saffron);   color: #3A2409;        --edge-fill: var(--saffron); }    /* Before You Climb */
.room--night    { background: var(--indigo-lo); color: var(--on-dark); --edge-fill: var(--indigo-lo); }

/* TWO of the seven rooms are dark grounds now, not three: room--palm moved to
   the light side when it became saffron, so it takes ink buttons like the rest
   of the light rooms and is deliberately absent from both selectors below. */
.room--night .btn, .room--marigold .btn {
  background: var(--snow); color: var(--ink); border-color: var(--snow);
}
.room--night .btn--ghost, .room--marigold .btn--ghost {
  background: transparent; color: var(--on-dark); border-color: rgba(247,247,249,.6);
}
/* saffron is light but strongly coloured, so its ghost button needs the brown
   ink rather than either default */
.room--palm .btn--ghost { color: #3A2409; border-color: rgba(58,36,9,.45); }
/* hairline rules: snow on the indigo route, rudraksha brown on the pale rooms */
.room--marigold .rule { background: var(--snow); opacity: .55; }
.room--teal .rule, .room--indigo .rule { background: var(--rudraksha); opacity: .45; }
/* rudraksha brown is the accent on the two pale grounds, never a ground itself */
.room--indigo .amp, .room--teal .amp { color: var(--rudraksha); }

/* 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-SP addition (ported from goa-shack's own T-GP conversion): 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-SP addition (ported from goa-shack's own T-GP conversion): 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 route room's ground is deep indigo, so a band painted in the same family
   would be invisible — Goa hit exactly this with a teal band on a teal room.
   Here the band is the SNOW the path is cut through: the one pale mass in a
   dark room, and the reason the room reads as a mountainside at all. */
.drive-band {
  position: absolute; left: 0; right: 0; bottom: 0; height: 34%;
  background: linear-gradient(#EDF1F6, #B9C3D6);
}
/* the snowline along the top of the band, and the trodden path Nandi walks on,
   so the band carries something even at scroll positions between poses */
.drive-band::before {
  content: ""; position: absolute; left: 0; right: 0; top: 0; height: 5px;
  background: linear-gradient(rgba(247,247,249,.9), rgba(247,247,249,0));
  opacity: .9;
}
/* Goa's version of this was a wake on water and T-TM's was a road centre line.
   On snow either reads as tarmac, so it is a broken track: short, faint, warm-grey
   strokes at an irregular rhythm — hoofprints, not markings. */
.drive-band::after {
  content: ""; position: absolute; left: 0; right: 0; top: 17%; height: 3px;
  background: repeating-linear-gradient(90deg,
    rgba(42,46,92,.16) 0 14px, rgba(42,46,92,0) 14px 58px,
    rgba(42,46,92,.10) 58px 68px, rgba(42,46,92,0) 68px 132px);
}
/* Nandi (and, optionally, a couple sitting on the rig) travel as one unit */
.drive-rig {
  position: absolute;
  /* WHOLLY inside the snow band, not straddling its top edge. The band is 34%
     of the stage and the bull draws about 200px on a 900px one, so anything
     above about 12% cuts the snowline across his shoulder and he reads as
     half-buried rather than as lying on the path. */
  bottom: 8%;
  left: 0;
  width: clamp(280px, 44vw, 680px);
  will-change: transform;
  z-index: 2;
}
/* CONTENT SWAP: the `car` key holds nandi — Shiva's bull, alone, crossing the
   snow. Unlike Goa's couple-on-boat this plate is LANDSCAPE (358x342, near
   square) and it is a RESTING calf, so it is bound by height like Goa's but to
   a shorter number: at Goa's 34vh the bull stood 306px on a 900px stage and
   filled a third of the screen, which is a bull in a room rather than one
   crossing a mountainside. object-position is BOTTOM, so the hooves — not the
   hump — land on the path. */
.drive-car {
  width: auto; max-width: 100%;
  height: clamp(120px, 22vh, 230px);
  object-fit: contain; object-position: bottom center;
  filter: drop-shadow(0 14px 18px rgba(26,30,60,.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-SP addition (ported from goa-shack's own T-GP
   conversion). 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(74,44,6,.22);
  background: rgba(255,252,246,.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(26,30,60,.75), transparent);
}

/* ------------------------------------------------------------------
   6c. THE SCRATCH CARD — a crust of mountain snow over a carved date-stone.
   T-SP addition (ported from goa-shack's own T-GP conversion): the same
   reveal moment as palace's own #scratch (a gold foil canvas, rubbed away
   with destination-out), in this skin's own material. Painted procedurally
   in wireScratchCard() in main.js, no image file.

   The card underneath reads as pale carved stone (a cool grey-white, dark
   ink) rather than this skin's usual warm paper, so the reveal itself feels
   like uncovering an inscription, not just a fourth cream card.
   ------------------------------------------------------------------ */
.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, #EDECE8, #D9D6CF);
  box-shadow: 0 24px 50px rgba(20, 22, 40, .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; }
#snowFoil {
  position: absolute; inset: 0; width: 100%; height: 100%; display: block;
  touch-action: none; cursor: grab; transition: opacity .8s ease;
}
#snowFoil.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: #4A5A66; text-align: center;
  transition: opacity .4s ease;
}
.scr-prompt .mark { 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) {
  #snowFoil, .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); }
/* T-SP fix: inherited from Lantern Palace's own styles.css unchanged — an
   author rule with an explicit `display` always beats the browser's default
   `[hidden] { display: none }`, so `form.hidden = true` on RSVP/wish submit
   (see renderRsvp()/renderWishes() in main.js) had no visual effect without
   this: the form kept rendering, disabled, right next to its own "Thank you"
   message. Found while testing the RSVP done-state; same latent bug in every
   sibling skin's copy of this rule. */
.form[hidden] { display: none; }
.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(--indigo-b); color: var(--snow); border-color: var(--indigo-b); }
.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-SP addition (ported from goa-shack's own T-GP conversion): 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(60vh, 46%); }
  .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: #E0C489; }   /* saffron-silver: warm enough to read as a lamp
                                    against the blue-hour plate, pale enough to
                                    stay a stone-carved colour rather than gold */

.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(--snow);
  letter-spacing: .04em;
  text-shadow: 0 0 30px rgba(247,247,249,.30);
}
.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 {
  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: 26.5% 25.5% 15.5% 19.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 lotus pond at the far end of the path. It sits ON the indigo ground, so
     it is only a shade or two off it — still water read against the room behind
     it, not a separate blue object. */
  background: linear-gradient(#3A3F72, #1D2145);
  opacity: .6;
}
/* piece-boat is the Shivling shrine — it STANDS, unlike Goa's floating boat, so
   it goes back to T-TM's reading and sits on the near bank of the strip rather
   than in the middle of it. */
.piece--boat {
  left: 34%; bottom: 18%;
  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}


/* 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); } }
