/* ==========================================================================
   GANESH CHATURTHI INVITATION — STAGE 1
   Sections in this file:
     1.  Design tokens (colours, type, spacing)  <- retheme here
     2.  Reset & page basics
     3.  Typography roles
     4.  Stage wrapper (the "walk through the doorway" zoom)
     5.  Door entry screen
     6.  Hero section
     7.  Falling petals
     8.  Navigation bar  (+ adaptive contrast over the hero)
     9.  Hamburger menu overlay
     10. Music toggle    (+ adaptive contrast over the hero)
     11. Reduced motion / accessibility
     12. Small screen tuning
   ========================================================================== */


/* ==========================================================================
   1. DESIGN TOKENS
   Change a value here and it changes everywhere on the site.
   (js/config.js can also overwrite the seven main colours at load time.)
   ========================================================================== */
:root{
  /* --- Palette --- */
  --cream:   #FBF7F0;   /* main background — the canvas */
  --ivory:   #FFFDF8;   /* raised surfaces, cards */
  --sand:    #EDE4D3;   /* subtle borders, dividers, section separation */
  --ink:     #3A322B;   /* primary text — soft dark brown, never pure black */
  --muted:   #7A6E60;   /* secondary text, captions */
  --brass:   #B08D57;   /* metallic accents, fine detail lines */
  --sindoor: #C1272D;   /* the single accent colour — used sparingly */

  /* --- Derived tones --- */
  --brass-line: rgba(176, 141, 87, 0.45);   /* hairline rules and fine edges */

  /* Frosted glass, light version — the nav bar past the hero, and the music
     button at all times. The -solid one is the fallback for browsers with
     no backdrop-filter, so the bar is never an unreadable transparent strip. */
  --glass-light:  rgba(251, 247, 240, 0.72);
  --glass-light-solid: rgba(251, 247, 240, 0.94);

  /* Door-screen only: the warm light through the crack between the panels. */
  --door-glow: rgba(238, 202, 146, 0.9);

  /* Wooden fallback shown underneath door.jpg, so a missing image
     still reads as a door instead of a blank rectangle. */
  --wood-fallback:
    repeating-linear-gradient(90deg, rgba(0,0,0,.22) 0 2px, rgba(255,255,255,.025) 2px 24px),
    linear-gradient(180deg, #4A2E1D 0%, #3A2216 55%, #26150C 100%);

  /* --- Type --- */
  --font-display: "Cormorant Garamond", "Times New Roman", serif;   /* English headings + invitation copy */
  --font-body:    "Karla", system-ui, -apple-system, "Segoe UI", sans-serif; /* nav, buttons, hints */
  --font-utility: "Lora", Georgia, serif;                           /* small supporting text */
  --font-tiro:    "Tiro Devanagari Marathi", "Nirmala UI", serif;   /* the salutation + shloka */
  --font-deva:    "GanpatiDeva", "Tiro Devanagari Marathi", "Nirmala UI", serif;  /* calligraphy — scratch card only */
  /* The two invitation faces, one per script.

     THE MARATHI FACE MUST BE A TEXT FACE, NOT A DISPLAY FACE. Two display
     faces were tried here and both failed the same way. Devanagari carries
     small marks above the letters — the reph hook above all — and in a
     display face those are hairlines. At the ~19px a phone reads at they fall
     below a single pixel, grey out, and stop looking attached to the letter
     they belong to. It reads as broken text. No CSS setting repairs it,
     because nothing is wrong with the shaping: the ink is simply too thin.

     Tiro Devanagari Marathi is drawn for running text and holds up at that
     size, which is why the Marathi copy uses the same face as the salutation.
     If you ever swap it, look at चतुर्थीच्या at 19px on a real phone first.

     Each face is scoped STRICTLY to one script in the rules below, because
     Playfair Display carries no Devanagari at all and Tiro carries no Latin
     styling intent. The fallbacks are real faces already on the page, so a
     failed download degrades to readable rather than to nothing. */
  --font-script:   "Playfair Display", "Cormorant Garamond", serif; /* English invitation copy */
  /* Same face as --font-tiro, named for its role so a future swap of the
     Marathi copy does not silently change the salutation too. */
  --font-devadisp: var(--font-tiro);                                /* ALL Marathi copy + event text */

  /* Fluid type scale. clamp(smallest, grows-with-screen, largest) */
  --fs-body:   1rem;
  --fs-small:  0.8125rem;
  --fs-nav:    0.72rem;
  --fs-menu:   clamp(1.9rem, 8.5vw, 3.2rem);    /* big menu links */

  /* --- Layout --- */
  --pad-x:      clamp(20px, 6vw, 56px);   /* left/right page padding */
  --pad-invite: clamp(24px, 6vw, 64px);   /* section 2 — never below 24px on a phone */
  /* Sections 3 and 4 MUST share this. The footprint trail is centred inside
     it, so if the two sections had different side padding the curve would
     jump sideways at the boundary between them. */
  --pad-trail:  clamp(28px, 8vw, 64px);
  --nav-h:   56px;                     /* nav bar height — deliberately thin */
  --safe-b:  env(safe-area-inset-bottom, 0px);

  /* --- Vertical rhythm ---
     The ladder sections 2 and 3 were already climbing, written down so the
     rest of the site steps on the same rungs instead of inventing new
     numbers. Every one of these grows with the screen, so the pauses stay
     proportional rather than collapsing on a phone or opening into a void
     on a desktop.

       para    between paragraphs of one thought
       block   between the parts of one block
       moment  between two moments in the same section
       chapter between two blocks that should read as separate things
       garland down to a section edge that a garland straddles

     `garland` is not a plain gap: the divider is pulled up over the seam by
     half its own height, and that half grows with the screen — 71px at
     360px, 170px on a laptop. A fixed pad would therefore look generous on
     a phone and vanish on a desktop, which is exactly what was happening.
     Adding 20vw tracks the overlap, so the gap the EYE sees below the last
     content stays around 90–115px at every width. It is the same formula
     section 2 uses above its own garland, pointed the other way. */
  --gap-para:    clamp(26px, 6vw, 38px);
  --gap-block:   clamp(44px, 10vw, 72px);
  --gap-moment:  clamp(72px, 16vw, 120px);
  --gap-chapter: clamp(104px, 22vw, 172px);
  --gap-garland: clamp(162px, 90px + 20vw, 285px);

  /* --- Motion --- */
  --ease-door: cubic-bezier(0.65, 0, 0.25, 1);   /* heavy wooden doors */
  --ease-soft: cubic-bezier(0.22, 1, 0.36, 1);   /* settling, arriving */
  --fade-contrast: 420ms;                        /* nav / music light-dark cross-fade */

  /* --- Stacking order (kept in one place so nothing fights) --- */
  --z-content: 1;
  --z-petals: 40;
  --z-menu:   55;
  --z-nav:    60;
  --z-music:  70;
  --z-door:   100;
}


/* ==========================================================================
   2. RESET & PAGE BASICS
   ========================================================================== */
*, *::before, *::after{ box-sizing: border-box; }

html{
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;
}

body{
  margin: 0;
  background: var(--cream);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--fs-body);
  line-height: 1.6;
  overflow-x: hidden;          /* never allow sideways scrolling */
  -webkit-font-smoothing: antialiased;
}

/* While the door screen is up, the page must not scroll. */
body.is-locked{
  overflow: hidden;
  height: 100%;
  position: relative;
}

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

button{
  font: inherit;
  color: inherit;
  background: none;
  border: 0;
  cursor: pointer;
}

a{ color: inherit; text-decoration: none; }

/* Keyboard users must always see where they are. */
:focus-visible{
  outline: 2px solid var(--sindoor);
  outline-offset: 3px;
  border-radius: 2px;
}

/* Anchors land below the fixed nav bar, not underneath it. */
section{ scroll-margin-top: var(--nav-h); }

/* Present for screen readers, invisible on screen. */
.visually-hidden{
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}


/* ==========================================================================
   3. TYPOGRAPHY ROLES
   ========================================================================== */

/* The calligraphy font is Devanagari-only. This class is the ONLY place it is
   allowed. Nothing uses it yet — it is here ready for the scratch card.
   Never put .deva on English text: it would silently fall back and look broken. */
.deva{
  font-family: var(--font-deva);
  font-weight: 400;
  line-height: 1.45;
  letter-spacing: normal;      /* Devanagari must never be letter-spaced */
}

/* The reading face for Devanagari on the page — the salutation and the
   shloka. This is a DIFFERENT job from .deva above: .tiro is for reading,
   .deva is the calligraphy display face reserved for the scratch card. */
.tiro{
  font-family: var(--font-tiro);
  font-weight: 400;
}

h1, h2, h3{ margin: 0; font-weight: 400; }
p{ margin: 0; }


/* ==========================================================================
   4. STAGE WRAPPER
   Holds the door layer + the page behind it. Scaling this (not just the
   doors) is what makes the entry feel like walking through the doorway.
   ========================================================================== */
/* The stage carries NO transform. It used to scale to 1.25 to fake walking
   through the doorway, and that scale moved the hero with it. The hero now
   has to sit perfectly still while the doors travel off it, so there is
   nothing here to animate — this wrapper is only a grouping element. */
.stage{
  position: relative;
}


/* ==========================================================================
   5. DOOR ENTRY SCREEN
   ========================================================================== */
/* The door layer itself is TRANSPARENT. It used to be filled with cream,
   which was invisible only because the panels covered every pixel. Now the
   panels travel off the sides, so whatever this layer is filled with would
   be left sitting over the hero — it has to be nothing at all. */
.door{
  position: fixed;
  inset: 0;
  z-index: var(--z-door);
  background: transparent;
  overflow: hidden;
  touch-action: none;
}

/* ---- The mist ----------------------------------------------------------
   A milky veil over the hero: white enough that the picture is only
   suggested, blurred enough that nothing in it resolves. Fogged glass.

   `backdrop-filter` blurs what is BEHIND the element rather than the element
   itself, which is what lets the hero stay a normal, untouched image while
   still appearing out of focus. Both the whiteness and the blur wind down as
   the doors travel, so the picture comes into focus at the same rate it is
   uncovered. */
.door__mist{
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background: rgba(255, 255, 255, .82);
  -webkit-backdrop-filter: blur(18px) saturate(.82);
  backdrop-filter: blur(18px) saturate(.82);
  transition:
    opacity var(--mist-clear, 2260ms) var(--ease-door),
    background-color var(--mist-clear, 2260ms) var(--ease-door),
    -webkit-backdrop-filter var(--mist-clear, 2260ms) var(--ease-door),
    backdrop-filter var(--mist-clear, 2260ms) var(--ease-door);
}

/* Without backdrop-filter there is no blur to wind down, so the veil leans
   harder on opacity alone. Still a mist, just a flatter one. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))){
  .door__mist{ background: rgba(255, 255, 255, .90); }
}

/* The two halves of ONE image.
   JS sets --door-size / --door-pos-* so the photo fills the screen at its
   true proportions and is then cut down the middle — no stretching. */
.door__panel{
  position: absolute;
  top: 0;
  bottom: 0;
  z-index: 2;                  /* over the mist */
  width: 50.2%;                /* the extra 0.2% hides any hairline gap */
  background-color: #2E1B10;
  background-image: var(--door-img, none), var(--wood-fallback);
  background-size: var(--door-size, 200% 100%), auto, auto;
  background-repeat: no-repeat;
  /* Straight sideways travel. No rotation, no scale, no perspective. */
  transition: transform var(--door-slide, 2000ms) var(--ease-door);
  will-change: transform;
}

.door__panel--left { left: 0;  background-position: var(--door-pos-left, left center); }
.door__panel--right{ right: 0; background-position: var(--door-pos-right, right center); }

/* Warm light leaking through the crack in the middle. */
.door__seam{
  position: absolute;
  top: -6%;
  bottom: -6%;
  left: 50%;
  width: 40px;
  margin-left: -20px;
  background: radial-gradient(closest-side, var(--door-glow), rgba(238,202,146,.16) 55%, transparent 78%);
  filter: blur(7px);
  opacity: .45;
  animation: seam-breathe 5.2s ease-in-out infinite;
  pointer-events: none;
}
@keyframes seam-breathe{
  0%, 100%{ opacity: .32; transform: scaleX(0.86); }
  50%     { opacity: .55; transform: scaleX(1.06); }
}

/* The whole screen is the tap target. */
.door__tap{
  position: absolute;
  inset: 0;
  z-index: 3;
  display: grid;
  align-items: end;          /* the words sit in the lower third, clear of the
                                door handles that usually sit at centre height */
  justify-items: center;
  width: 100%;
  padding: 0 0 clamp(70px, 17vh, 150px);
  opacity: 0;                       /* revealed once the assets have loaded */
  transition: opacity 700ms ease;
}
.door.is-ready .door__tap{ opacity: 1; }

.door__prompt{
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 18px;
  padding: 0 var(--pad-x);
  animation: prompt-pulse 3.4s ease-in-out infinite;  /* a diya, not a blinking button */
}
@keyframes prompt-pulse{
  0%, 100%{ opacity: .58; }
  50%     { opacity: 1; }
}

/* A single small flame above the words. Two overlapping timings so the
   flicker never looks mechanical. */
.door__diya{
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--door-glow);
  box-shadow: 0 0 10px 3px rgba(238,202,146,.6);
  animation: diya-flicker 2.3s ease-in-out infinite, diya-drift 3.7s ease-in-out infinite;
}
@keyframes diya-flicker{
  0%, 100%{ opacity: 1;   }
  35%     { opacity: .62; }
  60%     { opacity: .92; }
}
@keyframes diya-drift{
  0%, 100%{ transform: translateY(0) scale(1); }
  50%     { transform: translateY(-2px) scale(1.14); }
}

.door__prompttext{
  font-family: var(--font-body);
  font-size: var(--fs-small);
  font-weight: 500;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: var(--ivory);
  text-align: center;
  text-shadow: 0 2px 18px rgba(0,0,0,.85), 0 0 4px rgba(0,0,0,.5);
}

/* Loading dots — the door screen doubles as the loading screen. */
.door__loading{
  position: absolute;
  left: 50%;
  bottom: clamp(78px, 18vh, 158px);   /* same spot the prompt will appear in */
  z-index: 4;
  display: flex;
  gap: 8px;
  transform: translateX(-50%);
  opacity: 0;
  transition: opacity 400ms ease;
}
.door.is-loading .door__loading{ opacity: 1; }
.door__loading span{
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--door-glow);
  animation: dots 1.4s ease-in-out infinite;
}
.door__loading span:nth-child(2){ animation-delay: .18s; }
.door__loading span:nth-child(3){ animation-delay: .36s; }
@keyframes dots{
  0%, 100%{ opacity: .25; }
  50%     { opacity: 1; }
}

/* ---- The opening itself -------------------------------------------------
   Two panels sliding straight out of frame, and a mist thinning behind them.
   That is the whole animation.

   The panels are 50.2% of the screen wide, so moving each one 101% of its
   OWN width carries it fully past the edge with a little to spare. The right
   panel starts 70ms after the left: enough that they do not read as one
   mechanical unit, not enough to look like a mistake.

   The mist runs on a longer clock than the panels (see CONFIG.timing), so it
   is still settling for a beat after they have gone. */
.door.is-open .door__panel--left { transform: translate3d(-101%, 0, 0); }
.door.is-open .door__panel--right{ transform: translate3d(101%, 0, 0); transition-delay: 70ms; }

.door.is-open .door__mist{
  opacity: 0;
  background-color: rgba(255, 255, 255, 0);
  -webkit-backdrop-filter: blur(0px) saturate(1);
  backdrop-filter: blur(0px) saturate(1);
}

.door.is-open .door__tap { opacity: 0; transition-duration: 380ms; pointer-events: none; }
.door.is-open .door__seam{ opacity: 0; transition: opacity 600ms ease; animation: none; }

/* Final fade-out. JS removes the element from the page right after. */
.door.is-gone{
  opacity: 0;
  transition: opacity 600ms ease;
  pointer-events: none;
}


/* ==========================================================================
   6. HERO SECTION
   No text at all — the picture has to carry the screen on its own.
   ========================================================================== */
.hero{
  position: relative;
  z-index: var(--z-content);
  height: 100vh;         /* fallback for older browsers */
  height: 100dvh;        /* survives the mobile address bar hiding/showing */
  min-height: 520px;
  overflow: hidden;
  background: var(--cream);
}

.hero__media{ position: absolute; inset: 0; }

.hero__media img,
.hero__media video{
  width: 100%;
  height: 100%;
  object-fit: cover;       /* fills the frame edge to edge at every screen size */
  object-position: center; /* keeps the middle of the picture in view when cropped */
}

/* THE HERO NEVER MOVES. There was a slow 26s drift here that scaled the
   photo from 1.04 to 1.13 and eased it upwards, so that the picture was
   always creeping when the doors came off it. It is gone: the hero is in its
   final position and at its final size before the door is ever tapped, and
   it does not drop, fade, scale or drift at any point. The only thing that
   changes on this screen is how much of it is covered. */

/* The long milky fade that used to sit here is gone — the hibiscus divider
   handles the join now. All that is left is a short, soft footing in the
   last few percent of the hero, so no hard edge shows through the gaps
   between the flowers. It is entirely hidden behind the garland. */
.hero__scrim{
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(180deg,
    rgba(251,247,240,0) 88%,
    rgba(251,247,240,.55) 96%,
    var(--cream) 100%);
}

/* ---- Scroll hint: a hairline with a light travelling down it ---- */
.hero__hint{
  position: absolute;
  left: 50%;
  bottom: calc(20px + var(--safe-b));
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  transform: translateX(-50%);
  opacity: 0;
  transition: opacity 700ms ease;
  pointer-events: none;
}
body.is-entered .hero__hint{ opacity: 1; transition-delay: 500ms; }
body.is-entered .hero__hint.is-hidden{ opacity: 0; transition-delay: 0ms; }

.hero__hinttext{
  font-size: 0.625rem;
  font-weight: 600;
  letter-spacing: .3em;
  text-transform: uppercase;
  color: var(--muted);
}
.hero__hintline{
  position: relative;
  width: 1px;
  height: 42px;
  background: linear-gradient(180deg, var(--brass-line), transparent);
  overflow: hidden;
}
.hero__hintline::after{
  content: "";
  position: absolute;
  left: 0;
  width: 1px;
  height: 12px;
  background: var(--brass);
  animation: hint-travel 2.6s ease-in-out infinite;
}
@keyframes hint-travel{
  0%      { transform: translateY(-14px); opacity: 0; }
  35%     { opacity: 1; }
  100%    { transform: translateY(44px);  opacity: 0; }
}


/* ==========================================================================
   6a. SECTION DIVIDER
   A decorative band straddling the join between two sections. Built from
   CONFIG.dividers — one entry per divider, no HTML to write.
   ========================================================================== */
.divider{
  position: relative;
  z-index: 3;              /* over both the section above and the one below */
  line-height: 0;          /* belt and braces against inline whitespace */
  pointer-events: none;
  /* Both margins are negative and together cancel the band's own height, so
     the divider takes up NO space in the layout at all. The section below
     starts exactly where the section above ends, and the garland simply
     floats across that join. That is what makes a gap impossible rather
     than merely unlikely. */
  margin-top: var(--divider-pull, -6%);
  margin-bottom: var(--divider-push, -6%);
}

/* An INLINE divider — one that separates two blocks inside a single section
   rather than straddling the join between two sections. It keeps its own
   height instead of being pulled up over a seam, and carries a full chapter
   gap on each side so the two halves it separates read as distinct. */
.divider--inline{
  margin-top: var(--gap-chapter);
  margin-bottom: var(--gap-chapter);
}

.divider__img{
  display: block;          /* THE fix — an inline image leaves a few pixels of
                              whitespace underneath and you get a visible seam */
  width: 100%;
  /* Capped so a 2.55-wide garland cannot become a wall of flowers on a big
     screen, and centred once it stops growing. */
  max-width: 54rem;
  height: auto;
  margin: 0 auto;
}


/* ==========================================================================
   6b. SECTION 2 — THE INVITATION

   The pacing is the design here. The salutation sits small and high with air
   beneath it, the shloka owns the upper half, and the closing line is given
   the largest single block of space on the page so it lands as a final note.
   ========================================================================== */
.invite{
  position: relative;
  z-index: var(--z-content);
  overflow: hidden;
  background: var(--cream);
  /* The garland divider overlaps this section's top edge by half its own
     height, and that overlap grows with the screen. The top padding tracks
     it (100px + 20vw) so the gap the eye actually sees below the flowers
     stays a real pause — about 100px — at every width, rather than
     collapsing on a phone or opening into a void on a desktop. */
  padding: clamp(170px, 100px + 20vw, 290px) var(--pad-invite) clamp(96px, 19vw, 170px);
}

.invite__bg,
.invite__wash{
  position: absolute;
  inset: 0;
  pointer-events: none;
}

/* Covers the section at any height without stretching.
   The mask lets the mandala texture materialise over the first 90px instead
   of starting on a hard edge. This is NOT the old milky white fade — there is
   no wash over the picture, the pattern itself simply eases in — and it sits
   entirely behind the hibiscus divider. */
.invite__bg{
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  -webkit-mask-image: linear-gradient(180deg, transparent 0, #000 90px);
          mask-image: linear-gradient(180deg, transparent 0, #000 90px);
}

/* The cream wash. Its strength comes from config so it can be raised for a
   busier background — readability wins over the picture, every time. */
.invite__wash{
  background: var(--cream);
  opacity: var(--invite-wash, 0.62);
}

.invite__inner{
  position: relative;
  z-index: 1;
  margin: 0 auto;
  max-width: 40rem;
  text-align: center;
}

/* ---- 1. Salutation ---- */
/* Kept quiet by its size and spacing, not by greying it out: --muted over the
   darkest part of the background measures only 3.2:1, which fails for text
   this small. --ink measures 8.1:1 and still reads as a restrained salutation. */
/* The salutation introduces; the mantra artwork below is the centrepiece.
   It was set at ~14–17px, which was too quiet for the first words on the
   page. Roughly 1.5× larger now — 21px on a 360px phone, 30px on a desktop.

   The ceiling is what keeps the order right: the mantra image is capped at
   32rem and fills the column, so it stays comfortably the larger of the two
   at every width. This introduces, it does not compete. */
.invite__salutation{
  font-size: clamp(1.3rem, 1.1rem + 0.75vw, 1.85rem);
  line-height: 1.7;
  /* NEVER letter-space Devanagari. This carried .04em, which is small but
     is still spacing applied to a script whose letters are meant to sit
     tight under a shared headline stroke — श्री is a conjunct. Nothing on
     this site sets letter-spacing on Devanagari now; do not reintroduce it. */
  letter-spacing: normal;
  color: var(--ink);
}

/* ---- 2. Shloka — the centrepiece of the upper half ---- */
.invite__shloka{
  margin-top: clamp(38px, 9vw, 62px);
}

/* The mantra artwork. Transparent PNG, nothing painted behind it. */
.invite__shloka img{
  display: block;        /* no stray gap underneath an inline image */
  width: 100%;
  /* Wide enough to stay dignified on a big screen, and it always uses the
     full column on a phone so the scripture never shrinks to a whisper. */
  max-width: 32rem;
  height: auto;
  margin: 0 auto;
}

/* The live-text fallback, used until the SVG file exists */
/* Sized by measurement, not by eye: at 360px the longer verse line is 287px
   inside a 312px column, so both lines stay unbroken at every screen width
   while still reading a clear step larger than the invitation copy. */
.invite__shlokaline{
  font-size: clamp(1.35rem, 1.2rem + 0.85vw, 2.05rem);
  line-height: 1.8;
  color: var(--ink);
  text-wrap: balance;    /* if a client's longer verse must wrap, split it evenly */
}
.invite__shlokaline + .invite__shlokaline{ margin-top: .35em; }

/* ---- 3. The brass hairline ---- */
.invite__rule{
  display: flex;
  justify-content: center;
  margin-top: clamp(44px, 10vw, 72px);
}
.invite__rule span{
  display: block;
  width: clamp(56px, 15vw, 86px);
  height: 1px;
  background: var(--brass);
  transform-origin: center;
}

/* ---- 4. The invitation ----

   ONE FACE PER SCRIPT, AND THE SCOPING IS THE IMPORTANT PART.

   Playfair Display carries the English; Tiro Devanagari Marathi carries the
   Marathi. Playfair has no Devanagari at all, so it is never allowed near
   Marathi text.

   Neither is set on .invite__body and left to be overridden. Each is pinned
   to its own language with :not([lang="mr"]) and [lang="mr"], which means
   there is no state in which a rule can leak across scripts — not while the
   language is mid-swap, and not if one of the two rules is ever deleted.

   Unlike the pair they replaced, both of these are real text faces meant to
   be read in paragraphs, so the sizes are back to ordinary reading sizes
   rather than the oversized settings a display face needed. Size and leading
   still come from CONFIG.displayFonts so they can be tuned by eye. */
.invite__body{
  margin-top: clamp(44px, 10vw, 72px);
  /* Left aligned, not centred: these are paragraphs to be read, and a ragged
     left edge makes every line hunt for its own start. */
  text-align: left;
  /* The cross-fade when the language switches. */
  transition: opacity 260ms ease;
}
.invite__body.is-swapping{ opacity: 0; }

/* ---- ENGLISH — Playfair Display ---- */
.invite__body:not([lang="mr"]){
  font-family: var(--font-script);
  /* Which paragraph gap this language uses. Set here rather than on the
     paragraphs themselves so .invite__para keeps a low specificity and the
     first-paragraph rules further down can still override it. */
  --para-gap: var(--english-gap, 1.55em);
}
.invite__body:not([lang="mr"]) .invite__para,
.invite__body:not([lang="mr"]) .invite__closing{
  /* Playfair has very high stroke contrast — its hairlines thin out and
     disappear if it is set too small on a phone, so the floor here is a
     step above the site body size rather than equal to it. */
  font-size: var(--english-size, clamp(1.16rem, calc(0.98rem + 0.8vw), 1.32rem));
  line-height: var(--english-lh, 1.85);
  letter-spacing: var(--english-ls, 0.01em);
  font-weight: var(--english-weight, 400);
}
/* Hyphenation, English only. Justified text on a 360px screen has very few
   words per line, so without this a single long word (celebrating,
   extraordinary) forces enormous gaps into the line above it. Breaking the
   word is the lesser evil. Needs a language to be known: <html lang="en">
   supplies it, since this block carries no lang attribute in English. */
.invite__body:not([lang="mr"]) .invite__para{
  -webkit-hyphens: auto;
  hyphens: auto;
}

/* ---- MARATHI — Tiro Devanagari Marathi ---- */
.invite__body[lang="mr"]{
  font-family: var(--font-devadisp);
  letter-spacing: normal;
  --para-gap: var(--marathi-gap, 1.85em);
}
.invite__body[lang="mr"] .invite__para,
.invite__body[lang="mr"] .invite__closing{
  font-size: var(--marathi-size, clamp(1.2rem, calc(1.02rem + 0.8vw), 1.38rem));
  /* Kept generous. Devanagari hangs from a headline stroke with vowel signs
     stacked above and conjunct tails dropping below, so tight leading makes
     one line collide with the next. */
  line-height: var(--marathi-lh, 2.3);
  /* NEVER space Devanagari. letter-spacing inserts gaps between the glyphs
     of a cluster and pulls conjuncts and their marks away from the letter
     they belong to. `normal`, never a number. */
  letter-spacing: normal;
  /* Tiro ships in ONE weight. Anything other than 400 here is a fake bold —
     the browser smearing the outline sideways, which in Devanagari thickens
     the headline stroke and closes the conjuncts up into mud. */
  font-weight: var(--marathi-weight, 400);
}
/* Devanagari does not hyphenate the way Latin does, so it is switched off
   here explicitly rather than left to inherit. */
.invite__body[lang="mr"] .invite__para{
  -webkit-hyphens: none;
  hyphens: none;
}

/* ---- The sub-heading ----
   English only. In Marathi it is not rendered at all — the Marathi wording
   opens differently and has no equivalent line — so this never needs a
   Devanagari counterpart. */
.invite__headline{
  font-family: var(--font-script);
  /* Only a shade larger than the paragraphs. It was 1.3x and read as a
     headline competing with the invitation underneath it; the weight and the
     centring carry it now, so the size does not have to. Still derived from
     the paragraph size, so the relationship holds whatever config is tuned to. */
  font-size: calc(var(--english-size, 1.16rem) * 1.05);
  /* Real 700, not a faked one — Playfair Display is loaded at 400 and 700.
     A high-contrast serif faked bold loses its hairlines entirely. */
  font-weight: 700;
  line-height: 1.5;
  letter-spacing: 0.015em;
  color: var(--ink);
  /* Centred, against the justified paragraphs below — it reads as a spoken
     line addressed to the guest rather than the first line of the letter. */
  text-align: center;
  /* Evens the lines out. Without it the wrap left a single stranded word
     ("us.") centred on a line of its own, which looks like a mistake rather
     than a line break. */
  text-wrap: balance;
  /* Clear space beneath, so it reads as an opening line over the paragraphs
     rather than as a first paragraph of its own. */
  margin-bottom: clamp(30px, 7vw, 46px);
}

.invite__para{
  /* Set per language just above, in em so it scales with the text. */
  margin-top: var(--para-gap, clamp(26px, 6vw, 38px));
  color: var(--ink);
  /* Every paragraph starts indented, the first one included — the way a
     handwritten letter is set. In em, so it stays proportional to whichever
     of the two display faces is in use and does not swallow a narrow screen. */
  text-indent: 1.7em;
  /* Justified, so both edges of the column line up like a printed card.
     text-align-last is deliberately NOT set: the last line of each paragraph
     stays left, which is what stops a two-word final line being stretched
     across the whole column. */
  text-align: justify;
  /* Lets the browser look ahead at the whole paragraph when choosing breaks
     instead of filling line by line, which evens out the word gaps. */
  text-wrap: pretty;
}
/* The first paragraph sits a little further from the sub-heading than the
   others sit from each other, so the opening reads as one thought. */
.invite__para:first-of-type{ margin-top: calc(var(--para-gap, 34px) * 1.15); }

/* IN MARATHI THERE IS NO SUB-HEADING, so the first paragraph becomes the very
   first thing in the block. Without this it would keep the top margin meant to
   separate it from a heading that is not there, leaving a gap exactly where
   the English sub-heading used to be — which is the one thing hiding it was
   supposed to avoid. */
.invite__body > .invite__para:first-child{ margin-top: 0; }

/* The closing line, given the most air on the page.

   NOT ITALIC ANY MORE. It was set in Cormorant Garamond's italic, which is a
   true italic — a separately drawn alphabet, not a slanted roman — so beside
   the roman paragraphs it read as a second typeface even though the family
   never changed. The emphasis is now carried by size and weight alone, which
   is what the rest of this section already does.

   Set in --ink, not --brass: brass on cream measures 2.8:1, under the 3:1
   that large text needs. The brass stays where it reads well — the rule. */
.invite__closing{
  margin-top: clamp(56px, 14vw, 104px);
  font-size: clamp(1.35rem, 1.15rem + 1.35vw, 1.9rem);
  font-style: normal;
  font-weight: 500;
  line-height: 1.6;
  color: var(--ink);
  text-wrap: balance;
}


/* ==========================================================================
   6d. SECTION 3 — THE SCRATCH CARD

   One card sitting in open space. The card is built to feel like a physical
   object resting on the page: a fine brass edge, softly rounded corners, and
   a two-part shadow — a tight contact shadow plus a wide soft one.
   ========================================================================== */
.scratch{
  position: relative;
  z-index: var(--z-content);
  overflow: hidden;          /* keeps the confetti inside this section */
  background: var(--cream);
  /* The top space is what the confetti burst flies up into, so it is
     deliberately deep enough to hold the arc rather than clip it.
     There is NO bottom padding: the trail has to reach the section's
     bottom edge so the walk continues into section 4 unbroken. */
  padding: clamp(140px, 26vw, 200px) var(--pad-trail) 0;
}

.scratch__bg,
.scratch__wash{
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.scratch__bg{
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}

.scratch__wash{
  background: var(--cream);
  opacity: var(--scratch-wash, 0.58);
}

.scratch__inner{
  position: relative;
  z-index: 1;
  display: flex;
  justify-content: center;
}

/* ---- The card ---- */
.scratch__card{
  position: relative;
  width: 100%;
  max-width: 30rem;
  /* A starting shape only. JS replaces this with the reveal image's own
     proportions as soon as it loads, so the card never has spare space
     the picture cannot fill. */
  aspect-ratio: 3 / 2;
  border-radius: 10px;
  overflow: hidden;
  background: var(--ivory);
  box-shadow:
    0 1px 2px rgba(58, 50, 43, .14),        /* tight contact shadow */
    0 18px 38px -16px rgba(58, 50, 43, .34); /* wide soft shadow */
}
/* A fine brass edge drawn on top, so the corners stay crisp. */
.scratch__card::after{
  content: "";
  position: absolute;
  inset: 0;
  z-index: 4;
  border: 1px solid var(--brass-line);
  border-radius: inherit;
  pointer-events: none;
}
/* Older iOS has no aspect-ratio — fall back to the padding trick. */
@supports not (aspect-ratio: 3 / 2){
  .scratch__card{ height: 0; padding-bottom: 66.67%; }
}

/* ---- Bottom layer: what is underneath ----
   NO padding here. Padding on this box was what left a rim of empty space
   showing around all four sides of the reveal picture. Only the live-text
   stand-in needs insetting, and it gets that from its own modifier below. */
.scratch__reveal{
  position: absolute;
  inset: 0;
  z-index: 1;
}
/* The picture fills the card completely: same box, same edges, no letterbox. */
.scratch__reveal img{
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* Only used when no reveal image has been supplied. */
.scratch__reveal--text{
  display: grid;
  place-items: center;
  padding: 8%;
  text-align: center;
  background: radial-gradient(ellipse at 50% 42%, var(--ivory), #F4E9D6 78%);
}
/* The live-text stand-in, used until the reveal image is supplied.
   This is the calligraphy face's one intended job. */
.scratch__revealtext{
  font-size: clamp(1.05rem, 0.72rem + 1.5vw, 1.7rem);
  line-height: 1.75;
  color: var(--sindoor);
  text-wrap: balance;
}

/* ---- Top layer: the foil the guest scratches ---- */
.scratch__canvas{
  position: absolute;
  inset: 0;
  z-index: 2;
  width: 100%;
  height: 100%;
  /* THE mobile fix: without this, dragging a finger scrolls the page
     instead of scratching, and the whole feature is unusable. */
  touch-action: none;
  cursor: crosshair;
  transition: opacity 500ms ease;
}

/* ---- LOCKED UNTIL THE GUEST HAS ACTUALLY ARRIVED ----

   The card is the first thing to appear when you scroll into section 3, and
   it arrives under the finger that is doing the scrolling. Before this, that
   finger landed on the canvas, the page stopped moving, and the card started
   getting scratched before the guest had even seen what it was.

   pointer-events: none is the whole fix. The touch passes straight through
   to the page underneath, so a drag scrolls exactly as it would over an
   ordinary picture — and touch-action: none above stops applying too, since
   that only governs an element the touch actually lands on.

   NOTHING VISUAL CHANGES when it unlocks. No fade, no highlight, no cue.
   The guest should never know there was a locked state; the card should
   simply work the moment they think to try it. */
.scratch__canvas.is-locked{ pointer-events: none; }
.scratch__canvas.is-clearing{ opacity: 0; }

/* ---- The prompt, echoing the door entry screen ---- */
.scratch__prompt{
  position: absolute;
  inset: 0;
  z-index: 3;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  pointer-events: none;              /* never swallows a scratch */
  animation: prompt-pulse 3.4s ease-in-out infinite;   /* the same pulse as the door */
  transition: opacity 400ms ease;
}
/* The pulse animates opacity, so it has to be switched off before the
   fade-out can take effect — a running animation outranks a transition. */
.scratch__prompt.is-hidden{
  animation: none;
  opacity: 0;
}

/* The same small flame as the door prompt, same two timings. */
.scratch__diya{
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--door-glow);
  box-shadow: 0 0 10px 3px rgba(238,202,146,.6);
  animation: diya-flicker 2.3s ease-in-out infinite, diya-drift 3.7s ease-in-out infinite;
}

/* THE ONE INSTRUCTION ON THE PAGE. If this cannot be read at a glance the
   whole card is missed, so it is sized and armoured accordingly.

   It was 13px at weight 500, which disappeared into a busy cover image.
   It is now ~20px at 360px rising to ~27px, bold, and it survives ANY cover
   image because it carries its own background rather than relying on one:

     1. a soft dark halo painted behind the words (the ::before), which is
        what lifts them off a light cover;
     2. a tight black shadow plus a wide soft one, which is what holds the
        letterforms together over a dark or busy cover.

   Belt and braces on purpose — the cover image is a client's to change, so
   this cannot depend on knowing what is underneath it. */
.scratch__prompttext{
  position: relative;
  font-family: var(--font-body);
  font-size: clamp(1.25rem, 1.05rem + 0.9vw, 1.7rem);
  font-weight: 700;
  letter-spacing: .16em;
  text-indent: .16em;
  line-height: 1.3;
  text-transform: uppercase;
  color: var(--ivory);
  text-align: center;
  padding: 10px 22px;
  text-shadow:
    0 1px 2px rgba(0,0,0,.95),
    0 2px 10px rgba(0,0,0,.85),
    0 0 26px rgba(0,0,0,.7);
}

/* The halo. An ellipse of soft darkness sitting behind the words, blurred
   so it has no visible edge — it reads as shade, not as a label. */
.scratch__prompttext::before{
  content: "";
  position: absolute;
  inset: -18% -8%;
  z-index: -1;
  border-radius: 50%;
  background: radial-gradient(ellipse at center,
    rgba(0,0,0,.72) 0%,
    rgba(0,0,0,.52) 45%,
    rgba(0,0,0,.18) 72%,
    transparent 100%);
  filter: blur(9px);
}

/* ==========================================================================
   THE FOOTPRINT TRAIL
   Mushak wanders down the page. Every position, angle and text placement is
   worked out in js/main.js from the curve — nothing here is hard-coded to a
   particular screen width.
   ========================================================================== */
.trail{
  position: relative;
  z-index: 1;
  margin: clamp(38px, 9vw, 70px) auto 0;
  width: 100%;
  /* Height is worked out by JS from how many events sit in this stretch. */
}
/* In section 4 the walk carries straight on from the section above, so it
   must start flush at the top edge with no gap. */
/* .trail--finale is positioned in section 6e, against the artwork. */

.trail__prints,
.trail__events{
  position: absolute;
  inset: 0;
}
.trail__prints{ pointer-events: none; }

/* One footprint. JS sets --x, --y, --deg and --size. */
.trail__print{
  position: absolute;
  left: var(--x);
  top: var(--y);
  width: var(--size);
  height: var(--size);
  margin-left: calc(var(--size) / -2);
  margin-top: calc(var(--size) / -2);
  /* The rotation is what makes it read as walking rather than as a
     row of stickers. It is set from the direction of travel along the curve. */
  transform: rotate(var(--deg)) scale(0.82);
  opacity: 0;
  transition: opacity 520ms ease, transform 520ms var(--ease-soft);
  will-change: opacity, transform;
}
.trail__print.is-walked{
  opacity: var(--ink-fade, 0.88);
  transform: rotate(var(--deg)) scale(1);
}
.trail__print img{
  width: 100%;
  height: 100%;
  display: block;
  object-fit: contain;
}

/* ---- An event resting in the pocket the curve opens up ---- */
.trail__event{
  position: absolute;
  top: var(--y);
  /* JS sets left/right and width to whichever side of the curve is free. */
  opacity: 0;
  transform: translate3d(0, 16px, 0);
  transition: opacity 760ms var(--ease-soft), transform 760ms var(--ease-soft);
}
.trail__event.is-revealed{
  opacity: 1;
  transform: none;
}

.trail__title{
  font-family: var(--font-display);
  font-size: clamp(1.18rem, 1.02rem + 0.75vw, 1.6rem);   /* never below ~19px */
  font-weight: 600;
  line-height: 1.25;
  color: var(--ink);
  margin-bottom: 6px;
}
/* A short brass tick under the title, tying the block back to the path. */
.trail__title::after{
  content: "";
  display: block;
  width: 26px;
  height: 1px;
  margin-top: 8px;
  background: var(--brass);
}
.trail__meta{
  font-family: var(--font-utility);
  font-size: clamp(0.875rem, 0.83rem + 0.2vw, 0.98rem);  /* never below 14px */
  line-height: 1.55;
  color: var(--muted);
}

/* ---- The same events in Marathi ----
   Neither Cormorant Garamond nor Lora carries Devanagari, so both the title
   and the details hand over to Tiro Devanagari Marathi, the face the
   invitation paragraphs already use. गणेश स्थापना and गणेश विसर्जन both carry
   conjuncts, so this face has to render them solidly at this size.

   WEIGHT IS THE INTERESTING PART. Tiro Devanagari Marathi ships in one weight
   only, so asking for 600 here would get a synthetic bold — the browser
   smearing the outline sideways — which in Devanagari thickens the headline
   stroke and closes up the counters until the conjuncts turn to mud. It is
   noticeably worse than no bold at all.

   So the title keeps weight 400 and takes its emphasis from size and colour
   instead: a step larger than the English title, full --ink against the
   quieter details beneath it. That is the same hierarchy carried by
   different means.

   Line height is opened up on both, because Devanagari hangs from a headline
   stroke and stacks vowel signs above and below it. */
.trail__event[lang="mr"] .trail__title{
  font-family: var(--font-devadisp);
  font-weight: var(--marathi-weight, 400);
  /* The SAME point size as the English title, not larger. Devanagari has no
     case and a tall x-height, so it already reads bigger than Latin at equal
     points — setting it up a step made the Visarjan block 129px tall, which
     is more than the pocket beside the trail can hold, and it landed on a
     footprint. The step down from the details below carries the emphasis. */
  font-size: clamp(1.18rem, 1.02rem + 0.75vw, 1.6rem);
  line-height: 1.55;
  letter-spacing: normal;
  color: var(--ink);
}
.trail__event[lang="mr"] .trail__meta{
  font-family: var(--font-devadisp);
  font-weight: var(--marathi-weight, 400);
  font-size: clamp(0.875rem, 0.83rem + 0.2vw, 0.98rem);
  /* Still looser than the English 1.55, which Devanagari needs for its vowel
     signs, but every extra tenth here makes the block taller, and a taller
     block reaches further along the curve and is pushed nearer the
     footprints. 1.62 is the most this pocket will take. */
  line-height: 1.72;
  letter-spacing: normal;
}
.trail__meta span{ display: block; }

/* Text sits on the side the curve has left free. */
.trail__event--right{ text-align: left; }
.trail__event--left { text-align: right; }
.trail__event--left .trail__title::after{ margin-left: auto; }


/* ---- Confetti ---- */
.scratch__confetti{
  position: absolute;
  inset: 0;
  z-index: 2;                /* above the card, below the nav and music button */
  width: 100%;
  height: 100%;
  pointer-events: none;      /* must never block a tap */
  opacity: 0;
  transition: opacity 600ms ease;
}
.scratch__confetti.is-live{ opacity: 1; }


/* ==========================================================================
   6e. SECTION 4 — LOCATION
   The closing section. The trail arrives, Mushak is waiting, and the last
   thing the guest sees is where to come.
   ========================================================================== */
/* THE SECTION IS THE SHAPE OF ITS PICTURE.

   This is the whole fix for the zoom. The background is 1080 x 2400, and the
   section is given that same ratio, so at any width the box is exactly as
   tall as the image wants to be. The image then fills it at 1:1 — no crop,
   no upscale, the full mandala at its intended size.

   What was wrong before: the section's height came from its content, which
   made it far taller than 0.45 of its width. `cover` then had to scale the
   image until it was tall enough, which at 360px meant drawing it 723px wide
   inside a 360px box — the picture magnified 2.01x with half its width
   thrown away. That magnification was the "zoom".

   `max-width: 1080px` is the second half of the rule: the image is 1080px
   wide, so past that the section stops growing and the page's cream fills
   the sides. The picture is never enlarged beyond its own pixels. */
.finale{
  position: relative;
  z-index: var(--z-content);
  overflow: hidden;
  background: var(--cream);
  width: 100%;
  max-width: 1080px;
  margin: 0 auto;
  aspect-ratio: 1080 / 2400;
  /* No padding at all — every child is placed as a share of the height. */
  padding: 0;
}
/* Older iOS: the same shape via the padding trick. 2400/1080 = 222.222%. */
@supports not (aspect-ratio: 1 / 1){
  .finale{ height: 0; padding-bottom: 222.222%; }
}

/* object-fit: contain, NOT cover. With the box already at the image's own
   ratio the two behave identically and neither crops anything — but contain
   cannot crop by construction. If the picture is ever swapped for one of a
   different shape it will letterbox visibly rather than silently magnify,
   and a visible letterbox is the failure we want, because it is the one you
   would notice and tell me about. */
.finale__bg{
  position: absolute;
  inset: 0;
  pointer-events: none;
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: center;
}

/* ---- The banded wash ----
   ONE gradient element, six stops, every one of them set from config. Full
   strength at the top, nothing at all across the mushak so that character
   keeps its true colour, then full strength again for the address below.

   It has to be a single gradient rather than two stacked overlays: two
   overlays meeting would show a hard line exactly where they abut, and the
   whole point of the middle band is that you cannot see where it begins. */
.finale__wash{
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image: var(--finale-wash-image, none);
}

/* The event date and time are set in --muted, which read fine through a flat
   wash. This section's wash now clears away entirely across the middle, so
   where it does the date would sit on bare artwork at 3.79:1. #6B5F52 is the
   lightest tone that clears 4.5:1 against every pixel behind it. Scoped here:
   section 3 uses the same class over a flat wash and is left alone. */
.finale .trail__meta{ color: #6B5F52; }

/* ---- Placing the two blocks against the artwork ----

   Both are absolutely positioned by a PERCENTAGE OF THE SECTION HEIGHT, and
   that is the only way this works. The section is a fixed shape, so a
   percentage down the box is the same percentage down the picture at every
   screen size — the address lands under the mushak on a 360px phone and on a
   laptop alike. Laying them out in normal flow would let the content decide
   the height, which is exactly what made the section too tall and forced the
   background to magnify.

   The trail stops at 26%, which is where it has to stop: the mushak's crown
   begins at 28.5%, and at 40% the last few footprints were walking straight
   over his head. The walk now arrives at him and halts, which reads better
   anyway — the footprints lead to Mushak rather than trampling him.

   The address begins at 66%, which is where the wash returns to full
   strength. It was 62%, which left the heading sitting close under
   Mushak's feet at 53.8%. The extra 4 points give him room to breathe —
   the gap went from 8.2% to 12.2% of the section, 66px to 98px at 360px —
   and it is paid for out of the empty space below the address, which had
   12.4% spare. The wash stops in config moved with it, so the wash still
   comes back exactly at the heading rather than before it. */
.trail--finale{
  position: absolute;
  top: 0;
  left: var(--pad-trail);
  right: var(--pad-trail);
  width: auto;
  height: 26%;
  margin: 0;
}

.finale__inner{
  position: absolute;
  top: 66%;
  left: var(--pad-trail);
  right: var(--pad-trail);
  z-index: 1;
  margin: 0 auto;
  max-width: 34rem;
  text-align: center;
}

/* ---- The address ---- */
.finale__heading{
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 1.25rem + 1.3vw, 2.1rem);
  font-weight: 600;
  line-height: 1.25;
  color: var(--ink);
}
/* The same short brass tick used on the event titles, so the closing
   block reads as part of the same family. */
.finale__heading::after{
  content: "";
  display: block;
  width: 30px;
  height: 1px;
  margin: 14px auto 0;
  background: var(--brass);
}

.finale__address{
  margin-top: clamp(22px, 5vw, 32px);
  font-family: var(--font-display);
  font-size: clamp(1.1rem, 1rem + 0.7vw, 1.35rem);
  line-height: 1.85;                /* relaxed — practical, but still an invitation */
  color: var(--ink);
}
.finale__address span{
  display: block;
  text-wrap: balance;               /* never break a line awkwardly mid-phrase */
}

/* ---- The embedded map ----
   A square, framed like the scratch card: fine brass edge, soft corners. */
.mapframe{
  position: relative;
  width: 100%;
  max-width: 26rem;               /* never enormous on a desktop */
  aspect-ratio: 1 / 1;            /* square */
  margin: clamp(30px, 7vw, 46px) auto 0;
  border: 1px solid var(--brass-line);
  border-radius: 12px;
  overflow: hidden;
  background: var(--sand);
  box-shadow:
    0 1px 2px rgba(58, 50, 43, .12),
    0 14px 30px -14px rgba(58, 50, 43, .28);
}
@supports not (aspect-ratio: 1 / 1){
  .mapframe{ height: 0; padding-bottom: 100%; }
}

/* THE SECOND "OPEN IN MAPS" LABEL, AND WHY IT IS GONE.

   Google's own embed draws a control in its TOP-LEFT corner. It lives inside
   a cross-origin iframe, so it cannot be hidden, restyled or removed from
   out here — no CSS of ours reaches into it. Ours sits top-right, and the
   two together read as the same thing said twice.

   So the iframe is grown 56px taller than its frame and pulled up by the
   same 56px. Google's control is carried above the visible area and clipped
   away by the frame's `overflow: hidden`, leaving our own badge as the only
   label on the map.

   The offset is upward on purpose. Google's attribution sits along the
   BOTTOM of the embed and must stay visible — their terms require it — and
   because the iframe's bottom edge still lines up with the frame's, it does.
   The map's centre shifts down by 28px, which on a 300–420px square is a
   few percent and leaves the pin comfortably in view. */
.mapframe__map{
  position: absolute;
  left: 0;
  right: 0;
  top: -56px;
  display: block;
  width: 100%;
  height: calc(100% + 56px);
  border: 0;
}

/* The transparent sheet that catches the tap. An iframe handles its own
   clicks, so without this a tap would just pan the little embedded map
   instead of opening Google Maps. */
.mapframe__hit{
  position: absolute;
  inset: 0;
  z-index: 2;
  display: block;
  background: transparent;
  cursor: pointer;
}
.mapframe__hit:focus-visible{
  outline: 2px solid var(--sindoor);
  outline-offset: -4px;
}

/* A small badge so it reads as tappable, like a real map interface.
   Top-right on purpose: Google's own embed puts a control top-left and its
   attribution along the bottom, so this is the one corner that stays clear. */
.mapframe__badge{
  position: absolute;
  right: 10px;
  top: 10px;
  z-index: 3;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 7px 12px;
  border-radius: 999px;
  background: rgba(255, 253, 248, .93);
  box-shadow: 0 1px 6px rgba(58, 50, 43, .22);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: .1em;
  text-transform: uppercase;
  pointer-events: none;          /* the sheet underneath takes the tap */
}

/* A pin drawn in CSS — a circle with a tail — rather than a heavy icon. */
.mapframe__pin{
  position: relative;
  width: 9px;
  height: 9px;
  flex: none;
  border: 1.5px solid var(--sindoor);
  border-radius: 50% 50% 50% 0;
  transform: rotate(-45deg);
}


/* ==========================================================================
   OUR MOMENTS — the photo reel
   ========================================================================== */
/* The map and the photographs are two separate moments, not one block with a
   gap in it, so this is a full step up the ladder from the spacing used
   inside each of them. */
.moments{
  position: relative;
  z-index: 1;
  margin: var(--gap-chapter) auto 0;
  max-width: 34rem;
  text-align: center;
  scroll-margin-top: var(--nav-h);
}

/* ---- Section 5's opening half: the map and the photographs ----
   These moved out of section 4, which is now shaped to its background
   picture and has no room for them. They keep the same rhythm the rest of
   the site uses so the join does not announce itself. */
.about__lead{
  position: relative;
  z-index: 1;
  margin: 0 auto;
  max-width: 34rem;
  text-align: center;
}
/* The map is the first thing in the section, so it needs air above it in
   its own right rather than inheriting the section's top padding. */
.about__lead .mapframe{ margin-top: 0; }

/* ---- The sign-off ----
   The last thing the family says, between the photographs and the closing
   garland. It is the emotional end of the invitation, so it gets space on
   both sides and nothing else: no rule, no ornament, no flourish. The
   quietness IS the treatment — anything added here would make it a graphic
   instead of a parting word. */
.signoff{
  margin: var(--gap-chapter) auto 0;
  max-width: 30rem;
  text-align: center;
}

.signoff__message{
  font-family: var(--font-script);
  font-size: var(--english-size, clamp(1.16rem, calc(0.98rem + 0.8vw), 1.32rem));
  font-weight: 400;
  line-height: var(--english-lh, 1.75);
  color: var(--ink);
  text-wrap: balance;
}

/* A step larger than the message and in brass — the signature at the foot
   of a letter, set apart from the sentence above it. */
.signoff__name{
  margin-top: clamp(14px, 3.5vw, 20px);
  font-family: var(--font-script);
  /* The same size as the message above it. It was smaller at one point,
     which inverted the hierarchy — the signature ended up quieter than the
     sentence. The brass is what sets it apart. */
  font-size: var(--english-size, clamp(1.16rem, calc(0.98rem + 0.8vw), 1.32rem));
  font-weight: 400;   /* only weight 400 is loaded, so keep it at 400 */
  line-height: 1.3;
  color: var(--brass);
}

/* ---- The same words in Marathi ----
   The English face carries no Devanagari at all, so this hands over to Tiro
   Devanagari Marathi, the face the rest of the Marathi uses. Line height is opened right up: this is
   a long single sentence, and Devanagari stacks vowel signs above and below
   the headline stroke that collide at a Latin leading. Letter-spacing stays
   at zero — spacing Devanagari breaks its conjuncts apart. */
.signoff[lang="mr"] .signoff__message{
  font-family: var(--font-devadisp);
  font-size: clamp(1.05rem, 0.95rem + 0.55vw, 1.3rem);
  line-height: 2.25;
  letter-spacing: normal;
  font-weight: var(--marathi-weight, 400);
}
.signoff[lang="mr"] .signoff__name{
  font-family: var(--font-devadisp);
  /* Tiro Devanagari Marathi ships in one weight only, so asking for 600 here
     would get a synthetic bold — the browser smearing the outline sideways,
     which closes up the counters and muddies the conjuncts. The brass and
     the size step carry the emphasis instead. */
  font-weight: var(--marathi-weight, 400);
  font-size: clamp(1.2rem, 1.05rem + 0.7vw, 1.5rem);
  line-height: 1.9;
  letter-spacing: normal;
}

.reel{
  position: relative;
  margin-top: clamp(26px, 6vw, 40px);
  overflow: hidden;
  border-radius: 12px;
  touch-action: pan-y;           /* sideways drags belong to the reel */
  cursor: grab;
}
.reel.is-dragging{ cursor: grabbing; }

.reel__track{
  display: flex;
  will-change: transform;
}

/* One consistent frame, whatever shape the photo is. */
.reel__slide{
  position: relative;
  flex: 0 0 100%;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  border-radius: 12px;
  background: var(--sand);
  box-shadow:
    0 1px 2px rgba(58, 50, 43, .12),
    0 14px 30px -14px rgba(58, 50, 43, .28);
}
@supports not (aspect-ratio: 4 / 3){
  .reel__slide{ height: 0; padding-bottom: 75%; }
}
.reel__slide img{
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  /* Portrait and landscape both fill the frame without distorting. */
  object-fit: cover;
  object-position: center;
  -webkit-user-drag: none;
  user-select: none;
}

/* ---- Position dots ---- */
.reel__dots{
  display: flex;
  justify-content: center;
  gap: 9px;
  margin-top: 18px;
}
.reel__dot{
  width: 44px;                    /* a real tap target… */
  height: 24px;
  display: grid;
  place-items: center;
  background: none;
  border: 0;
  padding: 0;
}
.reel__dot::before{
  content: "";                    /* …around a deliberately small mark */
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--brass);
  opacity: .3;
  transition: opacity 320ms ease, transform 320ms ease;
}
.reel__dot[aria-selected="true"]::before{
  opacity: 1;
  transform: scale(1.35);
}


/* ==========================================================================
   6f. SECTION 5 — EVERINVITES  (the colophon)

   The maker's mark at the foot of the invitation, not a website footer.

   The whole section is written in a DELIBERATELY QUIETER REGISTER than the
   four above it. Those sections speak for the family: large Cormorant, warm,
   centred, unhurried. This one speaks for the studio, so it uses small
   letter-spaced sans labels, a tighter type scale and a two-column spread.
   The change of voice is the point — it should read as a signature on the
   back of the object rather than one more section of the invitation.

   The one flourish is the tagline mark, which quotes the logo's own
   construction: hairline, word, dot, word, dot, word, hairline. The two
   hairlines draw themselves outward from the words on reveal — the same
   trick the brass rule in section 2 uses, so it belongs to this site.
   ========================================================================== */
.about{
  position: relative;
  z-index: var(--z-content);
  overflow: hidden;
  background: var(--cream);

  /* The quiet tone for small secondary text in this section, and the reason
     it is not --muted.

     This background carries mandala line-work that is much darker than it
     looks: washed at 0.55 its darkest pixel composites to #D5B59B, and
     --muted (#7A6E60) measures only 2.58:1 against that — a clear fail.
     Raising the wash does not rescue it; --muted is only 4.66:1 over plain
     cream to begin with, so any texture at all puts it under.

     #4E443A measures 4.93:1 against that same worst-case pixel while still
     reading a step lighter than --ink, so the hierarchy survives and the
     text stays legible over the darkest thing the background can put
     behind it. Same reasoning that put the closing line in section 2 in
     --ink rather than --brass. */
  --about-quiet: #4E443A;
  /* Half a screen, as a floor. It closes the site; it must not compete
     with it, so nothing here is allowed to fill a whole viewport. */
  /* No longer a half-height closer — it now opens with the map and the photo
     gallery, so it is as tall as its content needs. */
  min-height: 50vh;
  min-height: 50svh;
  /* The garland no longer straddles the join above this section; it sits
     inside, between the gallery and the EverInvites block. So the top
     padding is an ordinary opening pause rather than clearance for flowers. */
  padding: var(--gap-moment) var(--pad-x) clamp(38px, 8vw, 56px);
}

.about__bg,
.about__wash{
  position: absolute;
  inset: 0;
  pointer-events: none;
}
.about__bg{
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}
.about__wash{
  background: var(--cream);
  opacity: var(--about-wash, 0.55);
}

/* ---- The signature block ----
   One column, left aligned, capped narrow. It was two columns; the redesign
   reads straight down instead, which suits a closing mark better than a
   spread and needs no re-ordering between phone and desktop at all. */
.about__inner{
  position: relative;
  z-index: 1;
  margin: 0 auto;
  max-width: 34rem;
  text-align: left;
}

/* ---- 1. Logo and name, side by side ---- */
.sig__id{
  display: flex;
  align-items: center;
  gap: 14px;
}
.sig__logo{
  width: auto;
  height: clamp(46px, 11vw, 58px);
  /* Stated up front so the row does not jump when the picture lands. */
  aspect-ratio: 166 / 209;
  flex: none;
}
.sig__name{
  font-family: var(--font-display);
  font-size: clamp(1.35rem, 1.15rem + 0.9vw, 1.75rem);
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: .12em;
  text-indent: .12em;
  text-transform: uppercase;
  color: var(--ink);
}

/* ---- 2. The tagline ----
   A brand mark, not a sentence. The tracking is the design, so the words are
   held far apart and the dots between them are given equal air on each side
   rather than sitting tight against the word before them the way punctuation
   would. Muted and small: it identifies, it does not announce. */
.sig__tagline{
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.9em;                 /* the even space either side of each dot */
  margin-top: 14px;
  font-family: var(--font-body);
  font-size: clamp(0.625rem, 0.58rem + 0.2vw, 0.72rem);
  font-weight: 600;
  letter-spacing: .3em;
  text-transform: uppercase;
  color: var(--about-quiet);
}
.sig__word{ white-space: nowrap; text-indent: .3em; }
/* Drawn rather than typed: a real middle dot carries its own side bearings,
   which would make the gaps uneven against the letter-spaced words. */
.sig__dot{
  width: 3px;
  height: 3px;
  flex: none;
  border-radius: 50%;
  background: currentColor;
  opacity: .75;
}

/* ---- 3. The description ---- */
.sig__copy{
  margin-top: 18px;
  font-family: var(--font-display);
}
/* Two LINES of one sentence, not two paragraphs, so they are set inside a
   single <p> with a break between them and no gap. `text-wrap: pretty` keeps
   a line that is too long for a narrow phone from dropping a single orphaned
   word onto the next row. */
.sig__copy p{
  font-size: clamp(1.0625rem, 1rem + 0.35vw, 1.1875rem);
  line-height: 1.7;
  color: var(--ink);
  text-wrap: pretty;
}
.sig__copy p + p{ margin-top: .7em; }

/* ---- The two quiet text links ---- */
.sig__links{
  display: flex;
  flex-wrap: wrap;
  gap: 22px;
  margin-top: 18px;
}
.sig__links[hidden]{ display: none; }
.sig__link{
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  font-family: var(--font-body);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: .12em;
  text-indent: .12em;
  text-transform: uppercase;
  color: var(--ink);
  border-bottom: 1px solid var(--brass);
  transition: color 240ms ease, border-color 240ms ease;
}
.sig__link[hidden]{ display: none; }
.sig__link:hover{ color: var(--sindoor); border-color: var(--sindoor); }

/* ---- 4 & 5. The two labelled icon groups ---- */
.sig__group{ margin-top: clamp(30px, 7vw, 40px); }
.sig__group[hidden]{ display: none; }

/* The same treatment as the tagline, so the three small caps lines in this
   block read as one family of labels rather than three separate ideas. */
.sig__label{
  font-family: var(--font-body);
  font-size: clamp(0.625rem, 0.58rem + 0.2vw, 0.72rem);
  font-weight: 600;
  line-height: 1.4;
  letter-spacing: .3em;
  text-indent: .3em;
  text-transform: uppercase;
  color: var(--about-quiet);
}

/* ---- The icon circles ----
   A dashed edge rather than a solid one. It is the single decorative liberty
   in the block, and it earns its place: a dashed ring reads as something
   drawn by hand on paper, which is the register the whole invitation is in,
   where a solid ring would read as a UI control.

   The platform colours inside them are the only saturated colour down here.
   Everything around is cream, brass and ink, and that contrast is the point. */
.orbs{
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  margin: 16px 0 0;
  padding: 0;
  list-style: none;
}

.orb{
  display: grid;
  place-items: center;
  width: 46px;
  height: 46px;
  border-radius: 50%;
  background: var(--ivory);
  border: 1px dashed var(--orb-edge, var(--brass));
  color: var(--brand, var(--ink));
  transition:
    transform 260ms var(--ease-soft),
    background-color 260ms ease,
    border-color 260ms ease,
    box-shadow 260ms ease;
}
.orb svg{
  width: 20px;
  height: 20px;
  display: block;
  fill: currentColor;
}
.orb:hover,
.orb:focus-visible{
  transform: translateY(-2px);
  /* The dashes tighten into a solid ring in the platform's own colour — the
     circle finishes itself when you reach for it. */
  border-style: solid;
  border-color: var(--brand, var(--brass));
  background: var(--brand-tint, var(--ivory));
  box-shadow: 0 8px 18px -10px var(--brand-glow, rgba(58, 50, 43, .45));
}
.orb:active{ transform: translateY(0) scale(.93); }

/* ---- 6. The hairline ---- */
.sig__rule{
  height: 1px;
  margin: clamp(34px, 8vw, 46px) 0 0;
  border: 0;
  background: var(--brass-line);
}

/* ---- 7. The closing line ---------------------------------------------- */
.about__copyright{
  margin: clamp(20px, 5vw, 28px) auto 0;
  text-align: center;
  font-family: var(--font-body);
  font-size: 0.75rem;
  letter-spacing: .06em;
  color: var(--about-quiet);
}


/* ==========================================================================
   6c. SCROLL REVEAL
   Each element fades and rises once, as the reader reaches it.
   ========================================================================== */
[data-reveal]{
  opacity: 0;
  transform: translate3d(0, 18px, 0);
  transition: opacity 760ms var(--ease-soft), transform 760ms var(--ease-soft);
  transition-delay: var(--reveal-delay, 0ms);
}
[data-reveal].is-revealed{
  opacity: 1;
  transform: none;
}

/* The hairline is drawn rather than nudged — the one small flourish here. */
.invite__rule[data-reveal]{ transform: none; }
.invite__rule span{
  transform: scaleX(0);
  transition: transform 820ms var(--ease-soft);
  transition-delay: calc(var(--reveal-delay, 0ms) + 120ms);
}
.invite__rule.is-revealed span{ transform: scaleX(1); }

/* (The footer tagline used to have two hairlines that drew themselves outward
    from the words. The redesigned mark is the words and their dots alone, so
    that flourish and its rules are gone.) */


/* ==========================================================================
   7. FALLING PETALS
   One fixed layer that stays put while the page scrolls beneath it.
   ========================================================================== */
.petals{
  position: fixed;
  inset: 0;
  z-index: var(--z-petals);
  overflow: hidden;
  pointer-events: none;   /* NON-NEGOTIABLE: petals must never block a tap */
  opacity: 0;
  transition: opacity 1600ms ease;   /* petals drift in, they don't pop in */
}
.petals.is-on{ opacity: 1; }

/* Outer element = the fall + sideways drift */
.petal{
  position: absolute;
  top: 0;
  left: var(--x);
  width: var(--size);
  height: var(--size);
  will-change: transform;
  animation: petal-fall var(--dur) linear var(--delay) infinite;
}
@keyframes petal-fall{
  from{ transform: translate3d(0, -18vh, 0); }
  to  { transform: translate3d(var(--drift), 112vh, 0); }
}

/* Inner element = the sway and tumble. Real petals catch the air. */
.petal > i{
  display: block;
  width: 100%;
  height: 100%;
  opacity: var(--alpha);
  animation: petal-sway var(--sway-dur) ease-in-out var(--delay) infinite;
}
@keyframes petal-sway{
  0%  { transform: translateX(calc(var(--sway) * -1)) rotate(0deg)   rotateX(0deg); }
  25% { transform: translateX(0)                      rotate(90deg)  rotateX(55deg); }
  50% { transform: translateX(var(--sway))            rotate(180deg) rotateX(0deg); }
  75% { transform: translateX(0)                      rotate(270deg) rotateX(-55deg); }
  100%{ transform: translateX(calc(var(--sway) * -1)) rotate(360deg) rotateX(0deg); }
}

.petal img{
  width: 100%;
  height: 100%;
  display: block;
  object-fit: contain;    /* never squash a petal out of shape */
}


/* ==========================================================================
   8. NAVIGATION BAR
   Thin, frosted, over the hero. A whisper, not a header.

   ADAPTIVE CONTRAST
   Over the hero the bar is dark-tinted with near-white text, so it stays
   readable whatever the hero photo turns out to be. Past the hero it becomes
   light cream glass with --ink text. The two states cross-fade.
   ========================================================================== */
.nav{
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: var(--z-nav);
  height: var(--nav-h);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 0 max(12px, calc(var(--pad-x) - 20px));
  padding-top: env(safe-area-inset-top, 0px);

  color: var(--ivory);                              /* State A: near-white text */
  text-shadow: 0 1px 10px rgba(0,0,0,.5);           /* extra insurance over a busy photo */
  transition: color var(--fade-contrast) ease,
              text-shadow var(--fade-contrast) ease,
              opacity 800ms ease,
              visibility 800ms;

  opacity: 0;
  visibility: hidden;
}
body.is-entered .nav{ opacity: 1; visibility: visible; }

/* The nav's own content sits above both background layers. */
.nav > *{ position: relative; z-index: 1; }

/* State A background — a soft dark scrim while over the hero. */
.nav::before,
.nav::after{
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  transition: opacity var(--fade-contrast) ease;
}
/* Measured, not guessed: the brightest pixels behind this bar are near-white,
   so the scrim has to reach roughly 0.6 opacity for white text to clear the
   4.5:1 contrast bar. Anything lighter looks pretty and reads badly. */
.nav::before{
  background: linear-gradient(180deg, rgba(26,20,15,.72), rgba(26,20,15,.50));
  opacity: 1;
}

/* State B background — light cream glass past the hero.
   Solid-ish by default so browsers without backdrop-filter stay readable. */
.nav::after{
  background: var(--glass-light-solid);
  box-shadow: inset 0 -1px 0 var(--sand);
  opacity: 0;
}

@supports ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))){
  .nav::before{
    -webkit-backdrop-filter: blur(14px) saturate(115%);
            backdrop-filter: blur(14px) saturate(115%);
    background: linear-gradient(180deg, rgba(26,20,15,.66), rgba(26,20,15,.44));
  }
  .nav::after{
    -webkit-backdrop-filter: blur(16px) saturate(120%);
            backdrop-filter: blur(16px) saturate(120%);
    background: var(--glass-light);
  }
}

/* State B — past the hero, or whenever the cream menu is open behind the bar. */
.nav.is-past-hero,
.nav.is-menu-open{ color: var(--ink); text-shadow: none; }
.nav.is-past-hero::before,
.nav.is-menu-open::before{ opacity: 0; }
.nav.is-past-hero::after,
.nav.is-menu-open::after{ opacity: 1; }

.nav__brand{
  display: flex;
  align-items: center;
  gap: 9px;
  min-height: 44px;
  min-width: 0;          /* lets the name shorten instead of shoving the links off screen */
  overflow: hidden;
}
.nav__logo{
  width: 26px;
  height: 26px;
  object-fit: contain;
  flex: none;
}
.nav__logo[hidden]{ display: none; }

.nav__brandname{
  font-family: var(--font-display);
  font-size: 1.05rem;
  font-weight: 600;
  letter-spacing: .02em;
  color: inherit;        /* follows the adaptive nav colour */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* The gap keeps the language switch off the hamburger. Without it the two
   sit edge to edge at 360px and read as one control. */
.nav__right{ display: flex; align-items: center; flex: none; gap: 10px; }

/* (A row of short text links used to live here, appearing over the hero and
    fading out past it. They are gone — the bar now holds the same four things
    in both states, and every one of those destinations is in the menu.) */

/* Hamburger: two hairlines, crossing into an X when the menu is open. */
/* Three bars, because two do not read as a menu.

   `place-content: center` centres the whole stack inside the 44px target, so
   the icon stays optically balanced whatever the bar count. */
.burger{
  position: relative;
  width: 44px;
  height: 44px;
  display: grid;
  place-content: center;
  gap: 5px;                     /* 3 bars + 2 gaps = 14.5px tall */
}
.burger span{
  display: block;
  width: 20px;                  /* equal length */
  height: 1.5px;                /* equal thickness */
  background: currentColor;     /* follows the adaptive nav colour */
  transition: transform 380ms var(--ease-soft), opacity 250ms ease;
}

/* Closing into an X. The travel has to be the distance between one bar's
   centre and the next — 1.5px of bar plus 5px of gap — or the two strokes
   cross off-centre and the X sits crooked. The middle bar has nowhere to go,
   so it simply leaves. */
.burger[aria-expanded="true"] span:nth-child(1){ transform: translateY(6.5px) rotate(45deg); }
.burger[aria-expanded="true"] span:nth-child(2){ opacity: 0; transform: scaleX(0); }
.burger[aria-expanded="true"] span:nth-child(3){ transform: translateY(-6.5px) rotate(-45deg); }


/* ---- The EN / मराठी switch ----
   A single pill split in two, rather than two separate buttons: the two
   languages are one choice, and a shared outline says that where a gap would
   not. It stays visible in both nav states, so it is drawn in currentColor
   and inherits the light-over-hero / dark-over-cream fade for free. */
.langswitch{
  display: inline-flex;
  align-items: center;
  flex: none;
  border: 1px solid currentColor;
  border-radius: 999px;
  /* The outline is the quietest thing in the bar until you look at it. */
  opacity: .72;
  transition: opacity 250ms ease;
}
.langswitch[hidden]{ display: none; }
.langswitch:hover{ opacity: 1; }

.langswitch__btn{
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 30px;
  padding: 6px 11px;
  border-radius: 999px;
  background: none;
  color: inherit;
  font-family: var(--font-body);
  font-size: 0.625rem;
  font-weight: 600;
  letter-spacing: .1em;
  line-height: 1;
  white-space: nowrap;
  opacity: .62;
  transition: opacity 250ms ease, background-color 250ms ease, color 250ms ease;
}
/* The Devanagari half needs its own face — Karla has no Devanagari — and no
   letter-spacing, which breaks the conjuncts. */
.langswitch__btn[lang="mr"]{
  font-family: var(--font-tiro);
  font-size: 0.6875rem;
  letter-spacing: normal;
}

/* The active half is filled rather than merely brighter, so which language
   is on is legible at a glance and not a judgement about opacity. */
.langswitch__btn[aria-pressed="true"]{
  opacity: 1;
  background: currentColor;
}
.langswitch__btn[aria-pressed="true"] span{
  /* The text has to invert against the filled pill. Blend mode rather than a
     fixed colour, because the fill is currentColor and currentColor changes
     between the two nav states. */
  mix-blend-mode: difference;
  color: var(--cream);
}
.langswitch__btn:hover{ opacity: 1; }

/* 44px of thumb without a 44px-tall pill.

   The visible control is 30px, because a 44px slab in a 56px bar would
   dominate it and crowd the logo — the brief asked for compact. So the tap
   target is extended past the ink instead: an invisible sheet, 44px tall,
   centred on the button and spilling out above and below it. The bar stays
   thin and a thumb still has somewhere safe to land. */
.langswitch__btn::after{
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  height: 44px;
  transform: translateY(-50%);
}


/* ==========================================================================
   9. HAMBURGER MENU OVERLAY
   ========================================================================== */
.menu{
  position: fixed;
  inset: 0;
  z-index: var(--z-menu);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 40px;
  padding: var(--nav-h) var(--pad-x) calc(40px + var(--safe-b));
  background: var(--cream);
  color: var(--ink);
  opacity: 0;
  transition: opacity 420ms ease;
}
.menu[hidden]{ display: none; }
.menu.is-open{ opacity: 1; }

.menu__list{
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(4px, 1.6vw, 12px);
}

.menu__list a{
  display: block;
  padding: 6px 10px;
  min-height: 44px;
  font-family: var(--font-display);
  font-size: var(--fs-menu);
  font-weight: 400;
  line-height: 1.3;
  color: var(--ink);
  /* each link starts low and faded, then staggers in */
  opacity: 0;
  transform: translate3d(0, 24px, 0);
  transition: opacity 620ms var(--ease-soft), transform 620ms var(--ease-soft), color 250ms ease;
  transition-delay: var(--stagger, 0ms);
}
.menu.is-open .menu__list a{ opacity: 1; transform: none; }
.menu__list a:hover{ color: var(--sindoor); }

/* ---- The three icons closing the menu ----
   A quiet footer to the navigation, not a second set of destinations. They
   are deliberately a fraction smaller than the same icons in the colophon,
   and they sit under a short hairline so the group reads as an ending rather
   than as more menu. */
.menu__social{
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 22px;
  /* Arrives after the last link, continuing the same stagger. */
  opacity: 0;
  transform: translate3d(0, 16px, 0);
  transition: opacity 620ms var(--ease-soft), transform 620ms var(--ease-soft);
  transition-delay: var(--stagger, 0ms);
}
.menu__social[hidden]{ display: none; }
.menu.is-open .menu__social{ opacity: 1; transform: none; }

.menu__rule{
  display: block;
  width: 36px;
  height: 1px;
  background: var(--brass);
}

/* Same component as the footer's row, one size down. */
.orbs--menu{ margin: 0; gap: 14px; justify-content: center; }
.orbs--menu .orb{ width: 44px; height: 44px; }
.orbs--menu .orb svg{ width: 19px; height: 19px; }

.menu__note{
  font-size: var(--fs-small);
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--muted);
  text-align: center;
  opacity: 0;
  transition: opacity 500ms ease 420ms;
}
.menu.is-open .menu__note{ opacity: 1; }


/* ==========================================================================
   10. FLOATING MUSIC TOGGLE

   WHY THIS ONE IS NOT ADAPTIVE LIKE THE NAV BAR
   The button sits at the very bottom of the screen, and the foot of the hero
   always fades into cream (see .hero__scrim). So unlike the nav bar, this
   button is never actually sitting on top of the photo — it is on cream at
   every scroll position. Giving it a dark state would make it a dark blob on
   a cream background, which is the exact problem we are trying to avoid.
   It therefore uses the light treatment throughout, which stays readable over
   cream sections and over dark ones too (a pale disc reads on both).
   ========================================================================== */
/* A solid maroon disc. Its own colour rather than frosted glass, so it stays
   legible over every background on the site — the hero photograph, cream
   sections, the full-bleed picture and the map alike. */
.music{
  position: fixed;
  right: max(16px, calc(var(--pad-x) - 20px));
  bottom: calc(22px + var(--safe-b));   /* clear of the phone's gesture bar */
  z-index: var(--z-music);
  width: var(--music-size, 60px);
  height: var(--music-size, 60px);
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--music-color, #7B1220);
  color: var(--music-icon, #FFFDF8);
  box-shadow:
    0 2px 6px rgba(40, 12, 16, .26),
    0 10px 26px -8px rgba(40, 12, 16, .45);
  transition: opacity 800ms ease 200ms, visibility 800ms, transform 260ms var(--ease-soft);
  opacity: 0;
  visibility: hidden;
}
body.is-entered .music{ opacity: 1; visibility: visible; }
.music:hover{ transform: scale(1.05); }
.music:active{ transform: scale(0.96); }

/* Both icons live in the same spot and cross-fade, so the swap between
   play and pause never flickers or jumps. */
.music__icon{
  grid-area: 1 / 1;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: opacity 260ms ease, transform 260ms var(--ease-soft);
}

/* Pause — two upright bars. Shown while the music is playing. */
.music__icon--pause{ gap: 5px; opacity: 0; transform: scale(0.82); }
.music__icon--pause i{
  display: block;
  width: 4.5px;
  height: 19px;
  border-radius: 1.5px;
  background: currentColor;
}

/* Play — a triangle, nudged right so it looks optically centred. */
.music__icon--play{
  width: 0;
  height: 0;
  margin-left: 4px;
  border-top: 10px solid transparent;
  border-bottom: 10px solid transparent;
  border-left: 17px solid currentColor;
  opacity: 1;
  transform: scale(1);
}

.music.is-playing .music__icon--pause{ opacity: 1; transform: scale(1); }
.music.is-playing .music__icon--play { opacity: 0; transform: scale(0.82); }


/* ==========================================================================
   11. REDUCED MOTION
   Guests who ask their phone for less movement get a calm, still page.
   ========================================================================== */
@media (prefers-reduced-motion: reduce){
  html{ scroll-behavior: auto; }

  /* No slide at all. The panels stay put and the whole door layer — mist
     included, because the mist is inside it — simply fades out together
     over 400ms. */
  .door__panel{ transition: none !important; transform: none !important; }
  .door__mist{ transition: none !important; }

  .door.is-gone{ transition: opacity 400ms ease; }

  .door__seam,
  .door__diya,
  .door__prompt,
  .scratch__prompt,
  .scratch__diya,
  .door__loading span,
  .hero__media img{ animation: none !important; }

  /* The reel still works — the guest just drives it themselves. */
  .reel__track{ transition: none !important; }

  .hero__hintline::after{ display: none; }

  /* Petals are switched off completely (JS also skips creating them). */
  .petals{ display: none; }

  /* The whole walk and every event are simply there, straight away. */
  .trail__print,
  .trail__print.is-walked{
    opacity: .88;
    transform: rotate(var(--deg));
    transition: none;
  }
  .trail__event,
  .trail__event.is-revealed{
    opacity: 1;
    transform: none;
    transition: none;
  }

  .menu__list a,
  .menu__social{ transition: opacity 200ms ease; transform: none; }

  /* Section 2 is simply there — no reveal, nothing to wait for. */
  [data-reveal],
  [data-reveal].is-revealed{
    opacity: 1;
    transform: none;
    transition: none;
  }
  .invite__rule span{ transform: scaleX(1); transition: none; }

  /* No lift on the icon circles. Their edge and colour still change on
     hover, so the feedback that matters is untouched. */
  .orb,
  .orb:hover,
  .orb:focus-visible,
  .orb:active{ transform: none !important; transition: none; }

  /* Language switch: swap instantly, no cross-fade. */
  .invite__body{ transition: none !important; opacity: 1 !important; }
  .langswitch__btn{ transition: none; }
}


/* ==========================================================================
   12. SMALL SCREEN TUNING (tested at 360px wide)
   ========================================================================== */
@media (max-width: 400px){
  :root{ --nav-h: 52px; --fs-nav: 0.68rem; }
  /* With only four things in the bar there is room to breathe again. The
     padding was squeezed to 10px to make space for the text links; at 16px
     nothing sits against the screen edge. */
  .nav{ gap: 10px; padding-left: 16px; padding-right: 16px; }
  .nav__brandname{ font-size: 0.95rem; }
  .nav__logo{ width: 24px; height: 24px; }
}

/* Very short screens (landscape phones) — keep the hero from crowding. */
@media (max-height: 520px){
  .hero{ min-height: 460px; }
  .hero__hint{ display: none; }
}
