/* ==========================================================================
   The journey — hero → dome → collections, as a forward-moving camera.

   How it works
   ------------
   `.journey` is a tall, ordinary section. `.journey-stage` sticks to the top
   of the screen while that section scrolls past. JavaScript turns how far you
   have scrolled into a set of custom properties; everything below is driven
   by those. Scrolling stays completely normal — nothing is hijacked.

   Only `transform` and `opacity` are animated. Both are handled by the
   graphics card, which is why this stays smooth on a phone.
   ========================================================================== */

.journey {
  position: relative;
  background: var(--ink);
  /* Height is written by the build from the number of featured collections. */
  height: var(--journey-height, 900svh);
}

.journey-stage {
  position: sticky; top: 0;
  height: 100svh; overflow: hidden;
  isolation: isolate; background: var(--ink);

  /* Written by journey.js on every frame. */
  --hero-push: 0;      /* 0 → 1 : camera moving through the hero */
  --hero-fade: 1;
  --dome-in: 0;        /* 0 → 1 : dome approach */
  --dome-fade: 0;
  --inside: 0;         /* 0 → 1 : arrival inside the Dome */
}

/* ---- backdrop: a blueprint elevation of the Dome, over setting-out lines --
   The drawing is a single 3.5KB SVG of pure geometry, so it stays sharp at
   any size. Opacity is 15% above the old plain grid. */
.journey-grid {
  position: absolute; inset: -20%; z-index: 0;
  background-image:
    url("/assets/img/dome-schematic.svg"),
    linear-gradient(#ffffff0f 1px, transparent 1px),
    linear-gradient(90deg, #ffffff0f 1px, transparent 1px);
  background-size: min(148%, 1600px) auto, 70px 70px, 70px 70px;
  background-position: center 42%, center, center;
  background-repeat: no-repeat, repeat, repeat;
  transform: scale(calc(1 + var(--hero-push) * 1.6));
  opacity: calc(.63 * (1 - var(--inside) * .45));
  mask-image: radial-gradient(ellipse 84% 70% at 50% 45%, #000 42%, transparent 82%);
  will-change: transform;
}

/* ---- 1. hero: portrait + typography, camera pushes through ---- */
.hero-scene {
  position: absolute; inset: 0; z-index: 2;
  opacity: var(--hero-fade);
  pointer-events: none;
}
.journey .hero-title {
  position: absolute; inset: 0;
  display: grid; align-content: start; justify-items: center;
  padding-top: 15svh;
  transform: scale(calc(1 + var(--hero-push) * 5.4));
  will-change: transform;
}
.journey .hero-title h1 { display: grid; justify-items: center; text-align: center; }
.journey .hero-title span {
  font: 600 clamp(30px, 7vw, 78px)/1 var(--font-display);
  letter-spacing: .34em; text-indent: .34em; color: var(--cream);
  text-transform: uppercase; margin-bottom: .18em;
}
.journey .hero-title strong {
  font: 700 clamp(72px, 26vw, 300px)/0.78 var(--font-display);
  letter-spacing: -0.02em; text-transform: uppercase;
  color: var(--gold); text-shadow: 0 18px 60px #000a; white-space: nowrap;
}
.journey .hero-portrait {
  position: absolute; left: 50%; bottom: 0;
  width: min(560px, 92vw);
  transform: translate(-50%, calc(var(--hero-push) * -6%))
             scale(calc(1 + var(--hero-push) * 0.55));
  will-change: transform;
}
@media (max-width: 800px) { .journey .hero-portrait { width: 118vw; } }
.journey .hero-portrait img {
  width: 100%; height: auto; object-fit: contain;
  /* A tight contact shadow grounds him against the floor of the scene, and a
     softer, wider one behind adds real depth -- both tuned dark-warm rather
     than pure black so they actually show up against the near-black
     background instead of vanishing into it. Purely visual: no extra image,
     no extra request, and it sits on the picture itself rather than on
     anything the scroll animation touches, so it costs nothing at load and
     nothing during the journey. */
  filter:
    drop-shadow(0 14px 22px rgba(0, 0, 0, .55))
    drop-shadow(0 48px 100px rgba(10, 8, 6, .65));
}

/* ---- 2. the dome, approached from a distance ---- */
.dome-scene {
  position: absolute; inset: 0; z-index: 1;
  opacity: var(--dome-fade);
  transform: scale(calc(.72 + var(--dome-in) * 3.1))
             translateY(calc((1 - var(--dome-in)) * 5%));
  will-change: transform, opacity;
}
.dome-scene img { position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; object-position: center 68%; }
.dome-vignette {
  position: absolute; inset: 0;
  background: radial-gradient(ellipse 66% 52% at 50% 46%, #0000 30%, #070606d9 78%, #070606 100%);
}
.dome-copy {
  position: absolute; z-index: 3; left: var(--gutter); bottom: 12%;
  opacity: calc(var(--dome-fade) * (1 - var(--inside)));
}
.dome-copy strong {
  display: block;
  font: 700 clamp(34px, 12vw, 120px)/0.92 var(--font-display);
  text-transform: uppercase; color: var(--cream); text-shadow: 0 10px 40px #000;
}

/* ---- 3. arrival ---- */
.inside-intro {
  position: absolute; inset: 0; z-index: 3;
  display: grid; align-content: center; justify-items: center; gap: 6px;
  text-align: center; padding-inline: var(--gutter);
  opacity: var(--intro-fade, 0); pointer-events: none;
}
.inside-intro span { color: var(--muted); }
.inside-intro strong {
  font: 700 clamp(34px, 11vw, 120px)/0.94 var(--font-display);
  text-transform: uppercase; color: var(--gold);
}

/* ---- 4. the collections, travelling towards the camera ---- */
.portal-space {
  position: absolute; inset: 0; z-index: 4;
  perspective: var(--perspective);
  perspective-origin: 50% 46%;
  transform-style: preserve-3d;
  opacity: var(--portals-in, 0);
  /* The cards sit at a negative Z, which puts them BEHIND this container in
     3D space. Without this, every tap landed on the container instead of the
     card and nothing opened. The docked card re-enables events for itself. */
  pointer-events: none;
}

.journey-portal {
  position: absolute; top: 50%; left: 50%;
  width: min(94vw, 1180px); height: min(82svh, 880px);
  margin: 0; padding: 0; overflow: hidden; text-align: left;
  background: var(--charcoal);
  border: 1px solid #d89a1240;
  box-shadow: 0 40px 130px #000, 0 0 0 1px #00000060;
  /* --pz and --po are written per portal, per frame, by journey.js */
  transform: translate3d(-50%, -50%, var(--pz, -3000px));
  opacity: var(--po, 0);
  pointer-events: none;            /* only the docked card is clickable */
  display: none;                   /* only the few near the camera exist */
}
/* Portals inside the render window. Keeping this list to three keeps the
   compositor's memory use flat no matter how many collections there are. */
.journey-portal.is-live { display: block; }
.journey-portal.is-live.is-moving { will-change: transform, opacity; }
/* The docked card is the one you can actually tap. */
.journey-portal.is-docked { pointer-events: auto; }

.journey-portal img {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; filter: saturate(.9) contrast(1.05);
}
.portal-wash {
  position: absolute; inset: 0;
  background:
    linear-gradient(180deg, #07060659 0%, #07060640 26%, #070606e3 58%, #070606fc 100%),
    linear-gradient(0deg, #07060640 0%, #0000 40%);
}
.portal-copy {
  position: absolute; left: clamp(20px, 3vw, 44px); right: clamp(20px, 3vw, 44px);
  bottom: clamp(64px, 8vh, 96px);
  display: grid; gap: 7px;
}
.portal-copy small { color: var(--muted); }
.portal-copy strong {
  font: 700 clamp(30px, 8vw, 78px)/0.94 var(--font-display);
  text-transform: uppercase; color: var(--white);
}
.portal-copy em { color: var(--gold); font-style: normal; }
.portal-number {
  position: absolute; top: clamp(18px, 2.4vw, 30px); left: clamp(20px, 3vw, 44px);
  font: 500 var(--step-label)/1 var(--font-mono); letter-spacing: .2em; color: var(--gold);
}
.portal-enter {
  position: absolute; left: clamp(20px, 3vw, 44px); bottom: clamp(20px, 3vh, 34px);
  display: inline-grid; grid-auto-flow: column; align-items: center; gap: 10px;
  min-height: 44px; padding: 11px 18px;
  border: 1px solid var(--gold); color: var(--gold);
  font: 500 var(--step-label)/1 var(--font-mono);
  letter-spacing: .14em; text-transform: uppercase;
  opacity: 0; transform: translateY(6px);
  transition: opacity var(--dur) var(--ease-out), transform var(--dur) var(--ease-out),
              background var(--dur) var(--ease-out), color var(--dur) var(--ease-out);
}
.journey-portal.is-docked .portal-enter { opacity: 1; transform: translateY(0); }
.journey-portal.is-docked:hover .portal-enter { background: var(--gold); color: var(--ink); }

/* ---- persistent controls ---- */
.journey-copy {
  position: absolute; z-index: 6; left: var(--gutter); bottom: clamp(24px, 5vh, 60px);
  max-width: min(62vw, 420px); display: grid; gap: 12px;
  opacity: var(--hero-fade); pointer-events: none;
}
.journey-copy p { font-size: var(--step-lead); line-height: 1.35; text-shadow: 0 2px 20px #000c; }
.journey-copy span { color: var(--gold); }

.journey-skip {
  position: absolute; z-index: 8; right: var(--gutter);
  top: calc(var(--header-h) + 10px);
  display: inline-grid; grid-auto-flow: column; align-items: center; gap: 8px;
  justify-content: center; white-space: nowrap;
  min-height: 44px; padding: 10px 15px;
  border: 1px solid #d89a1266; background: #0a0a09cc; color: var(--gold);
  font: 500 var(--step-label)/1 var(--font-mono);
  letter-spacing: .14em; text-transform: uppercase;
  max-width: calc(100vw - var(--gutter) * 2);
  transition: background var(--dur) var(--ease-out), color var(--dur) var(--ease-out);
}
.journey-skip:hover { background: var(--gold); color: var(--ink); }

.journey-progress {
  position: absolute; z-index: 7; left: 50%; bottom: clamp(14px, 2.4vh, 26px);
  transform: translateX(-50%);
  width: min(42vw, 260px); height: 2px; background: #ffffff1f;
}
.journey-progress i {
  display: block; height: 100%; width: calc(var(--progress, 0) * 100%);
  background: var(--gold);
}

/* On a phone the skip control is a quiet marker, not a banner. */
@media (max-width: 800px) {
  .journey-skip {
    top: auto; bottom: calc(env(safe-area-inset-bottom) + 34px);
    right: 12px; padding: 9px 11px; gap: 6px;
    font-size: 9px; letter-spacing: .1em;
    background: #0a0a09e6; border-color: #d89a1240;
  }
}

/* ---- snap stops (one per collection). Toggle in content/portfolio.json ---- */
.journey-stop {
  position: absolute; left: 0; width: 1px; height: 1px;
  /* The page reserves space at the top for the fixed header
     (scroll-padding-top). Snap points must ignore that, or every card would
     settle exactly one header-height short of the camera. */
  scroll-margin-top: calc(var(--header-h) * -1);
}
.journey[data-snap="on"] .journey-stop { scroll-snap-align: start; }
html:has(.journey[data-snap="on"]) { scroll-snap-type: y proximity; }

/* ==========================================================================
   Reduced motion — no camera, no 3D. The same content, stacked and still.
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  .journey { height: auto !important; }
  .journey-stage { position: relative; height: auto; overflow: visible; }
  .journey-grid, .dome-vignette { display: none; }

  .hero-scene { position: relative; height: 100svh; opacity: 1; }
  .journey .hero-title, .journey .hero-portrait { transform: none; }
  .journey .hero-portrait { position: absolute; left: 50%;
    transform: translateX(-50%); }

  .dome-scene { position: relative; height: 62svh; opacity: 1; transform: none; }
  .dome-copy { opacity: 1; }

  .inside-intro { position: relative; opacity: 1 !important; padding-block: 70px; }

  .portal-space {
    position: relative; opacity: 1; perspective: none; transform-style: flat;
    display: grid; gap: 14px; padding: 0 var(--gutter) 70px;
  }
  .journey-portal {
    position: relative; top: auto; left: auto; display: block !important;
    width: 100%; height: 66svh; min-height: 420px;
    transform: none !important; opacity: 1 !important; pointer-events: auto !important;
    will-change: auto !important;
  }
  .journey-portal .portal-enter { opacity: 1; transform: none; }
  .journey-progress, .journey-copy { display: none; }
  html:has(.journey) { scroll-snap-type: none; }
}
