/* =========================================================
   Swati & Sagar — Engagement Invitation
   A dark jewel ground; the invitation is a lit card upon it.
   ========================================================= */

:root{
  /* Ground */
  --ground-1:   #3a1a12;
  --ground-2:   #24100a;
  --ground-3:   #150a06;

  /* Card */
  --card-1:     #fdf7ea;
  --card-2:     #f6ecd4;

  /* Metal */
  --gold-deep:  #8a5a2b;
  --gold:       #a9762f;
  --gold-lit:   #e8c27a;
  --gold-pale:  #f4d99a;

  /* Ink on card */
  --ink:        #3a2712;
  --ink-soft:   #5c4a30;
  --ink-faint:  #8a7250;

  /* Fluid scale — one continuous curve, no breakpoint jumps */
  --step--2: clamp(.66rem, .62rem + .18vw, .76rem);
  --step--1: clamp(.8rem,  .76rem + .2vw,  .94rem);
  --step-0:  clamp(.95rem, .9rem  + .25vw, 1.09rem);
  --step-1:  clamp(1.15rem, 1.02rem + .6vw, 1.45rem);
  --step-2:  clamp(2rem,   1.4rem + 3.1vw, 3.4rem);

  --card-pad-x: clamp(1.75rem, 7vw, 4rem);
  --card-pad-y: clamp(2.4rem, 7vw, 4.2rem);
}

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

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

body{
  margin: 0;
  background: var(--ground-3);
  color: var(--card-1);
  font-family: "Cormorant Garamond", ui-serif, Georgia, serif;
  font-size: var(--step-0);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

.stage{
  position: relative;
  min-height: 100svh;
  display: grid;
  place-items: center;
  /* Breathing room between the card and the frame edge — but the
     vertical padding collapses toward zero when the card would
     otherwise not fit the visible area (Brave / mobile Chrome, where
     the toolbars take a slice svh does not fully anticipate). */
  padding: clamp(0.75rem, 5vh, 4rem) clamp(1.75rem, 6vw, 3rem);
  background: radial-gradient(circle at 50% 20%,
              var(--ground-1) 0%, var(--ground-2) 55%, var(--ground-3) 100%);
  overflow: hidden;
}

/* =========================================================
   PARALLAX DEPTH
   Planes that move at different rates, so the frame has space
   in it rather than being a flat backdrop.
   ========================================================= */
.depth{
  position: absolute;
  inset: -8%;                 /* overscan, so nothing shows an edge when it shifts */
  pointer-events: none;
  z-index: 0;
}
.depth__layer{
  position: absolute;
  inset: 0;
  will-change: transform;
  transition: transform .9s cubic-bezier(.22,1,.36,1);
}

/* Far: a slow wash of warm light pools */
.depth__layer--far{
  background:
    radial-gradient(38% 30% at 18% 22%, rgba(232,194,122,.16), transparent 70%),
    radial-gradient(42% 34% at 82% 74%, rgba(169,118,47,.20), transparent 72%);
  animation: driftFar 26s ease-in-out infinite alternate;
}

/* Mid: sharper, smaller pools that read as closer */
.depth__layer--mid{
  background:
    radial-gradient(20% 16% at 72% 24%, rgba(244,217,154,.16), transparent 68%),
    radial-gradient(18% 14% at 26% 78%, rgba(232,194,122,.14), transparent 68%);
  animation: driftMid 19s ease-in-out infinite alternate;
}

/* Haze: a soft veil just behind the subject, for atmosphere */
.depth__layer--haze{
  background: radial-gradient(60% 45% at 50% 52%, rgba(255,214,150,.10), transparent 70%);
  animation: driftMid 15s ease-in-out infinite alternate-reverse;
}

.vignette{
  position: absolute;
  inset: 0;
  background: radial-gradient(75% 65% at 50% 45%, transparent 40%, rgba(10,5,3,.62) 100%);
}

@keyframes driftFar{
  from{ transform: translate3d(-1.5%, -1%, 0) scale(1.04); }
  to  { transform: translate3d( 1.5%,  1%, 0) scale(1.08); }
}
@keyframes driftMid{
  from{ transform: translate3d( 2%, 1.5%, 0) scale(1.02); }
  to  { transform: translate3d(-2%, -1.5%, 0) scale(1.06); }
}

.motes{ position: absolute; inset: 0; pointer-events: none; overflow: hidden; }
.mote{
  position: absolute;
  bottom: -20px;
  border-radius: 50%;
  will-change: transform, opacity;
}
@keyframes drift{
  0%  { transform: translate3d(0,0,0);           opacity: 0; }
  10% { opacity: 1; }
  90% { opacity: 1; }
  100%{ transform: translate3d(30px,-620px,0);   opacity: 0; }
}

/* =========================================================
   THE CINEMATIC STAGE
   ========================================================= */

/* The rings play full-bleed, like a title sequence. */
#ring-canvas{
  position: absolute;
  inset: 0;
  z-index: 1;
  display: block;
  width: 100%;
  height: 100%;
  transition: opacity .6s ease;
  pointer-events: none;
}

/* Without WebGL the canvas simply stays empty. */
.no-webgl #ring-canvas{ display: none; }

/* ---------- Title sequence ---------- */
.titles{
  position: absolute;
  left: 50%;
  bottom: clamp(14%, 20svh, 24%);
  transform: translateX(-50%);
  z-index: 3;
  width: min(90vw, 40ch);
  text-align: center;
  pointer-events: none;
}
.titles__line{
  margin: 0;
  font-family: "Cormorant Garamond", ui-serif, Georgia, serif;
  font-size: clamp(1rem, .9rem + 1.1vw, 1.5rem);
  font-style: italic;
  letter-spacing: .06em;
  color: var(--gold-lit);
  opacity: 0;
  filter: blur(6px);
  transform: translateY(14px);
  text-shadow: 0 2px 18px rgba(0,0,0,.6);
}
/* Each line fades up out of blur, then away again. */
/* Duration matches the hold in playSequence(), so a line completes its
   fade out before the next one begins. */
.titles__line.is-in{
  animation: titleIn 2s cubic-bezier(.22,1,.36,1) forwards;
}
@keyframes titleIn{
  0%  { opacity: 0; filter: blur(6px); transform: translateY(14px); }
  22% { opacity: 1; filter: blur(0);   transform: translateY(0); }
  78% { opacity: 1; filter: blur(0);   transform: translateY(0); }
  100%{ opacity: 0; filter: blur(4px); transform: translateY(-10px); }
}
/* The second line sits in the same place as the first */
.titles__line--2{ position: absolute; inset: 0; }

/* ---------- Petals ----------
   In front of the card (z-index above it), so the frame has depth on
   both sides of the invitation rather than only behind it. */
.petals{
  position: absolute;
  inset: 0;
  z-index: 7;
  pointer-events: none;
  overflow: hidden;
}
.petal{
  position: absolute;
  top: -6%;
  /* An asymmetric radius reads as a petal rather than a disc. */
  border-radius: 60% 40% 55% 45% / 65% 60% 40% 35%;
  opacity: 0;
  will-change: transform, opacity;
  animation-name: petalFall;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
  filter: drop-shadow(0 2px 3px rgba(90,50,10,.25));
}
@keyframes petalFall{
  0%{
    transform: translate3d(0, 0, 0) rotate(0deg);
    opacity: 0;
  }
  8%   { opacity: .85; }
  50%  { transform: translate3d(var(--sway, 30px), 52svh, 0) rotate(180deg); }
  92%  { opacity: .7; }
  100% {
    transform: translate3d(calc(var(--sway, 30px) * -0.6), 108svh, 0) rotate(400deg);
    opacity: 0;
  }
}

/* =========================================================
   THE OPENING GATE
   A single lit press on the dark ground. It exists because audio
   cannot begin without a real gesture: this is that gesture, asked
   for plainly instead of guessed at. It sits above everything until
   pressed, then fades and takes itself out of the layout.
   ========================================================= */
.gate{
  position: absolute;
  inset: 0;
  z-index: 10;                       /* over the controls, under nothing */
  display: grid;
  place-content: center;
  justify-items: center;
  gap: 1.5rem;
  background: radial-gradient(circle at 50% 45%,
              rgba(58,26,18,.55) 0%, rgba(21,10,6,.88) 60%, rgba(21,10,6,.97) 100%);
  transition: opacity .7s ease, visibility .7s;
}
.gate.is-open{ opacity: 0; visibility: hidden; pointer-events: none; }

.gate__btn{
  position: relative;
  appearance: none;
  width: clamp(72px, 18vw, 96px);
  aspect-ratio: 1;
  display: grid;
  place-items: center;
  border: 1px solid rgba(232,194,122,.45);
  border-radius: 50%;
  background: radial-gradient(circle at 50% 35%,
              rgba(232,194,122,.18), rgba(232,194,122,.04) 70%);
  color: var(--gold-lit);
  cursor: pointer;
  box-shadow: 0 0 34px rgba(232,194,122,.13), inset 0 0 22px rgba(232,194,122,.09);
  transition: transform .3s cubic-bezier(.22,1,.36,1),
              border-color .3s ease, box-shadow .3s ease;
  animation: gateIn 1s cubic-bezier(.22,1,.36,1) both;
}
.gate__btn:hover,
.gate__btn:focus-visible{
  outline: none;
  transform: scale(1.06);
  border-color: rgba(232,194,122,.85);
  box-shadow: 0 0 48px rgba(232,194,122,.24), inset 0 0 26px rgba(232,194,122,.14);
}
.gate__btn:active{ transform: scale(.97); }

/* The triangle sits a touch right of centre: an optical correction,
   so the play mark reads as centred rather than measuring as such. */
.gate__icon{
  width: 38%;
  height: 38%;
  transform: translateX(6%);
}

/* A slow halo breathing outward — the press is invited, not demanded. */
.gate__ring{
  position: absolute;
  inset: -1px;
  border: 1px solid rgba(232,194,122,.5);
  border-radius: 50%;
  animation: gatePulse 2.8s ease-out infinite;
  pointer-events: none;
}

.gate__label{
  margin: 0;
  font-family: "Cinzel", ui-serif, Georgia, serif;
  font-size: var(--step--2);
  letter-spacing: .26em;
  text-transform: uppercase;
  color: var(--gold-lit);
  opacity: .62;
  animation: gateIn 1s .18s cubic-bezier(.22,1,.36,1) both;
}
.lang-kn .gate__label{
  font-family: "Noto Serif Kannada", ui-serif, serif;
  letter-spacing: .08em;
  text-transform: none;
}

@keyframes gateIn{
  from{ opacity: 0; transform: translateY(12px) scale(.94); }
  to  { opacity: 1; transform: translateY(0)    scale(1); }
}
@keyframes gatePulse{
  0%  { transform: scale(1);    opacity: .5; }
  70% { transform: scale(1.45); opacity: 0; }
  100%{ transform: scale(1.45); opacity: 0; }
}

/* ---------- Light sweep at the reveal ---------- */
.flash{
  position: absolute;
  inset: 0;
  z-index: 8;
  pointer-events: none;
  opacity: 0;
  background: radial-gradient(50% 40% at 50% 50%, rgba(255,240,210,.95), rgba(255,225,170,.35) 45%, transparent 72%);
}
.flash.is-firing{ animation: flashSweep .85s ease-out forwards; }
@keyframes flashSweep{
  0%  { opacity: 0;   transform: scale(.7); }
  18% { opacity: .95; transform: scale(1); }
  100%{ opacity: 0;   transform: scale(1.6); }
}

/* =========================================================
   FLOATING CONTROLS
   Kept as quiet as possible: no filled buttons, no rims. The two
   persistent controls are a hairline text switch (EN | ಕ) and a
   small line-drawn speaker, sitting on the dark ground the way a
   page number sits in a book's margin.
   ========================================================= */
.controls{
  position: absolute;
  top: clamp(1rem, 4vw, 1.7rem);
  right: clamp(1.1rem, 4.5vw, 2rem);
  z-index: 9;
  display: flex;
  align-items: center;
  gap: .9rem;
  color: var(--gold-lit);
}

.ctl{
  appearance: none;
  border: 0;
  background: none;
  padding: .4rem;
  margin: -.4rem;                    /* pad the hit-area without shifting layout */
  color: inherit;
  cursor: pointer;
  line-height: 1;
  opacity: .55;
  transition: opacity .25s ease, color .25s ease;
}
.ctl:hover,
.ctl:focus-visible{ opacity: 1; }
.ctl:focus-visible{ outline: none; text-shadow: 0 0 8px rgba(232,194,122,.6); }

.ctl svg{ display: block; width: 17px; height: 17px; }

/* Nudge the icon buttons down a hair so they sit on the EN text's
   optical centre rather than its cap-line. */
.ctl--sound{ transform: translateY(1px); }

/* ---------- Language ----------
   A hairline pair: EN | ಕ. The active language is bright, the other
   dim; tapping switches. Reads as a segmented toggle, not a button. */
.ctl--lang{
  display: inline-flex;
  align-items: baseline;
  gap: .42em;
  font-size: var(--step--2);
  letter-spacing: .12em;
  opacity: 1;                        /* the halves carry their own dimming */
}
.ctl__lang-en{ font-family: "Cinzel", ui-serif, Georgia, serif; font-weight: 500; }
.ctl__lang-kn{ font-family: "Noto Serif Kannada", ui-serif, serif; font-size: 1.15em; }
.ctl__lang-sep{ opacity: .4; font-weight: 300; }

/* English active by default. */
.ctl__lang-en{ color: var(--gold-lit); }
.ctl__lang-kn{ color: var(--gold-lit); opacity: .4; }
.lang-kn .ctl__lang-en{ opacity: .4; }
.lang-kn .ctl__lang-kn{ opacity: 1; }

/* ---------- Sound ---------- */
.ctl--sound{ display: grid; place-items: center; }
.ctl--sound .sound__off,
.ctl--sound .sound__on{ grid-area: 1 / 1; }
.ctl--sound .sound__on{ display: none; }
.ctl--sound.is-on{ opacity: 1; }
.ctl--sound.is-on .sound__off{ display: none; }
.ctl--sound.is-on .sound__on{ display: block; }

/* ---------- Skip ----------
   A quiet underlined word, not a pill. Gone once the card shows. */
.ctl--skip{
  font-family: "Cinzel", ui-serif, Georgia, serif;
  font-size: var(--step--2);
  letter-spacing: .2em;
  text-transform: uppercase;
  text-decoration: underline;
  text-underline-offset: 4px;
  text-decoration-color: rgba(232,194,122,.35);
  text-decoration-thickness: 1px;
}
.ctl--skip.is-gone{ display: none; }

/* ---------- Kannada ----------
   The display faces carry no Kannada glyphs, so a matched serif keeps
   the card's voice. The Kannada card should fill the screen the same
   way the English one does — so the type is set LARGER here, not
   smaller, to make up for Cinzel's small-caps reading big while
   Kannada's lowercase forms read small at the same pixel size. */
.lang-kn .sagai,
.lang-kn .names__one,
.lang-kn .names__two,
.lang-kn .names__amp,
.lang-kn .preamble,
.lang-kn .when__dow,
.lang-kn .when__month,
.lang-kn .when__time,
.lang-kn .where__venue,
.lang-kn .where__city,
.lang-kn .unit__label,
.lang-kn .today,
.lang-kn .act-status{
  font-family: "Noto Serif Kannada", ui-serif, Georgia, serif;
}

/* Kannada's regular weight reads heavier than the Latin serifs it
   sits in for. Drop the supporting text to 400 explicitly (Cinzel
   ran at 500) and the small labels lighter still. */
.lang-kn .when__dow,
.lang-kn .when__month,
.lang-kn .when__time,
.lang-kn .where__city,
.lang-kn .unit__label,
.lang-kn .act-status{
  font-weight: 400;
}
.lang-kn .sagai{ font-weight: 400; }

/* Kannada type is sized to read at a matched WEIGHT to the English —
   which is close to the same font-size, since the earlier "make it
   bigger" push made the Kannada card overflow. It fills the frame
   through line-height and tracking, not raw size. */
.lang-kn .sagai{ font-size: var(--step--1); letter-spacing: .04em; text-indent: 0; }

.lang-kn .preamble{
  font-size: var(--step-0);
  font-weight: 400;
  font-style: normal;
  line-height: 1.65;
  color: #2c1a0c;
  text-shadow: 0 1px 1px rgba(255, 255, 255, 0.7);
}
.lang-kn .when__dow{ font-size: var(--step--1); letter-spacing: .05em; text-indent: 0; }
.lang-kn .when__month,
.lang-kn .when__time{ font-size: var(--step-0); }
.lang-kn .where__venue{
  font-size: var(--step-1);
  font-weight: 400;
  line-height: 1.25;
}
.lang-kn .where__city{ font-size: var(--step--1); letter-spacing: .04em; }
.lang-kn .unit__label{ font-size: var(--step--2); letter-spacing: .02em; }
.lang-kn .today{ font-size: var(--step-0); }

/* Names: weight 500 not 700 (Kannada 700 is a slab), and sized so two
   lines of Kannada match the height of the two English lines. */
.lang-kn .names{
  font-size: clamp(1.9rem, 1.4rem + 2.4vw, 3rem);
  font-weight: 500;
  letter-spacing: 0;
  line-height: 1.24;
}
.lang-kn .names__one,
.lang-kn .names__two{ font-weight: 500; }

.lang-kn .act-status{ letter-spacing: .03em; text-transform: none; }

/* The date number renders as Kannada digits (via lang.js) but stays
   in Cinzel; keep it near the English size. */
.lang-kn .when__day{ font-size: clamp(2.4rem, 1.8rem + 3vw, 4rem); }

/* =========================================================
   THE CARD
   ========================================================= */
/* The UA rule for [hidden] is display:none, but any author rule that
   establishes layout beats it. Without this the card is on screen from
   the first frame and the opening sequence plays behind it. */
.card[hidden],
.modal[hidden]{ display: none !important; }

.card{
  position: relative;
  z-index: 6;
  width: min(100%, 640px);
  /* Rises out of depth as the light sweep fades */
  animation: cardArrive 1.1s cubic-bezier(.22,1,.36,1) both;
  /* Tilt is applied by JS; keep the transition short so it tracks */
  transition: transform .15s ease-out;
  transform-style: preserve-3d;
}
@keyframes cardArrive{
  0%  { opacity: 0; transform: perspective(1400px) translateZ(-380px) translateY(40px) rotateX(14deg); filter: blur(10px); }
  55% { opacity: 1; }
  100%{ opacity: 1; transform: perspective(1400px) translateZ(0) translateY(0) rotateX(0deg); filter: blur(0); }
}

/* The ambi field + corner mandalas worked into the stock. Behind the
   text (z 0), above the paper gradient. Fades in gently after the
   border has drawn, so it does not compete with the arrival. */
.card__pattern{
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 0;
  pointer-events: none;
  color: var(--gold-deep);
  opacity: 0;
}
.card.is-open .card__pattern{
  animation: patternIn 1.6s ease forwards 1.4s;
}
@keyframes patternIn{ to{ opacity: 1; } }

/* ---------- The couple, as the card's own background ----------
   couple-bg.png is a pre-processed watermark (see
   scripts/make_watermark.py): desaturated, its tonal range narrowed
   well clear of true black, and blended toward the card's own stock
   colour, so it can sit at close to full strength behind the text.
   A single veil gradient over it — dense at the very top margin and
   through the name/date band, clear where the faces sit just below
   "Engagement Ceremony" — is what keeps every line of text legible
   without masking or per-element shadows. */
.card__portrait{
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 8%;
  pointer-events: none;
  user-select: none;
  opacity: 1;
}

.card__veil{
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background: linear-gradient(
    to bottom,
    rgba(253,247,234,.7)  0%,
    rgba(253,247,234,.32) 7%,
    rgba(253,247,234,0)   13%,
    rgba(253,247,234,0)   30%,
    rgba(253,247,234,.5)  36%,
    rgba(253,247,234,.6)  55%,
    rgba(253,247,234,.68) 100%
  );
}

.card__sheet{
  position: relative;
  overflow: hidden;
  background:
    /* Fibre tooth: two fine noise layers so the stock reads as
       cotton paper rather than flat colour. */
    radial-gradient(circle at 20% 30%, rgba(138,90,43,.035) 0 1px, transparent 1px),
    radial-gradient(circle at 70% 65%, rgba(138,90,43,.028) 0 1px, transparent 1px),
    linear-gradient(160deg, var(--card-1) 0%, var(--card-2) 100%);
  background-size: 7px 7px, 11px 11px, 100% 100%;
  border-radius: 3px;
  box-shadow:
    /* Deckle edge, then the drop shadows that seat it in the frame. */
    inset 0 0 0 1px rgba(255,255,255,.55),
    inset 0 1px 0 rgba(255,255,255,.8),
    0 2px 4px rgba(0,0,0,.2),
    0 12px 24px rgba(0,0,0,.35),
    0 48px 100px rgba(0,0,0,.6),
    0 0 0 1px rgba(232,194,122,.45);
}

/* Specular sheen. Tracks the pointer, so tilting the card sweeps a
   highlight across the stock the way light moves over real paper. */
.card__sheet::before{
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  border-radius: inherit;
  background: radial-gradient(
    38% 46% at var(--sheen-x, 50%) var(--sheen-y, 0%),
    rgba(255,255,255,.55) 0%,
    rgba(255,246,225,.16) 38%,
    transparent 70%
  );
  /* screen rather than soft-light: soft-light forces the browser to
     read back the layers beneath it every frame, which stutters on
     mobile. screen composites cheaply and reads much the same over
     pale stock. */
  mix-blend-mode: screen;
  opacity: .55;
}
/* ---------- Light spill ----------
   The card is lit, so it throws warmth onto the ground around it.
   Sits behind everything and breathes slowly, which is what stops
   the card reading as a flat rectangle pasted onto the background.

   No filter: blur() here. A large blurred layer animating underneath
   an element that is itself transformed forces a full re-rasterise
   every frame on mobile GPUs, which is what made the card flicker.
   A soft-stopped gradient gives the same falloff for free. */
.card::before{
  content: "";
  position: absolute;
  inset: -18% -14%;
  z-index: -2;
  pointer-events: none;
  background: radial-gradient(
    54% 48% at 50% 46%,
    rgba(255, 216, 150, .26) 0%,
    rgba(233, 176, 96, .18) 26%,
    rgba(214, 150, 70, .10) 48%,
    rgba(180, 120, 55, .04) 66%,
    transparent 82%
  );
  opacity: 0;
}
.card.is-open::before{
  animation: spillIn 1.6s ease .5s forwards,
             spillBreathe 7s ease-in-out 2.1s infinite;
}
@keyframes spillIn{ to{ opacity: 1; } }
@keyframes spillBreathe{
  0%, 100%{ opacity: .82; transform: scale(1); }
  50%     { opacity: 1;   transform: scale(1.035); }
}

/* A second sheet behind, suggesting a card resting on another.
   Lives on .card, not .card__sheet, because the sheet clips its
   overflow to keep the sheen inside its rounded corners. */
.card::after{
  content: "";
  position: absolute;
  top: 12px; left: 12px; right: -12px; bottom: -12px;
  background: linear-gradient(160deg, #6d4520, var(--gold-deep));
  border-radius: 3px;
  opacity: .55;
  z-index: -1;
}

.card__frame{
  position: absolute;
  inset: 0;
  z-index: 1;
  width: 100%; height: 100%;
  pointer-events: none;
  opacity: .95;
}

/* The border draws itself on, the way a foil line is laid down.
   pathLength is normalised to 1 in the markup, so one dash value
   works for every path regardless of its real length. */
.card__frame [data-draw]{
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
}
.card.is-open .card__frame [data-draw]{
  animation: drawLine 1.6s cubic-bezier(.4,0,.2,1) forwards;
}
@keyframes drawLine{ to{ stroke-dashoffset: 0; } }

/* The corner kumkum marks: pressed on after the border draws, then a
   very slight breath — a daub of colour, not a light. */
.card__frame [data-dot]{ opacity: 0; transform-box: fill-box; transform-origin: center; }
.card.is-open .card__frame [data-dot]{
  animation: dotIn .45s cubic-bezier(.34,1.56,.64,1) forwards 1.5s,
             dotGlow 5s ease-in-out 2.2s infinite;
}
@keyframes dotIn{ from{ opacity: 0; transform: scale(.4); } to{ opacity: 1; transform: scale(1); } }
@keyframes dotGlow{
  0%, 100%{ opacity: .82; }
  50%     { opacity: 1; }
}

/* ---------- Border shimmer ----------
   A short bright segment travels the outer rule, as if a light were
   moving past the card. Drawn as a second copy of the border with a
   dash pattern that walks around it. */
.card__shimmer{
  position: absolute;
  inset: 0;
  z-index: 2;
  width: 100%; height: 100%;
  pointer-events: none;
  opacity: 0;
}
.card.is-open .card__shimmer{
  animation: shimmerIn 1s ease 1.9s forwards;
}
@keyframes shimmerIn{ to{ opacity: 1; } }

.card__shimmer rect{
  fill: none;
  stroke: rgba(255, 244, 214, .95);
  stroke-width: 2.5;
  /* One short lit segment, the rest of the perimeter empty. */
  stroke-dasharray: .06 .94;
  filter: drop-shadow(0 0 4px rgba(255, 226, 160, .9));
}
.card.is-open .card__shimmer rect{
  animation: shimmerRun 9s linear 1.9s infinite;
}
@keyframes shimmerRun{
  from{ stroke-dashoffset: 0; }
  to  { stroke-dashoffset: -1; }
}

.card__body{
  position: relative;
  z-index: 1;
  padding: var(--card-pad-y) var(--card-pad-x);
  display: grid;
  justify-items: center;
  text-align: center;
  color: var(--ink);
}

/* While a language swap is running, the card body carries the stock
   colour itself and is its own paint layer. applyLang() swaps the font
   on every row in one relayout; on weak mobile GPUs the card's layer
   can repaint tile by tile for a frame, and a not-yet-painted tile
   would otherwise show the dark ground through the card. With an opaque
   background of its own, a partial repaint just shows cream. */
.card__body.lang-swapping{
  background: linear-gradient(160deg, var(--card-1) 0%, var(--card-2) 100%);
  contain: paint;
}

/* ---------- Staged arrival ----------
   Each element rises out of blur in sequence rather than the card
   appearing all at once. The stagger is what gives it pacing. */
.card__body > *{
  opacity: 0;
  transform: translateY(14px);
  filter: blur(5px);
}
.card.is-open .card__body > *{
  animation: riseIn .9s cubic-bezier(.22,1,.36,1) forwards;
}
/* Belt and braces: once the card is open, the resting state is sharp
   regardless of animation support or interruption. */
.card.is-open .card__body > *{
  animation-fill-mode: forwards;
}
/* Once a language swap has run, lang.js has driven the rows inline
   and left this class on for good. riseIn must not replay (it would
   flash the whole card), and since the base `.card__body > *` rule is
   opacity:0, this rule owns the visible resting state from now on. */
.card__body.lang-swapped > *{
  animation: none !important;
  opacity: 1;
  transform: none;
  filter: none;
}
@keyframes riseIn{
  from{ opacity: 0; transform: translateY(14px); filter: blur(5px); }
  to  { opacity: 1; transform: none;             filter: blur(0); }
}
/* During a WebGL dust swap, dust-swap.js drives each row's opacity
   inline (0 while it is dust, 1 once it has re-formed). The particle
   canvas sits above the card at z-index 60. */
.dust-swap-canvas{ pointer-events: none; }
/* Delays are set per element; anything past the listed range still
   animates, just without its own stagger step. */
.card.is-open .card__body > *:nth-child(1){ animation-delay: .10s; }
.card.is-open .card__body > *:nth-child(2){ animation-delay: .18s; }
.card.is-open .card__body > *:nth-child(3){ animation-delay: .30s; }
.card.is-open .card__body > *:nth-child(4){ animation-delay: .42s; }
.card.is-open .card__body > *:nth-child(5){ animation-delay: .52s; }
.card.is-open .card__body > *:nth-child(6){ animation-delay: .60s; }
.card.is-open .card__body > *:nth-child(7){ animation-delay: .68s; }
.card.is-open .card__body > *:nth-child(8){ animation-delay: .76s; }
.card.is-open .card__body > *:nth-child(9){ animation-delay: .84s; }
.card.is-open .card__body > *:nth-child(n+10){ animation-delay: .92s; }

/* ---------- Card content ---------- */
/* ---------- Gold foil ----------
   Real foil stamping is a mirror: it is dark where it faces away from
   the light and blazes where it catches it. A moving gradient does
   the same job, and the sweep is what makes it read as metal rather
   than as a gold-coloured fill. */
.foil{
  /* Deep, lustrous royal gold: dark bronze core so letters have sharp definition */
  background: linear-gradient(
    100deg,
    #3d1f05 0%, #6d3d0f 15%, #9e691e 32%,
    #dfb652 48%, #9e691e 64%, #6d3d0f 82%, #3d1f05 100%
  );
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
  -webkit-text-stroke: 0.35px rgba(61, 31, 5, 0.45);
}
/* Foil needs a fallback: if background-clip:text is unsupported the
   text would be invisible. */
@supports not ((-webkit-background-clip: text) or (background-clip: text)){
  .foil{ color: #4a2807; -webkit-text-fill-color: currentColor; }
}

.sagai{
  margin: 0 0 .35rem;
  font-family: "Cinzel", ui-serif, Georgia, serif;
  font-size: var(--step--2);
  letter-spacing: .26em;
  text-indent: .26em;
  text-transform: uppercase;
  color: #4a2505;
  font-weight: 700;
  background-image: linear-gradient(
    100deg,
    #4a2505 0%, #7d4914 26%, #b37a24 50%, #7d4914 74%, #4a2505 100%
  );
  text-shadow: 0 1px 2px rgba(255, 255, 255, 0.9);
}

.preamble{
  margin: 0 0 clamp(90px, 13vh, 120px);
  max-width: 34ch;
  font-size: var(--step-0);
  font-style: italic;
  font-weight: 600;
  color: #1e1204;
  line-height: 1.55;
  text-shadow: 0 1px 1px rgba(255,255,255,.9), 0 0 14px rgba(255,255,255,.95), 0 0 26px rgba(255,255,255,.8);
}

.names{
  margin: 0 0 1.3rem;
  display: grid;
  justify-items: center;
  gap: .15rem;
  font-family: "Cinzel", ui-serif, Georgia, serif;
  font-weight: 700;
  font-size: clamp(2.1rem, 1.5rem + 2.6vw, 3.1rem);
  line-height: 1.1;
  letter-spacing: .03em;
  position: relative;
  overflow-wrap: anywhere;
}
/* A slow, soft glow behind the names — the design's nameGlow */
.names::before{
  content: "";
  position: absolute;
  inset: -10% -6%;
  z-index: -1;
  pointer-events: none;
  background: radial-gradient(46% 46% at 50% 46%, rgba(232,194,122,.18), rgba(201,162,39,.06) 42%, transparent 70%);
  animation: nameGlow 7s ease-in-out infinite;
}
@keyframes nameGlow{ 0%, 100%{ opacity: .15; } 50%{ opacity: .7; } }
/* ---------- Embossed names ----------
   Real embossing is a raised surface: a dark edge where the stock
   falls away below the letter, and a light edge on top where it
   catches the light. The two shadows sit on opposite sides, and the
   gap between them is what reads as depth.
   Applied to the spans, which carry the foil fill — the shadows sit
   behind the clipped text and become its relief. */
.names__one,
.names__two{
  /* One hairline shadow, half a pixel down. Two offset shadows —
     which is what was here — print a second faint copy of every
     letter and the name reads as doubled, worst on the dense Kannada
     glyphs. A single sub-pixel offset gives a seated edge without a
     ghost. */
  text-shadow: 0 .5px .5px rgba(74, 46, 14, .28);
}

.names__amp{
  font-family: "Cormorant Garamond", serif;
  font-weight: 400;
  font-size: .42em;
  font-style: italic;
  color: var(--gold);
  text-shadow: 0 1px 0 rgba(255,252,244,.7);
}

/* Ornament: a rule broken by a small diamond, the way a printed
   invitation separates its blocks. */
.ornament{
  display: flex;
  align-items: center;
  gap: .7rem;
  width: min(200px, 62%);
  margin: 0 0 clamp(1.1rem, 3.2vh, 1.6rem);
  color: var(--gold);
}
.ornament__rule{
  flex: 1;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--gold));
}
.ornament__rule:last-child{
  background: linear-gradient(90deg, var(--gold), transparent);
}
.ornament__mark{ flex: none; }

/* ---------- The date, set as a plate ----------
   The day number carries it, with the weekday above and the month and
   year flanking it on hairline rules — the way an engraved invitation
   sets a date, rather than one cramped line of text. */
.when{
  display: grid;
  justify-items: center;
  gap: .5rem;
  margin: 0 0 clamp(1.1rem, 3vh, 1.5rem);
  font-family: "Marcellus", ui-serif, Georgia, serif;
  color: var(--ink);
}

.when__dow{
  font-family: "Cinzel", ui-serif, Georgia, serif;
  font-size: var(--step--1);
  letter-spacing: .34em;
  text-indent: .34em;              /* offset the trailing letter-space */
  text-transform: uppercase;
  color: #4a2a0c;
  font-weight: 600;
  text-shadow: 0 0 10px rgba(255, 255, 255, 0.9);
}

.when__mid{
  display: flex;
  align-items: center;
  gap: clamp(.6rem, 2.5vw, 1.1rem);
}

/* Month and year sit small on either side of the day, each carrying a
   hairline rule outward. */
.when__month,
.when__year{
  position: relative;
  font-size: var(--step-0);
  letter-spacing: .12em;
  text-transform: uppercase;
  color: #1e1204;
  font-weight: 600;
  white-space: nowrap;
  text-shadow: 0 0 10px rgba(255, 255, 255, 0.9);
}
.when__month::before,
.when__year::after{
  content: "";
  position: absolute;
  top: 50%;
  width: clamp(.9rem, 4vw, 2rem);
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--gold));
}
.when__month::before{ right: 100%; margin-right: .6rem; }
.when__year::after{
  left: 100%; margin-left: .6rem;
  background: linear-gradient(90deg, var(--gold), transparent);
}

.when__day{
  font-family: "Cinzel", ui-serif, Georgia, serif;
  font-weight: 700;
  font-size: clamp(2.8rem, 2rem + 3.4vw, 4rem);
  line-height: .9;
  letter-spacing: 0;
  color: #1a0e03;
  font-variant-numeric: lining-nums;
  text-shadow: 0 0 14px rgba(255, 255, 255, 0.95);
}

.when__time{
  font-size: var(--step-0);
  letter-spacing: .12em;
  color: #4a2a0c;
  font-weight: 600;
  text-shadow: 0 0 10px rgba(255, 255, 255, 0.9);
}

/* ---------- Venue ---------- */
.where{
  display: grid;
  gap: .3rem;
  margin: 0 0 clamp(1.1rem, 3.2vh, 1.6rem);
  /* Wide enough that the venue name holds one line on a desktop card. */
  max-width: 40ch;
}
.where__venue{
  font-family: "Marcellus", ui-serif, Georgia, serif;
  font-size: var(--step-1);
  letter-spacing: .02em;
  color: #1a0e03;
  font-weight: 600;
  text-shadow: 0 0 12px rgba(255, 255, 255, 0.95);
}
.where__city{
  font-size: var(--step--1);
  letter-spacing: .18em;
  text-transform: uppercase;
  color: #4a2a0c;
  font-weight: 600;
  text-shadow: 0 0 10px rgba(255, 255, 255, 0.9);
}

/* ---------- Countdown ----------
   Wraps rather than overflowing: four units fit a phone at 2×2. */
.countdown{
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: clamp(.5rem, 2vw, .9rem);
  margin: 0 0 clamp(1.2rem, 3.2vh, 1.6rem);
}
.unit{
  display: grid;
  justify-items: center;
  min-width: clamp(48px, 13vw, 60px);
  padding: .6rem .4rem;
  border: 1px solid #d9c398;
  border-radius: 4px;
  /* Lit from above, with a hairline highlight along the top edge, so
     each box reads as pressed into the stock rather than drawn on. */
  background:
    linear-gradient(180deg, rgba(255,255,255,.85), rgba(255,255,255,.5));
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.9),
    inset 0 -6px 12px -8px rgba(138,90,43,.35),
    0 1px 2px rgba(138,90,43,.12);
}
.unit__num{
  font-family: "Cinzel", ui-serif, Georgia, serif;
  font-size: var(--step-1);
  line-height: 1.2;
  color: #1a0e03;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.unit__label{
  font-size: var(--step--2);
  letter-spacing: .12em;
  text-transform: uppercase;
  color: #4a2a0c;
  font-weight: 600;
}

/* ---------- Living details ----------
   Animations that continue after the card has arrived, so it never
   settles into a still image. All are slow and low-contrast: the card
   should feel alive, not busy. */

/* The names' shine is a light band that sweeps ACROSS the words on a
   transparent overlay — it never animates the gradient inside the
   clipped text, so page-zoom no longer makes the letters flicker.
   The overlay is masked to the letterforms with its own copy of the
   text via a data attribute set in the markup. */
.names{ position: relative; }
.names__one,
.names__two{ position: relative; }

.names .foil::after{
  content: attr(data-text);
  position: absolute;
  inset: 0;
  pointer-events: none;
  /* A narrow bright streak, most of the width transparent. */
  background: linear-gradient(
    100deg,
    transparent 38%,
    rgba(255, 250, 224, .85) 48%,
    rgba(255, 255, 255, .95) 50%,
    rgba(255, 250, 224, .85) 52%,
    transparent 62%
  );
  background-size: 260% 100%;
  background-position: 120% 0;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
}
.card.is-open .names .foil::after{
  animation: foilSweep 6.5s ease-in-out 2.4s infinite;
}
@keyframes foilSweep{
  0%, 62%, 100%{ background-position: 120% 0; }
  80%         { background-position: -20% 0; }
}
.names::before{
  content: "";
  position: absolute;
  inset: -8% -4%;
  z-index: -1;
  pointer-events: none;
  background: radial-gradient(
    46% 46% at 50% 46%,
    rgba(232,194,122,.16) 0%,
    rgba(201,162,39,.06) 42%,
    transparent 70%
  );
  opacity: 0;
}
.card.is-open .names::before{
  animation: nameGlow 7s ease-in-out 1.6s infinite;
}
@keyframes nameGlow{
  0%, 100%{ opacity: .15; }
  50%     { opacity: .7; }
}

/* The ornament diamonds breathe. */
.card.is-open .ornament__mark{
  animation: markPulse 4.5s ease-in-out infinite;
  transform-origin: center;
}
@keyframes markPulse{
  0%, 100%{ opacity: .75; transform: scale(1); }
  50%     { opacity: 1;   transform: scale(1.08); }
}

/* The countdown's seconds tick with a small lift, so the card has a
   visible heartbeat. */
.unit__num.is-ticking{ animation: tick .45s cubic-bezier(.22,1,.36,1); }
@keyframes tick{
  0%  { transform: translateY(-4px); opacity: .4; }
  100%{ transform: none; opacity: 1; }
}

/* ---------- Short viewport ----------
   In a browser tab (Brave, mobile Chrome) the URL bar and toolbar take
   a slice that svh does not fully anticipate, so the actions can fall
   below the fold. Tighten the padding and margins, and collapse the
   four countdown boxes into one quiet inline line — that recovers
   enough height to reach the buttons without scrolling. Placed after
   the base .unit rules so it wins on source order. */
@media (max-height: 820px){
  .stage{ padding-block: clamp(0.5rem, 2vh, 1.1rem); }
  :root{ --card-pad-y: clamp(1.6rem, 4vh, 2.6rem); }
  .preamble{ margin-bottom: clamp(.9rem, 2.2vh, 1.3rem); }
  .names{ margin-bottom: clamp(.85rem, 2.2vh, 1.2rem); }
  .ornament{ margin-bottom: clamp(.8rem, 2vh, 1.1rem); }
  .when{ margin-bottom: clamp(.8rem, 2vh, 1.1rem); }
  .where{ margin-bottom: clamp(.8rem, 2vh, 1.1rem); }

  .countdown{ gap: 0; flex-wrap: nowrap; margin-bottom: clamp(.9rem, 2.2vh, 1.2rem); }
  .unit{
    display: flex;
    flex-direction: row;
    align-items: baseline;
    gap: .22em;
    min-width: 0;
    padding: 0;
    border: 0;
    background: none;
    box-shadow: none;
  }
  .unit + .unit::before{
    content: "·";
    align-self: center;
    margin: 0 .5em;
    color: var(--ink-faint);
  }
  .unit__num{ font-size: var(--step-0); }
  .unit__label{ font-size: var(--step--2); }
}
@media (max-height: 680px){
  .stage{ align-items: start; }
  .names{ font-size: clamp(1.7rem, 1.3rem + 2.6vw, 2.4rem); }
  .preamble{ font-size: var(--step--1); }
}

/* The card drifts very slightly, so it reads as an object resting in
   space. Desktop only: on a phone the card is tilted by finger-drag
   and this competes with that. */
@media (hover: hover){
  .card.is-open{ animation: cardFloat 9s ease-in-out infinite; }
}
/* On touch: pan-y lets a vertical drag scroll a tall card, while a
   sideways drag is caught by the JS (which preventDefaults it) and
   turned into a tilt. */
@media (hover: none){
  .card{ touch-action: pan-y; }
  .card__body{ touch-action: pan-y; }
}
@keyframes cardFloat{
  0%, 100%{ translate: 0 0; }
  50%     { translate: 0 -6px; }
}

/* Shown in place of the countdown once the day arrives */
.today{
  margin: 0;
  font-family: "Marcellus", ui-serif, Georgia, serif;
  font-size: var(--step-1);
  letter-spacing: .08em;
  color: var(--gold-deep);
}

/* ---------- Actions ----------
   Two buttons side by side on wide screens, stacked on narrow ones
   so neither is ever cramped. */
/* ---------- Icon actions ----------
   Three round icon buttons in a row. No labels: a status line below
   speaks for them, and each carries a title tooltip and aria-label. */
.actions{
  display: flex;
  justify-content: center;
  gap: clamp(.9rem, 4vw, 1.5rem);
  margin: 0 0 .6rem;
}

/* .act-btn-labeled is the tappable element (an <a> or <button>): the
   circle plus its visible label, stacked, both moving together on
   hover/press. .act-btn is just the circle — kept as its own class so
   the existing entrance/ring/hop animations below (all authored
   against .act-btn) still apply without rewriting them. */
.act-btn-labeled{
  appearance: none;
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .4rem;
  border: none;
  background: none;
  padding: 0;
  color: #6b4a18;
  cursor: pointer;
  text-decoration: none;
  transition: transform .18s ease;
}
.act-btn{
  position: relative;
  display: grid;
  place-items: center;
  width: clamp(44px, 12vw, 52px);
  aspect-ratio: 1;
  border: 1px solid rgba(169,118,47,.55);
  border-radius: 50%;
  background: linear-gradient(160deg, var(--card-1), #f1e4c0);
  box-shadow: 0 4px 12px rgba(90,60,20,.18);
  transition: box-shadow .18s ease, border-color .18s ease;
}
.act-btn svg{
  width: 46%;
  height: 46%;
}
.act-btn__label{
  font-family: var(--font-display, inherit);
  font-size: clamp(.72rem, 2.6vw, .82rem);
  letter-spacing: .04em;
  color: #8a6a3a;
  white-space: nowrap;
}
.act-btn-labeled:hover,
.act-btn-labeled:focus-visible{
  transform: translateY(-2px);
}
.act-btn-labeled:hover .act-btn,
.act-btn-labeled:focus-visible .act-btn{
  border-color: var(--gold);
  box-shadow: 0 8px 18px rgba(90,60,20,.28);
}
.act-btn-labeled:active{ transform: translateY(0) scale(.96); transition-duration: .06s; }
.act-btn-labeled[disabled]{ opacity: .55; cursor: default; transform: none; }

/* ---------- Drawing the eye to the actions ----------
   1. When the card finishes arriving, each button lifts into place and
      throws a single expanding gold ring — a "here, tap these" beat.
   2. After that, a soft gold glow breathes across the row on a slow
      loop so the buttons keep reading as interactive, not decorative.
   3. The pin icon itself also bobs on a slow loop — a location pin
      that hops in place is a well-worn "tap for the map" convention,
      and it's a much stronger read at a glance than a glow alone.
   All three are transform/opacity only, and all three stop for
   reduced motion. */
.card.is-open .act-btn{
  animation: actBtnIn .7s cubic-bezier(.2,1.3,.4,1) 1.05s backwards;
}
.card.is-open .act-btn__label{
  animation: actBtnIn .7s cubic-bezier(.2,1.3,.4,1) 1.05s backwards;
}

@keyframes actBtnIn{
  0%   { opacity: 0; transform: translateY(10px) scale(.8); }
  60%  { opacity: 1; }
  100% { opacity: 1; transform: translateY(0) scale(1); }
}

/* The pin glyph hops, the way a map marker "drops" — pauses between
   hops so it reads as a repeated cue rather than constant fidgeting. */
.card.is-open #mapBtn svg{
  transform-origin: 50% 85%;   /* the pin's point, so it looks like it's tapping down */
  animation: pinHop 2.6s cubic-bezier(.34,1.56,.64,1) 2s infinite;
}
@keyframes pinHop{
  0%, 55%, 100% { transform: translateY(0)    scale(1); }
  70%           { transform: translateY(-7px) scale(1.05); }
  85%           { transform: translateY(0)    scale(.97); }
  92%           { transform: translateY(-2px) scale(1.01); }
}
/* Once tapped, the pin has made its point — let it rest. */
.act-btn-labeled.is-used svg{ animation: none !important; }

/* The expanding ring + the recurring glow both ride ::after so the
   button's own hover transform is untouched. */
.act-btn::after{
  content: "";
  position: absolute;
  inset: -4px;
  border-radius: 50%;
  border: 2px solid var(--gold-lit);
  opacity: 0;
  pointer-events: none;
}
.card.is-open .act-btn::after{
  animation:
    actRing 1s ease-out 1.25s backwards,
    actPulse 2.6s ease-in-out 2s infinite;
}

@keyframes actRing{
  0%   { opacity: .9; transform: scale(.7); }
  100% { opacity: 0;  transform: scale(1.7); }
}
@keyframes actPulse{
  0%, 55%, 100% { opacity: 0;   transform: scale(1);    box-shadow: 0 0 0 0 rgba(232,194,122,0); }
  70%           { opacity: .7;  transform: scale(1.15); box-shadow: 0 0 16px 3px rgba(232,194,122,.4); }
}
/* Once a button has been used it has made its point — calm it down. */
.act-btn-labeled.is-used .act-btn::after{ animation: none !important; opacity: 0; }

@media (prefers-reduced-motion: reduce){
  .card.is-open .act-btn,
  .card.is-open .act-btn__label,
  .card.is-open .act-btn::after,
  .card.is-open #mapBtn svg{ animation: none !important; }
  .act-btn::after{ opacity: 0; }
}

/* A tiny mark on the reminder button once it is set. */
.act-btn__pip{
  position: absolute;
  top: -2px; right: -2px;
  width: 12px; height: 12px;
  border-radius: 50%;
  background: var(--gold-deep);
  border: 2px solid var(--card-1);
}
.act-btn__pip[hidden]{ display: none; }

/* Status line: reads out what a tapped icon did, since it has no text.
   Reserves its line height so the card does not jump when it fills. */
.act-status{
  min-height: 1.2em;
  margin: 0 0 clamp(1rem, 3vh, 1.5rem);
  font-size: var(--step--2);
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--gold-deep);
  opacity: 0;
  transition: opacity .3s ease;
}
.act-status.is-shown{ opacity: 1; }

.dots{ display: flex; gap: .75rem; margin-top: clamp(1.5rem, 4vh, 2.2rem); }
.dots i{
  width: 5px; height: 5px;
  border-radius: 50%;
  background: var(--gold-lit);
  animation: pulseGlow 2s ease-in-out infinite;
}
.dots i:nth-child(2){ background: var(--gold); animation-delay: .3s; }
.dots i:nth-child(3){ animation-delay: .6s; }
@keyframes pulseGlow{ 0%,100%{ opacity: .5; } 50%{ opacity: 1; } }

/* =========================================================
   Accessibility floor
   ========================================================= */
:focus-visible{
  outline: 2px solid var(--gold-lit);
  outline-offset: 3px;
}

/* Phones in portrait: tighten the vertical rhythm so the whole card
   has a chance of fitting the screen without scrolling. */
@media (max-width: 30rem){
  :root{
    --card-pad-y: clamp(2rem, 8vw, 2.8rem);
    --card-pad-x: clamp(1.5rem, 7vw, 2rem);
  }
  /* Keep the gap over the couple's faces so names sit below their chins */
  .preamble{ margin-bottom: clamp(70px, 12vh, 100px); }
  .names{ margin-bottom: .85rem; font-size: clamp(1.75rem, 1.3rem + 2.4vw, 2.6rem); }
  .ornament{ margin-bottom: .85rem; }
  .when{ margin-bottom: .85rem; }
  .when__day{ font-size: clamp(2.2rem, 1.6rem + 3vw, 3.4rem); }
  .where{ margin-bottom: .85rem; }
  .countdown{ margin-bottom: .9rem; }
  .actions{ margin-bottom: .4rem; }
  .act-status{ margin-bottom: .6rem; }
  .dots{ margin-top: .85rem; }
  .stage{ padding: 1.4rem 1.25rem; }

  /* Kannada on a phone: one notch smaller again so the card holds. */
  .lang-kn .names{ font-size: clamp(1.7rem, 1.25rem + 2.4vw, 2.6rem); }
  .lang-kn .where__venue{ font-size: var(--step-0); }
  .lang-kn .when__day{ font-size: clamp(2rem, 1.5rem + 2.6vw, 3rem); }
}

@media (prefers-reduced-motion: reduce){
  .mote, .dots i, .depth__layer{ animation: none !important; }
  .titles__line.is-in{ animation: none !important; opacity: 0; }
  .flash.is-firing{ animation: none !important; opacity: 0; }
  .card{ animation: none; }
  .depth__layer{ transition: none !important; }
  /* The gate still gates — it is a gesture, not decoration — but it
     stops breathing and simply sits there. */
  .gate__ring{ animation: none !important; opacity: 0; }
  /* Content must be visible even though its arrival animation is off. */
  .card__body > *{ opacity: 1; transform: none; filter: none; }
  .card__frame [data-draw]{ stroke-dashoffset: 0; }
  .card__frame [data-dot]{ opacity: 1; transform: none; }
  .card__pattern{ opacity: 1; }
  .card__sheet::before{ display: none; }
  .names .foil::after{ display: none; }
  .petals{ display: none; }
  /* Light effects hold at a resting state rather than cycling. */
  .card::before{ opacity: 1; animation: none !important; }
  .card__shimmer{ display: none; }
  .card.is-open .names{ animation: none !important; filter: none; }
  *, *::before, *::after{
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
}

/* =========================================================
   PRINT
   Someone will try to print this. Without these rules it comes out
   as a dark rectangle: the ground is a background image browsers
   drop, and the foil text is transparent with nothing behind it.
   ========================================================= */
@media print{
  @page{ margin: 12mm; }

  html, body{
    background: #fff !important;
    color: #2a1c0c !important;
  }

  /* The stage's dark wash, the 3D canvas, the ambient layers, and
     every control are all screen furniture. */
  .depth, .vignette, .motes, .petals, .flash,
  #ring-canvas, .titles, .controls,
  .card__shimmer, .card__sheet::before{
    display: none !important;
  }

  .stage{
    display: block;
    min-height: 0;
    padding: 0;
    background: #fff !important;
    overflow: visible;
  }

  .card{
    width: 100%;
    max-width: none;
    animation: none !important;
    transform: none !important;
    filter: none !important;
    page-break-inside: avoid;
  }
  .card::before, .card::after{ display: none !important; }

  .card__sheet{
    background: #fff !important;
    box-shadow: none !important;
    border: 1px solid #b8956a;
  }
  .card__pattern{ opacity: 1 !important; animation: none !important; }
  /* A full-bleed photo under the text is a lot of ink coverage and a
     lot of room for a home printer to shift the card's colours; the
     card prints as it always did, clean ink on white. */
  .card__portrait, .card__veil{ display: none !important; }

  /* Foil is transparent text over a gradient; in print it must be
     ink. */
  .foil, .names__one, .names__two{
    background: none !important;
    -webkit-text-fill-color: #6b4a18 !important;
    color: #6b4a18 !important;
    text-shadow: none !important;
    filter: none !important;
    animation: none !important;
  }

  .card__body > *{
    opacity: 1 !important;
    transform: none !important;
    filter: none !important;
    animation: none !important;
  }

  .card__frame [data-draw]{ stroke-dashoffset: 0 !important; animation: none !important; }
  .card__frame [data-dot]{ opacity: 1 !important; animation: none !important; transform: none !important; }

  /* Icon buttons mean nothing on paper. Print the venue URL instead. */
  .actions, .act-status{ display: none !important; }
  .where::after{
    content: "Map: https://maps.app.goo.gl/sGa79usqNU3Z5MGT8";
    display: block;
    margin-top: .6rem;
    font-size: .75em;
    letter-spacing: 0;
    text-transform: none;
    color: #6b4a18;
  }

  .unit{ background: none !important; box-shadow: none !important; }
}
