/* ==========================================================================
   HAVEN. — the opening horizon: a photograph that becomes a window.

   core/hero.js publishes ONE number, --hero-p, across the section's travel.
   Every part of the transformation below is a function of it, which is why
   there is no animation code in assets/js/site/horizon.js beyond handing the
   window over to the lens.

   --fp is the framing progress: nothing happens for the first 40% of the
   travel — the photograph is simply the screen and the headline sits on it —
   and then the four edges close in, all the way to the rectangle the lens's
   first station occupies. The two are the same numbers (--open-x, --open-top,
   --open-bottom in tokens.css), so the handover lands on one rectangle.
   ========================================================================== */
.horizon {
  height: 260vh;
  background: var(--chalk);
}
.horizon__pin {
  position: sticky;
  top: 0;
  height: 100vh;
  overflow: hidden;
  --fp: clamp(0, calc((var(--hero-p, 0) - .40) * 1.79), 1);
  --tp: clamp(0, calc((var(--hero-p, 0) - .26) * 2.9), 1);
}

.horizon__photo {
  position: absolute;
  inset: 0;
  clip-path: inset(
    calc(var(--fp) * var(--open-top))
    calc(var(--fp) * var(--open-x))
    calc(var(--fp) * var(--open-bottom))
    calc(var(--fp) * var(--open-x)));
  /* the lens takes over on the last frames of the travel, on the same
     rectangle and holding the same photograph, so this simply stops being
     drawn. horizon.js writes --lens-on; there is no crossfade, because two
     identical pictures at half opacity are lighter than one at full. */
  opacity: calc(1 - var(--lens-on, 0));
}
.horizon__pic, .horizon__img { width: 100%; height: 100%; }
.horizon__img {
  object-fit: cover;
  /* a slow settle rather than a zoom: six per cent, over the whole travel */
  transform: scale(calc(1.06 - .06 * var(--fp)));
  transform-origin: 50% 46%;
}
.horizon__wash {
  position: absolute; inset: 0;
  background:
    linear-gradient(to bottom, rgba(11, 82, 100, .34) 0%, rgba(11, 82, 100, 0) 34%),
    linear-gradient(to top, rgba(21, 21, 21, .52) 0%, rgba(21, 21, 21, 0) 46%);
  opacity: calc(1 - var(--fp) * .55);
}

.horizon__type {
  position: absolute;
  left: var(--gutter);
  right: var(--gutter);
  bottom: 16vh;
  color: var(--chalk);
  opacity: calc(1 - var(--tp));
  transform: translateY(calc(var(--tp) * -6vh));
}
.horizon__line {
  letter-spacing: -.015em;
  /* the measure has to be set in the DISPLAY font's own em, not in ch on the
     container: `ch` there resolves against the 17px body face and turns a
     three-word headline into a narrow column. */
  max-width: min(92%, 9.5em);
}
.horizon__sub {
  font-size: clamp(1rem, 1.4vw, 1.2rem);
  line-height: 1.5;
  color: rgba(245, 241, 232, .82);
  max-width: 34ch;
  margin: 1.6rem 0 0;
}
.horizon__cta { display: flex; flex-wrap: wrap; gap: 1rem 1.8rem; margin-top: 2.2rem; }
.horizon__cta .btn--solid { background: var(--chalk); color: var(--ink); border-color: var(--chalk); }
.horizon__cta .btn--solid:hover { background: var(--sun); border-color: var(--sun); }
.horizon__cta .btn--quiet { color: var(--chalk); }

.horizon__cue {
  position: absolute;
  right: var(--gutter);
  bottom: 4.5vh;
  color: rgba(245, 241, 232, .7);
  opacity: calc(1 - var(--tp));
  writing-mode: vertical-rl;
  letter-spacing: .3em;
}

/* the entrance: core/hero.js releases these in order once the photograph has
   actually decoded, so the composition never animates around a grey box */
.horizon [data-enter] {
  opacity: 0;
  transform: translateY(1.4rem);
  transition: opacity .9s var(--ease), transform 1s var(--ease);
  transition-delay: var(--delay, 0ms);
}
.horizon [data-enter].is-in { opacity: 1; transform: none; }

/* STATION 01 — an empty box in the section's LAST viewport, which is exactly
   where the pinned photograph comes to rest. 260vh of section, a 100vh pin:
   the last screen starts at 160vh. This is arithmetic, not a magic number, and
   it is the same arithmetic as data-hero-travel in index.html. */
.horizon__frame {
  position: absolute;
  top: calc(160vh + var(--open-top));
  bottom: var(--open-bottom);
  left: var(--open-x);
  right: var(--open-x);
  width: auto;              /* left+right decide it, not the g-open default */
  aspect-ratio: auto;       /* and so does the height */
  pointer-events: none;
}

@media (max-width: 899px) {
  .horizon { height: 220vh; }
  .horizon__pin { --fp: clamp(0, calc((var(--hero-p, 0) - .44) * 1.92), 1); }
  .horizon__frame { top: calc(120vh + var(--open-top)); }
  .horizon__type { bottom: 13vh; }
  /* the headline lands on a white linen dress at this width, so the bottom of
     the wash has to carry it rather than the photograph */
  .horizon__wash {
    background:
      linear-gradient(to bottom, rgba(11, 82, 100, .3) 0%, rgba(11, 82, 100, 0) 28%),
      linear-gradient(to top, rgba(21, 21, 21, .74) 0%, rgba(21, 21, 21, .28) 34%,
                      rgba(21, 21, 21, 0) 58%);
  }
  .horizon__cue { display: none; }
}

/* --- reduced motion ------------------------------------------------------
   No pin, no cinematic sequence, no travel. The photograph is simply the top
   of the page at a readable size and the headline sits on it; --fp and --tp
   are pinned at 0 so nothing is a function of scroll any more. The station is
   removed with it, because in static mode the hero picture in the markup IS
   the photograph and mounting a second copy into an invisible box would be a
   download for nothing. */
@media (prefers-reduced-motion: reduce) {
  .horizon { height: auto; }
  .horizon__pin {
    position: static;
    height: auto;
    min-height: 86vh;
    --fp: 0;
    --tp: 0;
  }
  .horizon__photo { opacity: 1; }
  .horizon__frame { display: none; }
}
