/* =====================================================================
   Eligent Atelier — Invitation Template Styles (template.css)
   ---------------------------------------------------------------------
   Every color and typeface comes from the CSS variables injected by
   EA_TEMPLATE.cssVars():
     --bg --ink --ink-soft --accent --accent-soft --panel --on-accent
     --font-display --font-script --font-body
   Mobile-first, 480px centred column, same bones as the sample themes.
   Used by builder/preview.html and inlined into exported invitations.
   ===================================================================== */

* { box-sizing: border-box; }

/* Procedural cotton-paper grain — an feTurbulence data URI, so it costs no
   image file and no network request, and it survives the single-file
   export intact. Recovered from samples/05-burgundy-oldmoney. */
:root {
  --ea-paper-grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 300 300'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

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

body {
  margin: 0;
  font-family: var(--font-body);
  font-weight: 400;
  color: var(--ink);
  background-color: var(--bg);
  line-height: 1.7;
  letter-spacing: 0.01em;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img { max-width: 100%; }

[hidden] { display: none !important; }

/* ---------------------------------------------------------------------
   Column + sections
   --------------------------------------------------------------------- */
.invite {
  max-width: 480px;
  margin: 0 auto;
  overflow: hidden;
  transition: transform 1.2s cubic-bezier(0.5, 0, 0.2, 1) 0.45s;
}
/* while sealed, the letter waits slightly "inside" the envelope; when the
   envelope opens it eases up to its resting place — the rising-letter feel */
body.envelope-locked .invite { transform: translateY(6vh) scale(0.985); }

.sec {
  padding: 56px 26px;
  text-align: center;
}

.eyebrow {
  font-family: var(--font-body);
  font-weight: 500;
  font-size: 0.72rem;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: var(--accent);
  margin: 0 0 16px;
}

.script-h {
  font-family: var(--font-script);
  font-weight: 400;
  font-size: 2.4rem;
  line-height: 1.15;
  color: var(--accent);
  margin: 0 0 18px;
}

.lede {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 1.15rem;
  line-height: 1.65;
  color: var(--ink);
  margin: 0 auto;
  max-width: 30ch;
}

/* fine double rule */
.thin-rule {
  width: 120px;
  height: 0;
  margin: 0 auto 26px;
  border: 0;
  border-top: 1px solid var(--accent-soft);
  position: relative;
}
.thin-rule::after {
  content: "";
  position: absolute;
  left: 12px; right: 12px;
  top: 4px;
  border-top: 1px solid var(--accent-soft);
  opacity: 0.6;
}

/* ---------------------------------------------------------------------
   Buttons — generous tap targets (≥44px)
   --------------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 50px;
  padding: 13px 30px;
  border-radius: 6px;
  border: 1px solid transparent;
  font-family: var(--font-body);
  font-weight: 500;
  font-size: 0.8rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  text-decoration: none;
  cursor: pointer;
  transition: transform 0.25s ease, box-shadow 0.25s ease,
              background 0.25s ease, color 0.25s ease;
}
.btn--solid {
  background: var(--accent);
  color: var(--on-accent);
  border-color: var(--accent);
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.16);
}
.btn--solid:hover { transform: translateY(-2px); box-shadow: 0 12px 26px rgba(0, 0, 0, 0.2); }
.btn--outline {
  background: transparent;
  color: var(--accent);
  border-color: var(--accent);
}
.btn--outline:hover { background: var(--accent); color: var(--on-accent); transform: translateY(-2px); }

/* =====================================================================
   ENVELOPE — shared shell
   ===================================================================== */
body.envelope-locked { overflow: hidden; }

.env {
  position: fixed;
  inset: 0;
  z-index: 50;
  overflow: hidden;
}
.env[hidden] { display: none !important; }

.env__content {
  position: relative;
  z-index: 6;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 24px;
}
.env__eyebrow {
  font-family: var(--font-body);
  font-weight: 500;
  font-size: 0.7rem;
  letter-spacing: 0.32em;
  text-transform: uppercase;
  color: var(--env-ink, var(--on-accent));
  opacity: 0.85;
  margin: 0 0 10px;
}
.env__guest {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.9rem;
  line-height: 1.15;
  color: var(--env-ink, var(--on-accent));
  margin: 0 0 26px;
  max-width: 16ch;
}
.env__open {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 50px;
  margin-top: 6px;
  padding: 14px 34px;
  border-radius: 6px;
  border: 1px solid var(--accent-soft);
  background: var(--panel);
  color: var(--accent);
  font-family: var(--font-body);
  font-weight: 500;
  font-size: 0.8rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  cursor: pointer;
  box-shadow: 0 8px 22px rgba(0, 0, 0, 0.35), inset 0 1px 0 rgba(255, 255, 255, 0.5);
  transition: transform 0.25s ease, filter 0.25s ease, opacity 0.3s ease;
}
.env__open:hover { transform: translateY(-2px); filter: brightness(1.04); }
.env__hint {
  margin: 14px 0 0;
  font-size: 0.68rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--env-ink, var(--on-accent));
  opacity: 0.6;
  /* every neighbour eases out; without this the hint popped in one frame */
  transition: opacity 0.3s ease;
}

/* ---------------------------------------------------------------------
   Wax seal — pure CSS poured wax, tinted by the palette, monogram inside
   --------------------------------------------------------------------- */
.wax-seal {
  position: relative;
  display: inline-grid;
  place-items: center;
  width: 96px;
  height: 96px;
  margin: 4px auto 26px;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.35rem;
  letter-spacing: 0.04em;
  color: var(--on-accent);
  text-shadow: 0 1px 1px rgba(0, 0, 0, 0.4);
  /* uneven poured edge */
  border-radius: 46% 54% 51% 49% / 55% 47% 53% 45%;
  background-color: var(--accent);
  background-image:
    radial-gradient(circle at 32% 26%, rgba(255, 255, 255, 0.32), transparent 45%),
    radial-gradient(circle at 64% 80%, rgba(0, 0, 0, 0.32), transparent 50%);
  box-shadow:
    0 10px 22px rgba(0, 0, 0, 0.3),
    0 3px 6px rgba(0, 0, 0, 0.22),
    inset 0 2px 4px rgba(255, 255, 255, 0.3),
    inset 0 -5px 10px rgba(0, 0, 0, 0.4);
  transition: transform 0.5s ease, opacity 0.45s ease;
}
/* irregular wax lobe peeking out behind the disc */
.wax-seal::before {
  content: "";
  position: absolute;
  inset: -6%;
  z-index: -1;
  border-radius: 58% 42% 50% 50% / 45% 57% 43% 55%;
  transform: rotate(33deg);
  background-color: var(--accent);
  background-image: radial-gradient(circle at 40% 35%, rgba(255, 255, 255, 0.12), rgba(0, 0, 0, 0.3));
  box-shadow: inset 0 -3px 8px rgba(0, 0, 0, 0.4);
}
/* the ring pressed by the stamp die */
.wax-seal::after {
  content: "";
  position: absolute;
  inset: 14%;
  border-radius: 50%;
  box-shadow:
    inset 0 3px 7px rgba(0, 0, 0, 0.35),
    inset 0 -2px 4px rgba(255, 255, 255, 0.2);
}

/* =====================================================================
   ENVELOPE STYLE 1 — .env--fold
   An envelope OBJECT on a backdrop. The seal cracks, the flap folds back
   showing its liner, and the card LIFTS OUT of the mouth before the whole
   envelope sinks away. Total 2.4s (matches data-reveal-ms="2500").

   The layer sandwich is the whole trick, and the order matters:
     back(1) < card pocket(2) < throat(3) < side flaps(4) < bottom(5)
     < top flap(6, drops to 0 once folded) < addressee + seal(7,8)
   The panels are opaque, so they are what cuts the card off at the mouth
   as it rises. The card itself needs no clip.
   ===================================================================== */
.env--fold {
  display: flex;
  flex-direction: column;
  align-items: center;
  /* bottom-anchored on purpose: the flap folds UP past the envelope's top
     edge, so the headroom above the stage is what keeps the liner on screen */
  justify-content: flex-end;
  padding: 3vh 0 5vh;
  gap: 3.5vh;
  /* the field the envelope sits on — deep, vignetted, never flat */
  background-color: var(--env-field, #1b1013);
  background-image:
    radial-gradient(120% 85% at 50% 16%, rgba(255, 255, 255, 0.09), rgba(255, 255, 255, 0) 56%),
    radial-gradient(140% 120% at 50% 108%, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0) 64%);
  transition: opacity 0.45s ease 1.95s;
}
.env--fold.is-opening {
  opacity: 0;
  pointer-events: none;
}
/* cotton-paper grain over the whole field (recovered from sample 05) */
/* Negative z-index keeps it above the field's gradients but below the
   envelope and the tap target — the envelope's own panels carry their own
   grain, and an overlay blend across the button only muddied it. */
.env--fold::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  opacity: 0.3;
  mix-blend-mode: overlay;
  background-image: var(--ea-paper-grain);
  background-size: 180px 180px;
}

/* ---- the envelope object -------------------------------------------- */
.env__stage {
  position: relative;
  /* the 45vh term keeps the envelope plus its tap target on screen on
     short viewports (landscape phones, small Androids) */
  width: min(78vw, 45vh, 320px);
  aspect-ratio: 5 / 7;
  perspective: 1100px;
  perspective-origin: 50% 30%;
  transition: transform 0.6s cubic-bezier(0.65, 0, 0.35, 1) 1.72s,
              opacity 0.5s ease 1.78s;
}
.env--fold.is-opening .env__stage {
  transform: translateY(10%) scale(0.93);
  opacity: 0;
}

/* the envelope's back face. Without this the flap opens onto the page and
   the reveal reads as a triangular wipe instead of as mail. */
.env__back {
  position: absolute;
  inset: 0;
  z-index: 1;
  border-radius: 3px;
  background-color: var(--env-shell, var(--accent));
  background-image: linear-gradient(160deg, rgba(255, 255, 255, 0.10), rgba(0, 0, 0, 0.28) 60%, rgba(0, 0, 0, 0.40));
  box-shadow: 0 26px 60px rgba(0, 0, 0, 0.5), 0 6px 14px rgba(0, 0, 0, 0.36);
}

/* ---- the card inside ------------------------------------------------- */
/* The pocket is what keeps the card honest: its clip is open far above the
   envelope so the card can travel out of the mouth, but closed at the
   sides and bottom so it can never leak out through the paper. A negative
   inset() offset expands the clip box outward; overflow:hidden cannot do
   this selectively, and would trap the card inside. */
.env__pocket {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  clip-path: inset(-200% 0 0 0);
}
.env__card {
  position: absolute;
  left: 6%;
  right: 6%;
  bottom: 4%;
  height: 84%;
  border-radius: 2px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  padding: 26px 18px;
  text-align: center;
  background-color: var(--panel);
  background-image: linear-gradient(to bottom, rgba(255, 255, 255, 0.55), rgba(0, 0, 0, 0.04));
  /* the thin top edge is what reads as paper thickness at the mouth */
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.6);
}
.env--fold.is-opening .env__card {
  animation: ea-card-lift 1.1s cubic-bezier(0.2, 1, 0.3, 1) 0.52s forwards;
}
/* A constant-velocity rail is the loudest tell of a fake. The pause at
   16-22% is the grip, and the rotation changes sign so it is not a slide. */
@keyframes ea-card-lift {
  0%   { transform: translateY(0) rotate(0deg); }
  16%  { transform: translateY(-4%) rotate(0.35deg); }
  22%  { transform: translateY(-4.6%) rotate(0.3deg); }
  70%  { transform: translateY(-40%) rotate(-0.8deg); }
  88%  { transform: translateY(-48%) rotate(0.5deg); }
  100% { transform: translateY(-46%) rotate(-1deg); }
}
.env__card-rule { width: 46px; height: 1px; background: var(--accent-soft); flex: none; }
.env__card-names {
  margin: 0;
  /* the card is a centred flex column, so text boxes size to max-content
     and never wrap unless they are told to fill the line */
  width: 100%;
  font-family: var(--font-display);
  font-size: 1.5rem;
  line-height: 1.18;
  color: var(--accent);
  overflow-wrap: break-word;
}
.env__card-amp {
  font-family: var(--font-script);
  font-size: 1.05rem;
  color: var(--accent-soft);
  padding: 0 0.35em;
}
.env__card-date {
  margin: 0;
  font-family: var(--font-body);
  /* "Thursday, 27 August 2026" has to fit inside a 5:7 card at 6% inset,
     so the tracking is tighter here than elsewhere and it may wrap */
  width: 100%;
  font-size: 0.54rem;
  letter-spacing: 0.14em;
  line-height: 1.5;
  text-transform: uppercase;
  color: var(--ink-soft);
  overflow-wrap: break-word;
}

/* the three folded-in panels — one key light from the top-left, so
   adjacent panels differ in value at the seams and the folds read as
   creases. Opaque on purpose: they cut the rising card at the mouth. */
.env__side, .env__bottom, .env__flap {
  position: absolute;
  inset: 0;
}
.env__side--l {
  background-color: var(--env-shell, var(--accent));
  background-image: linear-gradient(115deg, rgba(255, 255, 255, 0.12), rgba(0, 0, 0, 0.26) 55%, rgba(0, 0, 0, 0.40));
  clip-path: polygon(0 0, 50% 50%, 0 100%);
  z-index: 4;
  filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.45));
}
.env__side--r {
  background-color: var(--env-shell, var(--accent));
  background-image: linear-gradient(245deg, rgba(255, 255, 255, 0.04), rgba(0, 0, 0, 0.32) 55%, rgba(0, 0, 0, 0.46));
  clip-path: polygon(100% 0, 100% 100%, 50% 50%);
  z-index: 4;
  filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.45));
}
.env__bottom {
  background-color: var(--env-shell, var(--accent));
  background-image: linear-gradient(to top, rgba(0, 0, 0, 0.34), rgba(0, 0, 0, 0.06) 62%, rgba(255, 255, 255, 0.08));
  clip-path: polygon(0 100%, 100% 100%, 50% 50%);
  z-index: 5;
  /* the lit mouth edge is what makes the card read as sliding BEHIND paper */
  filter: drop-shadow(0 -1px 0 rgba(255, 255, 255, 0.16)) drop-shadow(0 -6px 12px rgba(0, 0, 0, 0.34));
}
/* cotton-paper grain, clipped by each panel's own shape (sample 05) */
.env__back::after,
.env__side--l::after,
.env__side--r::after,
.env__bottom::after {
  content: "";
  position: absolute;
  inset: 0;
  background-image: var(--ea-paper-grain);
  background-size: 180px 180px;
  mix-blend-mode: soft-light;
  opacity: 0.45;
  pointer-events: none;
}

/* ---- the top flap ---------------------------------------------------- */
/* Hinged at the top edge and only half the envelope tall, so folding back
   leaves it VISIBLE against the backdrop. On the old full-screen envelope
   it swung off the top of the viewport, which is why the liner we already
   had was never seen once. */
/* The element is exactly as tall as the paper it draws — 54% of the stage,
   so its point lands just below where the side panels converge at 50%, and
   the closed envelope reads as one continuous sheet. Sizing the element to
   the paint also keeps the 3D fold predictable: a full-height element with
   an empty bottom half drags the perspective projection and throws the
   folded flap clean off the top of the screen. */
.env__flap {
  bottom: auto;
  height: 54%;
  transform-origin: top center;
  transform-style: preserve-3d;
  z-index: 6;
  /* z-index does not interpolate, so this 0s transition with a delay is a
     deliberate discrete step. It drops the flap behind the envelope just
     past vertical — where real paper leaves your view — which also puts
     the folded liner behind the rising card instead of over it. */
  transition: z-index 0s linear 0.62s;
}
/* the flap starts 260ms in, AFTER the seal has broken. The old sequence
   started it at 50ms while the seal was still intact until 500ms, so the
   envelope opened through an unbroken seal. */
.env--fold.is-opening .env__flap {
  animation: ea-flap-open 0.74s cubic-bezier(0.42, 0.08, 0.3, 1) 0.26s forwards;
  z-index: 0;
}
/* paper isn't rigid: it bows as it lifts. -164deg rather than -180deg, so
   the fold keeps some thickness instead of collapsing to a line. */
@keyframes ea-flap-open {
  0%   { transform: rotateX(0deg); }
  45%  { transform: rotateX(-94deg) scaleY(1.06); }
  75%  { transform: rotateX(-140deg) scaleY(0.98); }
  100% { transform: rotateX(-152deg) scaleY(1); }
}
.env__face {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
}
.env__face--back { transform: rotateX(180deg); }
/* Curved euro-flap shoulders instead of a mathematically sharp V, and
   every fill is a palette variable so all 16 packs work unchanged. */
.env__flap-paper { fill: var(--env-shell, var(--accent)); }
.env__flap-edge { fill: none; stroke: rgba(255, 236, 220, 0.16); stroke-width: 1; }
/* the outer face darkens as it turns away from the light — one property,
   and the flap suddenly has a light source */
.env__flap-shade { fill: #000; opacity: 0; transition: opacity 0.6s ease 0.3s; }
.env--fold.is-opening .env__flap-shade { opacity: 0.5; }
/* the liner is a lining SHEET inset inside the flap, not stripes running
   off the cut edge. It starts in shadow (it is inside a closed envelope)
   and brightens as it rotates into view. */
.env__liner { fill: url(#ea-liner-pat); stroke: rgba(0, 0, 0, 0.22); stroke-width: 0.4; }
.env__liner-bg { fill: var(--env-liner, var(--accent-soft)); }
.env__liner-motif { fill: none; stroke: rgba(0, 0, 0, 0.3); stroke-width: 0.5; }
.env__liner-dot { fill: rgba(0, 0, 0, 0.22); }
.env__liner-shade { fill: #000; opacity: 0.45; transition: opacity 0.5s ease 0.42s; }
.env--fold.is-opening .env__liner-shade { opacity: 0; }

/* the shade the closed flap casts down onto the card. It recedes as the
   flap opens, so the card emerges from shadow into light — that sells
   "inside becoming outside" harder than the travel alone does. */
.env__throat {
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 56%;
  z-index: 3;
  background: linear-gradient(to bottom, rgba(0, 0, 0, 0.55), rgba(0, 0, 0, 0) 88%);
  transition: opacity 0.5s ease 0.5s;
  pointer-events: none;
}
.env--fold.is-opening .env__throat { opacity: 0; }

/* ---- the addressee, printed on the envelope face like real mail ------ */
.env__addr {
  position: absolute;
  left: 9%;
  right: 9%;
  bottom: 14%;
  z-index: 7;
  text-align: center;
  transition: opacity 0.3s ease 0.1s;
}
.env--fold.is-opening .env__addr { opacity: 0; }
.env--fold .env__eyebrow { font-size: 0.55rem; letter-spacing: 0.26em; margin: 0 0 6px; }
.env--fold .env__guest {
  font-size: 1.4rem;
  font-weight: 500;
  margin: 0;
  /* household names from the paste importer run long ("Nimal & family") */
  max-width: none;
  overflow-wrap: break-word;
}
.env__addr-rule {
  display: block;
  width: 58%;
  height: 1px;
  margin: 10px auto 0;
  background: linear-gradient(to right, transparent, var(--accent-soft), transparent);
  opacity: 0.7;
}

/* ---- the tap target, on the backdrop below the envelope -------------- */
.env--fold .env__content {
  position: relative;
  z-index: 8;
  padding: 0;
  transition: opacity 0.3s ease;
}
.env--fold.is-opening .env__content { opacity: 0; }

/* ---- the wax seal, which now CRACKS --------------------------------- */
/* Two halves of one disc, sitting at the flap's point. Complementary
   clip-paths along a shared ragged polyline, overlapping ~1.5% so no
   hairline seam shows while the seal is still whole — the crack must
   appear only at the moment it breaks. */
.env__seal {
  position: absolute;
  left: 50%;
  top: 54%;
  width: 82px;
  height: 82px;
  margin: -41px 0 0 -41px;
  z-index: 8;
  pointer-events: none;
}
.env__seal--l { clip-path: polygon(0 0, 52.5% 0, 48.5% 34%, 54.5% 62%, 50.5% 100%, 0 100%); }
.env__seal--r { clip-path: polygon(49.5% 0, 100% 0, 100% 100%, 47.5% 100%, 51.5% 62%, 45.5% 34%); }
/* The seal keeps its own poured round edge; the wrapper does the cutting.
   It is inset inside the wrapper so the irregular ::before lobe stays
   within the clip instead of being squared off at the wrapper's corners. */
.env--fold .wax-seal {
  width: 86%;
  height: 86%;
  margin: 7%;
  font-size: 1.05rem;
  /* the shell is the accent on light packs, so an accent seal would be
     invisible against it — deepen it so the wax reads as a separate object */
  background-color: var(--env-seal, var(--accent));
}
/* the poured lobe behind the disc follows the same tone. ::after is the
   pressed ring and is box-shadow only — leave its background alone. */
.env--fold .wax-seal::before { background-color: var(--env-seal, var(--accent)); }
/* ~140ms of resistance before failure is what reads as CRACKING rather
   than fading. The two halves then break asymmetrically — equal halves
   travelling equal distances look mechanical. */
.env--fold.is-opening .env__seal--l { animation: ea-seal-l 0.84s cubic-bezier(0.3, 0.9, 0.4, 1) forwards; }
.env--fold.is-opening .env__seal--r { animation: ea-seal-r 0.89s cubic-bezier(0.3, 0.9, 0.4, 1) forwards; }
@keyframes ea-seal-l {
  0%   { transform: translate(0, 0) rotate(0deg); opacity: 1; }
  17%  { transform: translate(-1px, 0) rotate(-1.5deg); opacity: 1; }
  100% { transform: translate(-16px, 30px) rotate(-26deg); opacity: 0; }
}
@keyframes ea-seal-r {
  0%   { transform: translate(0, 0) rotate(0deg); opacity: 1; }
  16%  { transform: translate(1px, 0) rotate(1.5deg); opacity: 1; }
  100% { transform: translate(17px, 34px) rotate(29deg); opacity: 0; }
}

/* the fade style keeps its original single-seal behaviour */
.env--fade.is-opening .wax-seal { transform: scale(1.4) rotate(-12deg); opacity: 0; }
.env.is-opening .env__open,
.env.is-opening .env__hint { opacity: 0; }
/* .env::before is the heritage themes' inner rule (see the per-theme
   block) — its transition lives here so reduced motion can cancel it */
.env::before, .env__orn, .env__lantern { transition: opacity 0.4s ease 0.1s; }
.env.is-opening .env__orn,
.env.is-opening .env__lantern { opacity: 0; }

/* =====================================================================
   ENVELOPE STYLE 2 — .env--fade
   Elegant fade/scale-away (adapted from sample 06).
   ===================================================================== */
.env--fade {
  display: grid;
  place-items: center;
  padding: 28px;
  background-color: var(--env-shell, var(--accent));
  background-image:
    radial-gradient(120% 90% at 50% 8%, rgba(255, 255, 255, 0.14), rgba(255, 255, 255, 0) 45%),
    radial-gradient(130% 120% at 50% 112%, rgba(0, 0, 0, 0.30), rgba(0, 0, 0, 0) 60%);
  transition: opacity 0.9s ease, transform 1.1s ease;
}
.env--fade.is-opening {
  opacity: 0;
  transform: translateY(-14px) scale(1.04);
  pointer-events: none;
}
/* framed card in the middle, like a hand-addressed envelope face */
.env--fade .env__content {
  width: 100%;
  max-width: 340px;
  padding: 40px 26px 34px;
  border: 1px solid var(--accent-soft);
  border-radius: 6px;
  background: rgba(0, 0, 0, 0.14);
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.35);
}
.env--fade .env__content::before {
  content: "";
  position: absolute;
  inset: 8px;
  border: 1px solid var(--accent-soft);
  opacity: 0.45;
  border-radius: 3px;
  pointer-events: none;
}

/* =====================================================================
   ENVELOPE STYLE 3 — .env--cover
   One full-bleed illustration (the theme's cover art, or the couple's
   own hero photo) under a bottom-weighted scrim, with the invitation
   copy set on it. Opening fades and scales the whole cover away
   (≈1.2s, matches data-reveal-ms="1200").
   ===================================================================== */
.env--cover {
  /* a deep field behind the art, so the first paint is never white */
  background-color: #17090e;
  transition: opacity 1.1s ease, transform 1.2s ease;
}

/* =====================================================================
   ENVELOPE STYLE 4 — .env--reel
   A short film behind the invitation, sharing the cover style's scrim and
   copy treatment. Falls back through cover -> hero photo -> fade, and the
   single-file export always steps down (the film is megabytes and sits at
   a root-absolute path).
   ===================================================================== */
.env--reel {
  background-color: #0b0709;
  transition: opacity 1.1s ease, transform 1.2s ease;
}
/* CONTAIN, not cover. A 16:9 clip cropped to a portrait phone loses
   whatever sits at the left and right edges, which for a couple walking
   toward each other is both of them. Contain letterboxes it into a
   cinematic band on the field; a true 9:16 clip fills the screen. */
.env__reel {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: block;
  width: 100%;
  height: 100%;
  /* COVER, and the asset rules require a 9:16 portrait source (see
     art/README.md). Phones are taller than 16:9 — a 390x844 viewport is
     about 9:19.5 — so even a correct 9:16 clip letterboxes under contain.
     Cover fills the screen and crops a little off the top and bottom,
     which is why the asset rule also asks for a centred subject. */
  object-fit: cover;
  /* bias up: the faces matter more than the floor, and the bottom third is
     under the scrim carrying the names anyway */
  object-position: center 40%;
  background-color: #0b0709;
}
.env--reel.is-opening,
.env--cover.is-opening {
  opacity: 0;
  transform: scale(1.06);
  pointer-events: none;
}
/* the art fills any phone, however tall */
.env__cover-img {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}
/* dark at the bottom so the copy reads, a touch at the top so the
   phone's status bar stays legible over a bright sky */
.env__scrim {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background: linear-gradient(
    to top,
    rgba(0, 0, 0, 0.92) 0%,
    rgba(0, 0, 0, 0.86) 18%,
    rgba(0, 0, 0, 0.62) 32%,
    rgba(0, 0, 0, 0.22) 48%,
    rgba(0, 0, 0, 0.04) 66%,
    rgba(0, 0, 0, 0.3) 100%
  );
}
/* the copy sits in the bottom third, where the scrim is deepest */
.env--reel .env__content,
.env--cover .env__content {
  position: absolute;
  inset: 0;
  justify-content: flex-end;
  padding: 32px 26px calc(26px + env(safe-area-inset-bottom, 0px));
}
.env--reel .env__eyebrow,
.env--reel .env__guest,
.env--reel .env__hint,
.env--cover .env__eyebrow,
.env--cover .env__guest,
.env--cover .env__hint {
  color: #f7f1e8;
  text-shadow: 0 1px 10px rgba(0, 0, 0, 0.55);
}
.env--reel .env__guest,
.env--cover .env__guest {
  font-size: 1.5rem;
  margin: 0 0 18px;
}
.env__cover-names {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(2.1rem, 9vw, 3rem);
  line-height: 1.08;
  color: #f7f1e8;
  text-shadow: 0 2px 20px rgba(0, 0, 0, 0.6);
}
.env__cover-date {
  margin: 12px 0 26px;
  font-family: var(--font-body);
  font-size: 0.72rem;
  letter-spacing: 0.26em;
  text-transform: uppercase;
  color: #f7f1e8;
  opacity: 0.86;
  text-shadow: 0 1px 10px rgba(0, 0, 0, 0.55);
}
/* The open button reads as a gold pill on the art. The extra .env
   carries this past the per-theme corner overrides at the foot of this
   file — on a photograph the pill is the shape that works. */
.env.env--reel .env__open,
.env.env--cover .env__open {
  min-height: 52px;
  padding: 15px 38px;
  border: 0;
  border-radius: 999px;
  color: var(--ink);
  background-color: var(--accent-soft);
  background-image: linear-gradient(
    to bottom,
    rgba(255, 255, 255, 0.45),
    rgba(255, 255, 255, 0) 48%,
    rgba(0, 0, 0, 0.18)
  );
  letter-spacing: 0.2em;
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.45), inset 0 1px 0 rgba(255, 255, 255, 0.55);
}

/* ---------------------------------------------------------------------
   Envelope ornaments (themes that dress the envelope)
   --------------------------------------------------------------------- */
.env__orn {
  position: absolute;
  z-index: 5;
  pointer-events: none;
}
.env__orn-bou {
  width: min(52vw, 240px);
  height: auto;
  aspect-ratio: 420 / 140;
}
.env__orn-bou.env__orn--tl { top: 5vh; left: -8px; transform: rotate(6deg); }
.env__orn-bou.env__orn--br { bottom: 5vh; right: -8px; transform: rotate(186deg); }
.env__orn-roses {
  width: min(42vw, 170px);
  height: auto;
  aspect-ratio: 1;
}
.env__orn-roses.env__orn--tl { top: 10px; left: -10px; }
.env__orn-roses.env__orn--br { bottom: 10px; right: -10px; transform: rotate(180deg); }
.env__lantern {
  position: absolute;
  z-index: 5;
  top: 0;
  width: 40px;
  height: auto;
  aspect-ratio: 90 / 170;
  pointer-events: none;
  transform-origin: top center;
  animation: ea-sway 5s ease-in-out infinite;
}
.env__lantern--l { left: 14%; }
.env__lantern--r { right: 14%; animation-delay: -2.5s; }

/* heritage envelope dressing — the shell is the accent, so the gold
   companion is what stays legible on it. Opacities sit near the
   burgundy benchmark (1.0): a corner decal at half strength is what
   makes a closed envelope read as a flat colour field. */
.env__orn-liya {
  width: min(42vw, 165px);
  height: auto;
  aspect-ratio: 1;
  color: var(--accent-soft);
  opacity: 0.9;
}
.env__orn-liya.env__orn--tl { top: 8px; left: -6px; }
.env__orn-liya.env__orn--br { bottom: 8px; right: -6px; transform: scale(-1, -1); }
/* the khatim weave is chiral: rotate it between corners, never mirror */
.env__orn-star8 {
  width: min(26vw, 96px);
  height: auto;
  aspect-ratio: 1;
  color: var(--accent-soft);
  opacity: 0.85;
}
.env__orn-star8.env__orn--tl { top: 18px; left: 16px; }
.env__orn-star8.env__orn--br { bottom: 18px; right: 16px; transform: rotate(180deg); }
.env__orn-jasmine {
  top: 5vh;
  left: 50%;
  width: min(78vw, 280px);
  height: auto;
  aspect-ratio: 220 / 60;
  transform: translateX(-50%);
  color: var(--accent-soft);
  opacity: 1;
}
.env__orn-sprig {
  width: min(26vw, 94px);
  height: auto;
  aspect-ratio: 1;
  color: var(--accent-soft);
  opacity: 0.9;
}
.env__orn-sprig.env__orn--bl { bottom: 8px; left: 8px; }
.env__orn-sprig.env__orn--br { bottom: 8px; right: 8px; transform: scaleX(-1); }
@keyframes ea-sway {
  0%, 100% { transform: rotate(-3deg); }
  50% { transform: rotate(3deg); }
}

/* =====================================================================
   AMBIENT DRIFT — the optional particle layer (effects.drift): fixed
   above the letter (z 40), under the envelope (z 50). Transform and
   opacity ONLY — anything else stutters on mid-range Androids; the
   NEGATIVE delays mean the sky is full on first paint.
   ===================================================================== */
.drift { position: fixed; inset: 0; z-index: 40; overflow: hidden; pointer-events: none; }
.drift__p {
  position: absolute;
  top: 0;
  width: var(--s);
  height: var(--s);
  color: var(--accent-soft);
  opacity: 0;
  --sw: 7vw;
  --s: 18px;
  --d: -19s;
}
/* one scatter bag serves whichever variant renders; the > keeps it
   off the particles' own children (the butterfly wing <use> pair),
   which would overwrite the --t each wing inherits for its flap phase */
.drift>:nth-child(1){left:12%;--t:12s}
.drift>:nth-child(2){left:26%;--t:15s}
.drift>:nth-child(3){left:41%;--t:10s}
.drift>:nth-child(4){left:55%;--t:17s}
.drift>:nth-child(5){left:68%;--t:11s}
.drift>:nth-child(6){left:82%;--t:14s}
.drift>:nth-child(7){left:5%;--t:16s}
.drift>:nth-child(8){left:93%;--t:9.5s}
.drift>:nth-child(9){left:34%;--t:13s}
.drift>:nth-child(10){left:61%;--t:15.5s}
.drift>:nth-child(11){left:48%;--t:12.5s}
.drift>:nth-child(even){--sw:-6vw;--s:15px;--d:-7s}
.drift>:nth-child(3n){--s:21px;--d:-31s}

/* the accent-soft base above is the petals' no-color-mix fallback */
.drift--petals .drift__p,
.drift--leaves .drift__p { opacity: var(--o, 0.8); animation: ea-fall var(--t) linear var(--d) infinite; }
.drift--petals .drift__p { color: color-mix(in srgb, var(--accent) 34%, var(--panel)); }
.drift--leaves .drift__p { color: var(--accent); }

/* Leaves, 2026-08-06 richness pass (studied from a competitor's spawner,
   reimplemented as our own deterministic tables): per-leaf tonal variety
   mixed FROM THE PALETTE so every pack stays coherent, a wider size and
   opacity spread, and a leaf-specific fall with net crosswind plus a
   scale-down depth cue. Still transform/opacity only — the mid-range
   Android rule holds; the tonal variants are static per leaf. */
.drift>:nth-child(even) { --o: 0.62; }
.drift>:nth-child(3n)   { --o: 0.9; }
.drift>:nth-child(5n)   { --s: 25px; --o: 0.72; }
.drift>:nth-child(7n)   { --s: 14px; }
.drift--leaves>:nth-child(even) { color: color-mix(in srgb, var(--accent) 70%, var(--ink)); }
.drift--leaves>:nth-child(3n)   { color: color-mix(in srgb, var(--accent) 55%, var(--accent-soft)); }
.drift--leaves>:nth-child(5n)   { color: color-mix(in srgb, var(--accent) 62%, var(--panel)); }
.drift--leaves .drift__p { animation-name: ea-fall-leaf; }

@keyframes ea-fall {
  from { transform: translate(0, -12vh) rotate(0deg); }
  50% { transform: translate(var(--sw), 48vh) rotate(280deg); }
  to { transform: translate(calc(var(--sw) * -0.4), 112vh) rotate(560deg); }
}
/* net sideways wind (odd leaves ride it, even leaves fall near-straight,
   since --sw alternates sign) and a gentle shrink for depth */
@keyframes ea-fall-leaf {
  from { transform: translate(0, -12vh) rotate(0deg) scale(1); }
  50% { transform: translate(calc(var(--sw) * 1.6), 46vh) rotate(300deg) scale(0.9); }
  to { transform: translate(calc(var(--sw) * 2.6 + 12vw), 112vh) rotate(560deg) scale(0.72); }
}

.drift--butterflies .drift__p {
  left: 6%;
  top: 50%;
  color: var(--accent);
  opacity: 0.85;
  animation: ea-flit calc(var(--t) * 1.6) ease-in-out var(--d) infinite alternate;
}
.drift--butterflies>:nth-child(even){left:84%}
.drift--butterflies>:nth-child(3n){top:26%}
.drift--butterflies>:nth-child(3n+1){top:74%}
@keyframes ea-flit {
  33% { transform: translate(var(--sw), -12vh); }
  66% { transform: translate(calc(var(--sw) * 0.2), -22vh); }
  to { transform: translate(calc(var(--sw) * -0.6), -6vh); }
}
/* The wings are light-DOM <use> elements ON PURPOSE: animating a
   symbol's inner paths through the <use> shadow tree fails silently in
   older WebKit, but a CSS animation on the <use> itself runs
   everywhere. fill-box puts each wing's origin on the body seam, and
   the --t inherited from the particle staggers the beat per butterfly. */
.drift__wing { transform-box: fill-box; animation: ea-flap 0.55s ease-in-out calc(var(--t) * -1) infinite alternate; }
.drift__wing--l { transform-origin: 100% 50%; }
.drift__wing--r { transform-origin: 0 50%; }
@keyframes ea-flap { to { transform: scaleX(0.3); } }

/* fireflies: a warm dot whose radial skirt IS the (static) glow; the
   pulse is opacity. --drift-dim tames them on light paper */
.drift--fireflies .drift__p {
  top: 46%;
  --fs: calc(var(--s) * (0.35 + 0.15 * var(--drift-dim)));
  width: var(--fs);
  height: var(--fs);
  border-radius: 50%;
  /* plain-gradient first: engines without color-mix drop the whole
     declaration below, and a transparent firefly is an invisible one */
  background: radial-gradient(circle, #fff3cf 0 30%, transparent 72%);
  background: radial-gradient(circle, color-mix(in srgb, var(--accent-soft) 30%, #fff3cf) 0 30%, transparent 72%);
  animation: ea-flit var(--t) ease-in-out var(--d) infinite alternate,
      ea-glow calc(var(--t)*0.35) ease-in-out var(--d) infinite;
}
.drift--fireflies>:nth-child(even){top:70%}
.drift--fireflies>:nth-child(3n){top:20%}
@keyframes ea-glow { 50% { opacity: calc(0.9 * var(--drift-dim, 1)); } }

/* =====================================================================
   HERO
   ===================================================================== */
.hero {
  position: relative;
  padding: 84px 26px 64px;
  text-align: center;
  overflow: hidden;
}
.hero__kicker, .hero__names, .hero__rule, .hero__date,
.hero__city, .hero__photo, .crest, .mihrab, .orn-flow {
  position: relative;
  z-index: 1;
}
.hero__kicker {
  font-family: var(--font-body);
  font-weight: 500;
  font-size: 0.72rem;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: var(--accent);
  margin: 0 0 14px;
}
.hero__names {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(2.4rem, 11vw, 3.2rem);
  line-height: 1.12;
  color: var(--ink);
  margin: 6px 0 12px;
}
.hero__amp {
  font-family: var(--font-script);
  font-weight: 400;
  font-size: 0.8em;
  color: var(--accent);
  padding: 0 0.06em;
}
.hero__rule {
  width: 120px;
  margin: 0 auto 18px;
  border-top: 1px solid var(--accent-soft);
  position: relative;
}
.hero__rule::after {
  content: "";
  position: absolute;
  left: 50%;
  top: -4px;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  transform: translateX(-50%);
  background: var(--accent);
}
.hero__date {
  font-size: 0.78rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin: 0 0 4px;
}
.hero__city {
  font-size: 0.72rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--ink-soft);
  opacity: 0.85;
  margin: 0;
}

/* hero photo in an elegant frame — shape via .hero__photo--<shape>,
   "arch" being the classic default */
.hero__photo {
  margin: 30px auto 0;
  max-width: 290px;
  padding: 10px;
  background: var(--panel);
  border: 1px solid var(--accent-soft);
  border-radius: 999px 999px 12px 12px;
  box-shadow: 0 16px 36px rgba(0, 0, 0, 0.14);
}
.hero__photo img {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  border-radius: 999px 999px 6px 6px;
}
.hero__photo--oval,
.hero__photo--oval img {
  border-radius: 50%;
}
.hero__photo--oval img {
  aspect-ratio: 3 / 4;
}
.hero__photo--circle,
.hero__photo--circle img {
  border-radius: 50%;
}
.hero__photo--circle {
  max-width: 260px;
}
.hero__photo--circle img {
  aspect-ratio: 1 / 1;
}
.hero__photo--soft {
  border-radius: 20px;
}
.hero__photo--soft img {
  border-radius: 12px;
}
.hero__photo--petal {
  border-radius: 62% 12% 62% 12%;
}
.hero__photo--petal img {
  border-radius: 58% 10% 58% 10%;
  aspect-ratio: 1 / 1;
}

/* ---------------------------------------------------------------------
   Hero ornaments
   --------------------------------------------------------------------- */
.orn { position: absolute; z-index: 0; pointer-events: none; }
.orn-roses { width: min(44vw, 180px); height: auto; aspect-ratio: 1; }
.orn--tl { top: -6px; left: -8px; }
.orn--br { bottom: -6px; right: -8px; transform: rotate(180deg); }
.orn-leaves {
  width: 52px;
  height: auto;
  aspect-ratio: 80 / 320;
  top: 28px;
  color: var(--accent);
  opacity: 0.5;
}
.orn-leaves--l { left: 6px; }
.orn-leaves--r { right: 6px; transform: scaleX(-1); }
.orn-bou { width: min(52vw, 230px); height: auto; aspect-ratio: 420 / 140; }
.orn-bou--top { top: 6px; left: -8px; transform: rotate(4deg); }
.orn-bou--bottom { bottom: 6px; right: -8px; transform: rotate(184deg); }
.orn-flow { display: block; margin: 0 auto; pointer-events: none; }
.orn-boho-sun {
  width: 64px;
  height: auto;
  aspect-ratio: 1;
  margin-bottom: 16px;
  color: var(--accent);
  opacity: 0.85;
}
.orn-boho-pampas {
  width: 150px;
  height: auto;
  aspect-ratio: 200 / 90;
  margin-top: 22px;
  color: var(--accent);
  opacity: 0.8;
}

/* --- Kandyan liyawela: one corner creeper, flipped per corner --- */
/* sized so the creeper's tapering tail stops short of the swan crest */
.orn-liya {
  width: min(32vw, 118px);
  height: auto;
  aspect-ratio: 1;
  color: var(--accent-soft);
  opacity: 0.8;
}
.orn-liya--tl { top: -4px; left: -6px; }
.orn-liya--tr { top: -4px; right: -6px; transform: scaleX(-1); }
.orn-liya--br { bottom: -4px; right: -6px; transform: scale(-1, -1); }
/* the swan pair crowns the names; the moonstone is the threshold below.
   The top margin is the clearance the creeper's tail needs above it. */
.orn-hansa {
  width: min(54vw, 200px);
  height: auto;
  aspect-ratio: 200 / 90;
  margin: 24px auto 12px;
  color: var(--accent);
  opacity: 0.9;
}
.orn-moonstone {
  width: min(64vw, 244px);
  height: auto;
  aspect-ratio: 240 / 130;
  margin-top: 12px;
  color: var(--accent);
  opacity: 1;
}

/* --- Nikah: khatim units hold the corners, the arch crowns the names --- */
/* chiral weave — rotate between corners, never mirror */
.orn-star8 {
  width: min(24vw, 84px);
  height: auto;
  aspect-ratio: 1;
  color: var(--accent-soft);
  opacity: 0.62;
}
.orn-star8--tl { top: 4px; left: 2px; }
.orn-star8--tr { top: 4px; right: 2px; transform: rotate(90deg); }
.orn-star8--br { bottom: 10px; right: 12px; transform: rotate(180deg); }
.orn-star8--bl { bottom: 10px; left: 12px; transform: rotate(270deg); }
/* the lower pair is an echo, not a repeat: half the scale and quieter,
   so the frame reads as a hierarchy instead of wallpaper — and so the
   khatim stops crowding the date and city lines */
.orn-star8--bl,
.orn-star8--br {
  width: min(13vw, 46px);
  opacity: 0.4;
}

/* --- Temple mandapam: pillars flank, kolam holds the lower corners --- */
/* the pillars flank the band above the names — narrow enough that the
   shaft stays clear of the widest line, and behind it (z-index 0) if a
   couple's names run long */
/* gold, like the garland and the kolam — a 40%-opacity temple red on
   cream reads as a misregistered print, not as carved stone */
.orn-pillar {
  top: 26px;
  width: 32px;
  height: auto;
  aspect-ratio: 70 / 300;
  color: var(--accent-soft);
  opacity: 0.8;
}
.orn-pillar--l { left: 8px; }
.orn-pillar--r { right: 8px; transform: scaleX(-1); }
/* the kolam is drawn from its origin corner, so a flip serves each one.
   It is a symmetric geometric square, so it is NOT bled off the edge —
   a cut kolam reads as half-drawn. The hero's bottom padding (see the
   per-theme rules) is what keeps it clear of the date and city lines. */
.orn-kolam {
  width: min(26vw, 100px);
  height: auto;
  aspect-ratio: 1;
  color: var(--accent-soft);
  opacity: 0.75;
}
.orn-kolam--bl { bottom: 4px; left: 4px; transform: scaleY(-1); }
.orn-kolam--br { bottom: 4px; right: 4px; transform: scale(-1, -1); }
/* the strand hangs like bunting, so it rides high in the hero */
.orn-jasmine {
  width: min(74vw, 262px);
  height: auto;
  aspect-ratio: 220 / 60;
  margin: -34px auto 16px;
  color: var(--accent-soft);
  opacity: 0.95;
}

.crest {
  width: 110px;
  margin: 0 auto 10px;
  color: var(--accent);
}
.crest__svg { display: block; width: 100%; height: auto; aspect-ratio: 160 / 180; }
.crest__mono {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  padding-top: 8px;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.25rem;
  letter-spacing: 0.05em;
  color: var(--accent);
}

/* the mihrab crowns the names, monogram set in its opening */
.mihrab {
  position: relative;
  width: 104px;
  margin: 0 auto 14px;
  color: var(--accent-soft);
}
.mihrab__svg { display: block; width: 100%; height: auto; aspect-ratio: 180 / 260; }
.mihrab__mono {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  /* the opening's optical centre sits below the springing line */
  padding-top: 32%;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.05rem;
  letter-spacing: 0.05em;
  color: var(--accent);
}

/* =====================================================================
   GREETING
   ===================================================================== */
.script-line {
  font-family: var(--font-script);
  font-weight: 400;
  font-size: 2rem;
  line-height: 1.2;
  color: var(--accent);
  margin: 0 0 14px;
  min-height: 1.4em;
}
.guest-line {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.6rem;
  line-height: 1.2;
  color: var(--ink);
  margin: 0 0 14px;
}
.seats {
  font-size: 0.95rem;
  color: var(--ink-soft);
  margin: 14px 0 0;
}
.seats strong { color: var(--accent); font-weight: 600; }
.guest-note {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 1.05rem;
  color: var(--ink-soft);
  max-width: 30ch;
  margin: 14px auto 0;
}

/* typewriter caret */
.tw-caret {
  display: inline-block;
  width: 2px;
  height: 1.05em;
  margin-left: 2px;
  background: var(--accent);
  vertical-align: -0.12em;
  animation: ea-caret 0.9s step-end infinite;
}
@keyframes ea-caret { 50% { opacity: 0; } }
[data-typewriter].tw-done + .tw-caret { display: none; }

/* =====================================================================
   COUNTDOWN — pills
   ===================================================================== */
.countdown {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 10px;
  max-width: 360px;
  margin: 0 auto;
}
.countdown > div {
  background: var(--panel);
  border: 1px solid var(--accent-soft);
  border-radius: 12px;
  padding: 16px 4px 12px;
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.06);
}
.countdown span {
  display: block;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.9rem;
  line-height: 1;
  color: var(--accent);
}
.countdown small {
  display: block;
  margin-top: 6px;
  font-size: 0.6rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

/* =====================================================================
   GALLERY — framed photos
   ===================================================================== */
.gallery__grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
  max-width: 380px;
  margin: 0 auto;
}
.gallery__item {
  margin: 0;
  padding: 8px;
  background: var(--panel);
  border: 1px solid var(--accent-soft);
  border-radius: 10px;
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.08);
}
.gallery__item img {
  display: block;
  width: 100%;
  aspect-ratio: 1;
  object-fit: cover;
  border-radius: 6px;
}
/* an odd count: let the first photo breathe full-width */
.gallery__item:first-child:nth-last-child(odd) { grid-column: 1 / -1; }
.gallery__item:first-child:nth-last-child(odd) img { aspect-ratio: 16 / 10; }

/* =====================================================================
   SCHEDULE — vertical timeline
   ===================================================================== */
.timeline {
  list-style: none;
  margin: 8px auto 0;
  padding: 0;
  max-width: 360px;
  text-align: left;
  position: relative;
}
.timeline::before {
  content: "";
  position: absolute;
  left: 7px;
  top: 6px;
  bottom: 6px;
  width: 1px;
  background: linear-gradient(180deg, transparent, var(--accent-soft), var(--accent), var(--accent-soft), transparent);
}
.timeline__item {
  position: relative;
  padding: 0 0 28px 36px;
}
.timeline__item:last-child { padding-bottom: 0; }
.timeline__dot {
  position: absolute;
  left: 0;
  top: 6px;
  width: 15px;
  height: 15px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 0 4px var(--bg), 0 0 0 5px var(--accent-soft);
}
.timeline__time {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 0.92rem;
  color: var(--accent);
  margin: 0 0 2px;
}
.timeline__title {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.3rem;
  color: var(--ink);
  margin: 0 0 3px;
}
.timeline__note {
  font-size: 0.92rem;
  color: var(--ink-soft);
  margin: 0;
}

/* =====================================================================
   DETAILS CARD (venue + calendar)
   ===================================================================== */
.detail-card {
  position: relative;
  background: var(--panel);
  border: 1px solid var(--accent-soft);
  border-radius: 10px;
  padding: 32px 24px;
  max-width: 380px;
  margin: 0 auto;
  box-shadow: 0 10px 26px rgba(0, 0, 0, 0.07);
}
.detail-card__venue {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.45rem;
  color: var(--ink);
  margin: 0 0 8px;
}
.detail-card__time {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 1rem;
  color: var(--accent);
  margin: 0 0 8px;
}
.detail-card__addr {
  font-size: 0.9rem;
  color: var(--ink-soft);
  margin: 0 0 18px;
}
.map-link {
  display: inline-block;
  min-height: 44px;
  line-height: 44px;
  padding: 0 24px;
  margin: 0 6px 12px;
  border: 1px solid var(--accent-soft);
  border-radius: 6px;
  color: var(--accent);
  text-decoration: none;
  font-size: 0.74rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  transition: background 0.25s ease, color 0.25s ease;
}
.map-link:hover { background: var(--accent); color: var(--on-accent); }
.detail-card .btn { margin-top: 4px; }

/* =====================================================================
   RSVP
   ===================================================================== */
.rsvp__actions {
  display: flex;
  flex-direction: column;
  gap: 14px;
  max-width: 300px;
  margin: 6px auto 0;
}
.rsvp__actions .btn { width: 100%; }

/* =====================================================================
   FOOTER
   ===================================================================== */
.footer {
  text-align: center;
  padding: 52px 26px 64px;
}
.footer .thin-rule { margin-bottom: 24px; }
.footer__love {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 1.05rem;
  color: var(--ink-soft);
  margin: 0 0 4px;
}
.footer__names {
  font-family: var(--font-script);
  font-weight: 400;
  font-size: 2.2rem;
  line-height: 1.15;
  color: var(--accent);
  margin: 0;
}
.footer__mono {
  display: inline-block;
  margin-top: 16px;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.2rem;
  letter-spacing: 0.06em;
  color: var(--accent);
}

/* =====================================================================
   REVEAL (scroll-in) — per CONTRACT.md §5
   ===================================================================== */
.reveal {
  opacity: 0;
  transform: translateY(24px);
  transition: opacity 0.8s ease, transform 0.8s ease;
}
.reveal.is-visible {
  opacity: 1;
  transform: none;
}

/* hide seat info for anonymous visitors */
body.no-guest [data-guest-seats] { display: none; }
body.no-guest .seats { display: none; }
/* hide the whole seats line when the guest link carries no seat count —
   the engine only fills [data-guest-seats] when seats are given
   (older browsers without :has() simply keep the previous behavior) */
.seats:has([data-guest-seats]:empty) { display: none; }
/* hide the personal-note paragraph unless the engine filled it */
.guest-note:empty { display: none; }

/* =====================================================================
   PER-THEME FLOURISHES (kept minimal)
   ===================================================================== */
/* formal squared corners for the old-money & royal looks */
html[data-theme="burgundy-oldmoney"] .btn,
html[data-theme="burgundy-oldmoney"] .env__open,
html[data-theme="royal-emerald"] .btn,
html[data-theme="royal-emerald"] .env__open { border-radius: 2px; }
html[data-theme="burgundy-oldmoney"] .countdown > div,
html[data-theme="burgundy-oldmoney"] .detail-card,
html[data-theme="burgundy-oldmoney"] .gallery__item,
html[data-theme="royal-emerald"] .countdown > div,
html[data-theme="royal-emerald"] .detail-card,
html[data-theme="royal-emerald"] .gallery__item { border-radius: 3px; }
/* relaxed pill shapes for the boho look */
html[data-theme="terracotta-boho"] .btn,
html[data-theme="terracotta-boho"] .env__open { border-radius: 999px; }
html[data-theme="terracotta-boho"] .countdown > div { border-radius: 16px; }

/* Ceylon Ivory wears the Kandyan vine at a whisper: SMALLER, but still
   bled off the corner. The whisper is scale, never position — pulling
   the motif inward walks it straight into the eyebrow and the date. */
html[data-theme="ceylon-ivory"] .orn-liya {
  width: min(24vw, 92px);
  /* the pale sand companion disappears on ivory paper; the brass accent
     at low opacity is the same whisper, but a legible one */
  color: var(--accent);
  opacity: 0.6;
}
html[data-theme="ceylon-ivory"] .orn-liya--tl { top: -6px; left: -8px; }
html[data-theme="ceylon-ivory"] .orn-liya--br { bottom: -8px; right: -10px; }
html[data-theme="ceylon-ivory"] .env__orn-liya {
  width: min(34vw, 132px);
  opacity: 0.9;
}
/* fine gold rule-work: a longer, hairline centre rule */
html[data-theme="ceylon-ivory"] .hero__rule,
html[data-theme="ceylon-ivory"] .thin-rule { width: 168px; }
html[data-theme="ceylon-ivory"] .hero__rule::after { width: 5px; height: 5px; top: -3px; }
/* Ceylon's hero motif IS its rule-work — a broken gold rule with a
   lozenge on the break, set between the eyebrow and the names so the
   top of the page is composed rather than bare. (.hero__names is
   already position:relative, which the lozenge hangs off.) */
html[data-theme="ceylon-ivory"] .hero__names::before {
  content: "";
  display: block;
  width: 148px;
  height: 8px;
  margin: 2px auto 18px;
  background-image:
    linear-gradient(to right, transparent, var(--accent-soft) 24%, var(--accent-soft) 40%, transparent 43%),
    linear-gradient(to right, transparent 57%, var(--accent-soft) 60%, var(--accent-soft) 76%, transparent);
  background-size: 100% 1px;
  background-position: center;
  background-repeat: no-repeat;
}
html[data-theme="ceylon-ivory"] .hero__names::after {
  content: "";
  position: absolute;
  top: 2px;
  left: 50%;
  width: 8px;
  height: 8px;
  margin-left: -4px;
  border: 1px solid var(--accent);
  transform: rotate(45deg);
}

/* Temple Mandapam: the kolam is a closed geometric square, so the hero
   gives it a footer band of its own instead of letting it ride up into
   the date line (or get sliced flat by the page edge) */
html[data-theme="temple-mandapam"] .hero { padding-bottom: 96px; }

/* Long heritage names wrap at phone width, which strands the script
   ampersand at the end of the first line. These two stack instead:
   partner / & / partner, the ampersand centred on its own line. */
html[data-theme="kandyan-heritage"] .hero__names,
html[data-theme="nikah-serenity"] .hero__names {
  display: flex;
  flex-direction: column;
  align-items: center;
}
html[data-theme="kandyan-heritage"] .hero__amp,
html[data-theme="nikah-serenity"] .hero__amp {
  padding: 0;
  line-height: 1;
}

/* A second layer on the heritage envelopes: a fine rule inside the
   shell, so the closed envelope is more than a flat field with a corner
   decal (the burgundy benchmark gets this from its hanging lanterns).
   On the fold shell it doubles the existing inset frame; on the fade
   shell it is the envelope's own edge rule. */
html[data-theme="kandyan-heritage"] .env::before,
html[data-theme="nikah-serenity"] .env::before,
html[data-theme="temple-mandapam"] .env::before,
html[data-theme="ceylon-ivory"] .env::before {
  content: "";
  position: absolute;
  inset: 12px;
  z-index: 6;
  border: 1px solid var(--accent-soft);
  opacity: 0.38;
  pointer-events: none;
}
html[data-theme="kandyan-heritage"] .env--fold::before,
html[data-theme="nikah-serenity"] .env--fold::before,
html[data-theme="temple-mandapam"] .env--fold::before,
html[data-theme="ceylon-ivory"] .env--fold::before { inset: 24px; }
html[data-theme="kandyan-heritage"] .env.is-opening::before,
html[data-theme="nikah-serenity"] .env.is-opening::before,
html[data-theme="temple-mandapam"] .env.is-opening::before,
html[data-theme="ceylon-ivory"] .env.is-opening::before { opacity: 0; }
/* squared corners suit the carved heritage looks */
html[data-theme="kandyan-heritage"] .btn,
html[data-theme="kandyan-heritage"] .env__open,
html[data-theme="nikah-serenity"] .btn,
html[data-theme="nikah-serenity"] .env__open,
html[data-theme="ceylon-ivory"] .btn,
html[data-theme="ceylon-ivory"] .env__open { border-radius: 2px; }
html[data-theme="kandyan-heritage"] .countdown > div,
html[data-theme="kandyan-heritage"] .detail-card,
html[data-theme="nikah-serenity"] .countdown > div,
html[data-theme="nikah-serenity"] .detail-card,
html[data-theme="ceylon-ivory"] .countdown > div,
html[data-theme="ceylon-ivory"] .detail-card { border-radius: 3px; }

/* =====================================================================
   RESPONSIVE — gentle up-scaling beyond the phone
   ===================================================================== */
@media (min-width: 600px) {
  .sec { padding: 68px 26px; }
  .hero__names { font-size: 3.4rem; }
  .rsvp__actions { flex-direction: row; max-width: none; justify-content: center; }
  .rsvp__actions .btn { width: auto; }
}

/* =====================================================================
   REDUCED MOTION — everything settles instantly
   ===================================================================== */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .reveal { opacity: 1; transform: none; transition: none; }
  .env, .env::before, .env::after, .env--fold::after, .env__flap, .env__throat,
  .env__content, .wax-seal, .env__orn, .env__lantern, .invite,
  .env__stage, .env__back, .env__pocket, .env__card, .env__addr,
  .env__flap-shade, .env__liner-shade {
    transition: none;
    animation: none;
  }
  /* invite.js hides the whole envelope on the same frame under reduced
     motion, so these end states only have to be non-jarring for one
     paint — but transition:none does NOT undo an end state, so they are
     stated explicitly rather than left to chance. */
  .env--fold.is-opening .env__flap { animation: none; transform: rotateX(-164deg); }
  .env--fold.is-opening .env__card { animation: none; transform: translateY(-46%); }
  .env__seal, .env--fold.is-opening .env__seal--l,
  .env--fold.is-opening .env__seal--r { animation: none; opacity: 0; }
  .env--fold.is-opening .env__stage { transform: none; opacity: 0; }
  /* the cover leaves by simply disappearing — no zoom, no fade curve */
  .env--cover, .env__cover-img, .env__scrim,
  .env--reel, .env__reel { transition: none; animation: none; }
  .env--cover.is-opening, .env--reel.is-opening { transform: none; }
  body.envelope-locked .invite { transform: none; }
  .env__lantern { animation: none; }
  .drift { display: none; } /* pure decoration */
  .tw-caret { animation: none; display: none; }
  .btn, .btn--solid, .btn--outline, .env__open, .map-link { transition: none; }
}
