/* AQcredix — motion layer styles.
 *
 * No colours are declared here at all. Everything is opacity and transform, so the
 * palette (light / dark / neon) is untouched by definition and there is nothing to keep
 * in step when the palette changes. Nothing here belongs inside a media query either.
 */

/* Reveal. The hidden state lives on .aq-reveal and is only ever applied by JS, so a
   visitor with JavaScript disabled — or with reduced motion — sees ordinary content
   rather than a blank page waiting for an animation that will never run. */
.aq-reveal {
  opacity: 0;
  transform: translate3d(0, 18px, 0);
  transition:
    opacity 620ms cubic-bezier(.22, .61, .36, 1) var(--aq-delay, 0ms),
    transform 620ms cubic-bezier(.22, .61, .36, 1) var(--aq-delay, 0ms);
  will-change: opacity, transform;
}
.aq-reveal.aq-in {
  opacity: 1;
  transform: none;
}
/* Drop the hint once the element has arrived: a permanent will-change on dozens of
   sections keeps a compositor layer alive for each one, which costs more on a low-end
   hospital desktop than the animation ever saved. */
.aq-reveal.aq-in {
  will-change: auto;
}

/* Parallax. transform is written by JS each frame; this only promotes the element and
   stops the drift from spilling outside its section. */
[data-parallax] {
  will-change: transform;
}

/* ------------------------------- split text -------------------------------
   Two nested spans per word: .aq-w clips, .aq-w-i moves. The outer keeps the word on
   the baseline and inline, so line wrapping and justification behave exactly as they
   did before the split. */
.aq-split .aq-w {
  display: inline-block;
  overflow: hidden;
  vertical-align: top;
  /* Descenders in g, y, p sit below the baseline and would be clipped by an overflow
     box sized to the line. The padding gives them room and the negative margin gives
     the space back, so nothing shifts. */
  padding-bottom: 0.12em;
  margin-bottom: -0.12em;
}
.aq-split .aq-w-i {
  display: inline-block;
  transform: translate3d(0, 105%, 0);
  opacity: 0;
  transition:
    transform 900ms cubic-bezier(.16, .84, .32, 1),
    opacity 700ms ease;
  transition-delay: calc(var(--aq-i, 0) * 85ms);
  will-change: transform;
}
.aq-split.aq-split-in .aq-w-i {
  transform: none;
  opacity: 1;
}

/* The hero headline slides RIGHT out from behind the ring mark rather than rising.
   Slower and longer-travelled than the section headings, because it plays once on
   arrival and is the first thing anyone sees. */
.hero-headline h1.aq-split .aq-w-i {
  transform: translate3d(-0.7em, 0, 0);
  transition:
    transform 1250ms cubic-bezier(.19, 1, .22, 1),
    opacity 900ms cubic-bezier(.19, 1, .22, 1);
  transition-delay: calc(var(--aq-i, 0) * 110ms);
}
/* The finished state must out-rank the hero start state above. The generic
   `.aq-split.aq-split-in .aq-w-i` rule is (0,3,0) while the hero rule is (0,3,1), so the
   generic one LOST and the -0.7em offset never cleared — every word sat shifted left with
   its first letter eaten by its own clipping box. This selector is (0,4,1) and wins. */
.hero-headline h1.aq-split.aq-split-in .aq-w-i {
  transform: none;
  opacity: 1;
}

/* Once the words have arrived there is nothing left to clip, and leaving overflow hidden
   would shave italic and accented glyphs that legitimately extend past their box —
   the italic "assessor" in this very headline is one. */
.hero-headline h1.aq-split.aq-split-in .aq-w {
  overflow: visible;
}

.hero-headline h1.aq-split .aq-w {
  /* Clip sideways here: the words emerge from the left edge, in the direction of the
     mark, instead of from below. */
  overflow: hidden;
  padding-left: 0.08em;
  margin-left: -0.08em;
}

/* The mark itself settles in first, so the headline appears to come from it. The arc
   draws around the ring; 100 is comfortably longer than the real path, and overshooting
   a dash array simply completes the stroke. */
.hero-logo {
  opacity: 0;
  transform: translate3d(0, 0, 0) scale(.86) rotate(-12deg);
  transition:
    opacity 900ms ease,
    transform 1100ms cubic-bezier(.19, 1, .22, 1);
}
.hero-logo-arc {
  stroke-dasharray: 100;
  stroke-dashoffset: 100;
  transition: stroke-dashoffset 1400ms cubic-bezier(.22, .61, .36, 1) 180ms;
}
html.aq-hero-in .hero-logo {
  opacity: 1;
  transform: none;
}
html.aq-hero-in .hero-logo-arc {
  stroke-dashoffset: 0;
}

/* Page transition. Short and shallow on purpose — this is a working tool, and a long
   theatrical wipe between an audit page and a checklist becomes an obstacle by the
   twentieth navigation of the morning. */
html.aq-page-enter body {
  opacity: 0;
  transform: translate3d(0, 8px, 0);
}
html.aq-page-leave body {
  opacity: 0;
  transform: translate3d(0, -6px, 0);
  transition: opacity 180ms ease, transform 180ms ease;
  pointer-events: none;
}
body {
  transition: opacity 340ms ease, transform 340ms ease;
}

/* The inertial engine drives window.scrollTo itself, so the browser's own smooth
   behaviour must be off or the two fight and the page stutters. */
html.aq-smooth {
  scroll-behavior: auto;
}

/* Reduced motion, and the class the engine sets when it stands down. Both are belt and
   braces: the JS already refuses to animate, and this guarantees nothing is left
   hidden or mid-transform if a class was applied before the preference was read. */
html.aq-motion-off .aq-reveal,
html.aq-motion-off [data-parallax],
html.aq-motion-off .aq-split .aq-w-i,
html.aq-motion-off .hero-logo {
  opacity: 1 !important;
  transform: none !important;
  transition: none !important;
}
html.aq-motion-off .hero-logo-arc { stroke-dashoffset: 0 !important; }

@media (prefers-reduced-motion: reduce) {
  .hero-logo-arc { stroke-dashoffset: 0 !important; transition: none !important; }
  .aq-reveal,
  .aq-reveal.aq-in,
  [data-parallax],
  .aq-split .aq-w-i,
  .hero-logo,
  html.aq-page-enter body,
  html.aq-page-leave body,
  body {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
    animation: none !important;
  }
}

/* Printing an audit or an SOP must never inherit a half-revealed state. */
@media print {
  .aq-reveal,
  [data-parallax],
  .aq-split .aq-w-i,
  .hero-logo,
  body {
    opacity: 1 !important;
    transform: none !important;
  }
}

/* ------------------------------ scrollytelling ------------------------------
   position:sticky does the pinning; no transforms, no wheel interception. Colours are
   tokens throughout and no colour appears inside a media query. */

.scrolly {
  position: relative;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 48px;
  align-items: start;
}

.scrolly-sticky {
  position: sticky;
  /* Cleared by the header height plus breathing room, or the pinned card sits under the
     sticky nav — which has backdrop-filter and therefore paints above it. */
  top: 96px;
  align-self: start;
}

.scrolly-steps > [data-scrolly-step] {
  /* Each step needs roughly a screen of travel to read as its own beat. Less and the
     stages flicker past; more and the section outstays its welcome. */
  min-height: 78vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  opacity: .34;
  transition: opacity 420ms ease;
}
.scrolly-steps > [data-scrolly-step].is-active { opacity: 1; }

/* Stacked fallback: reduced motion and phones get every stage, unpinned and fully
   readable. Nothing is hidden — the content is the point, the pinning is decoration. */
html.scrolly-off .scrolly { grid-template-columns: 1fr; gap: 26px; }
html.scrolly-off .scrolly-sticky { position: static; top: auto; }
html.scrolly-off .scrolly-steps > [data-scrolly-step] {
  min-height: 0;
  opacity: 1;
  transition: none;
}

@media (max-width: 900px) {
  /* Layout only — scrolly.js disables pinning below 760px, and between 760 and 900 a
     two-column split is too cramped for the card and the text to share. */
  .scrolly { grid-template-columns: 1fr; gap: 24px; }
  .scrolly-sticky { position: static; }
  .scrolly-steps > [data-scrolly-step] { min-height: 0; opacity: 1; }
}
