/* ============================================================================
   FINNECT CAPITAL — SCROLL MOTION LAYER
   Adds AOS-style scroll reveals, staggered card entrances, count-up stats and
   light parallax to the EXISTING light marketing design. No repaint: colours,
   layout and type are untouched.

   FAIL-SAFE CONTRACT — read before editing:
   Every hidden state below is gated behind `html.fm-on`, and that class is
   added by finnect-motion.js only after it has decided to run. So if the JS
   fails to load, is blocked, or the visitor prefers reduced motion, NOTHING is
   ever hidden and the page renders exactly as it does today. Never move these
   rules out from under `.fm-on`.

   This layer is deliberately separate from the site's existing `.reveal`
   system (finone.css:61 + the IntersectionObserver at common.js:29). That
   observer collects its elements once at load, so adding `.reveal` to anything
   afterwards would leave it permanently invisible. We use our own `[data-fm]`
   contract instead and skip any element already carrying `.reveal`.
   ========================================================================== */

/* No `will-change` here on purpose. This rule matches every tagged element on
   the page at once — on a content-heavy page that was 50+ permanently promoted
   compositor layers, all waiting to play a single one-shot transition. The
   memory cost outweighs any gain; browsers composite a plain opacity/transform
   transition perfectly well without the hint. */
html.fm-on [data-fm] {
  opacity: 0;
  transition:
    opacity   .78s cubic-bezier(.22, 1, .36, 1) var(--fm-d, 0ms),
    transform .78s cubic-bezier(.22, 1, .36, 1) var(--fm-d, 0ms),
    clip-path .9s  cubic-bezier(.22, 1, .36, 1) var(--fm-d, 0ms);
}

html.fm-on [data-fm="up"]    { transform: translate3d(0, 26px, 0); }
html.fm-on [data-fm="down"]  { transform: translate3d(0, -22px, 0); }
html.fm-on [data-fm="left"]  { transform: translate3d(-34px, 0, 0); }
html.fm-on [data-fm="right"] { transform: translate3d(34px, 0, 0); }
html.fm-on [data-fm="scale"] { transform: scale(.94); }
html.fm-on [data-fm="wipe"]  { clip-path: inset(0 0 100% 0); transform: scale(1.03); }

/* Revealed state — one rule wins for every variant. */
html.fm-on [data-fm].fm-in {
  opacity: 1;
  transform: none;
  clip-path: inset(0 0 0 0);
}

/* Once a reveal has played, drop the compositor hint so long pages don't hold
   dozens of promoted layers. */
html.fm-on [data-fm].fm-done { will-change: auto; }

/* ---- hero entrance -------------------------------------------------------
   The homepage intro overlay sets html.intro-lock while it plays; the JS holds
   the hero entrance until that clears so the two never overlap. */
html.fm-on [data-fm-hero] {
  opacity: 0;
  transform: translate3d(0, 20px, 0);
  transition: opacity .9s cubic-bezier(.22, 1, .36, 1) var(--fm-d, 0ms),
              transform .9s cubic-bezier(.22, 1, .36, 1) var(--fm-d, 0ms);
}
html.fm-on [data-fm-hero].fm-in { opacity: 1; transform: none; }

/* ---- word-split reveal (Splitting.js-style, hand-rolled) ----------------
   Safety net: if a split ever lands inside a flex/grid container, let it wrap
   rather than overflowing the screen. The JS also refuses to split those. */
:has(> .fm-word) { flex-wrap: wrap; }

.fm-word {
  display: inline-block;
  overflow: hidden;
  vertical-align: top;
}
.fm-word > i {
  display: inline-block;
  font-style: inherit;
  transform: translate3d(0, 105%, 0);
  transition: transform .82s cubic-bezier(.22, 1, .36, 1) var(--fm-d, 0ms);
}
.fm-in .fm-word > i,
.fm-word.fm-in > i { transform: none; }

/* ---- parallax ------------------------------------------------------------
   Only transform is touched, so these stay off the paint path. */
[data-fm-par] { will-change: transform; }

/* ---- continuous scroll drift --------------------------------------------
   Once a reveal has finished, the element keeps drifting as you scroll. The
   offset arrives as --fm-dy so the JS never writes `transform` directly and
   therefore can't clobber the reveal. Critically, .fm-done also STOPS
   transitioning transform — otherwise every scroll frame would start a fresh
   .78s tween and the drift would lag behind the page. */
html.fm-on [data-fm].fm-done {
  transition: opacity .4s linear;
  transform: translate3d(0, var(--fm-dy, 0px), 0);
}

/* Drift is armed by JS only after whichever reveal owns the element has
   finished. Declared AFTER .fm-done (equal specificity, so source order
   decides) and it out-specifies the site's own `.reveal.in{transform:none}`,
   which would otherwise pin these elements in place. */
html.fm-on [data-fm-drift].fm-drift-on {
  transform: translate3d(0, var(--fm-dy, 0px), 0);
  transition: opacity .4s linear;
  will-change: transform;
}


/* ---- ambient "surroundings" ---------------------------------------------
   Slow-drifting teal washes and outlined rings behind each section, so the
   page feels alive even when nothing is being revealed. Purely decorative and
   inert: aria-hidden, pointer-events:none, and always behind .wrap. */
.fm-host { position: relative; }
.fm-amb {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  z-index: 0;
  /* Drifts as a unit with the pointer — set by JS. */
  transform: translate3d(var(--fm-mx, 0px), var(--fm-my, 0px), 0);
  transition: transform 1.4s cubic-bezier(.22, 1, .36, 1);
}
.fm-host > .wrap,
.fm-host > .sec-head { position: relative; z-index: 1; }

/* Soft diffuse washes only — no rings, no visible circular outlines. These are
   heavily blurred radial gradients at ~8% alpha, so they read as a faint shift
   in the background tone rather than as shapes. */
/* PERFORMANCE — do not reintroduce `filter: blur()` here.
   These are ~660x660px (440k pixels) and animate on an infinite loop. Blurring
   them forced the compositor to re-rasterise that entire surface every frame,
   which was the single biggest cause of scroll jank on the site. A radial
   gradient with a soft stop is already blurry — the filter bought nothing and
   cost everything. `will-change` is also deliberately absent: promoting four
   large layers permanently is not worth it for a 26s drift. */
.fm-blob {
  position: absolute;
  display: block;
  border-radius: 50%;
  background: radial-gradient(closest-side,
              rgba(14, 140, 116, .085),
              rgba(14, 140, 116, .045) 42%,
              rgba(14, 140, 116, 0) 78%);
}

/* Dark band sections need the accent lifted, not darkened. */
.section.navy .fm-blob { background: radial-gradient(closest-side, rgba(95, 217, 190, .12), rgba(95, 217, 190, 0) 74%); }

.fm-a1 { width: 46vmax; height: 46vmax; left: -14vmax; top: -18vmax; animation: fmDrift1 26s ease-in-out infinite; }
.fm-a2 { width: 34vmax; height: 34vmax; right: -12vmax; bottom: -16vmax; animation: fmDrift2 34s ease-in-out infinite; }

/* Mirrored placement on alternate sections so the rhythm doesn't repeat. */
.fm-amb.fm-flip { transform: translate3d(var(--fm-mx, 0px), var(--fm-my, 0px), 0) scaleX(-1); }

/* Translate only — no scale. Scaling a large painted surface re-rasterises it;
   translation is a pure compositor transform and costs essentially nothing. */
@keyframes fmDrift1 {
  0%, 100% { transform: translate3d(0, 0, 0); }
  50%      { transform: translate3d(4vmax, 3vmax, 0); }
}
@keyframes fmDrift2 {
  0%, 100% { transform: translate3d(0, 0, 0); }
  50%      { transform: translate3d(-5vmax, -3vmax, 0); }
}

/* ---- counters ------------------------------------------------------------
   Tabular figures stop the stat strip juddering as digits tick over. */
.fm-count { font-variant-numeric: tabular-nums; font-feature-settings: "tnum" 1; }

/* ---- accent rule that draws itself in ---------------------------------- */
html.fm-on [data-fm-rule] {
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform .8s cubic-bezier(.22, 1, .36, 1) var(--fm-d, 0ms);
}
/* Either the rule itself was observed, or a revealing ancestor carries it. */
html.fm-on [data-fm-rule].fm-in,
html.fm-on .fm-in [data-fm-rule] { transform: scaleX(1); }

/* Belt-and-braces: finone.css:814 already neutralises .reveal under this
   query, and the JS bails before adding .fm-on — but if fm-on is ever set by
   something else, nothing may stay hidden. */
@media (prefers-reduced-motion: reduce) {
  html.fm-on [data-fm],
  html.fm-on [data-fm-hero],
  html.fm-on [data-fm-rule],
  html.fm-on [data-fm].fm-done,
  html.fm-on [data-fm-drift].fm-drift-on {
    opacity: 1 !important;
    transform: none !important;
    clip-path: none !important;
    transition: none !important;
  }
  .fm-word > i { transform: none !important; transition: none !important; }
  .fm-amb { display: none !important; }
}
