/* Motion layer: header settle, appear animations, text reveal, cursor,
   floating illustrations. The curve tokens (--spring-*, --tween-button)
   live in base.css; main.js documents the data attributes.

   Rule: content stays fully visible without JavaScript. Anything hidden
   here is hidden only under html.js, only when the user has not asked
   for reduced motion, and a CSS failsafe reveals it after 4s if main.js
   never runs (main.js sets html.motion-ready, which disables the
   failsafe). */

/* --------------------------------------------------------------------------
   1. Header per page and its settle animation
   -------------------------------------------------------------------------- */

/* Contact page: the original's page root has overflow hidden, which breaks
   sticky, so the header simply scrolls away with the page. */
.page--contact .site-header { position: relative; }

/* Home: entering the "scroll" variant, the navy bar appears instantly 10px
   low and settles into place: on the header spring (stiffness 200, damping
   30) at desktop width, on the faster "bounce" spring below 1440px.
   Leaving it is instant: the logo row does not move (measured). */
@keyframes header-settle {
  from { transform: translateY(10px); }
  to { transform: none; }
}
.site-header.is-settling .site-header__bar {
  animation: header-settle var(--spring-header-dur) var(--spring-header) both;
}
@media (max-width: 1439.98px) {
  .site-header.is-settling .site-header__bar {
    animation-duration: var(--spring-bounce-dur);
    animation-timing-function: var(--spring-bounce);
  }
}

/* Same-page and cross-page anchors land 50px above the target, like the
   original. main.js reads each target's scroll-margin-top for its smooth
   scroll, so a page can override the offset per target. */
[id] { scroll-margin-top: 50px; }
/* Sections focused by a keyboard-activated anchor link (main.js) */
[data-anchor-focus]:focus { outline: none; }

/* --------------------------------------------------------------------------
   2. Appear animations (data-appear). main.js adds .is-in.
   Hidden states apply only under html.js and without reduced motion.
   "fade" is the original's 300ms tween; "slide-up" uses the appear spring
   (stiffness 200, damping 40), 1.2s.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  html.js [data-appear="fade"]:not(.is-in) { opacity: 0; }
  html.js [data-appear="fade"].is-in {
    transition: opacity var(--appear-duration, var(--tween-button-dur)) var(--tween-button) var(--appear-delay, 0ms);
  }

  /* A slide-up waits at its offset. Once main.js has found its clip box
     (.appear--clipped) it stays VISIBLE there while it waits, and the clip
     box hides whatever falls outside it, exactly like the original (course
     hero, contact "Get Involved", footer email); it also stays available
     to assistive tech and keyboard focus. Until then, or without a clip
     box, it is hidden. Scroll-linked ones (.appear--linked) get their
     offset from main.js every frame. */
  html.js [data-appear="slide-up"]:not(.is-in) {
    transform: translateY(var(--appear-distance, 40px));
  }
  html.js [data-appear="slide-up"]:not(.appear--clipped):not(.appear--linked):not(.is-in) { visibility: hidden; }
  /* Transitions run only on the way in. */
  html.js [data-appear="slide-up"].is-in {
    transition:
      transform var(--appear-duration, var(--spring-appear-dur)) var(--spring-appear) var(--appear-delay, 0ms),
      visibility 0s linear var(--appear-delay, 0ms);
  }

  /* Failsafe: if main.js never runs, show everything after 4s. */
  html.js:not(.motion-ready) [data-appear] {
    animation: appear-failsafe 0s linear 4s forwards;
  }
}
@keyframes appear-failsafe {
  to { opacity: 1; transform: none; visibility: visible; }
}

/* Footer email (tagged by main.js): the text slides inside the link's clip. */
.footer-email__text { display: block; }

/* --------------------------------------------------------------------------
   3. Scroll-linked text reveal (data-reveal). main.js splits the text into
   .reveal-word / .reveal-char spans and sets their colour or opacity.
   -------------------------------------------------------------------------- */
.reveal-word, .reveal-char { display: inline; }

/* --------------------------------------------------------------------------
   4. Floating illustrations (data-float). The original's home domain
   illustrations float on phones only (data-float="phone"):
   translate(1px, -10px), 1s tween, alternate, forever, first rise after
   240ms (measured against the original's loop phase).
   -------------------------------------------------------------------------- */
@keyframes float-y {
  from { transform: translate(0, 0); }
  to { transform: translate(1px, -10px); }
}
@media (prefers-reduced-motion: no-preference) and (max-width: 809.98px) {
  [data-float="phone"] {
    animation: float-y 1s var(--tween-button) 240ms infinite alternate;
  }
}

/* --------------------------------------------------------------------------
   5. Custom cursor ("Circle Cursor"), created by main.js for fine pointers.
   -------------------------------------------------------------------------- */
.cursor {                        /* main.js sets `translate` every frame */
  position: fixed;
  top: 0;
  left: 0;
  z-index: 13;
  width: 40px;
  height: 40px;
  overflow: hidden;
  pointer-events: none;
  mix-blend-mode: difference;
  opacity: 0;
  scale: 0;                       /* the whole box grows from its top-left on the first move */
  transform-origin: 0 0;
  will-change: translate, scale, opacity;
  transition:
    opacity 0.2s ease-out,
    scale var(--spring-appear-dur) var(--spring-appear);
}
.cursor.is-active { scale: 1; }
.cursor.is-visible { opacity: 1; }
.cursor__dot {                   /* shrinks about its centre while the button is down */
  position: absolute;
  top: 7px;
  left: 7px;
  width: 26px;
  height: 26px;
  border: 1px solid rgba(255, 255, 255, 0.15);
  border-radius: 100px;
  background-color: var(--c-white);
  transition: transform var(--spring-appear-dur) var(--spring-appear) 15ms;   /* measured lag after press/release */
}
.cursor.is-down .cursor__dot { transform: scale(0.6923); }   /* 26px -> 18px */
@media (prefers-reduced-motion: reduce) {
  .cursor, .cursor__dot { transition: none; }
}

/* --------------------------------------------------------------------------
   6. Form success state (original "Form Success Message" variant of the
   submit button): transparent background, no fill or arrow, the message
   wraps at 270px. main.js adds .is-success. In a button narrower than the
   message box (270px + 48px padding, e.g. the 20%-wide course Register
   button) the box shrinks to the button and the message wraps inside it,
   so it is never cut off by the button's rounded clip.
   -------------------------------------------------------------------------- */
.btn-dark.is-success {
  height: auto;
  background-color: transparent;
  cursor: auto;
}
.btn-dark.is-success .btn-dark__inner { max-width: 100%; }
.btn-dark.is-success .btn-dark__fill,
.btn-dark.is-success .icon-arrow { display: none; }
.btn-dark.is-success .btn-dark__text,
.btn-dark.is-success:hover .btn-dark__text,
.btn-dark.is-success:focus-visible .btn-dark__text {
  width: 270px;
  max-width: 100%;
  white-space: pre-wrap;
  overflow-wrap: break-word;
  color: var(--c-white);
}

/* --------------------------------------------------------------------------
   7. Print: appear elements that were never scrolled into view would print
   invisible, so print everything in its final state.
   -------------------------------------------------------------------------- */
@media print {
  html.js [data-appear] {
    opacity: 1 !important;
    transform: none !important;
    visibility: visible !important;
  }
  /* Gradient text is painted by its background, which print drops by
     default: print it as solid text in the element's own colour. */
  .text-gradient {
    -webkit-text-fill-color: currentColor !important;
    background: none !important;
  }
}
