/* SANS camera journey - motion and states.
 *
 * The layout of the journey (pinned stage, runway height, phone boundary, arc,
 * callout look, static list, the seam shade) lives in sections.css and is not
 * repeated here. This file holds only what moves and what changes state.
 * journey.js writes the per-frame values inline (the vignette's scale and the
 * grain's strength included - they live only there); the list is in the header
 * of journey.js. Durations and curves are the motion tokens of tokens.css.
 *
 * Everything is scoped to the running camera (.journey without --static,
 * .hero.is-camera), so the static list and the still hero - no script,
 * reduced motion - are untouched.
 */

/* --- camera layers ------------------------------------------------------ */

.journey:not(.journey--static) .scene__media,
.journey-ring {
  transform-origin: 50% 50%;
  backface-visibility: hidden;
}

/* Only the scenes on screen get their own layers. */

.journey:not(.journey--static) .scene.is-live {
  will-change: opacity;
}

.journey:not(.journey--static) .scene.is-live .scene__media,
.journey:not(.journey--static) .scene.is-live .journey-ring {
  will-change: transform;
}

/* A tall narrow screen would crop the equipment out of a cover-fit picture:
 * the whole picture is shown instead, with its top and bottom edge melted
 * into the stage. --band is the picture's share of the stage height. */

.journey:not(.journey--static) .scene.is-boxed .scene__media img {
  object-fit: contain;
  -webkit-mask: linear-gradient(180deg, transparent, #000 18%, #000 82%, transparent) center / 100% var(--band, 100%) no-repeat;
  mask: linear-gradient(180deg, transparent, #000 18%, #000 82%, transparent) center / 100% var(--band, 100%) no-repeat;
}

/* Scene 01's car is held under the hero's car while the stage rises; boxed,
 * its side edges melt like the hero's picture, so no straight edge shows. */

.journey:not(.journey--static) #scene-0.is-boxed .scene__media img {
  -webkit-mask:
    linear-gradient(180deg, transparent, #000 18%, #000 82%, transparent) center / 100% var(--band, 100%) no-repeat,
    linear-gradient(90deg, transparent 2%, #000 9%, #000 91%, transparent 98%);
  -webkit-mask-composite: source-in;
  mask:
    linear-gradient(180deg, transparent, #000 18%, #000 82%, transparent) center / 100% var(--band, 100%) no-repeat,
    linear-gradient(90deg, transparent 2%, #000 9%, #000 91%, transparent 98%);
  mask-composite: intersect;
}

/* On a wide screen journey.js writes the same edge fade on #scene-0 (in stage
 * pixels) and on the hero's picture while the two cars are one; the fade
 * closes as the stage pins. */

.journey:not(.journey--static) #scene-0.is-held {
  -webkit-mask-composite: source-in;
  mask-composite: intersect;
}

/* --- scene 01: the red arch and the lens -------------------------------- */

/* Two layers journey.js creates inside #scene-0 and moves with inline
 * transform / opacity only, so the compositor handles them frame to frame:
 * the vignette (the lit arch tightens and lifts, the edges fall dark) and the
 * grain (comes up as the camera dives). */

.journey-shade,
.journey-grain {
  position: absolute;
  pointer-events: none;
}

.journey-shade {
  inset: -50%;
  z-index: 2;
  background: radial-gradient(ellipse 31% 33% at 50% 47%, transparent 56%, color-mix(in srgb, var(--bg) 58%, transparent) 78%, var(--bg) 100%);
}

.journey-grain {
  inset: 0;
  z-index: 3;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 .9 -.35'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23g)'/%3E%3C/svg%3E");
  opacity: 0;
}

.journey:not(.journey--static) .scene.is-live .journey-shade {
  will-change: transform;
}

.journey:not(.journey--static) .scene.is-live .journey-grain {
  will-change: opacity;
}

.journey--static .journey-shade,
.journey--static .journey-grain {
  display: none;
}

/* --- scene 03: ring gauge around the tank ------------------------------- */

.journey-ring {
  position: absolute;
  inset: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
  overflow: visible;
  pointer-events: none;
}

.journey--static .journey-ring {
  display: none;
}

.journey-ring__ticks {
  fill: none;
  stroke: var(--tick);
  stroke-width: 16;
  stroke-dasharray: .0014 .0186;
}

.journey-ring__arc {
  fill: none;
  stroke: var(--red);
  stroke-width: 4;
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
}

/* --- callouts ------------------------------------------------------------ */

/* In: the dot surfaces, the leader draws, the label slides out.
 * Out: the same in reverse, and quicker - over by --slow, when the callout
 * stops being visible. */

.journey:not(.journey--static) .hotspot {
  width: max-content;
  visibility: hidden;
  transition: visibility 0s linear var(--slow);
}

.journey:not(.journey--static) .hotspot.is-shown {
  visibility: visible;
  transition-delay: 0s;
}

.journey:not(.journey--static) .hotspot__dot {
  position: relative;
  opacity: 0;
  transform: scale(.3);
  transition:
    opacity var(--fast) var(--ease) var(--dur-short),
    transform var(--dur-short) var(--ease) var(--dur-short);
}

.journey:not(.journey--static) .hotspot.is-shown .hotspot__dot {
  opacity: 1;
  transform: none;
  transition:
    opacity var(--slow) var(--ease),
    transform var(--dur-long) var(--ease-out);
}

.journey:not(.journey--static) .hotspot__dot::after {
  content: '';
  position: absolute;
  inset: -1.5px;
  border: 1.5px solid var(--red);
  border-radius: 50%;
  opacity: 0;
}

.journey:not(.journey--static) .hotspot.is-shown .hotspot__dot::after {
  animation: hotspot-pulse var(--dur-pulse) var(--ease-out) var(--slow) infinite;
}

@keyframes hotspot-pulse {
  0% {
    opacity: .85;
    transform: scale(1);
  }

  100% {
    opacity: 0;
    transform: scale(2.8);
  }
}

.journey:not(.journey--static) .hotspot__line {
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform var(--dur-short) var(--ease) calc(var(--dur-short) / 2);
}

.journey:not(.journey--static) .hotspot[data-side='left'] .hotspot__line {
  transform-origin: 100% 50%;
}

.journey:not(.journey--static) .hotspot.is-shown .hotspot__line {
  transform: none;
  transition: transform var(--slow) var(--ease-out) var(--fast);
}

.journey:not(.journey--static) .hotspot__label {
  opacity: 0;
  transform: translate3d(-10px, 0, 0);
  transition:
    opacity var(--fast) var(--ease),
    transform var(--dur-short) var(--ease);
}

.journey:not(.journey--static) .hotspot[data-side='left'] .hotspot__label {
  transform: translate3d(10px, 0, 0);
}

.journey:not(.journey--static) .hotspot.is-shown .hotspot__label {
  opacity: 1;
  transform: none;
  transition:
    opacity var(--slow) var(--ease) var(--slow),
    transform var(--dur-long) var(--ease-out) var(--slow);
}

/* --- the arc ------------------------------------------------------------- */

.journey:not(.journey--static) .hud__num,
.journey:not(.journey--static) .hud__label {
  transition: fill var(--slow) var(--ease);
}

.journey:not(.journey--static) .hud__icon {
  transition: color var(--slow) var(--ease);
}

.journey:not(.journey--static) .hud__seg.is-active .hud__cell {
  stroke-width: 1.5;
}

/* Marks the progress line has already passed turn red with it. */

.journey:not(.journey--static) .hud__tick {
  transition: fill var(--fast) var(--ease);
}

.journey:not(.journey--static) .hud__tick.is-passed {
  fill: var(--red);
}

/* --- phones: no layer parallax, no blur ---------------------------------- */

@media (max-width: 767.98px) {
  .journey:not(.journey--static) .hotspot__line {
    width: 18px;
  }

  .journey:not(.journey--static) .hotspot__label {
    max-width: 11em;
    white-space: normal;
  }
}

/* --- hero: the first move of the same camera ----------------------------- */

/* journey.js sets .is-camera and writes every value inline from the scroll.
 * The car scales from its top-left corner so its rect can be matched to the
 * picture of scene 01. On a wide screen it gets no will-change on purpose: the
 * browser then re-rasterises it as it grows, and the handoff stays sharp. */

.hero.is-camera .hero__car {
  transform-origin: 0 0;
}

.hero.is-camera .hero__arch {
  will-change: transform, opacity;
}

/* On a phone only the compositor works: every moving part has its own layer,
 * and the heavy word and glass frame only fade (journey.js). */

@media (max-width: 767.98px) {
  .hero.is-camera .hero__car {
    will-change: transform;
  }

  .hero.is-camera .hero__title,
  .hero.is-camera .hero__frame,
  .hero.is-camera .hero__car ~ * {
    will-change: opacity;
  }
}

/* A part that has faded out takes no taps. A keyboard user tabbing back up
 * must still see - and be able to press - the control that holds focus. */

.hero.is-camera .is-gone {
  pointer-events: none;
}

.hero.is-camera .trust:focus-within,
.hero.is-camera .hero__actions:focus-within {
  opacity: 1 !important;
  pointer-events: auto;
}

/* The seam. The veil darkens the hero's bottom edge with the same shade the
 * stage carries on its top edge (--seam-* in sections.css), so the car passes
 * under one soft shadow instead of a hard line. Above the car, below the copy. */

.hero-veil {
  position: absolute;
  inset: auto 0 0;
  z-index: 1;
  height: var(--seam-h);
  pointer-events: none;
  opacity: 0;
  background: var(--seam-side), var(--seam-tint);
  -webkit-mask-image: linear-gradient(180deg, transparent, #000);
  mask-image: linear-gradient(180deg, transparent, #000);
  will-change: opacity;
}

@media (prefers-reduced-motion: reduce) {
  .journey:not(.journey--static) .hotspot.is-shown .hotspot__dot::after {
    animation: none;
  }
}
