/* Author: Claude (Anthropic AI), written 2026-10-06. */
/* Save-the-date reveal landing. Brick-dominant, Fraunces, candlelit.
   Scoped under body.landing so it never touches the Dash/DMC pages. */

/* Self-hosted fonts (assets/fonts/README.md): Google's latin subset, variable
   weights. Fraunces is preloaded by the template; the spree wordmark additionally
   waits for it (.fonts-ready), so swap never shows there. */
@font-face {
  font-family: "Fraunces"; font-style: normal; font-weight: 400 600; font-display: swap;
  src: url("/assets/fonts/fraunces-latin.woff2") format("woff2");
}
@font-face {
  font-family: "Fraunces"; font-style: italic; font-weight: 400 600; font-display: swap;
  src: url("/assets/fonts/fraunces-italic-latin.woff2") format("woff2");
}
@font-face {
  font-family: "Inter"; font-style: normal; font-weight: 400 500; font-display: swap;
  src: url("/assets/fonts/inter-latin.woff2") format("woff2");
}

/* border-box everywhere on the landing so padding is included in sizes (the
   viewport-sized boxes below must not grow by their padding). Scoped to
   body.landing (this sheet is auto-loaded on Dash pages too, which must not be
   touched). */
body.landing,
body.landing *,
body.landing *::before,
body.landing *::after { box-sizing: border-box; }

/* ---------- viewport model: a bleed box and a visible box ----------
   iOS 26 Safari floats translucent toolbars over the page; ordinary content
   renders behind them, but it clips position:fixed layers at the toolbar and
   tints the bars from fixed elements' backgrounds (even at opacity 0). So:
   - nothing here is position:fixed (the page never scrolls, so absolute at the
     document origin is equivalent);
   - the BLEED box (everything visual: stage + shader, spree photos, vignette)
     starts at the document top and spans the --runway, the large viewport and a
     margin, so it reaches under any toolbar;
   - the VISIBLE box (everything legible: wordmarks, eyebrow, footer, frame, tap
     prompt) is the small viewport, starting --runway down the document.
   The top runway is for the status bar: at scrollY 0 Safari paints that strip
   with the root background, but once scrolled it shows the content beneath. So
   landing.js keeps the document scrolled to exactly --runway (programmatic
   scrolling still works under overflow: hidden), which puts the visible box at
   the viewport top and the runway's slice of the bleed box under the status bar.
   Each height is declared twice: vh first for browsers without svh/lvh. Users
   can't scroll the extra document height (body overflow: hidden). */
html:has(> body.landing) {
  background: #1c1917; /* = --stone-black. Safari also blends the root colour
                          into its blur under the status bar, so keep it dark:
                          a mid-tone reads as a grey seam over the colour field */
  overscroll-behavior: none;
}
.stage, .spree, .vignette {
  height: calc(var(--runway) + 100vh + 5rem);
  height: calc(var(--runway) + 100lvh + 5rem);
}
.content, .spree-names, .tap-prompt {
  top: var(--runway);
  height: 100vh;
  height: 100svh;
}

body.landing {
  /* --- palette: raw stone field, deep draped-cloth colour, candlelit gilt. ---
     Stone is the cold neutral ground; colour enters only as garment/velvet
     accents (wine, sapphire); gold reads as gilt on stone; light is candle. */
  --stone-pale: #8f8578;   /* lit limestone */
  --stone: #5f584e;        /* mid stone */
  --stone-deep: #332f2a;   /* shadowed stone */
  --stone-black: #1c1917;  /* deepest */
  --wine: #6d1f2b;         /* bordeaux velvet (primary garment/drape) */
  --sapphire: #1c3a63;     /* deep blue cloth / dusk */
  --gold: #c9a24b;         /* gilt */
  --candle: #ffcf9e;       /* warm candle highlight */
  --amber: #ffb169;        /* candle glow */
  --parchment: #efe7d6;    /* bone / parchment text */

  /* Hidden document space above the visible box, scrolled under the status bar
     (viewport model, above). Px, read by landing.js; must exceed the tallest
     status bar (~62px with the Dynamic Island). */
  --runway: 120px;

  margin: 0;
  background: var(--stone-black);
  color: var(--parchment);
  font-family: "Fraunces", Georgia, serif;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  /* Single-screen landing: never scroll. The bleed box is taller than the
     visible area on purpose (see the viewport model above); locking overflow on
     both axes keeps that from becoming scrollable on phone and desktop alike.
     svh/lvh (not dvh) keep it stable as mobile browser chrome shows/hides. */
  overflow: hidden;
}

/* ---------- the rich ground ---------- */
.stage {
  position: relative; /* height: the bleed box, above */
  /* raw stone, candle pool rising from below; grain adds the stone's tooth */
  background:
    radial-gradient(130% 100% at 50% 120%, var(--stone) 0%, var(--stone-deep) 52%, var(--stone-black) 100%);
  isolation: isolate;
  overflow: hidden; /* clip the drifting glow/grain so it can't inflate scroll */
}

/* animated mesh-gradient background (WebGL2, shader-bg.js). Sits above the
   stage's stone gradient (the no-WebGL fallback) and below the
   candle pools, grain, frame and content. Fades in on reveal via .on. */
.bg-shader {
  position: absolute; inset: 0; z-index: 0;
  width: 100%; height: 100%;
  display: block;
  opacity: 0;
  transition: opacity 1.1s ease .1s;
}
.bg-shader.on { opacity: 1; }

/* candle pools — emission, not fill. Drift + flicker. */
.stage::before {
  content: "";
  position: absolute; inset: -12%;
  pointer-events: none; z-index: 1;
  background:
    radial-gradient(260px 320px at 50% 96%, rgba(255,177,105,.34), transparent 70%),
    radial-gradient(200px 260px at 26% 90%, rgba(255,207,158,.22), transparent 72%),
    radial-gradient(220px 280px at 76% 94%, rgba(228,98,31,.20), transparent 72%);
  filter: blur(8px);
  animation: drift 15s ease-in-out infinite alternate, flicker 3.4s ease-in-out infinite;
}
/* film grain — cheapest "this cost money" cue; also kills gradient banding */
.stage::after {
  content: "";
  position: absolute; inset: 0;
  pointer-events: none; z-index: 2; opacity: .45;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

@keyframes drift {
  from { transform: translate3d(-1.5%, 1%, 0) scale(1.02); }
  to   { transform: translate3d(2%, -1.5%, 0) scale(1.05); }
}
@keyframes flicker {
  0%,100% { opacity: .9; } 25% { opacity: 1; } 42% { opacity: .84; }
  63% { opacity: .97; } 80% { opacity: .9; }
}

/* ---------- thin gold hairline frame (engraved-invitation nod) ---------- */
/* In the visible box, clear of notches / rounded corners / the home indicator
   (landscape) via the safe-area insets. */
.frame {
  --ft: max(14px, env(safe-area-inset-top, 0px));
  --fb: max(14px, env(safe-area-inset-bottom, 0px));
  position: absolute; z-index: 3;
  top: calc(var(--runway) + var(--ft));
  left: max(14px, env(safe-area-inset-left, 0px));
  right: max(14px, env(safe-area-inset-right, 0px));
  height: calc(100vh - var(--ft) - var(--fb));
  height: calc(100svh - var(--ft) - var(--fb));
  border: 1px solid rgba(217,164,65,.55);
  border-radius: 3px;
  pointer-events: none;
}
.frame::before {
  content: ""; position: absolute; inset: 4px;
  border: 1px solid rgba(217,164,65,.22); border-radius: 2px;
}

/* ---------- centred composition ----------
   Names are pinned dead-centre of the viewport in BOTH the spree and the
   landing (see .spree-names) so the handoff is a morph, not a jump. The
   eyebrow and footer float to the top/bottom edges and never shift the names. */
.content {
  position: absolute; left: 0; right: 0; z-index: 3; text-align: center; /* visible box */
  /* starts hidden; settles in once the spree releases (see .revealed .content) */
  opacity: 0; transform: translateY(14px) scale(.985);
}

.eyebrow {
  position: absolute; top: max(6svh, 2rem); left: 0; right: 0; margin: 0;
  padding: 0 1.75rem; /* stay inside the frame */
  font-family: "Inter", system-ui, sans-serif;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: .34em;
  font-size: clamp(.6rem, 2.6vw, .72rem);
  color: var(--gold);
}

.footer {
  position: absolute; bottom: max(5svh, 1.6rem); left: 0; right: 0;
  padding: 0 1.75rem; /* a long greeting wraps inside the frame, not across it */
}

/* The wordmark. Identical metrics in both the spree overlay and the landing so
   the handoff morphs rather than jumps — the shared rule is what keeps them in
   lockstep; only the fill differs (gilt foil here, knockout white on .spree-names). */
.names, .spree-names {
  position: absolute; inset: 0; margin: 0;
  display: grid; place-items: center;
  font-family: "Fraunces", Georgia, serif;
  font-weight: 500;
  line-height: .82;
  letter-spacing: -.01em;
  font-size: clamp(3rem, 16vw, 6.5rem);
}
.names .amp, .spree-names .amp { font-style: italic; font-weight: 400; }

.names {
  /* gilt-leaf foil swept across the glyphs (gilded inscription on stone) */
  background: linear-gradient(100deg,
    #7a5e22 0%, var(--gold) 20%, #efd79a 42%, #fff7e4 50%,
    #efd79a 58%, var(--gold) 80%, #7a5e22 100%);
  background-size: 220% 100%;
  -webkit-background-clip: text; background-clip: text; color: transparent;
  animation: sweep 7s ease-in-out infinite;
}
@keyframes sweep {
  0%,100% { background-position: 0% 0; } 50% { background-position: 100% 0; }
}

/* per-guest greeting (shown only for a named guest); placement refined with the
   reveal choreography later */
.greeting {
  font-family: "Inter", system-ui, sans-serif;
  text-transform: uppercase;
  letter-spacing: .24em;
  font-size: clamp(.6rem, 2.6vw, .72rem);
  color: var(--candle);
  margin: 0 0 .8rem;
}

.marrying {
  font-style: italic;
  font-size: clamp(1rem, 4.6vw, 1.4rem);
  color: var(--parchment);
  opacity: .85;
  margin: 0 0 1rem;
}

.date {
  font-family: "Inter", system-ui, sans-serif;
  text-transform: uppercase;
  letter-spacing: .3em;
  font-size: clamp(.78rem, 3.4vw, .95rem);
  color: var(--candle);
  margin: 0 0 .3rem;
}
.location {
  font-family: "Inter", system-ui, sans-serif;
  text-transform: uppercase;
  letter-spacing: .24em;
  font-size: clamp(.58rem, 2.6vw, .68rem);
  color: rgba(242,228,207,.6);
  margin: 0 0 1.4rem;
}

/* ---------- countdown ---------- */
.countdown {
  display: flex; justify-content: center; gap: clamp(.9rem, 5vw, 1.8rem);
  margin: 0;
}
.cd-unit { display: flex; flex-direction: column; align-items: center; }
.cd-num {
  font-weight: 500; font-variant-numeric: tabular-nums;
  font-size: clamp(1.7rem, 8vw, 2.6rem); line-height: 1;
  color: var(--gold);
}
.cd-lab {
  font-family: "Inter", system-ui, sans-serif;
  text-transform: uppercase; letter-spacing: .22em;
  font-size: clamp(.5rem, 2.2vw, .6rem);
  color: rgba(242,228,207,.55); margin-top: .5rem;
}

/* ---------- add to calendar ----------
   A gilt-hairline plate echoing the frame; a light stone tint keeps the label
   legible over the brighter parts of the colour field. */
.add-cal {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 44px; /* thumb-sized tap target */
  margin-top: 1.4rem;
  /* trailing letter-spacing makes the label look off-centre; trim it on the right */
  padding: 0 calc(1.4rem - .26em) 0 1.4rem;
  font-family: "Inter", system-ui, sans-serif;
  font-weight: 500; text-transform: uppercase;
  letter-spacing: .26em;
  font-size: clamp(.58rem, 2.6vw, .68rem);
  color: var(--candle);
  text-decoration: none;
  background: rgba(28,25,23,.28);
  border: 1px solid rgba(217,164,65,.55);
  border-radius: 2px;
  transition: background .25s ease, border-color .25s ease;
}
.add-cal:hover, .add-cal:active {
  background: rgba(28,25,23,.45);
  border-color: rgba(217,164,65,.9);
}
.add-cal:focus-visible {
  outline: 1px solid rgba(217,164,65,.7);
  outline-offset: 3px;
}

/* ---------- flash-spree overlay ---------- */
.spree {
  position: absolute; top: 0; left: 0; right: 0; z-index: 10; /* bleed box */
  background: var(--stone-black);
}
/* The frames' box. Phones and portrait tablets: the whole screen, photos
   cover-cropped (modern phones are taller than 9:16, so only the sides trim). */
.spree-reel { position: absolute; inset: 0; }
/* Landscape / wide screens: cover-cropping a portrait photo to 16:10 would keep
   about a third of its height, so the photos sit in a centred 9:16 column on the
   stone-black ground instead, its sides feathered out rather than hard-edged. */
@media (min-aspect-ratio: 4/5) {
  .spree-reel {
    left: 50%; right: auto;
    width: calc(100vh * 9 / 16);
    width: calc(100dvh * 9 / 16);
    transform: translateX(-50%);
    -webkit-mask-image: linear-gradient(to right, transparent, #000 8%, #000 92%, transparent);
            mask-image: linear-gradient(to right, transparent, #000 8%, #000 92%, transparent);
  }
}
/* Photos are <img> (decoded before the spree runs, see landing.js); the colour
   fallbacks are <div>s with a gradient — each rule pair covers one of them. */
.spree-frame {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover; object-position: center;
  background-size: cover; background-position: center;
  opacity: 0;
}
.spree-frame.on { opacity: 1; }

/* The wordmark stays hidden until Fraunces has loaded (landing.js adds
   .fonts-ready, capped), so it never visibly swaps from the fallback face. Must
   precede the spree-exit rules below, which have equal specificity. */
.spree-names { opacity: 0; }
body.landing.fonts-ready .spree-names { opacity: 1; transition: opacity .25s ease; }

/* Spree exit, leaving the spree's stone-black ground for the tap gate. Normally
   the vignette closes over the held frame + wordmark and landing.js cuts them
   away beneath it, unseen (.spree-closed). Without the vignette (no WebGL2) they
   fade out instead (.spree-fading). The wordmark returns, restyled, on reveal. */
body.landing.spree-closed .spree-frame,
body.landing.spree-closed .spree-names { visibility: hidden; }
body.landing.spree-fading .spree-frame.on,
body.landing.spree-fading .spree-names {
  opacity: 0;
  transition: opacity .8s ease;
}

/* the wordmark over the spree — inverts against every frame (knockout look).
   Shared metrics live in the .names, .spree-names rule above. */
.spree-names {
  top: var(--runway); /* after the shared inset: 0 — this one sits in the visible box */
  z-index: 2; padding: 0 1.5rem; text-align: center;
  color: #fff; mix-blend-mode: difference;
  pointer-events: none;
}
body.landing.revealed .spree {
  opacity: 0; transform: scale(1.04);
  transition: opacity .7s ease, transform 1.1s ease;
  pointer-events: none;
}

/* ---------- "tap to reveal" prompt ----------
   A full-screen button (above the spree) so a tap/click anywhere reveals; the
   label floats in the lower third. Keeps keyboard focus + a screen-reader label. */
.tap-prompt {
  position: absolute; left: 0; width: 100%; z-index: 15; /* visible box */
  display: flex; align-items: flex-end; justify-content: center;
  padding-bottom: 22vh;
  padding-bottom: 22svh;
  margin: 0; border: 0; background: transparent; cursor: pointer;
  font-family: "Inter", system-ui, sans-serif;
  font-weight: 500; text-transform: uppercase;
  letter-spacing: .3em;
  font-size: clamp(.64rem, 2.8vw, .78rem);
  color: var(--candle);
  opacity: 0; pointer-events: none;
  transition: opacity .6s ease;
}
body.landing.awaiting-tap .tap-prompt {
  opacity: 1; pointer-events: auto;
  animation: tap-pulse 2.2s ease-in-out infinite;
}
.tap-prompt:focus-visible {
  outline: 1px solid rgba(217,164,65,.7);
  outline-offset: -8px;
  border-radius: 2px;
}
@keyframes tap-pulse { 0%,100% { opacity: .55; } 50% { opacity: 1; } }

/* ---------- spotlight vignette (top canvas of the sandwich) ----------
   A WebGL canvas (vignette.js) darkening everything below, with a transparent hole
   at the tap point that grows until the scene is revealed. Above everything (incl.
   the fading spree) so the spotlight appears right at the tap; pointer-events: none
   so clicks/mousemove pass through to the tap gate and the colour shader. JS toggles
   display while the one-shot animation runs. */
.vignette {
  position: absolute; top: 0; left: 0; z-index: 20; /* bleed box */
  width: 100%;
  display: none; pointer-events: none;
}

/* content settles in once the spree releases */
body.landing.revealed .content {
  opacity: 1; transform: none;
  transition: opacity 1s ease .15s, transform 1.2s cubic-bezier(.16,.84,.3,1) .15s;
}
