/* ===========================================================================
   Wedding e-invite — mobile-first, delivered as a single WhatsApp link.

   Layout note: the interior artwork is pre-extended to 1024x2220 (0.461) by
   tools/build-assets.py, so it already matches a phone viewport and needs no
   crop. The cover is NOT extended -- its ornate frame is the point -- so it is
   presented as a contained card on a paper ground.
   =========================================================================== */

:root {
  /* Ink is the cover's own navy sky, sampled from the artwork. */
  --ink:          #162b3d;
  --ink-soft:     #3a5068;
  --paper-cover:  #f2e4ce;
  --paper-english:#d7e0df;
  --paper-tamil:  #bcc4b1;
  --paper-closing:#d5d6c7;

  --serif: "Cormorant Garamond", Cormorant, "Iowan Old Style", Georgia, serif;
  --tamil: "Noto Serif Tamil", "Nirmala UI", "Latha", serif;

  /* Fraction of scene height that stays clear of artwork detail. Measured from
     the built images: first content row is at 53.6% / 53.3% / 44.9%. */
  --safe: 52%;

  --dur: 700ms;
  --ease: cubic-bezier(.22,.61,.36,1);
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  height: 100%;
  background: var(--paper-cover);
  color: var(--ink);
  font-family: var(--serif);
  -webkit-font-smoothing: antialiased;
  -webkit-text-size-adjust: 100%;
}

/* --- deck ---------------------------------------------------------------- */
/* NB: never put overflow:hidden on body -- it breaks scroll-snap in the
   WhatsApp in-app browser. The deck owns the scrolling. */
.deck {
  height: 100vh;
  height: calc(var(--vh, 1vh) * 100);
  height: 100dvh;
  overflow-y: auto;
  overflow-x: hidden;
  scroll-snap-type: y proximity;
  overscroll-behavior-y: contain;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.deck::-webkit-scrollbar { display: none; }

.scene {
  position: relative;
  height: 100vh;
  height: calc(var(--vh, 1vh) * 100);
  height: 100dvh;
  scroll-snap-align: start;
  overflow: hidden;
  display: grid;
  /* Beyond phone width the artwork would distort, so the deck becomes a
     centred column on tablet/desktop. The cap only ever bites ABOVE phone
     width -- min(100vw, N) is 100vw on every phone -- so it is a desktop knob
     and changing it cannot affect a phone.

     600px is not arbitrary: on a 1512x900 desktop the venue scene (the one
     carrying a QR plus three actions) ends 77.2% down the artwork against a
     foliage skyline at 78.9%. Wider pushes that copy onto the planting. */
  --col: min(100vw, 600px);
}

.scene__art {
  position: absolute;
  inset: 0;
  width: var(--col);
  margin-inline: auto;
  overflow: hidden;
}

.scene__art img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center bottom;
  display: block;
  /* Oversized so parallax can translate without exposing an edge. */
  transform: scale(calc(1.08 * var(--enter, 1.035))) translate3d(0, calc(var(--par, 0%) * var(--bg-depth, 1)), 0) scaleX(var(--flip, 1));
  transition: transform 1500ms cubic-bezier(.16,.8,.3,1);
  background-size: cover;
  background-position: center bottom;
}

/* Promote only what actually moves. main.js skips parallax on .scene--tall,
   so promoting those images -- the Tamil page is 1024x4376 and is rendered
   twice -- costs layer memory on cheap phones and buys nothing. */
.scene:not(.scene--tall) .scene__art img,
.scene--cover .card img { will-change: transform; }

/* Scenes 4 and 5 reuse scene 2 and 3 artwork mirrored: the compositions are
   near-symmetric, so it reads as a companion page at zero extra bytes. */
.scene--mirror { --flip: -1; }

/* --- depth layer (backlog #7) -------------------------------------------
   The near planting, matted out of the closing artwork by the build script and
   laid back over its own inpainted base. It travels on the same --par the base
   uses, multiplied by a larger depth, and THAT DIFFERENCE is the parallax --
   real separation rather than the simulated kind flat art is limited to.

   The two depths are chosen so the larger excursion still lands inside the 4%
   of overscan the 1.08 scale provides. Exceed that and the layer's own edge
   walks into view, which is unrecoverable. */
/* NB the selector: .scene__fg is itself an <img> inside .scene__art, so the
   plain `.scene__art img` rule above (0,1,1) outranks a bare `.scene__fg`
   (0,1,0) and the layer silently inherits the BACKGROUND depth -- which zeroes
   the differential and makes the whole layer pointless while looking fine. */
.scene__art img.scene__fg {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover; object-position: center bottom;
  display: block; z-index: 1; pointer-events: none;
  transform: scale(calc(1.08 * var(--enter, 1.035))) translate3d(0, calc(var(--par, 0%) * var(--fg-depth, 1)), 0) scaleX(var(--flip, 1));
  transition: transform 1500ms cubic-bezier(.16,.8,.3,1);
  will-change: transform;
}

/* Only the closing artwork is layered -- the cover was tried and rejected
   (its peacock straddles the pond, so no colour rule cuts it cleanly), and the
   tall pages take no parallax at all. */
#s-reception, #s-venue, #s-closing { --bg-depth: .55; --fg-depth: .92; }

/* --- cover --------------------------------------------------------------- */
/* The scroll cue is position:absolute, so .cover-stack's margin:auto happily
   centres the artwork straight through it -- on a 320x568 phone the date line
   overlapped the cue by 30px. Reserve the band the cue actually occupies
   (its own offset plus its height) so the auto-centring happens in the space
   that is really free. Tall phones have slack and simply centre a little
   higher; short phones shrink the card a few px instead of colliding. */
.scene--cover {
  background: var(--paper-cover);
  padding-bottom: calc(max(3.4vh, env(safe-area-inset-bottom)) + 3.2rem);
  /* .cover-stack caps itself with max-height:100%, but an implicit grid row
     sizes to its own content -- so that 100% resolved to the stack's own
     height and constrained nothing. Pin the row to the free space and the cap
     becomes real, which is what lets the card shrink instead of overflow. */
  grid-template-rows: minmax(0, 1fr);
}

.cover-stack {
  margin: auto;
  width: var(--col);
  display: flex;
  flex-direction: column;
  max-height: 100%;
}

.scene--cover .card {
  position: relative;
  width: 100%;
  aspect-ratio: 1024 / 1536;
  flex: 0 1 auto;
  min-height: 0;
}

.scene--cover .card img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
  /* Starts under 1 so the slow breathe never overflows and clips the frame. */
  transform: scale(calc(var(--breathe, .98) * var(--enter, 1.02)));
}

/* The cover's clear navy runs from 7.7% to 29.4% of the card -- measured off
   the artwork, not guessed. The title is anchored to exactly that band so it
   can never drift down onto the mandapam. */
.cover-title {
  position: absolute;
  top: 7.7%; height: 21.7%; left: 9%; right: 9%;
  display: flex; flex-direction: column; justify-content: center;
  color: var(--paper-cover);
  text-align: center;
}
.cover-title .eyebrow { color: rgba(242,228,206,.72); margin: 10px 0 .7em; letter-spacing: .24em; }
.cover-title .names   { font-size: clamp(1.5rem, 7.2vw, 2.15rem); }

.cover-foot {
  flex: 0 0 auto;
  padding: 1.1em 10% 0;
  text-align: center;
}
.cover-foot .meta { margin: 0; }

/* --- text ---------------------------------------------------------------- */
/* Centred within the arch's clear zone rather than top-aligned, so short and
   long copy both sit correctly inside the arch instead of riding its crown. */
.scene__text {
  position: relative;
  z-index: 2;
  width: var(--col);
  margin-inline: auto;
  /* Against --col, never against the containing block. Percentage padding
     resolves against the containing block's width, and this element's
     containing block is the full-width scene, not the column it is drawn in.
     On a phone those are identical, which is exactly why this survived. */
  padding: calc(var(--col) * .07) calc(var(--col) * .11);
  height: var(--safe);
  display: flex;
  flex-direction: column;
  justify-content: center;
  text-align: center;
  overflow: hidden;
}

.scene--closing { --safe: 44%; }

.eyebrow {
  font-size: clamp(.62rem, 2.9vw, .78rem);
  letter-spacing: .34em;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin: 0 0 1.1em;
}

.names {
  font-size: clamp(2.1rem, 11vw, 3.4rem);
  font-weight: 300;
  line-height: 1.06;
  letter-spacing: .01em;
  margin: 0;
}

.amp {
  display: block;
  font-size: .42em;
  font-style: italic;
  letter-spacing: .1em;
  margin: .5em 0;
  opacity: .8;
}

.line   { font-size: clamp(1rem, 4.4vw, 1.22rem); line-height: 1.62; margin: 0 0 .85em; font-weight: 300; }
.strong { font-size: clamp(1.24rem, 5.6vw, 1.6rem); line-height: 1.34; margin: .3em 0 .5em; font-weight: 400; }
.meta   { font-size: clamp(.84rem, 3.5vw, .96rem); letter-spacing: .05em; color: var(--ink-soft); margin: 0 0 .5em; }

.tamil-text { font-family: var(--tamil); }
.tamil-text .line   { line-height: 1.95; font-size: clamp(.94rem, 4.1vw, 1.12rem); }
.tamil-text .strong { line-height: 1.7;  font-size: clamp(1.1rem, 5vw, 1.36rem); }

/* Ornamental divider, replacing a plain 1px rule. */
.rule { margin: 1.4em auto; line-height: 0; }
.divider {
  width: clamp(96px, 34vw, 132px); height: auto;
  fill: currentColor; stroke: currentColor;
  stroke-width: .9; opacity: .34;
}
.divider path:first-child { fill: none; }
.divider path + path { stroke: none; }

/* --- actions ------------------------------------------------------------- */
.actions { display: flex; flex-direction: column; gap: .7rem; align-items: center; margin-top: 1.2em; }

.btn {
  display: inline-flex; align-items: center; gap: .5em;
  padding: .74em 1.5em;
  font-family: var(--serif);
  font-size: clamp(.9rem, 3.8vw, 1rem);
  letter-spacing: .09em; text-transform: uppercase;
  color: var(--ink); text-decoration: none;
  border: 1px solid currentColor; border-radius: 999px;
  background: rgba(255,255,255,.42);
  -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px);
  transition: background .25s var(--ease), transform .25s var(--ease);
}
.btn:active { transform: scale(.97); background: rgba(255,255,255,.62); }

/* QR must stay crisp and high-contrast or it will not scan off a screen. */
/* The dvh term is what keeps the venue scene -- a QR plus three actions, the
   most furniture on any page -- inside its clear zone on a SHORT window. At
   30dvh it only engaged below 720px and the block overflowed by 8px on an
   ordinary 1440x789 laptop; 26dvh engages below 831px. Never drops under the
   ~170px floor for scanning off a screen, and it is tappable regardless. */
/* Also bounded by viewport HEIGHT: on a short phone (iPhone SE, 375x667) a
   width-only cap overflowed the clear zone by ~90px. Never goes below ~170px,
   which is the floor for reliable scanning off a screen. */
a.qr { display: block; text-decoration: none; }
.qr {
  width: min(52vw, 26dvh, 216px); aspect-ratio: 1; margin: .2em auto .6em;
  padding: 12px; background: #fff; border-radius: 6px;
  box-shadow: 0 1px 12px rgba(22,43,61,.14);
}
.qr img {
  width: 100%; height: 100%; display: block;
  /* NOT image-rendering: pixelated -- the source is 640px scaled down into a
     ~195px card, and nearest-neighbour downscaling drops whole dot modules.
     Smooth downscaling is what keeps the code readable. */
  image-rendering: auto;
}

/* --- scroll cue ---------------------------------------------------------- */
.cue {
  position: absolute; left: 0; right: 0; bottom: max(3.4vh, env(safe-area-inset-bottom));
  z-index: 3; text-align: center;
  font-size: .62rem; letter-spacing: .3em; text-transform: uppercase;
  color: var(--ink-soft); opacity: .6;
  animation: drift 2.8s var(--ease) infinite;
}
.cue span { display: block; margin-top: .5em; font-size: 1rem; letter-spacing: 0; }
@keyframes drift { 0%,100% { transform: translateY(0); } 50% { transform: translateY(5px); } }

/* --- water shimmer ------------------------------------------------------- */
/* Two sweeps, not one. A single travelling band reads as a bar crossing a
   photograph; two at different angles and coprime periods (13s / 21s, so the
   pair does not visibly repeat for minutes) interfere, and interference is what
   water actually looks like. Both are deliberately near-invisible, and their
   summed opacity stays in the same register as the single .06 sweep this
   replaces.

   --pond-x insets the band horizontally. The interior pages run their pond to
   the full bleed, but the cover's pond sits inside an ornate cream frame and a
   sweep must never cross onto it. */
.shimmer {
  position: absolute;
  left: var(--pond-x, 0); right: var(--pond-x, 0);
  top: var(--pond-top, 70%); height: var(--pond-h, 12%);
  z-index: 1; pointer-events: none; opacity: .042;
  background: linear-gradient(100deg, transparent 30%, #fff 50%, transparent 70%);
  background-size: 260% 100%;
  animation: sweep 13s linear infinite;
  /* Feather the band's top and bottom, the way .iridesce feathers its
     rectangle. Without this the sweep is a hard-edged strip: invisible at
     these opacities on the artwork, but it is the only hard geometry in the
     whole effect, and water has no straight edges. */
  -webkit-mask-image: linear-gradient(to bottom, transparent, #000 22%, #000 78%, transparent);
          mask-image: linear-gradient(to bottom, transparent, #000 22%, #000 78%, transparent);
}
@keyframes sweep { from { background-position: 130% 0; } to { background-position: -130% 0; } }

/* The counter-sweep: shallower angle, longer period, opposite direction. */
.shimmer--slow {
  opacity: .028;
  background: linear-gradient(84deg, transparent 34%, #fff 50%, transparent 66%);
  background-size: 320% 100%;
  animation: sweep-slow 21s linear infinite;
}
@keyframes sweep-slow { from { background-position: -140% 0; } to { background-position: 140% 0; } }

/* --- countdown ----------------------------------------------------------- */
/* Whole days, set as an aside under the date -- not a second fact competing
   with it, and never a ticking clock. Empty until main.js fills it, and empty
   is invisible: no JS, no line, no reserved space. */
.countdown {
  font-style: italic;
  letter-spacing: .08em;
  opacity: .72;
  margin-top: .35em;
}
.countdown:empty { display: none; }

/* --- reveal -------------------------------------------------------------- */
.reveal > * { opacity: 0; transform: translateY(12px); }
.is-visible .reveal > * {
  opacity: 1; transform: none;
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease);
  transition-delay: calc(min(var(--i, 0), 6) * 110ms);
}

/* --- reduced motion ------------------------------------------------------ */
@media (prefers-reduced-motion: reduce) {
  /* Keep the framing, drop the movement. Tall pages carry no overscan, so they
     must not inherit the 1.08 the short pages need for parallax headroom. */
  .scene__art img,
  .scene__art img.scene__fg { transform: scale(1.08) scaleX(var(--flip, 1)); }
  .scene--tall .scene__art img { transform: scaleX(var(--flip, 1)); }
  .scene--cover .card img { transform: none; }
  /* Stopping the sweep would leave a static pale band frozen across the
     pond -- an artifact, not a picture. Hide it outright, the way
     .iridesce does. */
  .shimmer { animation: none; opacity: 0; }
  .cue { animation: none; }
  .reveal > * { opacity: 1; transform: none; transition: none; }
  .deck { scroll-behavior: auto; }
}


/* ===========================================================================
   Tall pages (English and Tamil)

   The real copy is a full printed page per language and does not fit inside
   the arch at phone-viewport height. Rather than veil the artwork behind a
   panel, tools/build-assets.py stretches the arch TALLER for these pages
   (english 1024x3150, tamil 1024x4376) so the text sits inside the arch with
   no veil at all. Art and type both scale with viewport width, so the text
   stays inside the arch on every screen size.

   --clear-* is published by the build script from the measured first-content
   row, so the CSS never guesses where the artwork's detail begins.
   =========================================================================== */

.scene--tall {
  display: block;
  height: auto;
  min-height: 100dvh;
  position: relative;
  overflow: hidden;
}

.scene--tall .scene__art {
  position: relative;
  inset: auto;
  height: auto;
}

.scene--tall .scene__art img {
  width: 100%;
  height: auto;
  object-fit: fill;
  transform: scale(var(--enter, 1.035)) scaleX(var(--flip, 1));  /* no overscan */
  transition: transform 1500ms cubic-bezier(.16,.8,.3,1);
}

.scene--tall .scene__text {
  position: absolute;
  top: 0; left: 0; right: 0;
  height: var(--clear, 60%);
  width: var(--col);
  margin-inline: auto;
  padding: calc(var(--col) * .05) calc(var(--col) * .09) calc(var(--col) * .07);
  justify-content: center;
}

#s-english { --clear: var(--clear-english); background: var(--paper-english-sampled); }
#s-tamil-1,
#s-tamil-2 { --clear: var(--clear-tamil);   background: var(--paper-tamil-sampled); }
#s-reception, #s-venue, #s-closing { background: var(--paper-closing-sampled); }

/* The venue scene carries a QR plus two actions -- more than the closing art's
   44.9% clear zone holds on a short phone. It reaches a little further down,
   into the pale mandapam band where ink still measures ~7:1. */
#s-venue .scene__text { height: 67%; }

/* --- typographic roles from the Figma hierarchy -------------------------- */
.invocation {
  font-size: clamp(.72rem, 3vw, .84rem);
  letter-spacing: .04em; line-height: 1.7;
  color: var(--ink-soft); margin: 0 0 1.4em;
}

.title {
  font-size: clamp(1.7rem, 8.4vw, 2.5rem);
  font-weight: 500; line-height: 1.14; margin: 0 0 .9em;
}
.title--sm { font-size: clamp(1.3rem, 6vw, 1.7rem); letter-spacing: .16em; text-transform: uppercase; }

.host    { font-size: clamp(1.16rem, 5.2vw, 1.44rem); font-weight: 400; line-height: 1.3; margin: 0; }
.host + .line, .host ~ .line { margin-top: 1.5em; }
.amp-line{ font-size: clamp(.94rem, 4vw, 1.1rem); font-style: italic; opacity: .75; margin: .18em 0; }
.couple  { font-size: clamp(1.24rem, 5.6vw, 1.55rem); font-weight: 500; line-height: 1.3; margin: .5em 0 0; }
.with    { font-size: clamp(.86rem, 3.6vw, 1rem); font-style: italic; color: var(--ink-soft); margin: .35em 0; }

.lineage {
  font-size: clamp(.78rem, 3.3vw, .9rem);
  line-height: 2.05; margin: 1em 0 .2em; color: var(--ink-soft);
}

.om     { font-size: .9rem; margin: 0 0 .5em; opacity: .7; }
.kural  { font-size: clamp(.8rem, 3.4vw, .92rem); line-height: 1.95; margin: 0 0 1.5em; color: var(--ink-soft); }
.kural-src { display: block; font-size: .86em; opacity: .75; }

.hosts-grid {
  display: grid; gap: 1.3em; margin: 1.4em 0 .6em;
  grid-template-columns: repeat(auto-fit, minmax(0, max-content));
  justify-content: center;
  justify-items: center;
  align-items: center;
}
@media (min-width: 400px) { .hosts-grid { gap: 1em 2em; } }
.hosts-grid .meta { margin: 0; line-height: 1.85; }

.tamil-text .title { line-height: 1.5; }


/* ---------------------------------------------------------------------------
   Tall-page type scales with the COLUMN, not the viewport

   The docs claim art and type scale together at every screen size. They do on a
   phone, where --col IS the viewport. Above phone width the artwork keeps
   growing with --col while every font-size hits its rem ceiling, so the
   type/art ratio falls from .044 to .0325 -- the copy fills less and less of
   the arch, and the empty stretch below it grows until a whole screenful of a
   tall page can contain no words at all. That is what "copy is missing" was.

   `max(<floor>, calc(var(--col) * r))` restores the phone relationship at every
   width. On a phone `calc(var(--col) * .044)` IS `4.4vw`, so these are
   identical to what shipped before; --col is capped at 600px, so they need no
   upper bound of their own.
   --------------------------------------------------------------------------- */
.scene--tall .line       { font-size: max(1rem,    calc(var(--col) * .044)); }
.scene--tall .strong     { font-size: max(1.24rem, calc(var(--col) * .056)); }
.scene--tall .title      { font-size: max(1.7rem,  calc(var(--col) * .084)); }
.scene--tall .host       { font-size: max(1.16rem, calc(var(--col) * .052)); }
.scene--tall .couple     { font-size: max(1.24rem, calc(var(--col) * .056)); }
.scene--tall .meta       { font-size: max(.84rem,  calc(var(--col) * .035)); }
.scene--tall .with       { font-size: max(.86rem,  calc(var(--col) * .036)); }
.scene--tall .amp-line   { font-size: max(.94rem,  calc(var(--col) * .04)); }
.scene--tall .lineage    { font-size: max(.78rem,  calc(var(--col) * .033)); }
.scene--tall .invocation { font-size: max(.72rem,  calc(var(--col) * .03)); }
.scene--tall .kural      { font-size: max(.8rem,   calc(var(--col) * .034)); }
.scene--tall .tamil-text .line { font-size: max(.94rem, calc(var(--col) * .041)); }

/* Above phone width the column stops growing but the artwork stays as tall as
   it was built, so centring the copy in the clear zone leaves a dead stretch
   below it that can exceed a whole laptop screen -- scroll into it and the page
   shows no words at all. Resting the copy on the base of the arch instead of
   floating it mid-zone puts that slack above the copy, where the arch is
   supposed to be empty, and caps the tail at the artwork's own clear fraction.

   Phones keep the centred composition: the breakpoint sits above the widest
   phone (430px), and below it --col is the viewport, so nothing changes. */
@media (min-width: 520px) {
  .scene--tall .scene__text { justify-content: flex-end; }
}
.tamil-text .host, .tamil-text .couple { line-height: 1.6; }

.btn--quiet { border-color: transparent; background: transparent; opacity: .78; text-transform: none; letter-spacing: .05em; }


/* ---------------------------------------------------------------------------
   Invocation block (Tamil page 1)

   Groups the உ with the invocation lines behind a faint warm wash. The editor
   captured this as position:absolute at 434x79 with left/top offsets measured
   at one viewport; that would overflow a 390px phone and drift everywhere else,
   so it is rebuilt in normal flow with stretch + padding for the same look.
   --------------------------------------------------------------------------- */
.invocation-block {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 12px 16px;
  margin: 0 0 1.3em;
  align-self: stretch;
  background: rgba(152, 72, 32, .08);
  border-radius: 8px;
}
.invocation-block .om,
.invocation-block .invocation { margin: 0; }

/* Orphan control. The manifest pinned one paragraph to width:270px to stop
   "2026" falling alone; that fixes a single viewport and breaks the rest.
   Balancing the lines prevents orphans at every width instead, and is ignored
   harmlessly by browsers that do not support it. */
.line, .strong, .meta, .couple, .host, .cover-foot .meta, .kural {
  text-wrap: balance;
}


/* ===========================================================================
   Arch opens
   Each scene's artwork settles the last few percent as you arrive -- the
   mandapam as a gateway you step through rather than a picture that is simply
   there. One variable folded into the transforms that already exist, so it
   costs no extra compositing.
   =========================================================================== */
.scene.is-visible { --enter: 1; }
.scene--cover.is-visible { --enter: 1; }

/* ===========================================================================
   Peacock iridescence
   A slow band of warm light travelling across the tail, the way the eyes catch
   the sun. Masked to the bird's own rectangle -- hand-tuned against each built
   image, NOT computed by the build script -- so it needs no layer separation.
   If the artwork is ever re-cropped these four numbers must be re-checked by
   eye; nothing will fail loudly.
   Long rest between passes: it should be caught out of the corner of the eye.
   =========================================================================== */
.iridesce {
  position: absolute;
  left: var(--pk-x0); top: var(--pk-y0);
  width: calc(var(--pk-x1) - var(--pk-x0));
  height: calc(var(--pk-y1) - var(--pk-y0));
  z-index: 1;
  pointer-events: none;
  mix-blend-mode: screen;
  opacity: 0;
  background: linear-gradient(102deg,
    transparent 40%, rgba(255,240,205,.9) 50%, transparent 60%);
  background-size: 280% 100%;
  background-repeat: no-repeat;
  /* feather the rectangle away so no edge of the mask is ever visible */
  -webkit-mask-image: radial-gradient(ellipse 62% 58% at 50% 50%, #000 30%, transparent 100%);
          mask-image: radial-gradient(ellipse 62% 58% at 50% 50%, #000 30%, transparent 100%);
  animation: iridesce 15s ease-in-out infinite;
}

@keyframes iridesce {
  0%   { background-position: 150% 0; opacity: 0; }
  6%   { opacity: .55; }
  26%  { opacity: .55; }
  32%  { background-position: -60% 0; opacity: 0; }
  100% { background-position: -60% 0; opacity: 0; }
}

/* Peacock rectangles, as a fraction of each artwork. */
#s-cover     { --pk-x0: 44%; --pk-x1: 74%; --pk-y0: 72%; --pk-y1: 87%; }
#s-reception,
#s-venue,
#s-closing   { --pk-x0: 18%; --pk-x1: 74%; --pk-y0: 74%; --pk-y1: 91%; }

/* The cover's iridescence belongs to the card, which is the framed artwork. */
.scene--cover .card { position: relative; }

@media (prefers-reduced-motion: reduce) {
  .iridesce { animation: none; opacity: 0; }
  .scene__art img,
  .scene__art img.scene__fg,
  .scene--tall .scene__art img,
  .scene--cover .card img { transition: none; }
  .scene { --enter: 1; }
}


/* ===========================================================================
   Kolam
   A sikku kolam is one unbroken looping line drawn around a grid of dots, and
   it is how a Tamil household marks a threshold on the morning of something
   important. Generated as a hypotrochoid, which has exactly that topology and
   is guaranteed to be a single closed path -- which is what lets it draw
   itself in one continuous stroke.

   The pulli appear first, then the line loops around them: the order it is
   actually drawn in.
   =========================================================================== */
.kolam-wrap { line-height: 0; margin: 0 0 1.6em; }

.kolam {
  width: clamp(84px, 26vw, 116px);
  height: auto;
  overflow: visible;
}

.kolam .stroke {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.15;
  stroke-linejoin: round;
  stroke-linecap: round;
  opacity: .5;
  /* pathLength="1" normalises the geometry, so the dash maths is independent
     of the path's real length. */
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
}

.kolam .pulli circle { fill: currentColor; opacity: 0; }

.is-visible .kolam .stroke {
  stroke-dashoffset: 0;
  transition: stroke-dashoffset 3200ms cubic-bezier(.45,.05,.25,1) 620ms;
}
.is-visible .kolam .pulli circle {
  opacity: .42;
  transition: opacity 420ms ease-out;
  transition-delay: calc(var(--d, 0) * 55ms);
}

@media (prefers-reduced-motion: reduce) {
  .kolam .stroke { stroke-dashoffset: 0; transition: none; }
  .kolam .pulli circle { opacity: .42; transition: none; }
}


/* ===========================================================================
   Dawn
   The cover is a moonlit navy garden; every page after it is pale daylight.
   Left alone that reads as an inconsistency, so make it the point: as the
   cover scrolls away, first light comes up behind the mandapam. The muhurtham
   is at 7.30 in the morning -- the invitation opens at dawn.

   Driven by --dawn, which main.js sets from the cover's scroll position, so
   scrolling back up puts the night back.
   =========================================================================== */
.dawn {
  position: absolute;
  left: 9%; right: 9%; top: 5%; height: 44%;
  z-index: 1;
  pointer-events: none;
  opacity: var(--dawn, 0);
  mix-blend-mode: screen;
  background:
    radial-gradient(120% 82% at 50% 100%,
      rgba(255,196,132,.85) 0%,
      rgba(255,146,110,.45) 34%,
      rgba(150,120,160,.18) 62%,
      transparent 82%);
  /* feather every edge so the rectangle never shows against the cream frame */
  -webkit-mask-image: radial-gradient(105% 92% at 50% 100%, #000 42%, transparent 88%);
          mask-image: radial-gradient(105% 92% at 50% 100%, #000 42%, transparent 88%);
}

@media (prefers-reduced-motion: reduce) {
  .dawn { opacity: 0; }
}

/* ===========================================================================
   Compact tier — 320px-class phones (iPhone 5 / SE 1st gen)

   At 320x568 the clear zone inside the arch is ~60px short for the English
   page and the venue actions. Everything is trimmed proportionally rather than
   letting text spill onto the architecture. The QR stays above ~5px per module,
   which is comfortably scannable, and it is tappable regardless.
   =========================================================================== */
@media (max-width: 350px) {
  .scene__text { padding-left: calc(var(--col) * .07); padding-right: calc(var(--col) * .07); }

  .line       { font-size: .93rem; line-height: 1.52; margin-bottom: .7em; }
  .host       { font-size: 1.06rem; }
  .couple     { font-size: 1.12rem; margin-top: .38em; }
  .strong     { font-size: 1.14rem; }
  .title      { font-size: 1.5rem; margin-bottom: .7em; }
  .meta       { font-size: .8rem; }
  .invocation { margin-bottom: .95em; font-size: .7rem; }
  .with       { margin: .25em 0; }
  .rule       { margin: 1em auto; }
  .kolam-wrap { margin-bottom: 1.1em; }

  .cover-foot { padding-top: .7em; }
  .countdown  { margin-top: .2em; }

  .actions    { gap: .5rem; margin-top: .9em; }
  .btn        { padding: .6em 1.2em; font-size: .86rem; }
  .qr         { width: min(52vw, 27dvh, 200px); padding: 9px; margin-bottom: .45em; }

  /* the venue scene carries the most furniture, so it reaches furthest down */
  #s-venue .scene__text { height: 75%; }

  .tamil-text .line    { line-height: 1.8; font-size: .89rem; }
  .tamil-text .lineage { line-height: 1.85; font-size: .74rem; }
}
