/* ==========================================================================
   Motion: reveal, count-up, sticky header, page transitions.

   Only transform and opacity are animated. Motion changes how an element
   arrives, never where it ends up. Everything is switched off under
   prefers-reduced-motion.
   ========================================================================== */

/* --- Scroll reveal ------------------------------------------------------ */

/* .js-reveal is set by motion.js, so without JS everything is visible. */
.js-reveal [data-reveal] {
  opacity: 0;
  transform: translateY(18px);
}

.js-reveal [data-reveal].is-revealed {
  opacity: 1;
  transform: none;
  transition: opacity 0.7s var(--ease), transform 0.7s var(--ease);
}

/* Stagger: motion.js sets --stagger-index on each child.

   An animation, not a transition: setting `transition` here would replace
   the children's own transitions and break their hover effects. The fill is
   `backwards` only; `forwards` would pin transform: none and beat every
   hover. */
.js-reveal [data-reveal-stagger] > * {
  opacity: 0;
  transform: translateY(18px);
}

.js-reveal [data-reveal-stagger].is-revealed > * {
  opacity: 1;
  transform: none;
  animation: reveal-in 0.6s var(--ease) backwards;
  animation-delay: calc(var(--stagger-index, 0) * 60ms);
}

@keyframes reveal-in {
  from { opacity: 0; transform: translateY(18px); }
  to   { opacity: 1; transform: none; }
}

/* --- Sticky header ------------------------------------------------------ */

.site-header {
  transition: box-shadow var(--dur) var(--ease), background-color var(--dur) var(--ease),
              transform var(--dur) var(--ease);
}

/* On a phone the bar leaves once the page scrolls; the floating menu button
   (main.js) takes its place. */
@media (max-width: 1100px) {
  .site-header.is-away { transform: translateY(-100%); }
}

/* Set once the sentinel at the top scrolls out of view. */
.site-header.is-stuck {
  box-shadow: 0 4px 20px oklch(0% 0 0 / 0.06);
  background: oklch(99% 0.002 100 / 0.97);
}

.header-sentinel {
  position: absolute;
  top: 0;
  height: 1px;
  width: 100%;
  pointer-events: none;
}

/* --- Images ------------------------------------------------------------- */

/* Fade in once loaded. Gated on .js-reveal: .is-loaded comes from motion.js,
   so without JS the images must stay visible. motion.js marks already
   decoded images in the same task, so a ready hero image just appears. */
/* transform is listed too: this rule outranks .photo-card__media img, and
   naming only opacity left the gallery hover zoom with no transition at all. */
.js-reveal .media-fade {
  opacity: 0;
  transition: opacity 0.5s var(--ease), transform 0.45s var(--ease);
}
.js-reveal .media-fade.is-loaded { opacity: 1; }

/* --- Image placeholder ---------------------------------------------------- */

/* .media-box wraps an image. While it loads, the box is a pale green surface
   with a light band sweeping across; motion.js adds .is-loaded to the box when
   the image arrives. The band sits behind the image (z-index -1 in an isolated
   box), so the photo covers it as it fades in and badges on top stay on top. */
/* position has zero specificity: components that place the box themselves,
   like the hero photo lying behind the text on a phone, must win. */
:where(.js-reveal .media-box) { position: relative; }
.js-reveal .media-box {
  isolation: isolate;
  background: var(--green-50);
}

.js-reveal .media-box::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(100deg, transparent 20%, oklch(100% 0 0 / 0.6) 50%, transparent 80%);
  transform: translateX(-100%);
  animation: media-shimmer 1.4s var(--ease) infinite;
}

.js-reveal .media-box.is-loaded { background: transparent; }
.js-reveal .media-box.is-loaded::before { animation: none; }

/* Partner logos blend into the footer with mix-blend-mode, which an isolated
   box would break. Their placeholder is drawn over the logo instead (surface
   in ::after, band in ::before) and goes away once the logo is there. */
.js-reveal .media-box--over {
  isolation: auto;
  background: none;
  overflow: hidden;
  border-radius: 8px;
}
.js-reveal .media-box--over::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background: var(--green-50);
}
.js-reveal .media-box--over::before { z-index: 2; }
.js-reveal .media-box--over.is-loaded::before,
.js-reveal .media-box--over.is-loaded::after { display: none; }

@keyframes media-shimmer {
  to { transform: translateX(100%); }
}

/* --- Anchors ------------------------------------------------------------ */

/* Keeps anchor targets clear of the sticky header. */
:target {
  scroll-margin-top: 96px;
}

html {
  scroll-behavior: smooth;
}

/* --- Reduced motion ----------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }

  .js-reveal [data-reveal],
  .js-reveal [data-reveal-stagger] > *,
  .js-reveal .media-fade {
    opacity: 1 !important;
    transform: none !important;
  }

  /* A still surface instead of the sweep. */
  .js-reveal .media-box::before {
    animation: none !important;
  }
}

/* --- Cross-document page transitions ------------------------------------ */

/* A short cross-fade between pages. Unsupported browsers navigate as usual.
   Both pages must opt in, and this file is on every page. */
@view-transition {
  navigation: auto;
}

::view-transition-old(root) {
  animation: vt-fade-out 90ms var(--ease) both;
}
::view-transition-new(root) {
  animation: vt-fade-in 220ms var(--ease) both;
}

@keyframes vt-fade-out {
  to { opacity: 0; }
}
@keyframes vt-fade-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}

/* Header and footer stay still while the page changes. Only elements with
   the same height on every page may get a view-transition-name. */
.site-header { view-transition-name: site-header; }
.site-footer { view-transition-name: site-footer; }

/* --- Counting statistics ------------------------------------------------ */

/* Tabular figures, so the counting number does not reflow its neighbours. */
.stats__value { font-variant-numeric: tabular-nums; }

/* --- Reduced motion ----------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  ::view-transition-old(root),
  ::view-transition-new(root) {
    animation: none !important;
  }
}
