/* ==========================================================================
   Page intro & landing veil
   ========================================================================== */

/* Hard-reload landing intro: a dark curtain over the hero background that
   fades away, revealing whatever sits behind it (the organic-water canvas on
   home, a plain photo background on project/article pages). Keyed off
   body.is-loaded rather than a per-page class - body persists across swup
   transitions while #landing itself gets replaced, so on every visit after
   the very first hard load, a fresh .landing-veil is simply born already
   matching this selector and never animates at all. See initLandingIntro in
   main.js for the one-time, hard-reload-only trigger.

   The covering opacity: 1 only applies under .js (see header.php) - without
   JS, body.is-loaded never gets added, so the curtain must default to
   opacity: 0 (never drawn) instead of permanently hiding the hero. */
.landing-veil {
  position: absolute;
  inset: 0;
  z-index: 1;
  background: var(--c-dark);
  opacity: 0;
  pointer-events: none;
}

.js .landing-veil {
  opacity: 1;
  transition: opacity 1.1s cubic-bezier(0.16, 1, 0.3, 1);
}

body.is-loaded .landing-veil {
  opacity: 0;
}

.js .title-container,
.js .project-title-container {
  opacity: 0;
  transform: translateY(2rem);
  transition: opacity 1s cubic-bezier(0.16, 1, 0.3, 1) .3s, transform 1s cubic-bezier(0.16, 1, 0.3, 1) .3s;
}

body.is-loaded .title-container,
body.is-loaded .project-title-container {
  opacity: 1;
  transform: translateY(0);
}

/* ==========================================================================
   Scroll reveal
   Progressive enhancement: every rule below that hides an element for an
   entrance animation is scoped under .js (added on <html> in header.php).
   No JavaScript - or JS that fails before that line runs - means these
   selectors never match, so content just stays in its normal, fully visible
   resting state instead of getting stuck hidden. With JS, elements start
   hidden and initScrollReveal (main.js) reveals them via IntersectionObserver
   as they scroll into view - and reveals everything immediately instead if
   IntersectionObserver isn't available, for the same fail-safe reason.
   ========================================================================== */

/* Generic subtle fade + rise, applied via JS (initScrollReveal in main.js) to
   a curated set of already-existing classes - no template markup needed.
   Reveals once, then leaves it; this is a first-impression cue, not something
   that should re-trigger every time a section re-enters the viewport. */
.js .reveal {
  opacity: 0;
  transform: translateY(1.5rem);
  transition: opacity .7s cubic-bezier(0.16, 1, 0.3, 1), transform .7s cubic-bezier(0.16, 1, 0.3, 1);
}

.reveal.is-visible {
  opacity: 1;
  transform: translateY(0);
}

/* .section-article-container already transitions box-shadow on hover; a
   plain .reveal rule here would just as easily lose that cascade fight as
   win it (both single-class selectors), so this combined, more specific
   rule folds both concerns into one list instead of leaving it to source
   order. */
.js .section-article-container.reveal {
  transition: box-shadow .3s ease-in-out, opacity .7s cubic-bezier(0.16, 1, 0.3, 1), transform .7s cubic-bezier(0.16, 1, 0.3, 1);
}

/* Numberslider: each slide fades and rises, staggered one after another.
   Triggered by .numberslider-wrapper.is-visible (set by initScrollReveal in
   main.js) rather than observing every slide individually, since most sit
   off to the right inside a horizontally-scrolling row at reveal time. */
.js .numberslide {
  opacity: 0;
  transform: translateY(4rem);
  transition: opacity .6s cubic-bezier(0.16, 1, 0.3, 1), transform .6s cubic-bezier(0.16, 1, 0.3, 1);
}

.numberslider-wrapper.is-visible .numberslide {
  opacity: 1;
  transform: translateY(0);
}

.numberslider-wrapper.is-visible .numberslide:nth-child(1) {
  transition-delay: 0s;
}

.numberslider-wrapper.is-visible .numberslide:nth-child(2) {
  transition-delay: .1s;
}

.numberslider-wrapper.is-visible .numberslide:nth-child(3) {
  transition-delay: .2s;
}

.numberslider-wrapper.is-visible .numberslide:nth-child(4) {
  transition-delay: .3s;
}

.numberslider-wrapper.is-visible .numberslide:nth-child(5) {
  transition-delay: .4s;
}

.numberslider-wrapper.is-visible .numberslide:nth-child(6) {
  transition-delay: .5s;
}

/* Contentslider & publications: fade in from the right, staggered - same
   mechanism as the numberslider above, just a different resting transform
   and a different trigger container per block. */
.js .contentslide,
.js .publication {
  opacity: 0;
  transform: translateX(3rem);
  transition: opacity .7s cubic-bezier(0.16, 1, 0.3, 1), transform .7s cubic-bezier(0.16, 1, 0.3, 1);
}

.contentslider-wrapper.is-visible .contentslide,
.publications-wrapper.is-visible .publication {
  opacity: 1;
  transform: translateX(0);
}

.contentslider-wrapper.is-visible .contentslide:nth-child(1),
.publications-wrapper.is-visible .publication:nth-child(1) {
  transition-delay: 0s;
}

.contentslider-wrapper.is-visible .contentslide:nth-child(2),
.publications-wrapper.is-visible .publication:nth-child(2) {
  transition-delay: .12s;
}

.contentslider-wrapper.is-visible .contentslide:nth-child(3),
.publications-wrapper.is-visible .publication:nth-child(3) {
  transition-delay: .24s;
}

.contentslider-wrapper.is-visible .contentslide:nth-child(4),
.publications-wrapper.is-visible .publication:nth-child(4) {
  transition-delay: .36s;
}

.contentslider-wrapper.is-visible .contentslide:nth-child(5),
.publications-wrapper.is-visible .publication:nth-child(5) {
  transition-delay: .48s;
}

.contentslider-wrapper.is-visible .contentslide:nth-child(6),
.publications-wrapper.is-visible .publication:nth-child(6) {
  transition-delay: .6s;
}

/* Blockquote typewriter: each .quote-char (split from .txt-quote by
   initBlockquoteTypewriter in main.js) fades in one after another via a
   JS-computed inline transition-delay, since the character count - and so
   the total typing duration .section-quote-meta needs to wait out - varies
   per quote. The un-split text stays available to assistive tech via
   .sr-only; the visual, character-split copy is aria-hidden. */
.js .quote-char {
  opacity: 0;
  transition: opacity .12s ease;
}

.section-quote.is-visible .quote-char {
  opacity: 1;
}

.js .section-quote-meta {
  opacity: 0;
  transition: opacity .8s ease;
}

.section-quote-meta.is-visible {
  opacity: 1;
}

/* ==========================================================================
   Section: Projects Entrance
   ========================================================================== */

/* Project entries: icon container rises in from below on
   mobile, since the stacked layout there has no left/right side to speak of;
   from the left, or from the right on every second entry, once the 1024px
   layout splits into alternating sides - see below), then the icon itself
   fades in on top of it, then the content card rises in last - one gentle
   chain per entry instead of everything appearing at once. Triggered by JS
   adding .is-visible to .section-project on scroll (see initScrollReveal). */
.js .section-project-icon-container {
  opacity: 0;
  transform: translateY(4rem);
  transition: opacity .8s cubic-bezier(0.16, 1, 0.3, 1), transform .8s cubic-bezier(0.16, 1, 0.3, 1);
}

/* Scoped to .section-project specifically: this class is also reused for the
   plain, always-visible hero icon on project.php/article.php's landing
   section, which has no .section-project ancestor and must never be hidden
   waiting on a reveal trigger that can't reach it. */
.js .section-project .section-project-icon {
  opacity: 0;
  transition: opacity .5s cubic-bezier(0.16, 1, 0.3, 1) .45s;
}

.js .section-project-content-wrapper {
  opacity: 0;
  transform: translateY(2rem);
  transition: opacity .7s cubic-bezier(0.16, 1, 0.3, 1) .65s, transform .7s cubic-bezier(0.16, 1, 0.3, 1) .65s;
}

.section-project.is-visible .section-project-icon-container {
  opacity: 1;
  transform: translateY(0);
}

.section-project.is-visible .section-project-icon {
  opacity: 1;
}

.section-project.is-visible .section-project-content-wrapper {
  opacity: 1;
  transform: translateY(0);
}

/* ==========================================================================
   Section: Funders Stagger
   ========================================================================== */

/* Funder logos fade in separately, one after another. Targeting direct
   children rather than .funder-logo itself, since a logo with a link is
   wrapped in an <a> - this keeps nth-child staggering consistent whichever
   markup shape a given funder renders as. */
.js .funders-container>* {
  opacity: 0;
  transition: opacity .6s cubic-bezier(0.16, 1, 0.3, 1);
}

.funders-container.is-visible>* {
  opacity: 1;
}

.funders-container.is-visible>*:nth-child(1) {
  transition-delay: 0s;
}

.funders-container.is-visible>*:nth-child(2) {
  transition-delay: .1s;
}

.funders-container.is-visible>*:nth-child(3) {
  transition-delay: .2s;
}

.funders-container.is-visible>*:nth-child(4) {
  transition-delay: .3s;
}

.funders-container.is-visible>*:nth-child(5) {
  transition-delay: .4s;
}

.funders-container.is-visible>*:nth-child(6) {
  transition-delay: .5s;
}

.funders-container.is-visible>*:nth-child(7) {
  transition-delay: .6s;
}

.funders-container.is-visible>*:nth-child(8) {
  transition-delay: .7s;
}

/* ==========================================================================
   Reduced motion
   One consolidated override per feature query - see each component above for
   why it animates the way it does.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {

  .js .reveal,
  .js .numberslide,
  .js .contentslide,
  .js .publication,
  .js .quote-char,
  .js .section-quote-meta,
  .js .funders-container>* {
    opacity: 1;
    transform: none;
    transition: none;
  }

  .js .landing-veil,
  .js .title-container,
  .js .project-title-container {
    opacity: 1;
    transform: none;
    transition: none;
  }

  .js .section-project-icon-container,
  .js .section-project .section-project-icon,
  .js .section-project-content-wrapper {
    opacity: 1;
    transform: none;
    transition: none;
  }
}