/* ==========================================================================
   Scene — one perspective camera, one stage, everything lives inside it
   ========================================================================== */

.scene {
  position: fixed;
  inset: 0;
  perspective: 1100px;
  perspective-origin: 50% 46%;
  /* Scaffolding planes sit at z=0, which puts them in front of every element
     pushed back for depth. They are never hit targets; the few genuinely
     interactive descendants (.media, .letter) opt back in by themselves. */
  pointer-events: none;
  background:
    radial-gradient(120% 90% at 50% 8%, oklch(0.26 0.13 300 / 0.55), transparent 62%),
    radial-gradient(100% 80% at 12% 96%, oklch(0.24 0.14 330 / 0.42), transparent 60%),
    radial-gradient(90% 70% at 92% 88%, oklch(0.22 0.15 275 / 0.45), transparent 62%),
    linear-gradient(178deg, oklch(0.175 0.095 292) 0%, oklch(0.115 0.062 285) 52%, oklch(0.085 0.045 278) 100%);
}

.stage {
  position: absolute;
  inset: 0;
  z-index: 1;
  transform-style: preserve-3d;
  transform: rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg));
  transition: transform 900ms var(--ease-out-expo);
}

/* --- Backdrop: optional CDN photograph + animated aurora -------------- */
.backdrop {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  /* This plane sits at z=0, in front of every tile that is pushed back for
     depth. Its blobs are purely decorative, so they must never be hit
     targets — otherwise they swallow clicks aimed at distant photos. */
  pointer-events: none;
  transform: translate3d(calc(var(--px, 0px) * 0.16), calc(var(--py, 0px) * 0.16), 0);
}

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

.backdrop__frame {
  position: absolute;
  inset: 0;
  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
  opacity: 0;
  transform: scale(1.08);
  transition: opacity 1600ms var(--ease-out-quint), transform 9000ms linear;
}

.backdrop__frame.is-active {
  opacity: 1;
  transform: scale(1);
}

.backdrop__veil {
  position: absolute;
  inset: 0;
  background:
    radial-gradient(105% 78% at 50% 42%, transparent 22%, oklch(0.07 0.04 280 / 0.72) 100%),
    linear-gradient(180deg, oklch(0.09 0.05 282 / 0.55), oklch(0.07 0.04 278 / 0.82));
  opacity: var(--veil, 0);
  transition: opacity 600ms var(--ease-out-quint);
}

.aurora {
  position: absolute;
  display: block;
  border-radius: 50%;
  /* The blobs are sized in vmax, so the blur has to scale with them. A fixed
     radius turns a phone viewport into one flat wash. */
  filter: blur(clamp(40px, 9vmax, 110px));
  will-change: transform;
  opacity: 0.5;
}

.aurora--a {
  width: 52vmax;
  height: 52vmax;
  top: -22vmax;
  left: -10vmax;
  background: radial-gradient(circle, oklch(0.55 0.24 330 / 0.55), transparent 68%);
  animation: aurora-drift-a 34s ease-in-out infinite alternate;
}

.aurora--b {
  width: 44vmax;
  height: 44vmax;
  bottom: -20vmax;
  right: -12vmax;
  background: radial-gradient(circle, oklch(0.48 0.22 292 / 0.5), transparent 68%);
  animation: aurora-drift-b 42s ease-in-out infinite alternate;
}

.aurora--c {
  width: 36vmax;
  height: 36vmax;
  top: 42%;
  right: 24%;
  background: radial-gradient(circle, oklch(0.58 0.2 356 / 0.34), transparent 70%);
  animation: aurora-drift-c 50s ease-in-out infinite alternate;
}

/* --- Dust / heart particle field --------------------------------------
   Sits between the backdrop and the stage, so the hearts are ambient
   background and can never drift across the letter or the photos.
   ---------------------------------------------------------------------- */
.dust {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  transition: opacity var(--dur-4) var(--ease-out-quint);
}

/* --- 3D layers --------------------------------------------------------
   Note: never put opacity on a layer — it would flatten preserve-3d and
   kill the depth of everything inside it.
   ---------------------------------------------------------------------- */
.layer {
  position: absolute;
  inset: 0;
  transform-style: preserve-3d;
  pointer-events: none;
}

.layer--text {
  z-index: 2;
  transform: translate3d(calc(var(--px, 0px) * -0.42), calc(var(--py, 0px) * -0.42), 0);
}

.layer--media {
  z-index: 3;
  transform: translate3d(calc(var(--px, 0px) * -0.9), calc(var(--py, 0px) * -0.9), 0);
}

/* --- Falling glyphs --------------------------------------------------- */
.glyph {
  position: absolute;
  top: 0;
  left: 0;
  font-weight: 900;
  font-size: var(--size, 20px);
  line-height: 1;
  letter-spacing: 0.01em;
  white-space: nowrap;
  color: var(--tint, var(--rose-300));
  text-shadow: 0 0 20px color-mix(in oklab, var(--tint, var(--rose-500)) 52%, transparent);
  will-change: transform, opacity;
  user-select: none;
  backface-visibility: hidden;
}

.glyph--outline {
  color: transparent;
  -webkit-text-stroke: 1.4px color-mix(in oklab, var(--tint, var(--rose-300)) 72%, transparent);
  text-shadow: none;
}

/* --- Floating photo planes -------------------------------------------- */
.media {
  position: absolute;
  top: 0;
  left: 0;
  width: var(--w, 148px);
  aspect-ratio: 3 / 4;
  padding: 0;
  border-radius: var(--r-md);
  background: none;
  pointer-events: auto;
  transform-style: preserve-3d;
  will-change: transform;
  -webkit-tap-highlight-color: transparent;
}

.media__frame {
  position: relative;
  display: block;
  width: 100%;
  height: 100%;
  border-radius: inherit;
  overflow: hidden;
  background: color-mix(in oklab, var(--ink-700) 60%, transparent);
  box-shadow:
    0 32px 62px -28px oklch(0.03 0.05 288 / 0.98),
    0 0 0 1px var(--glass-line),
    0 0 36px -8px color-mix(in oklab, var(--rose-500) 42%, transparent);
  transform: translateZ(0);
  transition:
    transform var(--dur-3) var(--ease-spring),
    box-shadow var(--dur-3) var(--ease-out-quint);
}

.media__frame::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  box-shadow: inset 0 0 0 1px color-mix(in oklab, white 16%, transparent);
  pointer-events: none;
}

.media__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--dur-4) var(--ease-out-quint), filter var(--dur-3) var(--ease-out-quint);
  filter: saturate(0.94) brightness(0.96);
}

/* Depth of field. Applied as a static class (never animated) so the blur is
   rasterised once and the per-frame cost stays on transform only. Kept gentle:
   these are photographs of people, and heavy dimming turns them to mud. */
.media.is-far .media__img {
  filter: blur(1.1px) saturate(0.9) brightness(0.9);
}

.media.is-farthest .media__img {
  filter: blur(2px) saturate(0.78) brightness(0.8);
}

@media (hover: hover) {
  .media:hover .media__frame {
    transform: translateZ(80px) scale(1.06);
    box-shadow:
      var(--shadow-tile),
      0 0 0 1px var(--glass-line-hi),
      0 0 46px -6px color-mix(in oklab, var(--rose-500) 62%, transparent);
  }

  .media:hover .media__img {
    transform: scale(1.06);
    filter: saturate(1.05) brightness(1.04);
  }
}

.media:focus-visible {
  outline: none;
  box-shadow: none;
}

.media:focus-visible .media__frame {
  transform: translateZ(80px) scale(1.06);
  box-shadow: var(--focus-ring), 0 0 46px -6px color-mix(in oklab, var(--rose-500) 62%, transparent);
}

.media:active .media__frame {
  transform: translateZ(52px) scale(0.98);
  transition-duration: var(--dur-1);
}

.media__badge {
  position: absolute;
  inset: auto var(--s-2) var(--s-2) auto;
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border-radius: var(--r-pill);
  background: color-mix(in oklab, var(--ink-950) 62%, transparent);
  backdrop-filter: blur(8px);
  color: var(--text-strong);
  opacity: 0;
  transform: translateY(4px);
  transition: opacity var(--dur-2) var(--ease-out-quint), transform var(--dur-2) var(--ease-out-quint);
}

.media__badge svg {
  width: 14px;
  height: 14px;
}

@media (hover: hover) {
  .media:hover .media__badge,
  .media:focus-visible .media__badge {
    opacity: 1;
    transform: translateY(0);
  }
}
