/* Section label: the ascent mark, then the name ---------------------------- */
/* History, so this is not re-litigated. It was "// 01 The problem": the slashes
   and numbering read as generic template dressing, and outside the genuinely
   sequential pages the numbers encoded nothing. That became a plain steel rule,
   which was honest but carried no brand. It is now "the ascent", an approach
   line that steps up and runs out level into the type. Chosen over a literal
   mountain because this mark appears on ~79 labels and a drawn range at that
   frequency becomes wallpaper; the ascent stays quiet while still being ours.
   The mark is a mask so the fill stays --steel and the colour stays tokenised. */
.label {
  display: flex;
  align-items: center;
  gap: 0.9em;
  font-family: var(--font-mono);
  font-size: var(--step--1);
  font-weight: 400;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-4);
}
/* Fallback first: without mask support this stays the plain steel rule rather
   than becoming a solid block. */
.label::before {
  content: "";
  flex: none;
  width: 1.75em;
  height: 1px;
  background: var(--steel);
}
@supports ((-webkit-mask-image: url("")) or (mask-image: url(""))) {
  .label::before {
    width: 2.3em;
    height: 1em;
    -webkit-mask: var(--mark-ascent) no-repeat center / contain;
    mask: var(--mark-ascent) no-repeat center / contain;
  }
}

/* Buttons ----------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.6em;
  padding: 0.95em 1.7em;
  border: 1px solid transparent;
  border-radius: var(--radius);
  font-family: var(--font-body);
  font-size: var(--step-0);
  font-weight: 500;
  line-height: 1;
  text-decoration: none;
  white-space: nowrap;
  transition: background-color var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease),
              color var(--dur-fast) var(--ease),
              transform var(--dur-fast) var(--ease);
}
.btn:hover { transform: translateY(-1px); }
.btn:active { transform: translateY(0); }

.btn--primary { background: var(--text); color: var(--ink); }
.btn--primary:hover { background: #fff; color: var(--ink); }

.btn--ghost {
  border-color: var(--line-2);
  color: var(--text);
  background: transparent;
}
.btn--ghost:hover { border-color: var(--steel); color: var(--steel-bright); background: rgba(127, 160, 200, 0.06); }

.btn--sm { padding: 0.7em 1.25em; font-size: var(--step--1); }

/* Text link with a moving rule ------------------------------------------- */
.link-arrow {
  display: inline-flex;
  align-items: center;
  gap: 0.55em;
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--steel);
}
.link-arrow::after {
  content: "";
  width: 2.2em;
  height: 1px;
  background: currentColor;
  transition: width var(--dur-fast) var(--ease);
}
.link-arrow:hover::after { width: 3.4em; }

/* Header ------------------------------------------------------------------ */
.site-header {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: var(--z-header);
  height: var(--header-h);
  display: flex;
  align-items: center;
  border-bottom: 1px solid transparent;
  transition: height var(--dur-fast) var(--ease),
              background-color var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease);
}
.site-header[data-stuck] {
  height: var(--header-h-stuck);
  background: rgba(14, 15, 18, 0.82);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border-bottom-color: var(--line);
}
.site-header__inner {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  width: 100%;
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 0.9rem;
  color: var(--text);
  text-decoration: none;
  margin-right: auto;
}
.brand__mark {
  width: 60px;
  height: auto;
  color: var(--text);
  flex: none;
  transition: width var(--dur-fast) var(--ease);
}
.brand__word {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.7rem;
  letter-spacing: 0.2em;
  text-indent: 0.2em;
  transition: font-size var(--dur-fast) var(--ease);
}
/* Scale the lockup down as the header shrinks, so it never dominates once stuck. */
.site-header[data-stuck] .brand__mark { width: 42px; }
.site-header[data-stuck] .brand__word { font-size: 1.3rem; }

@media (max-width: 560px) {
  .brand__mark { width: 42px; }
  .brand__word { font-size: 1.25rem; }
}

/* The nav fills the header height so a dropdown li's top:100% lands exactly on
   the header's bottom edge (and re-anchors when the header shrinks on scroll). */
.nav { align-self: stretch; display: flex; }
.nav__list {
  display: flex;
  align-items: stretch;
  height: 100%;
  gap: var(--space-5);
  list-style: none;
  padding: 0;
  margin: 0;
}
.nav__list > li { display: flex; align-items: center; }
.nav__link {
  position: relative;
  color: var(--text-3);
  font-size: var(--step-0);
  font-weight: 500;
  text-decoration: none;
  padding-block: 0.4rem;
}
.nav__link::after {
  content: "";
  position: absolute;
  left: 0; bottom: 0;
  width: 0; height: 1px;
  background: var(--steel);
  transition: width var(--dur-fast) var(--ease);
}
.nav__link:hover { color: var(--text); }
.nav__link:hover::after { width: 100%; }
.nav__link[aria-current="page"] { color: var(--text); }
.nav__link[aria-current="page"]::after { width: 100%; }

/* CTA as a full-height block flush to the corner, like the reference's
   "Portal Inversores". Fills the header height and sits hard against the right
   edge (escaping the shell gutter). */
.nav__cta {
  align-self: stretch;
  height: var(--header-h);
  margin-right: calc(-1 * var(--gutter));
  padding-inline: clamp(1.6rem, 2.6vw, 2.9rem);
  border-radius: 0;
  font-size: var(--step-0);
  transition: height var(--dur-fast) var(--ease), background-color var(--dur-fast) var(--ease);
}
.nav__cta:hover { transform: none; }
.site-header[data-stuck] .nav__cta { height: var(--header-h-stuck); }

.hamburger {
  display: none;
  width: 44px; height: 44px;
  margin-right: -10px;
  place-items: center;
}
.hamburger__box { display: block; width: 22px; height: 12px; position: relative; }
.hamburger__box::before,
.hamburger__box::after {
  content: "";
  position: absolute;
  left: 0;
  width: 100%; height: 1.5px;
  background: var(--text);
  transition: transform var(--dur-fast) var(--ease), top var(--dur-fast) var(--ease);
}
.hamburger__box::before { top: 0; }
.hamburger__box::after { top: 10.5px; }
.hamburger[aria-expanded="true"] .hamburger__box::before { top: 5px; transform: rotate(45deg); }
.hamburger[aria-expanded="true"] .hamburger__box::after { top: 5px; transform: rotate(-45deg); }

@media (max-width: 900px) {
  .nav, .nav__cta { display: none; }
  .hamburger { display: grid; }
}

/* Mobile drawer -----------------------------------------------------------
   Lives outside <nav>, as a direct child of <body>. Nested inside the header
   it gets clipped by the header's stacking context. */
.nav-overlay {
  position: fixed;
  inset: 0;
  z-index: var(--z-overlay);
  background: rgba(8, 9, 11, 0.6);
  backdrop-filter: blur(2px);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-fast) var(--ease);
}
.nav-overlay[data-open] { opacity: 1; pointer-events: auto; }

.nav-drawer {
  position: fixed;
  inset: 0 0 0 auto;
  z-index: var(--z-drawer);
  width: min(88vw, 400px);
  background: var(--ink-2);
  border-left: 1px solid var(--line);
  padding: calc(var(--header-h) + var(--space-4)) var(--gutter) var(--space-5);
  transform: translateX(100%);
  transition: transform var(--dur) var(--ease);
  overflow-y: auto;
  overscroll-behavior: contain;
}
.nav-drawer[data-open] { transform: none; }

.nav-drawer__list { list-style: none; padding: 0; margin: 0; }
.nav-drawer__list li + li { border-top: 1px solid var(--line); }
.nav-drawer__link {
  display: block;
  padding: 1.1rem 0;
  color: var(--text);
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--step-2);
  text-decoration: none;
}
.nav-drawer__link[aria-current="page"] { color: var(--steel); }
.nav-drawer__foot { margin-top: var(--space-5); }
.nav-drawer__foot .btn { width: 100%; }

body[data-nav-open] { overflow: hidden; }

/* The drawer outranks the header, so while it is open the close control would
   sit underneath it. Lift the header (and only the header) above the drawer. */
body[data-nav-open] .site-header { z-index: calc(var(--z-drawer) + 1); }

/* Hero: "The Descent" ----------------------------------------------------- */
/* Base state is a plain 100svh hero showing the static composed scene. The tall
   pinned track, parallax and fog-clear are added ONLY for engines that support
   scroll timelines AND for users who have not asked for less motion. So
   reduced-motion, no-JS and non-supporting browsers all get the calm static
   version, and nothing has to be undone. */
.hero {
  position: relative;
  min-height: 100svh;
  isolation: isolate;
}
.hero__stage {
  position: relative;
  min-height: 100svh;
  display: grid;
  align-items: center;
  padding-top: var(--header-h);
  overflow: clip;
}


.hero__scene { position: absolute; inset: 0; z-index: -1; overflow: clip; }

/* Photographic depth planes. Each is oversized and centred so parallax
   translation never exposes an edge. The CSS descent animates `transform`;
   pointer parallax writes the independent `translate` property, so the two
   compose instead of fighting. */
.hero__photo {
  position: absolute;
  inset: -3%;
  display: block;
  will-change: transform, translate;
}
.hero__photo img {
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: 62% 42%;   /* keep the peak in frame */
}
/* On phones the <picture> swaps in a portrait crop that is already composed
   with the summit centred, so the landscape nudge would push it back off. */
@media (max-aspect-ratio: 3/5) {
  .hero__photo img { object-position: center; }
}
/* The mountain cutout parallaxes more than the sky, so it pops in front. */
.hero__photo--mtn { inset: -4%; }

/* Scrim: darkens the left and base so the headline reads over the bright sky,
   and blends the warm photo down into the site's ink at the fold. */
.hero__veil {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    linear-gradient(96deg, var(--ink) 4%, rgba(14, 15, 18, 0.78) 24%, rgba(14, 15, 18, 0.28) 46%, transparent 66%),
    linear-gradient(180deg, rgba(14, 15, 18, 0.35) 0%, transparent 22%, transparent 52%, var(--ink) 100%);
}

/* Drifting particles: sparse, slow, desktop-only, painted by JS into a canvas
   that pauses when the hero leaves view. */
.hero__particles {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  pointer-events: none;
  opacity: 0.5;
}

/* Grain is dither, not decoration: near-black gradients cross few 8-bit code
   values and band. `overlay` keeps the mean (for b < 0.5 it computes 2*b*s), so
   it textures without lifting the blacks. */
.hero__grain {
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0.12;
  mix-blend-mode: overlay;
  background-size: 180px 180px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)'/%3E%3C/svg%3E");
}

.hero__inner { padding-block: var(--space-7) var(--space-6); position: relative; }
.hero__eyebrow { margin-bottom: var(--space-3); }
.hero__title { max-width: 16ch; }
.hero__sub {
  margin-top: var(--space-3);
  max-width: 52ch;
  font-size: var(--step-1);
  color: var(--text-2);
  line-height: 1.5;
}
.hero__actions {
  margin-top: var(--space-5);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

/* Persistent altimeter (SSTR-flavoured). Fixed for the whole homepage; the
   elevation descends from summit to sea level as you scroll. */
.site-readout {
  position: fixed;
  right: var(--gutter);
  bottom: var(--space-4);
  z-index: 50;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.35em;
  pointer-events: none;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.14em;
  color: var(--text-4);
  transition: opacity var(--dur-fast) var(--ease);
}
.site-readout__elev [data-elev] { color: var(--steel); }
@media (max-width: 620px) { .site-readout { display: none; } }

/* Scroll-progress rail down the left edge */
.hero__rail {
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 2px;
  background: var(--line);
  z-index: 1;
}
.hero__rail-fill {
  display: block;
  width: 100%;
  height: 100%;
  transform: scaleY(0);
  transform-origin: top;
  background: linear-gradient(180deg, var(--steel), transparent);
}

.scroll-cue {
  position: absolute;
  left: var(--gutter);
  bottom: var(--space-4);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.9em;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--text-4);
}
/* The altimeter. A dim ticked scale with a steel indicator descending it.
   Replaced a pulsing gradient line, which scaled up and down and so read as a
   tick rather than as a direction. This states the page's own idea: the site
   readout above counts 2,340 M down to sea level across the homepage.
   Animated on transform, never on `top`, per the perf rule. The resting state
   is a visible indicator at the top of the scale, which is what shows when the
   global reduced-motion rule in base.css kills the animation. */
/* The cue is decorative and absolutely positioned, so it reserves no space and
   will sit under the CTA buttons wherever the hero content already fills the
   stage. The content is ~590px plus a 120px header offset, and the cue needs
   ~117px, so all three only coexist above ~840px of viewport height. Below that
   the cue is hidden: neither padding nor alignment can create room that is not
   there, and an overlapping cue is worse than no cue. */
@media (max-height: 840px) {
  .scroll-cue { display: none; }
}

.scroll-cue__alt {
  position: relative;
  display: block;
  width: 22px; height: 56px;
}
.scroll-cue__alt svg {
  position: absolute;
  inset: 0;
  color: var(--line-2);
}
.scroll-cue__ind {
  position: absolute;
  top: 0; left: 0;
  width: 13px; height: 1px;
  background: var(--steel);
  transform: translateY(2px);
  animation: descend 2.8s var(--ease) infinite;
}
@keyframes descend {
  0%   { transform: translateY(2px);  opacity: 0; }
  12%  { opacity: 1; }
  85%  { opacity: 1; }
  100% { transform: translateY(52px); opacity: 0; }
}

/* --- The descent: scroll-driven, only where scroll timelines exist --------
   The camera pushes into the peak as you scroll. Both planes scale up, the
   mountain faster than the sky, so you move into the mountain; then the
   headline lifts away and the first chapter surfaces from fog. */
@supports (animation-timeline: scroll()) {
  @media (prefers-reduced-motion: no-preference) {
    .hero[data-descent] { min-height: 240vh; }
    .hero[data-descent] .hero__stage {
      position: sticky;
      top: 0;
      height: 100svh;
      min-height: 0;
    }

    .hero__photo {
      animation-timeline: scroll(root block);
      animation-range: 0 165vh;
    }
    .hero__photo--bg  { animation: push-bg linear both; }
    .hero__photo--mtn { animation: push-mtn linear both; }

    /* The headline holds, then lifts away as the pin releases. It is the LCP
       element, so it is never touched on initial load. */
    .hero__inner {
      animation: hero-lift linear both;
      animation-timeline: scroll(root block);
      animation-range: 72vh 172vh;
    }
    .scroll-cue {
      animation: fade-out linear both;
      animation-timeline: scroll(root block);
      animation-range: 26vh 100vh;
    }
    .hero__rail-fill {
      animation: rail-fill linear both;
      animation-timeline: scroll(root block);
      animation-range: 0 165vh;
    }
  }

  /* Shorter descent on phones so it isn't a marathon thumb-scroll. */
  @media (prefers-reduced-motion: no-preference) and (max-width: 760px) {
    .hero[data-descent] { min-height: 180vh; }
    .hero__photo { animation-range: 0 115vh; }
    .hero__inner { animation-range: 48vh 122vh; }
    .scroll-cue { animation-range: 16vh 80vh; }
    .hero__rail-fill { animation-range: 0 115vh; }
  }
}

@keyframes push-bg  { to { transform: scale(1.06); } }
@keyframes push-mtn { to { transform: scale(1.14) translateY(-3%); } }
@keyframes hero-lift { from { opacity: 1; transform: none; } to { opacity: 0; transform: translateY(-6%); } }
@keyframes fade-out  { to { opacity: 0; } }
@keyframes rail-fill { to { transform: scaleY(1); } }
@keyframes breathe   { 0%, 100% { opacity: 0.72; } 50% { opacity: 1; } }
@keyframes fog-drift     { from { transform: translateX(-2%); } to { transform: translateX(2%); } }
@keyframes fog-drift-alt { from { transform: translateX(2.5%) translateY(1%); } to { transform: translateX(-2.5%) translateY(-1%); } }

/* Chapter ----------------------------------------------------------------- */
.chapter__head { margin-bottom: var(--space-5); }
.chapter__grid {
  display: grid;
  gap: var(--space-5) var(--space-6);
  grid-template-columns: 1fr;
}
@media (min-width: 900px) {
  .chapter__grid { grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr); gap: var(--space-7); }
}
.chapter__title { max-width: 18ch; }
.chapter__body > p { max-width: var(--measure); }
.chapter__body > p:last-of-type {
  margin-top: var(--space-3);
  color: var(--text);
  font-size: var(--step-1);
}

/* Stat readouts ----------------------------------------------------------- */
.stats {
  display: grid;
  gap: 1px;
  background: var(--line);
  border: 1px solid var(--line);
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
}
.stat { background: var(--ink); padding: var(--space-3) var(--space-4); }
.stat__num {
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: var(--step-3);
  color: var(--text);
  letter-spacing: -0.02em;
}
.stat__label {
  margin-top: 0.35rem;
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-4);
}

/* Footer ------------------------------------------------------------------ */
.site-footer {
  border-top: 1px solid var(--line);
  padding-block: var(--space-6) var(--space-4);
}
/* Four children: brand, Site, Services, Contact. The wide breakpoint MUST declare
   four columns. It declared three, so Contact orphaned onto a second row at brand
   width and the footer stood 942px tall, over a full screen on a 1440x900 desktop.
   If a fifth column is ever added, change this rule in the same commit. */
.site-footer__top {
  display: grid;
  gap: var(--space-5);
  grid-template-columns: 1fr;
}
/* minmax(0, …) on EVERY track, not a bare fr. A bare 1fr floors at the track's
   min-content width, and `contact@ironpeakconsulting.com.au` is a 33 character
   token with no break opportunity, so it sets that floor and pushes the grid
   wider than the page. That shipped a 13px horizontal overflow at 620px on
   2026-09-03. Paired with overflow-wrap on the links below; both are needed,
   since minmax stops the grid blowing out and the wrap stops the address
   spilling out of its own column. */
/* Phones wide enough to hold two short link lists. Site and Services pair, while
   the brand block and Contact stay full width: Contact holds the email, and at
   this size a paired Contact column forces the address to break mid string,
   which looks broken rather than tight. Measured saving at 480px: 248px, with
   only the location line wrapping, which it already did. */
@media (min-width: 440px) and (max-width: 619px) {
  .site-footer__top {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-5);
  }
  .site-footer__brand,
  .site-footer__top > .footer-col:last-child { grid-column: 1 / -1; }
}
@media (min-width: 620px) {
  .site-footer__top {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-5) var(--space-6);
  }
}
@media (min-width: 1000px) {
  .site-footer__top {
    grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1.15fr);
    gap: var(--space-6);
  }
}
.site-footer__tag { margin-top: var(--space-2); color: var(--text-4); max-width: 30ch; }
.footer-col__h {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-4);
  margin-bottom: var(--space-2);
}
.footer-col ul { list-style: none; padding: 0; margin: 0; display: grid; gap: 0.55rem; }
.footer-col a {
  color: var(--text-3);
  text-decoration: underline;
  text-decoration-color: var(--line-2);
  text-underline-offset: 0.2em;
}
.footer-col a:hover { color: var(--text); text-decoration-color: var(--steel); }
/* The contact address has no break opportunity in it. Without this it cannot wrap
   at any width and spills out of its column. See the grid note above. */
.footer-col a, .footer-col li { overflow-wrap: anywhere; }

.site-footer__legal {
  margin-top: var(--space-6);
  padding-top: var(--space-3);
  border-top: 1px solid var(--line);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
  align-items: baseline;
  font-size: var(--step--1);
  color: var(--text-4);
}
.site-footer__legal a {
  color: var(--text-3);
  text-decoration: underline;
  text-decoration-color: var(--line-2);
  text-underline-offset: 0.2em;
}
.site-footer__legal a:hover { color: var(--text); text-decoration-color: var(--steel); }
.site-footer__legal-spacer { margin-left: auto; }

/* Preloader ---------------------------------------------------------------
   Hidden entirely without JS: it must never trap a visitor behind an
   overlay that nothing will remove. */
html.no-js .preloader { display: none; }

body[data-loading] { overflow: hidden; }

#header-sentinel {
  position: absolute;
  top: 0;
  left: 0;
  width: 1px;
  height: var(--header-h);
  pointer-events: none;
}

/* "The Survey". The mark traces itself in steel hairline as the progress
   indicator (terrain first, then the arrow's ascent), resolves into the solid
   mark at the summit, and the whole ink sheet wipes upward into the hero.
   preloader() writes --lp (0..1) once per frame; the trace maths lives here,
   same pathLength="1" + dashoffset mechanism as the artefact scenes. */
.preloader {
  position: fixed;
  inset: 0;
  z-index: var(--z-preloader);
  background: var(--ink);
  /* Where the two halves meet. Invisible until the exit parts them; set so
     the composition sits on its optical centre, the mark carrying more mass
     above the seam than the instrument does below it. */
  --datum: 61%;
  /* Geist Mono arrives from Google Fonts with display=swap and would very
     likely swap mid run, so the readout uses local fixed pitch faces only. */
  --font-mono-pl: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  /* CSS-only failsafe: even if main.js never executes, the sheet dismisses
     itself, so nothing can trap a visitor behind an overlay. */
  animation: preloader-failsafe 0.5s var(--ease) 8s forwards;
}
@media (max-aspect-ratio: 3 / 4) { .preloader { --datum: 56%; } }
@keyframes preloader-failsafe { to { opacity: 0; visibility: hidden; } }
.preloader[data-done] { pointer-events: none; }

/* The two panes. Each is anchored to the datum and parts away from it. */
.preloader__pane {
  position: absolute;
  left: 0; right: 0;
  background: var(--ink);
  overflow: hidden;
  display: flex;
  justify-content: center;
  transition: transform 0.62s cubic-bezier(0.76, 0, 0.24, 1);
}
.preloader__pane--top { top: 0; height: var(--datum); align-items: flex-end; }
/* the half pixel overlap prevents a seam at fractional viewport heights */
.preloader__pane--bottom { top: calc(var(--datum) - 0.5px); bottom: 0; align-items: flex-start; }
.preloader[data-done] .preloader__pane--top { transform: translateY(-100%); }
.preloader[data-done] .preloader__pane--bottom { transform: translateY(100%); }
.preloader[data-done] .preloader__pane { will-change: transform; }


/* ---- The upper half: the identity ---- */
.preloader__lockup {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding-bottom: clamp(1.5rem, 3.6vmin, 2.25rem);
}
.preloader__mark { display: block; width: clamp(180px, 34vmin, 360px); height: auto; }
/* Stroke widths in user units, NOT non-scaling strokes: Chromium can
   miscompute pathLength dashes with those on time-driven draws. */
.preloader__trace {
  fill: none;
  stroke: var(--steel);
  stroke-width: 2.25;
  stroke-linejoin: round;
  stroke-linecap: round;
  stroke-dasharray: 1;
}
.preloader__trace--mtn { stroke-dashoffset: calc(1 - clamp(0, var(--lp) / 0.55, 1)); }
.preloader__trace--arrow {
  stroke: var(--steel-bright);   /* the ascent is the one bright layer */
  stroke-width: 3;
  stroke-dashoffset: calc(1 - clamp(0, (var(--lp) - 0.42) / 0.58, 1));
}
/* Asymmetric handover, so the mark only ever gains ink and never passes
   through a washed out half state. */
.preloader__solid { fill: var(--text); opacity: 0; }
.preloader[data-resolve] .preloader__solid { opacity: 1; transition: opacity 0.38s var(--ease); }
.preloader[data-resolve] .preloader__trace { opacity: 0; transition: opacity 0.24s var(--ease) 0.14s; }

/* The name writes itself, complete at 62. */
.preloader__word {
  margin-top: clamp(1.1rem, 2.8vmin, 1.75rem);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(0.9rem, 2.2vmin, 1.15rem);
  letter-spacing: 0.42em;
  text-indent: 0.42em;
  color: var(--text);
  clip-path: inset(0 calc((1 - clamp(0, (var(--lp) - 0.12) / 0.5, 1)) * 100%) 0 0);
}

/* ---- The lower half: the instrument ---- */
.preloader__instrument {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding-top: clamp(1.35rem, 3.4vmin, 2rem);
}
.preloader__count {
  display: inline-flex;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(3.25rem, 12vmin, 7.25rem);
  line-height: 1;
  color: var(--text);
  font-kerning: none;
  font-variant-ligatures: none;
  /* trims the box to cap height, so its bottom sits on the baseline */
  margin-block: -0.15em -0.18em;
}
@supports (text-box: trim-both cap alphabetic) {
  .preloader__count { margin-block: 0; text-box: trim-both cap alphabetic; }
}
/* Clash Display ships no tabular figures: its 1 is 56 per cent the width of
   its 0, so font-variant-numeric is a no op and a right aligned number lurches
   sideways. Every digit therefore gets a fixed slot and is centred in it. */
.preloader__slot { position: relative; width: 0.72em; height: 1em; text-align: center; }
/* The hundreds digit hangs left of the field, so 99 to 100 does not move the
   two right hand digits, or the sign, by a single pixel. */
.preloader__slot--hundreds { width: 0.52em; margin-left: -0.52em; }
/* The per cent sign is a slot like any other, so it is the same size as the
   figures and the light rises through it with them. Measured at 0.823em in
   Clash Display Bold, with its box exactly as tall as a digit's. */
.preloader__slot--pct { width: 0.92em; }

/* Light rising through the digits. The dim figure sits underneath and the
   bright one is clipped to the cap line and the baseline of the slot box, not
   to the box itself, or the gauge would read full at 85. No glow, no gradient. */
.preloader__slot::before { content: attr(data-v); color: var(--text-4); }
.preloader__slot::after {
  content: attr(data-v);
  position: absolute;
  inset: 0;
  color: var(--text);
  clip-path: inset(calc(0.15em + (1 - var(--lp)) * 0.67em) 0 0.18em 0);
}

/* The continuous readout: moves every frame even while the big figure holds. */
.preloader__read {
  display: flex;
  gap: 0.5em;
  margin-top: clamp(0.85rem, 2.2vmin, 1.2rem);
  font-family: var(--font-mono-pl);
  font-size: var(--step--1);
  letter-spacing: 0.14em;
  color: var(--text-4);
}
/* The "//" that used to sit before the readout is now a short steel rule. */
.preloader__read i {
  align-self: center;
  width: 1.1em;
  height: 1px;
  background: var(--steel);
}
.preloader__read b { font-weight: 400; }

/* The altimeter takes over from the count: while the sheet is up it is held
   back, and it fades in as the instrument pane drops away. One instrument
   hands to another, with nothing flying across the screen. */
body[data-loading]:not([data-exit]) .site-readout { opacity: 0; }
.site-readout { transition: opacity 0.5s var(--ease) 0.35s; }

@media (prefers-reduced-motion: reduce) {
  .preloader { animation: none; }
  .preloader__pane { transition: none; }
  .preloader[data-done] .preloader__pane--top,
  .preloader[data-done] .preloader__pane--bottom { transform: none; }
  .preloader__word { clip-path: none; }
  .preloader__slot::after { clip-path: none; }
  .preloader__solid { opacity: 1; }
  .preloader__trace { opacity: 0; }
  .site-readout { transition: none; }
}

/* Stages (// 02) ---------------------------------------------------------- */
.stages { border-top: 1px solid var(--line); }
.stage {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-1) var(--space-5);
  padding-block: var(--space-4);
  border-bottom: 1px solid var(--line);
  text-decoration: none;
  color: inherit;
  transition: background-color var(--dur-fast) var(--ease);
}
.stage:hover { background: rgba(127, 160, 200, 0.035); }
@media (min-width: 900px) {
  .stage { grid-template-columns: 5.5rem minmax(0, 15rem) minmax(0, 1fr) 2.5rem; align-items: start; }
}
.stage__num {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: 0.1em;
  color: var(--steel);
  padding-top: 0.35em;
}
.stage__title {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--step-2);
  color: var(--text);
  line-height: 1.1;
}
.stage__text { color: var(--text-3); max-width: 56ch; }
.stage__arrow {
  justify-self: end;
  align-self: center;
  color: var(--text-4);
  transition: transform var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.stage:hover .stage__arrow { transform: translateX(5px); color: var(--steel); }
@media (max-width: 899px) { .stage__arrow { display: none; } }

.chapter__intro {
  margin-top: var(--space-3);
  max-width: 54ch;
  color: var(--text-2);
  font-size: var(--step-1);
}
.chapter__actions { margin-top: var(--space-5); }

/* Journey (// 03) --------------------------------------------------------- */
.journey { display: grid; gap: var(--space-4); }
@media (min-width: 900px) { .journey { grid-template-columns: repeat(3, 1fr); gap: var(--space-5); } }
.step { padding-top: var(--space-3); border-top: 1px solid var(--line-2); }
.step__key {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--steel);
  margin-bottom: var(--space-2);
}
.step__text { color: var(--text-3); }

.chapter__closing {
  margin-top: var(--space-5);
  max-width: 54ch;
  color: var(--text);
  font-size: var(--step-1);
}

/* Proof (// 04) ----------------------------------------------------------- */
.proof__grid { display: grid; gap: var(--space-5); }
@media (min-width: 900px) {
  .proof__grid { grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr); gap: var(--space-7); align-items: center; }
}
.proof__figure {
  position: relative;
  margin: 0;
  aspect-ratio: 4 / 3;
  background: var(--ink-2);
  border: 1px solid var(--line);
  overflow: hidden;
  display: grid;
  place-items: end stretch;
}
.proof__figure img {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
}
.proof__caption {
  position: relative;
  padding: var(--space-5) var(--space-3) var(--space-3);
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: 0.08em;
  color: var(--text-4);
  background: linear-gradient(to top, rgba(10, 11, 13, 0.9), rgba(10, 11, 13, 0.5) 55%, rgba(10, 11, 13, 0));
}
.proof__stats { margin-top: var(--space-4); }
.proof__actions { margin-top: var(--space-4); }

/* Pillars (// 05) --------------------------------------------------------- */
.pillars { display: grid; gap: var(--space-4); margin-top: var(--space-5); }
@media (min-width: 900px) { .pillars { grid-template-columns: repeat(3, 1fr); gap: var(--space-5); } }
.pillar { padding-top: var(--space-3); border-top: 1px solid var(--line-2); }
.pillar__title {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--step-1);
  color: var(--text);
  line-height: 1.2;
  margin-bottom: var(--space-2);
}
.pillar__text { color: var(--text-3); }

.note {
  margin-top: var(--space-6);
  padding: var(--space-3) var(--space-4);
  border-left: 1px solid var(--steel);
  background: var(--ink-2);
  color: var(--text-3);
  max-width: 78ch;
}

/* Engage block ------------------------------------------------------------ */
.cta-block { background: var(--ink-2); border-top: 1px solid var(--line); }
.cta-block__inner { display: grid; justify-items: start; gap: var(--space-3); }
.cta-block__title { max-width: 16ch; }
.cta-block__body { max-width: 56ch; font-size: var(--step-1); color: var(--text-2); }

/* --- Descend into the content ------------------------------------------- */
/* A faint fixed atmosphere so the whole page reads as one continuous descent
   through the same valley air, not flat black between sections. */
.atmosphere { display: none; }
@media (prefers-reduced-motion: no-preference) {
  .atmosphere {
    display: block;
    position: fixed;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    background:
      radial-gradient(150% 55% at 50% 112%, rgba(34, 46, 64, 0.22), transparent 62%),
      radial-gradient(90% 45% at 82% -10%, rgba(24, 32, 46, 0.14), transparent 58%);
  }
}

/* Each tagged section rises out of a fog cap that lifts as the section enters
   view. Combined with the content reveals, sections materialise from mist as
   you descend to them. Scroll-driven (compositor), gated, with a plain visible
   fallback where view timelines or motion are unavailable. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    [data-fog] { position: relative; }
    [data-fog]::before {
      content: "";
      position: absolute;
      left: 0; right: 0; top: 0;
      height: 62%;
      z-index: 4;
      pointer-events: none;
      background: linear-gradient(180deg,
        rgba(15, 19, 27, 0.92) 0%, rgba(15, 19, 27, 0.5) 42%, transparent 100%);
      animation: fog-lift linear both;
      animation-timeline: view();
      animation-range: entry 0% cover 32%;
    }
  }
}
@keyframes fog-lift { from { opacity: 1; } to { opacity: 0; } }

/* --- Studio components --------------------------------------------------- */
.ico { width: 26px; height: 26px; display: block; }

/* Refined card: hairline frame, corner tick, hover lift. */
.card {
  position: relative;
  background: var(--ink-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--space-4);
  transition: transform var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease),
              background-color var(--dur-fast) var(--ease);
}
.card::before {
  content: "";
  position: absolute;
  top: 11px; left: 11px;
  width: 9px; height: 9px;
  border-top: 1px solid var(--steel);
  border-left: 1px solid var(--steel);
  opacity: 0.45;
  transition: opacity var(--dur-fast) var(--ease);
}
.card:hover { transform: translateY(-3px); border-color: var(--line-2); background: var(--ink-3); }
.card:hover::before { opacity: 1; }

/* Pillars as icon cards */
.pillar__ico { display: inline-flex; color: var(--steel); margin-bottom: var(--space-3); }

/* Stage rows: icon stacked above the number */
.stage__lead { display: flex; align-items: center; gap: 0.75rem; }
.stage__ico { color: var(--steel); display: inline-flex; }
.stage__ico .ico { width: 22px; height: 22px; }
@media (min-width: 900px) {
  .stage__lead { flex-direction: column; align-items: flex-start; gap: 0.7rem; }
}

/* Standards marquee: SSTR-style keyword band, sector fluency before the ask. */
.marquee {
  border-block: 1px solid var(--line);
  background: var(--ink-2);
  padding-block: var(--space-3);
  overflow: hidden;
}
.marquee__track {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  white-space: nowrap;
  will-change: transform;
}
@media (prefers-reduced-motion: no-preference) {
  .marquee__track { animation: marquee 46s linear infinite; }
}
.marquee__item {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-3);
}
.marquee__sep { color: var(--steel); }
@keyframes marquee { from { transform: translateX(0); } to { transform: translateX(-50%); } }

/* Stat count-up: hold the layout while counting so nothing reflows. */
.stat__num [data-count] { font-variant-numeric: tabular-nums; }

/* --- Our reach: the orbit, assembled on scroll ----------------------------
   Chapter six, now a real .section.chapter[data-fog] sibling: it rises out of
   the same fog cap as its neighbours and carries a top-left section label. The
   eight capital photos ride ONE true circle (radius in vmin -> perfect circle
   at any aspect) around a live centre readout, graded into the steel palette so
   only the front city resolves to colour. On desktop the scene pins for one
   screen and the cities FALL in and SWIRL to catch the orbit that is already
   turning (assembly maths in reach() in main.js); the last stretch of the pin
   holds the completed, still-turning orbit before it un-sticks. Radial measures
   use vmin. Baseline (no JS / reduced motion / < 900px / coarse pointer)
   degrades to the head above a responsive grid of the eight city cards. */
.reach { position: relative; background: var(--ink); }

/* ---- Baseline: chapter head + a responsive grid of the eight cities ------- */
.reach__title { margin-top: var(--space-3); font-size: var(--step-4); }
.reach__hl { color: var(--steel-bright); }
.reach__intro {
  margin-top: var(--space-3);
  max-width: 48ch;
  font-size: var(--step-1);
  color: var(--text-2);
}

/* Instrument chrome + centre readout exist only in the live orbit. */
.reach__ring-guide,
.reach__glow,
.reach__marker,
.reach__center { display: none; }

/* The ring is a plain responsive grid in the baseline. */
.reach__ring {
  list-style: none;
  margin: var(--space-6) auto 0;
  padding-inline: var(--gutter);
  max-width: 1180px;
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-4) var(--space-3);
}
@media (min-width: 720px) { .reach__ring { grid-template-columns: repeat(4, 1fr); } }
.reach__slot { margin: 0; }
.reach__node {
  display: block;
  width: 100%;
  margin: 0; padding: 0; border: 0;
  background: none; font: inherit; color: inherit;
  text-align: center; cursor: default;
}
.reach__node-img {
  display: block;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--ink-2);
}
.reach__node-img img {
  display: block; width: 100%; height: auto;
  aspect-ratio: 1 / 1; object-fit: cover;
}
.reach__node-name {
  display: block;
  margin-top: var(--space-2);
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-2);
}
.reach__node-state {
  display: block; margin-top: 0.35rem;
  font-size: var(--step--1); color: var(--text-4);
}
.reach__node-coord {
  display: block; margin-top: 0.2rem;
  font-family: var(--font-mono); font-size: var(--step--1);
  letter-spacing: 0.02em; color: var(--steel);
}

/* ---- Live orbit + scroll assembly: wide, fine-pointer, motion allowed ----- */
@media (min-width: 900px) and (pointer: fine) {
  /* The pin: one extra viewport of track that the sticky scene scrubs through.
     Scoped to .reach--live (JS adds it only with motion allowed), so no-JS and
     reduced motion never inherit a phantom spacer. */
  .reach--live { padding-block: 0; }
  .reach--live .reach-track {
    --reach-pin: 100vh;
    height: calc(100vh + var(--reach-pin));
  }
  .reach--live .reach-scene {
    position: sticky;
    top: 0;
    height: 100vh;
    padding: 0;
    overflow: hidden;
    --R: clamp(206px, 30vmin, 300px);
    --D: clamp(116px, 15.5vmin, 150px);
  }

  /* Head holds top-left through the pin, aligned to the site's gutter. */
  .reach--live .reach__head-shell {
    position: absolute;
    left: 0; right: 0;
    top: clamp(5rem, 13vh, 8.5rem);
    z-index: 210;
    pointer-events: none;
  }
  .reach--live .reach__head { max-width: 30ch; }
  .reach--live .reach__intro { display: none; }

  /* Every orbit layer is absolutely centred in the scene. */
  .reach--live .reach__ring-guide,
  .reach--live .reach__glow,
  .reach--live .reach__marker,
  .reach--live .reach__ring,
  .reach--live .reach__center {
    position: absolute;
    top: 50%; left: 50%;
  }

  /* Concentric contour rings: an alpine survey datum, not a lone circle. */
  .reach--live .reach__ring-guide {
    display: block;
    width: calc(2 * var(--R));
    height: calc(2 * var(--R));
    margin: calc(-1 * var(--R)) 0 0 calc(-1 * var(--R));
    border: 1px solid rgba(127, 160, 200, 0.12);
    border-radius: 50%;
    z-index: 40;
    pointer-events: none;
  }
  .reach--live .reach__ring-guide::before,
  .reach--live .reach__ring-guide::after {
    content: "";
    position: absolute;
    top: 50%; left: 50%;
    transform: translate(-50%, -50%);
    border: 1px solid;
    border-radius: 50%;
  }
  .reach--live .reach__ring-guide::before { width: 72%; height: 72%; border-color: rgba(127, 160, 200, 0.08); }
  .reach--live .reach__ring-guide::after  { width: 44%; height: 44%; border-color: rgba(127, 160, 200, 0.055); }

  /* Datum glow behind the readout (softer than a sun). */
  .reach--live .reach__glow {
    display: block;
    width: calc(1.9 * var(--R));
    height: calc(1.9 * var(--R));
    margin: calc(-0.95 * var(--R)) 0 0 calc(-0.95 * var(--R));
    border-radius: 50%;
    background: radial-gradient(circle, rgba(127, 160, 200, 0.08), rgba(127, 160, 200, 0) 66%);
    z-index: 90;
    pointer-events: none;
  }
  /* Single arrival mark at 6 o'clock (front). */
  .reach--live .reach__marker {
    display: block;
    width: 2px; height: 13px;
    margin: 0;
    transform: translate(-50%, -50%) rotate(180deg) translateY(calc(-1 * var(--R) - 15px));
    background: var(--steel);
    box-shadow: 0 0 10px 1px rgba(127, 160, 200, 0.5);
    z-index: 120;
    pointer-events: none;
  }

  /* The ring collapses to a point; JS places each node with the gondola
     transform (radius stays in CSS via var(--R)). */
  .reach--live .reach__ring {
    display: block;
    width: 0; height: 0;
    margin: 0; padding: 0;
    max-width: none;
    pointer-events: none;
    z-index: 100;
  }
  .reach--live .reach__slot { display: contents; }
  .reach--live .reach__node {
    position: absolute;
    top: 0; left: 0;
    width: var(--D); height: var(--D);
    margin: calc(-0.5 * var(--D)) 0 0 calc(-0.5 * var(--D));
    padding: 0;
    transform-origin: 50% 50%;
    /* Static pre-JS placement; JS overrides every frame during the orbit. */
    transform: rotate(var(--a)) translateY(calc(-1 * var(--R))) rotate(calc(-1 * var(--a)));
    opacity: 0;                 /* JS fades them in as they fall */
    pointer-events: auto;
    cursor: pointer;
  }
  .reach--live .reach__node-img {
    position: relative;
    width: 100%; height: 100%;
    border: 1px solid var(--line-2);
    border-radius: 8px;
    box-shadow: 0 24px 60px -30px rgba(0, 0, 0, 0.9);
    transition: border-color 0.4s var(--ease), box-shadow 0.4s var(--ease);
  }
  .reach--live .reach__node-img img {
    height: 100%;
    /* Graded into the steel family; the front city resolves to full colour. */
    filter: grayscale(0.5) contrast(1.02) brightness(0.96);
    transition: filter 0.6s var(--ease);
  }
  .reach--live .reach__node-img::after {
    content: "";
    position: absolute; inset: 0;
    background: linear-gradient(155deg, rgba(127, 160, 200, 0.10), rgba(14, 15, 18, 0.42));
    opacity: 1;
    transition: opacity 0.6s var(--ease);
    pointer-events: none;
  }
  /* Melbourne: permanent faint steel home ring. */
  .reach--live .reach__node--home .reach__node-img {
    box-shadow: 0 0 0 1px rgba(127, 160, 200, 0.38), 0 24px 60px -30px rgba(0, 0, 0, 0.9);
  }
  /* Front city: steel ring + resolves to colour. */
  .reach--live .reach__node.is-active .reach__node-img {
    border-color: var(--steel);
    box-shadow:
      0 0 0 1px var(--steel),
      0 0 46px -8px rgba(127, 160, 200, 0.5),
      0 26px 64px -30px rgba(0, 0, 0, 0.9);
  }
  .reach--live .reach__node.is-active .reach__node-img img { filter: none; }
  .reach--live .reach__node.is-active .reach__node-img::after { opacity: 0; }

  .reach--live .reach__node-name {
    position: absolute;
    left: 50%; top: 100%;
    transform: translateX(-50%);
    margin-top: 0.7rem;
    white-space: nowrap;
    font-family: var(--font-mono);
    font-size: var(--step--1);
    letter-spacing: 0.06em;
    text-transform: none;
    color: var(--text-4);
    transition: opacity 0.4s var(--ease), color 0.4s var(--ease);
  }
  /* The front city hands its name to the centre readout. */
  .reach--live .reach__node.is-active .reach__node-name { opacity: 0; }
  .reach--live .reach__node-state,
  .reach--live .reach__node-coord { display: none; }
  .reach--live .reach__node:focus-visible {
    outline: 2px solid var(--steel-bright);
    outline-offset: 3px;
    border-radius: 8px;
  }
  .reach--live .reach__node:focus:not(:focus-visible) { outline: none; }

  /* Live readout in the ring's centre. */
  .reach--live .reach__center {
    display: block;
    transform: translate(-50%, -50%);
    max-width: clamp(210px, 26vmin, 300px);
    margin: 0;
    text-align: center;
    z-index: 200;
    pointer-events: none;
  }
  .reach--live .reach__readout { transition: opacity 0.22s var(--ease); }
  .reach--live .reach__readout.is-swapping { opacity: 0; }
  .reach__readout-home {
    display: none;
    margin-bottom: 0.35rem;
    font-family: var(--font-mono);
    font-size: var(--step--1);
    letter-spacing: 0.08em;
    color: var(--steel);
  }
  .reach--live .reach__readout.is-home .reach__readout-home { display: block; }
  .reach__readout-city {
    display: block;
    font-family: var(--font-display);
    font-weight: 600;
    font-size: var(--step-1);
    line-height: 1.05;
    color: var(--text);
  }
  .reach__readout-state {
    display: block;
    margin-top: 0.4rem;
    font-size: var(--step--1);
    color: var(--text-3);
  }
  .reach__readout-coord {
    display: block;
    margin-top: 0.3rem;
    font-family: var(--font-mono);
    font-size: var(--step--1);
    letter-spacing: 0.02em;
    color: var(--steel);
  }
}

@media (max-width: 899px) {
  .reach__title { font-size: var(--step-3); }
}

/* Page transition: a clean crossfade on the page body ----------------------
   Only #main animates, so the header and footer hold steady while the content
   dissolves from one page to the next. Driven by a few lines of JS (see
   pageCrossfade in main.js + the head script in base.njk) rather than the
   browser's native cross-document view transitions, which are still absent or
   disabled in too many browsers (Firefox entirely, Safari < 18.2, some
   Chromium builds) to be relied on. Enhancement only: the transitions hang off
   .js and a `no-preference` guard, so no-JS and reduced-motion users simply get
   an instant native navigation with content always visible. */
@media (prefers-reduced-motion: no-preference) {
  /* Entrance: fade the body in on arrival. A CSS animation (the class is set
     pre-paint by the head script) rather than a JS-removed class, so the
     content always resolves to opacity 1 on the document timeline and can
     never be left stuck hidden by a missed or throttled JS callback. */
  .js.xn-enter #main { animation: ip-xn-in 0.5s cubic-bezier(0.16, 1, 0.3, 1) both; }
  @keyframes ip-xn-in { from { opacity: 0; } to { opacity: 1; } }
  /* Exit: a quick fade-out before navigating away. */
  .js #main { transition: opacity 0.2s ease; }
  .js.xn-leave #main { opacity: 0; }
}

/* Service-page bullet lists: steel rule markers. */
.svc-list {
  list-style: none;
  margin: var(--space-3) 0 0;
  padding: 0;
  display: grid;
  gap: var(--space-2);
}
.svc-list li {
  position: relative;
  padding-left: 1.7rem;
  color: var(--text-2);
}
/* Marker was a literal "//" in content. Now the same short steel rule used by
   .label and .careful__list, so every marker on the site is one device. */
.svc-list li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.72em;
  width: 1.1rem;
  height: 1px;
  background: var(--steel);
}
.svc-list strong { color: var(--text); font-weight: 600; }

/* The two standing statements: Acknowledgement of Country, and the independence
   note. One hairline-topped band holding both as a pair, rather than two stacked
   full-width prose blocks, which is what made the footer run long.

   Both are peers, so both sit at --text-3 rather than one dimming to --text-4:
   side by side, a brightness difference reads as one of them mattering less.
   Headings reuse .footer-col__h, the footer's own label treatment, which is not
   parent-scoped, so the whole footer stays consistent by construction. */
.site-footer__mid {
  margin-top: var(--space-6);
  padding-top: var(--space-4);
  border-top: 1px solid var(--line);
  display: grid;
  gap: var(--space-4);
  grid-template-columns: 1fr;
}
@media (min-width: 860px) {
  .site-footer__mid { grid-template-columns: 1.15fr 1fr; gap: var(--space-6); }
}
.site-footer__ack p,
.site-footer__note p {
  margin: 0;
  max-width: 62ch;
  font-size: var(--step--1);
  line-height: 1.65;
  color: var(--text-3);
}

/* Primary-nav "What we do" preview dropdown ------------------------------------
   A contained two-column panel: the three service labels (left) and a preview
   that crossfades per item (right). Reveals on hover / :focus-within (so it is a
   plain working link list with no JS) and on the .is-open class the JS adds; the
   JS also drives the right-side crossfade and the "menu-open" acknowledge (the
   header goes solid and a scrim dims the page below). Disclosure a11y (Escape,
   outside-click, focus-out). The mobile drawer renders the children as nested
   items instead. Transform/opacity only. */
.nav__item--has-sub { position: relative; }
.nav__panel {
  position: absolute;
  top: 100%; left: 0;
  margin-top: 0.5rem;
  display: grid;
  grid-template-columns: 1fr 0.92fr;
  width: 560px;
  background: var(--ink-2);
  border: 1px solid var(--line-2);
  border-radius: var(--radius);
  box-shadow: 0 30px 70px -28px rgba(0, 0, 0, 0.9);
  overflow: hidden;
  opacity: 0;
  transform: translateY(8px);
  pointer-events: none;
  transition: opacity var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
  z-index: 5;
}
/* Transparent bridge over the small gap below the header so hover never drops. */
.nav__panel::before {
  content: "";
  position: absolute;
  top: -0.6rem; left: 0; right: 0;
  height: 0.6rem;
}
.nav__item--has-sub:hover .nav__panel,
.nav__item--has-sub:focus-within .nav__panel,
.nav__item--has-sub.is-open .nav__panel {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}
.nav__panel-items { list-style: none; margin: 0; padding: 0.5rem; border-right: 1px solid var(--line); }
.nav__panel-item {
  position: relative;
  display: grid;
  gap: 0.1rem;
  padding: 0.7rem 0.8rem 0.7rem 1.15rem;
  text-decoration: none;
  border-radius: 4px;
  opacity: 0;
  transform: translateY(6px);
  transition: background-color var(--dur-fast) var(--ease);
}
.nav__item--has-sub:hover .nav__panel-item,
.nav__item--has-sub:focus-within .nav__panel-item,
.nav__item--has-sub.is-open .nav__panel-item {
  opacity: 1;
  transform: none;
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease), background-color var(--dur-fast) var(--ease);
}
.nav__panel-item:nth-child(1) { transition-delay: 0.04s; }
.nav__panel-item:nth-child(2) { transition-delay: 0.09s; }
.nav__panel-item:nth-child(3) { transition-delay: 0.14s; }
.nav__panel-item:hover,
.nav__panel-item.is-active { background: var(--ink-3); }
.nav__panel-item:focus-visible { outline: 2px solid var(--steel-bright); outline-offset: -2px; }
.nav__panel-tick {
  position: absolute;
  left: 0.5rem; top: 0.75rem; bottom: 0.75rem;
  width: 1.5px;
  background: var(--steel);
  transform: scaleY(0);
  transform-origin: top;
  transition: transform var(--dur-fast) var(--ease);
}
.nav__panel-item:hover .nav__panel-tick,
.nav__panel-item.is-active .nav__panel-tick { transform: scaleY(1); }
.nav__panel-ix { font-family: var(--font-mono); font-size: 0.72rem; letter-spacing: 0.08em; color: var(--steel); }
.nav__panel-name { font-family: var(--font-display); font-weight: 600; font-size: var(--step-0); color: var(--text); line-height: 1.15; }
.nav__panel-desc { font-size: var(--step--1); color: var(--text-4); }

.nav__panel-aside { position: relative; padding: 1.15rem; min-height: 186px; }
.nav__prev {
  position: absolute;
  inset: 1.15rem;
  display: flex;
  flex-direction: column;
  opacity: 0;
  transform: translateY(6px);
  pointer-events: none;
  transition: opacity var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
}
.nav__prev.is-active { opacity: 1; transform: none; }
.nav__prev-glyph { color: var(--steel); line-height: 0; }
.nav__prev-glyph svg { width: 40px; height: 40px; }
.nav__prev-lead { margin: 0.95rem 0 auto; font-size: var(--step-0); color: var(--text-2); line-height: 1.4; }
.nav__prev-meta { margin: 0.95rem 0 0; display: grid; gap: 0.4rem; }
.nav__prev-meta > div { display: flex; justify-content: space-between; gap: 1rem; border-top: 1px solid var(--line); padding-top: 0.4rem; }
.nav__prev-meta > div:first-child { border-top: 0; padding-top: 0; }
.nav__prev-meta dt { font-family: var(--font-mono); font-size: 0.72rem; letter-spacing: 0.05em; color: var(--text-4); margin: 0; }
.nav__prev-meta dd { font-family: var(--font-mono); font-size: 0.72rem; color: var(--text-2); margin: 0; text-align: right; }

/* Menu-open: the header goes solid and a scrim dims the page below it. */
.menu-scrim {
  position: fixed;
  inset: 0;
  background: rgba(9, 10, 13, 0.5);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur) var(--ease);
  z-index: 90;
}
html.menu-open .menu-scrim { opacity: 1; pointer-events: auto; }
html.menu-open .site-header {
  background: rgba(14, 15, 18, 0.9);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border-bottom-color: var(--line-2);
}
/* No-JS: show the first preview statically (the crossfade is JS-only). */
html.no-js .nav__prev:first-child { opacity: 1; transform: none; }

/* Drawer nested service links (mobile). */
.nav-drawer__sub { list-style: none; margin: 0.15rem 0 0.7rem; padding: 0; }
.nav-drawer__sub li + li { border-top: 0; }
.nav-drawer__sub-link {
  display: flex;
  align-items: baseline;
  gap: 0.55rem;
  padding: 0.5rem 0 0.5rem 1.2rem;
  color: var(--text-4);
  font-size: var(--step--1);
  text-decoration: none;
}
.nav-drawer__sub-ix { font-family: var(--font-mono); font-size: 0.7rem; color: var(--steel); }
.nav-drawer__sub-link:hover,
.nav-drawer__sub-link[aria-current="page"] { color: var(--steel); }

@media (prefers-reduced-motion: reduce) {
  .nav__panel, .nav__panel-item, .nav__prev, .nav__panel-tick { transform: none !important; transition: opacity var(--dur-fast) linear; }
}

/* --- Stage 2 page polish -------------------------------------------------- */

/* Header scroll-progress hairline: a 1.5px steel line along the header bottom
   that grows with scroll. CSS scroll-driven where supported; a JS fallback
   (scrollProgress() in main.js) drives it in Safari/Firefox. */
.scroll-progress {
  position: absolute;
  left: 0; bottom: 0;
  width: 100%; height: 1.5px;
  background: var(--steel);
  transform: scaleX(0);
  transform-origin: left;
  opacity: 0.9;
  z-index: 2;
  pointer-events: none;
}
@supports (animation-timeline: scroll()) {
  @media (prefers-reduced-motion: no-preference) {
    .scroll-progress { animation: sp-grow linear both; animation-timeline: scroll(root block); }
  }
}
@keyframes sp-grow { from { transform: scaleX(0); } to { transform: scaleX(1); } }

/* Kinetic pull: the steel accent word draws in an underline once its statement
   reveals. */
.pull__hi { position: relative; }
.js .pull__hi::after {
  content: "";
  position: absolute;
  left: 0; bottom: -0.06em;
  width: 100%; height: 2px;
  background: var(--steel);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--dur) var(--ease) 0.35s;
}
.js .pull__text.is-in .pull__hi::after { transform: scaleX(1); }

/* Reveal signature: a steel hairline wipes in from the left then retracts to the
   right as a block enters, binding the service pages into an authored set. */
.reveal--wipe { position: relative; }
.js .reveal--wipe::before {
  content: "";
  position: absolute;
  top: -0.7rem; left: 0;
  width: 100%; height: 1.5px;
  background: var(--steel);
  transform: scaleX(0);
  transform-origin: left;
  opacity: 0;
}
.js .reveal--wipe.is-in::before { animation: hairline-wipe 0.75s var(--ease) forwards; }
@keyframes hairline-wipe {
  0%   { opacity: 1; transform: scaleX(0); transform-origin: left; }
  45%  { opacity: 1; transform: scaleX(1); transform-origin: left; }
  55%  { opacity: 1; transform: scaleX(1); transform-origin: right; }
  100% { opacity: 0; transform: scaleX(0); transform-origin: right; }
}

@media (prefers-reduced-motion: reduce) {
  .js .pull__hi::after { transform: scaleX(1); transition: none; }
  .js .reveal--wipe::before { display: none; }
  .scroll-progress { display: none; }
}

/* =========================================================================
   ARTEFACT SCENES: three pinned line-diagram scenes (capability, quad,
   website). One shared kit, three artefacts. Abstract survey contours resolve
   into the literal artefact as the scene scrubs.

   Static first. --p is registered with initial-value 1, so with no JS, reduced
   motion, a coarse pointer or under 900px the artefact renders fully resolved
   and the pin never exists. artifactScenes() writes --p once per frame; every
   reveal is computed here in CSS, not in JS.
   ========================================================================= */

/* scrub progress 0..1, written on the stage. initial-value 1 IS the fallback. */
@property --p { syntax: "<number>"; inherits: true;  initial-value: 1; }
/* per element reveal 0..1, derived from --p. */
@property --r { syntax: "<number>"; inherits: false; initial-value: 1; }

.artifact { position: relative; background: var(--ink); }
.artifact-stage { --p: 1; }
.artifact__head { max-width: 34ch; }
.artifact__title { margin-top: var(--space-3); font-size: var(--step-3); }
.artifact__hl { color: var(--steel-bright); }

.artifact__svg {
  display: block;
  width: 100%; height: auto;
  max-height: 78vh;
  margin-inline: auto;
  margin-top: clamp(2rem, 5vh, 3.5rem);
}
.scene-capability .artifact__svg { max-width: 620px; }
.scene-quad      .artifact__svg  { max-width: 820px; }
.scene-website   .artifact__svg  { max-width: 760px; }
.scene-stand     .artifact__svg  { max-width: 880px; }
.scene-shoot     .artifact__svg  { max-width: 900px; }

/* Reveal primitive: --s (start) and --w (window) resolve --p into a clamped --r. */
.rv        { --s: 0; --w: 1; --r: clamp(0, (var(--p) - var(--s)) / var(--w), 1); }
.rv--draw  { stroke-dasharray: 1; stroke-dashoffset: calc(1 - var(--r)); }
.rv--in    { opacity: calc(var(--op, 1) * var(--r)); }
.rv--panel { stroke-dasharray: 1; stroke-dashoffset: calc(1 - var(--r));
             fill-opacity: calc(var(--fo, 0.4) * var(--r)); }

/* One stroke and fill vocabulary shared by all three scenes. */
.art-hair    { fill: none; stroke: var(--steel);        stroke-width: 1; }
.art-bright  { fill: none; stroke: var(--steel-bright); stroke-width: 1.5; }
.art-contour { fill: none; stroke: var(--steel);        stroke-width: 1; stroke-dasharray: 3 5; }
.art-guide   { fill: none; stroke: var(--steel);        stroke-width: 1; stroke-dasharray: 2 6; }
.art-panel   { fill: var(--ink-2); stroke: var(--steel); stroke-width: 1; }
.art-inset   { fill: var(--ink-3); stroke: var(--steel); stroke-width: 1; }
.art-mark    { fill: var(--steel); stroke: none; }
.art-label   { font-family: var(--font-mono); font-size: 14px; letter-spacing: 0.08em; fill: var(--text-4); }

/* The contour field retreats on one inherited envelope, not per element. */
.art-contour-field {
  opacity: clamp(0, 1 - (var(--p) - var(--fade-s, 0.06)) / var(--fade-w, 0.18), 1);
}

/* ---- The pin. Scoped to .artifact--live, which artifactScenes() adds only
   when the gate below matches and motion is allowed, so no-JS, reduced motion
   and mobile never inherit a phantom spacer. Gate string is byte identical to
   the matchMedia string in main.js. ---- */
@media (min-width: 900px) and (pointer: fine) {
  .artifact--live { padding-block: 0; }
  .artifact--live .artifact-track {
    --artifact-pin: 100vh;
    height: calc(100vh + var(--artifact-pin));
  }
  .artifact--live .artifact-stage {
    position: sticky;
    top: 0;
    height: 100vh;
    padding: 0;
    overflow: hidden;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .artifact--live .artifact__svg { margin-top: 0; max-height: 74vh; }
  .artifact--live .artifact__head-shell {
    position: absolute;
    left: 0; right: 0;
    top: clamp(5rem, 13vh, 8.5rem);
    z-index: 6;
    pointer-events: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .artifact-stage { --p: 1; }
}

/* =========================================================================
   PROCESS ROW: the shared engagement band. Baseline is a semantic vertical
   stack that needs no JS. On wide screens with motion allowed it enhances into
   a horizontal snap strip. It never pins and never listens for wheel, so page
   scroll is never hijacked.
   ========================================================================= */
.process {
  position: relative;
  background: var(--ink);
  border-top: 1px solid var(--line);
  padding-block: clamp(4rem, 9vh, 7rem);
}
.process__head { margin-bottom: clamp(2rem, 5vh, 3rem); }
.process__eyebrow {
  font-family: var(--font-mono); font-size: var(--step--1);
  letter-spacing: 0.08em; color: var(--text-4); margin: 0 0 0.6rem;
}
.process__title { font-size: var(--step-3); margin: 0; }

.process__nav { display: none; }
.process__scroller { overflow: visible; }
.process__track {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-direction: column; gap: 1px;
  background: var(--line);
  border-block: 1px solid var(--line);
}
.process__step { background: var(--ink-2); padding: clamp(1.75rem, 3vw, 2.25rem); }
.process__index {
  font-family: var(--font-mono); font-size: var(--step--1);
  letter-spacing: 0.1em; color: var(--steel); margin: 0 0 0.9rem;
}
.process__step-title { font-size: var(--step-1); line-height: 1.1; margin: 0 0 0.5rem; }
.process__step-body { color: var(--text-2); line-height: 1.55; max-width: 42ch; margin: 0; }

.process__progress { display: none; }
.process__progress-bar {
  display: block; height: 1px; background: var(--steel);
  transform: scaleX(0); transform-origin: left center;
}

@media (min-width: 900px) and (prefers-reduced-motion: no-preference) {
  .js .process__scroller {
    overflow-x: auto; overflow-y: hidden;
    scroll-snap-type: x proximity;
    /* The scroller carries .shell, so its content starts one gutter in. Inset
       the snap port by the same gutter, otherwise the first card snaps flush to
       the scrollport edge and the row starts one gutter out of step with every
       other section on the page. */
    scroll-padding-inline-start: var(--gutter);
    scrollbar-width: none;
    overscroll-behavior-x: contain;
  }
  .js .process__scroller::-webkit-scrollbar { display: none; }
  .js .process__scroller:focus-visible { outline: 2px solid var(--steel-bright); outline-offset: 6px; }

  .js .process__track { flex-direction: row; }
  .js .process__step {
    flex: 0 0 auto;
    width: min(76vw, 26rem);
    min-height: 15rem;
    scroll-snap-align: start;
    transition: background var(--dur) var(--ease);
  }
  .js .process__step:hover { background: var(--ink-3); }

  .js .process__scroller[data-overflow="true"] {
    -webkit-mask-image: linear-gradient(to right, #000 88%, transparent);
            mask-image: linear-gradient(to right, #000 88%, transparent);
  }
  .js .process__scroller[data-overflow="end"] { -webkit-mask-image: none; mask-image: none; }

  .js .process__nav { display: inline-flex; gap: 0.5rem; margin-top: 1.25rem; }
  .js .process__btn {
    inline-size: 2.5rem; block-size: 2.5rem;
    display: inline-flex; align-items: center; justify-content: center;
    background: var(--ink-2); border: 1px solid var(--line-2);
    color: var(--text-3); font-family: var(--font-mono); cursor: pointer;
    transition: border-color var(--dur) var(--ease), color var(--dur) var(--ease);
  }
  .js .process__btn:hover { border-color: var(--steel); color: var(--steel-bright); }
  .js .process__btn[disabled] { opacity: 0.35; cursor: default; }
  .js .process__progress { display: block; margin-top: 1.5rem; }
}

@media (prefers-reduced-motion: reduce) {
  .process__step, .process__btn, .process__progress-bar { transition: none; }
}
