/* ==========================================================================
   G&M Training Hub — motion system
   --------------------------------------------------------------------------
   One stylesheet for every animated behaviour on the site, so the same
   vocabulary can be rolled across all pages instead of re-invented per page.

   PRINCIPLES
   1. Only `transform` and `opacity` are animated. Both are composited by the
      GPU, so nothing here forces layout or paint on scroll.
   2. Everything is a progressive enhancement. With JavaScript off, every
      [data-rv] element is visible — the "hidden" state is only applied by
      motion.js once it knows it can reveal it again.
   3. `prefers-reduced-motion: reduce` switches the whole system off at the
      bottom of this file. Content still appears; it just stops moving.
   4. No libraries. This has to run on cPanel static hosting with no build.

   THE SIGNATURE
   The "rising pulse" — a short dash of light that travels UP a hairline.
   It reads as growth rather than data transfer, which is the point of a
   training company, and it is the one motif repeated across the site.
   ========================================================================== */

:root {
  /* Expo-out. Fast departure, long settle — the reason good motion reads as
     "expensive" rather than "bouncy". */
  --ease-out-expo:  cubic-bezier(0.16, 1, 0.30, 1);
  --ease-out-quart: cubic-bezier(0.25, 1, 0.50, 1);
  --ease-in-out:    cubic-bezier(0.65, 0, 0.35, 1);

  --dur-micro: 0.18s;
  --dur-fast:  0.32s;
  --dur-base:  0.62s;
  --dur-slow:  0.9s;

  /* Stagger step between siblings. Kept short: past ~90ms a list starts to
     feel like it is loading rather than arriving. */
  --stagger: 70ms;
}

/* ==========================================================================
   1. SCROLL REVEALS
   [data-rv] marks an element to reveal. [data-rv="up|fade|left|right|scale|
   clip"] picks the movement. [style="--i:3"] delays it by 3 stagger steps.
   `.rv-ready` is set on <html> by motion.js — without it nothing hides, so a
   JS failure can never leave the page blank.
   ========================================================================== */

.rv-ready [data-rv] {
  opacity: 0;
  will-change: transform, opacity;
  transition:
    opacity    var(--dur-base) var(--ease-out-expo),
    transform  var(--dur-base) var(--ease-out-expo),
    clip-path  var(--dur-slow) var(--ease-out-expo);
  transition-delay: calc(var(--i, 0) * var(--stagger));
}

.rv-ready [data-rv="up"]    { transform: translate3d(0, 26px, 0); }
.rv-ready [data-rv="fade"]  { transform: none; }
.rv-ready [data-rv="left"]  { transform: translate3d(-26px, 0, 0); }
.rv-ready [data-rv="right"] { transform: translate3d(26px, 0, 0); }
.rv-ready [data-rv="scale"] { transform: scale(0.965); }
.rv-ready [data-rv="clip"]  { clip-path: inset(0 0 100% 0); transform: none; }

.rv-ready [data-rv].rv-in {
  opacity: 1;
  transform: none;
  clip-path: inset(0 0 0 0);
}

/* Once it has played, drop the compositor hint so we are not holding a layer
   for every element on a long page. */
.rv-ready [data-rv].rv-done { will-change: auto; }

/* --- masked line reveal, for headlines ---------------------------------- */
/* Each line sits in an overflow-hidden box and slides up from below it, so
   the text appears to rise out of the rule rather than fade in place. */
.line-mask { display: block; overflow: hidden; padding-bottom: 0.08em; margin-bottom: -0.08em; }
.line-mask > span {
  display: block;
  transform: translate3d(0, 105%, 0);
  transition: transform var(--dur-slow) var(--ease-out-expo);
  transition-delay: calc(var(--i, 0) * 90ms);
}
html:not(.rv-ready) .line-mask > span { transform: none; }
.line-mask.rv-in > span { transform: none; }
/* Released by motion.js after the slide finishes, so a later resize cannot
   clip a line that re-wraps. */
.line-mask.mask-open { overflow: visible; }

/* --- hairline rules that draw in ---------------------------------------- */
.rule-draw {
  transform-origin: left center;
  transform: scaleX(0);
  transition: transform var(--dur-slow) var(--ease-out-expo);
}
html:not(.rv-ready) .rule-draw { transform: none; }
.rule-draw.rv-in { transform: scaleX(1); }

/* ==========================================================================
   2. THE RISING PULSE
   An SVG line with `pathLength="1"` gets a dash pattern of one short lit
   segment and one long gap. Sliding the dash offset walks that lit segment
   along the line. Because pathLength is normalised, the same numbers work on
   a line of any real length.
   ========================================================================== */

.pulse-line {
  stroke-dasharray: 0.14 1;
  stroke-dashoffset: 0.14;
  animation: pulse-rise var(--pulse-dur, 7s) linear infinite;
  animation-delay: var(--pulse-delay, 0s);
}
@keyframes pulse-rise {
  from { stroke-dashoffset:  0.14; }
  to   { stroke-dashoffset: -1;    }
}

/* Node dots on the field, breathing out of phase with each other. */
.node-dot {
  animation: node-breathe var(--node-dur, 4.5s) var(--ease-in-out) infinite;
  animation-delay: var(--node-delay, 0s);
  transform-box: fill-box;
  transform-origin: center;
}
@keyframes node-breathe {
  0%, 100% { opacity: 0.25; transform: scale(1); }
  50%      { opacity: 0.9;  transform: scale(1.5); }
}

/* A single live indicator, e.g. beside the next intake date. */
.live-dot {
  display: inline-block; width: 7px; height: 7px; border-radius: 50%;
  background: currentColor; flex: none;
  animation: live-blink 2.4s var(--ease-in-out) infinite;
}
@keyframes live-blink { 0%, 100% { opacity: 1; } 50% { opacity: 0.2; } }

/* ==========================================================================
   3. PARALLAX
   Native scroll-driven animation where the browser supports it (~85% of
   traffic in 2026), which keeps it off the main thread entirely. Browsers
   without it simply get a static image, which is a perfectly good result.
   ========================================================================== */

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .par-slow {
      animation: par-drift linear both;
      animation-timeline: view();
      animation-range: cover 0% cover 100%;
    }
    @keyframes par-drift {
      from { transform: translate3d(0, -6%, 0) scale(1.12); }
      to   { transform: translate3d(0,  6%, 0) scale(1.12); }
    }

    /* Section backdrops drift a shorter distance than their foreground. */
    .par-bg {
      animation: par-bg-drift linear both;
      animation-timeline: view();
      animation-range: cover 0% cover 100%;
    }
    @keyframes par-bg-drift {
      from { transform: translate3d(0, -3%, 0); }
      to   { transform: translate3d(0,  3%, 0); }
    }
  }
}

/* ==========================================================================
   4. MICRO-INTERACTIONS
   ========================================================================== */

/* --- arrows that lean into the direction of travel ---------------------- */
.mi-arrow { transition: transform var(--dur-fast) var(--ease-out-expo); }
.mi:hover .mi-arrow,
.mi:focus-visible .mi-arrow { transform: translateX(4px); }

/* --- buttons: a wash that wipes across on hover ------------------------- */
.mi { position: relative; overflow: hidden; isolation: isolate; }
.mi::before {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background: currentColor; opacity: 0;
  transform: translate3d(-101%, 0, 0);
  transition: transform var(--dur-fast) var(--ease-out-expo), opacity var(--dur-fast) linear;
}
.mi:hover::before, .mi:focus-visible::before { opacity: 0.09; transform: translate3d(0, 0, 0); }

/* --- cards: lift, and a soft light that follows the cursor -------------- */
/* --mx/--my are written by motion.js on pointermove. */
.mi-card {
  position: relative; isolation: isolate;
  transition: transform var(--dur-fast) var(--ease-out-expo),
              border-color var(--dur-fast) linear,
              box-shadow var(--dur-fast) var(--ease-out-expo);
}
.mi-card::after {
  content: ""; position: absolute; inset: 0; z-index: -1; opacity: 0;
  border-radius: inherit; pointer-events: none;
  background: radial-gradient(
    380px circle at var(--mx, 50%) var(--my, 50%),
    rgba(14, 123, 212, 0.10), transparent 62%);
  transition: opacity var(--dur-fast) linear;
}
.mi-card:hover { transform: translate3d(0, -3px, 0); }
.mi-card:hover::after { opacity: 1; }

/* --- text links: an underline that draws from the left ------------------ */
.mi-link { position: relative; text-decoration: none; }
.mi-link::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: -2px; height: 1.5px;
  background: currentColor; transform: scaleX(0); transform-origin: left center;
  transition: transform var(--dur-fast) var(--ease-out-expo);
}
.mi-link:hover::after, .mi-link:focus-visible::after { transform: scaleX(1); }

/* ==========================================================================
   5. CHROME — scroll progress and the auto-hiding nav
   ========================================================================== */

.scroll-progress {
  position: fixed; top: 0; left: 0; right: 0; height: 2px; z-index: 100;
  transform-origin: left center; transform: scaleX(0);
  background: linear-gradient(90deg, var(--accent, #0E7BD4), #6FBBF2);
  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); } }
  }
}

/* Nav slides away as you read down and comes back the moment you scroll up,
   which keeps the CTA one gesture away without ever covering content. */
nav#nav {
  transition: transform var(--dur-fast) var(--ease-out-expo),
              background-color var(--dur-fast) linear,
              box-shadow var(--dur-fast) linear;
}
nav#nav.nav-hidden { transform: translate3d(0, -102%, 0); }
nav#nav.nav-solid  { box-shadow: 0 10px 30px rgba(4, 18, 38, 0.28); }

/* ==========================================================================
   6. HORIZONTAL DRAG RAIL
   Cards on a rail read as a collection you move through rather than a grid
   you scan. Native scroll-snap does the work; motion.js only adds
   click-and-drag for mouse users.
   ========================================================================== */

.rail {
  display: flex; gap: 0; overflow-x: auto; overscroll-behavior-x: contain;
  scroll-snap-type: x proximity; scrollbar-width: none;
  cursor: grab; -webkit-overflow-scrolling: touch;
}
.rail::-webkit-scrollbar { display: none; }
.rail.is-dragging { cursor: grabbing; scroll-snap-type: none; }
.rail.is-dragging * { pointer-events: none; user-select: none; }
.rail > * { scroll-snap-align: start; flex: none; }

.rail-progress { height: 2px; background: rgba(11, 27, 51, 0.10); border-radius: 2px; overflow: hidden; }
.rail-progress i {
  display: block; height: 100%; width: var(--rail-w, 30%);
  background: var(--accent, #0E7BD4); border-radius: 2px;
  transform: translateX(var(--rail-x, 0%));
  transition: transform 90ms linear;
}

/* An affordance label, because an interactive element nobody notices is a
   decoration. Borrowed thinking, not borrowed design. */
.affordance {
  display: inline-flex; align-items: center; gap: 8px;
  font-size: 12px; font-weight: 600; letter-spacing: 0.1em; text-transform: uppercase;
}
.affordance svg { animation: nudge 2.6s var(--ease-in-out) infinite; }
@keyframes nudge {
  0%, 100% { transform: translateX(0); }
  50%      { transform: translateX(5px); }
}

/* ==========================================================================
   7. MARQUEE
   The track is duplicated in the markup, so translating by -50% loops
   seamlessly with no JS and no reflow.
   ========================================================================== */

.marquee { overflow: hidden; -webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
           mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent); }
.marquee-track { display: flex; width: max-content; animation: marquee-run var(--marquee-dur, 48s) linear infinite; }
.marquee:hover .marquee-track { animation-play-state: paused; }
@keyframes marquee-run { to { transform: translate3d(-50%, 0, 0); } }

/* ==========================================================================
   8. COUNTERS
   motion.js writes the value; tabular figures stop the number jittering as
   digits change width mid-count.
   ========================================================================== */
.count { font-variant-numeric: tabular-nums; }

/* ==========================================================================
   9. REDUCED MOTION
   Everything above collapses to an instant, static result. Nothing is hidden
   and nothing moves. This block is last so it wins.
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  .rv-ready [data-rv],
  .rv-ready [data-rv].rv-in {
    opacity: 1 !important; transform: none !important;
    clip-path: none !important; transition: none !important;
  }
  .line-mask > span, .rule-draw { transform: none !important; transition: none !important; }
  .pulse-line, .node-dot, .live-dot, .marquee-track, .affordance svg { animation: none !important; }
  .pulse-line { stroke-dasharray: none; stroke-dashoffset: 0; opacity: 0.5; }
  .node-dot { opacity: 0.55; }
  .mi-card:hover { transform: none; }
  nav#nav.nav-hidden { transform: none; }
  .scroll-progress { display: none; }
  .rail { scroll-behavior: auto; }
}
