/* ==========================================================================
   The ascent — shared scroll-seeked summit fold
   --------------------------------------------------------------------------
   Drop-in for any page in the network. Self-contained: every value it needs
   is declared here with a fallback, so it works alongside the root site's
   tokens.css and alongside the sub-sites' styles.css without either.

   Where tokens.css IS present its values win, so the root page and the six
   practice sites stay in step automatically.

   Requires: assets/ascent/ascent.js and the markup in the README.
   ========================================================================== */

/* Declared on :root, not on .ascent — the masthead inverts to cream over the
   fold, and it is NOT a descendant of .ascent, so a palette scoped to the
   section would leave var(--a-on-ink) unresolved on the nav. */
:root {
  /* Brand surface. Falls back to the network's midnight navy. */
  --a-ink:        var(--color-massif-deep, #0a1020);
  --a-on-ink:     var(--color-on-ink, #faf7f1);
  --a-scrim-rgb:  var(--scrim-rgb, 18, 26, 47);
  --a-shell:      var(--shell, 1180px);
  --a-display:    var(--font-display, "Fraunces", "Playfair Display", Georgia, serif);

  /* "OWN" and the practice name. Sized against both vw and vh so a long name
     ("YOUR UNI ADMISSION") cannot overflow a short landscape viewport. */
  --a-summit:     var(--text-summit, clamp(2.75rem, min(15vw, 17vh), 12rem));
  --a-base-camp:  var(--text-base-camp, clamp(1.5rem, min(7vw, 8.5vh), 5.5rem));
}

.ascent {
  --p: 0;
  position: relative;
  /* 200vh of scroll for 2.02x of travel — about 100vh per doubling. Shorten
     it and the push feels hurried; lengthen it and a 2x move reads as drift. */
  height: 300vh;
  background: var(--a-ink);
  scroll-margin-top: 0;
}

/* Large viewport, not small. On a phone the browser bar retracts the moment
   you start scrolling, and the fold is pinned for the whole of that scroll —
   so `100svh` left a band of --a-ink across the bottom of every frame from
   the first flick to the last. `100lvh` is the height the stage is actually
   looked at. The only things it can hide are the caption and the cue, which
   sit at rest under an expanded bar; they are lifted by the bar's own height
   below rather than the whole stage being shortened for them. */
.ascent__stage {
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100lvh;
  overflow: clip;
  isolation: isolate;
  background: var(--a-ink);
  /* Zero everywhere the two viewports agree — every desktop, and a phone with
     the bar already retracted. */
  --a-bar: 0px;
}
@supports (height: 100lvh) {
  .ascent__stage { --a-bar: calc(100lvh - 100svh); }
}

.ascent__frames { position: absolute; inset: 0; z-index: 1; }

/* The camera move is inside the file. Nothing here is transformed and nothing
   is cross-faded — scroll only changes which frame is decoded, so the fold
   costs no layout and no paint, just the decode.

   object-position sits above centre because the summit does: the clip is 16:9
   and the stage is usually taller, so the crop must keep the peak and flag in
   frame rather than the foreground rock. */
.ascent__video,
.ascent__still {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 42%;
  background: var(--a-ink);
}

/* The still sits UNDER the video and is revealed by the video hiding, not by
   being raised — so a failed decode uncovers a photograph, not a dark box. */
.ascent__still { z-index: 0; opacity: 0; }
.ascent__video { z-index: 1; }

/* -- Scrim ----------------------------------------------------------------
   Three bands in one element. The top band is always on (the nav sits over a
   genuinely bright sunrise). The side and bottom bands ramp in with the type,
   so the wide opening frame stays ungraded.                                  */

.ascent__scrim {
  --your:  clamp(0, calc((var(--p) - 0.42) / 0.22), 1);
  --words: clamp(0, calc((var(--p) - 0.14) / 0.16), 1);
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background:
    /* Side grade — the lockup is left-aligned, and at sunrise that third of
       frame is bright orange, which cream alone only reaches ~2.5:1 against.
       Grading the type's own side leaves the climber and flag untouched. */
    linear-gradient(to right,
      rgba(var(--a-scrim-rgb), calc(0.58 * var(--words))) 0%,
      rgba(var(--a-scrim-rgb), calc(0.34 * var(--words))) 26%,
      rgba(var(--a-scrim-rgb), calc(0.08 * var(--words))) 44%,
      rgba(var(--a-scrim-rgb), 0) 58%),
    /* Top band — buys the nav its contrast for the whole fold. */
    linear-gradient(to bottom,
      rgba(var(--a-scrim-rgb), 0.62) 0%,
      rgba(var(--a-scrim-rgb), 0.3)  12%,
      rgba(var(--a-scrim-rgb), 0)    30%),
    /* Bottom band — arrives with the practice name. */
    linear-gradient(to top,
      rgba(var(--a-scrim-rgb), calc(0.86 * var(--your))) 0%,
      rgba(var(--a-scrim-rgb), calc(0.52 * var(--your))) 18%,
      rgba(var(--a-scrim-rgb), calc(0.16 * var(--your))) 34%,
      rgba(var(--a-scrim-rgb), 0) 48%);
}

/* -- The words ------------------------------------------------------------
   Left-aligned, not centred: the flag flies right-of-centre through most of
   the sequence and a centred word runs straight into it. Split top and bottom
   on one left axis, the phrase still reads down the page with the climb
   happening between its two halves.                                          */

.ascent__word {
  position: absolute;
  left: 0;
  z-index: 3;
  width: 100%;
  max-width: var(--a-shell);
  padding-inline: clamp(1rem, 4vw, 1.5rem);
  margin: 0;
  font-family: var(--a-display);
  font-variation-settings: "opsz" 144;
  font-style: normal;              /* roman display — never italic */
  font-weight: 700;
  line-height: 0.88;
  color: var(--a-on-ink);
  text-wrap: balance;
  pointer-events: none;
  will-change: transform, opacity;
}

@media (min-width: 1180px) {
  /* Keep the lockup on the same left rail as every section below it. */
  .ascent__word { left: 50%; transform: translateX(-50%); }
}

.ascent__word--own {
  --own: clamp(0, calc((var(--p) - 0.16) / 0.18), 1);
  top: 15%;
  font-size: var(--a-summit);
  letter-spacing: -0.045em;
  opacity: var(--own);
}

.ascent__word--your {
  --your: clamp(0, calc((var(--p) - 0.5) / 0.2), 1);
  bottom: 15%;
  font-size: var(--a-base-camp);
  letter-spacing: -0.02em;
  opacity: var(--your);
}

/* The rise rides `translate`, not `transform`, so it composes with the
   centring translateX above instead of overwriting it. */
.ascent__word--own  { translate: 0 calc((1 - var(--own))  * 30px); }
.ascent__word--your { translate: 0 calc((1 - var(--your)) * 24px); }

/* -- Caption and cue ------------------------------------------------------ */

.ascent__caption {
  position: absolute;
  left: 0; bottom: calc(6% + var(--a-bar, 0px));
  z-index: 3;
  width: 100%;
  max-width: var(--a-shell);
  padding-inline: clamp(1rem, 4vw, 1.5rem);
  margin: 0;
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--a-on-ink);
  /* An opening subtitle, not a closing one — it is present on the wide shot
     and gone by the time the climb starts, matching the root page. */
  opacity: clamp(0, calc((0.14 - var(--p)) / 0.1), 1);
  pointer-events: none;
}
@media (min-width: 1180px) {
  .ascent__caption { left: 50%; transform: translateX(-50%); }
}

.ascent__cue {
  position: absolute;
  right: clamp(1rem, 4vw, 2rem);
  bottom: calc(6% + var(--a-bar, 0px));
  z-index: 3;
  margin: 0;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.74rem;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--a-on-ink);
  /* Fades out as soon as the reader has taken the hint. */
  opacity: calc(1 - clamp(0, calc(var(--p) / 0.12), 1));
  pointer-events: none;
}

/* -- Masthead over the fold ------------------------------------------------
   The bar floats on a sunrise photograph for the length of the fold, where
   navy type is unreadable. `is-over-hero` is set by ascent.js on whichever
   header the page nominates. Sub-sites use .site-header; the root uses .mast,
   which carries its own richer rules in index.css.                           */

.site-header.is-over-hero {
  background: transparent !important;
  border-bottom-color: transparent !important;
  box-shadow: none !important;
  color: var(--a-on-ink);
  text-shadow: 0 1px 14px rgba(var(--a-scrim-rgb), 0.55);
}
.site-header.is-over-hero .logo,
.site-header.is-over-hero .main-nav a { color: var(--a-on-ink); }
.site-header.is-over-hero .hamburger span { background: var(--a-on-ink); }

/* -- Reduced motion --------------------------------------------------------
   No scrubbing, no pin. The fold collapses to one viewport showing the
   resolved composition, with --p frozen where the lockup is fully present. */

@media (prefers-reduced-motion: reduce) {
  .ascent {
    --p: 0.82 !important;
    height: auto;
  }
  .ascent__stage { position: relative; }
  .ascent__video { opacity: 0; }
  .ascent__still { opacity: 1; }
  .ascent__word  { will-change: auto; }
  .ascent__cue   { display: none; }
}

/* -- Route back to the network -------------------------------------------
   The practice sites had no way home: six sites presented as one network,
   with no link between them. This is deliberately quieter than the peer nav
   items — it leaves the site rather than moving within it.

   It is drawn as a pill rather than as bare text with an arrow in front. As
   bare text it failed twice: the chevron sat 1.1em from a wordmark and read
   as a glyph belonging to it ("‹Own Your Study" looks like a logo, not a way
   back), and over the fold the bar's text-shadow lit the type while leaving
   the chevron's two hairlines unlit, so the mark smudged out against the
   sunrise. An outline gives the chevron its own ground and its own edge, and
   says "control" where the peer items say "page". */
.to-network {
  display: inline-flex;
  align-items: center;
  gap: 0.55em;
  border: 1px solid rgba(var(--a-scrim-rgb), 0.24);
  border-radius: 999px;
  line-height: 1;
  white-space: nowrap;
  opacity: 0.78;
  transition: opacity var(--dur-mid, 220ms) ease,
              border-color var(--dur-mid, 220ms) ease,
              background-color var(--dur-mid, 220ms) ease;
}
.to-network::before {
  content: "";
  flex: none;
  width: 0.44em;
  height: 0.44em;
  /* The rotated square hangs low against a cap-height word; lift it onto the
     same optical line rather than the same box line. */
  margin-block: -0.04em 0.04em;
  border-left: 1.6px solid currentColor;
  border-bottom: 1.6px solid currentColor;
  transform: rotate(45deg);
}
.to-network:hover,
.to-network:focus-visible {
  opacity: 1;
  border-color: rgba(var(--a-scrim-rgb), 0.5);
  background: rgba(var(--a-scrim-rgb), 0.06);
}

/* The practice sites set `nav.main-nav a { padding: 4px 0 }`, which is one
   element more specific than a lone class and would flatten the pill. Padding
   and hit target are therefore claimed at a weight that beats it. */
.main-nav .to-network {
  padding: 0.5em 0.9em 0.5em 0.78em;
  min-height: 34px;
}

/* Over the photograph the bar goes transparent and every item picks up a
   glow. Ink at 78% on a sunrise is not a link, so the pill takes a real
   ground here: solid cream type, a visible edge, and a scrim of its own that
   holds whichever frame is on screen behind it. */
.site-header.is-over-hero .to-network {
  opacity: 1;
  color: var(--a-on-ink);
  border-color: rgba(250, 247, 241, 0.46);
  background: rgba(var(--a-scrim-rgb), 0.38);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  text-shadow: none;
}
.site-header.is-over-hero .to-network:hover,
.site-header.is-over-hero .to-network:focus-visible {
  border-color: rgba(250, 247, 241, 0.8);
  background: rgba(var(--a-scrim-rgb), 0.56);
}
