/* ==========================================================================
   Mind Motion Investments — motion

   Three things live here: the opening animation, the scroll reveals, and the
   interaction polish. All of it is enhancement. Nothing in this file is
   required for the page to be readable or usable, which is why every rule is
   gated behind an attribute that only JavaScript sets, and why the whole file
   is neutralised under prefers-reduced-motion.

   The brand is restrained, so the motion is too: short durations, small
   distances, and easing that decelerates rather than bounces. The one
   flourish is the opening, and it is built from the logo's own geometry
   rather than laid on top of it.
   ========================================================================== */

:root {
  /* Decelerating, no overshoot. The house curve for anything arriving. */
  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
  /* Symmetrical, for something that both leaves and arrives. */
  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);

  --reveal-distance: 12px;
  --reveal-duration: 450ms;
  --reveal-stagger: 60ms;
}

/* ==========================================================================
   Opening animation
   ========================================================================== */

/*
   The curtain is absent from the document until the head script decides it
   should play, so a visitor without JavaScript, or one who has already seen it
   this session, never has it in their way.
*/
.intro {
  display: none;
}

html[data-intro='pending'] .intro,
html[data-intro='playing'] .intro {
  display: block;
  position: fixed;
  inset: 0;
  /* Above the sticky masthead, below nothing else. */
  z-index: 200;
  /* Never intercepts a click: the page underneath stays operable throughout,
     and a test or a person can act on it before the animation ends. */
  pointer-events: none;
}

.intro__curtain {
  position: absolute;
  inset: 0;
  background: var(--surface-inverse);
}

/* Holds the lockup on the optical centre, above the curtain. */
.intro__stage {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
}

/*
   Three nested boxes, one transform each.
   A single element cannot run two transform animations at once, so the
   closing move (land) and the opening move (settle) get a box apiece, and the
   innermost box is the lockup itself.
*/
.intro__land,
.intro__settle {
  display: block;
}

.intro__lockup {
  position: relative;
  display: block;
  width: min(86vw, 560px);
  aspect-ratio: var(--lockup-ratio);
}

/*
   Per-language geometry, read straight off the brand kit's SVGs.

   These live on the overlay root, not on the lockup, because custom
   properties inherit downwards only: the zoom and shift are consumed by
   .intro__settle, which is the lockup's *parent*.

   Each lockup is one artwork containing the Stride symbol and the name. The
   values below say where the symbol sits inside that artwork, as fractions of
   the whole, so the animated symbol can be laid exactly over its printed
   counterpart and cross-faded with it invisibly.

     English  viewBox 0 -1.75 1419.95 103.50, symbol at translate(0,0)
     Arabic   viewBox 0 -1.75 1156.10 129.64, symbol at translate(1048.1,0)

   The symbol is 108 x 100 units in both.
*/
.intro--en {
  --lockup-ratio: 1419.95 / 103.5;

  --symbol-left: 0%;
  --symbol-top: 1.6908%;      /* 1.75 / 103.5   */
  --symbol-width: 7.6059%;    /* 108 / 1419.95  */
  --symbol-height: 96.6184%;  /* 100 / 103.5    */

  /* Zoom for the first phase, and the shift that brings the symbol's centre
     to the centre of the screen while it is zoomed.
     shift = (0.5 - symbolCentreFraction) x zoom x 100% */
  --zoom: 2.4;
  --shift-x: 110.87%;         /* (0.5 - 0.03803) x 2.4  */
  --shift-y: 0%;              /* the symbol is already on the optical centre */
}

.intro--ar {
  --lockup-ratio: 1156.1 / 129.64;

  --symbol-left: 90.6582%;    /* 1048.1 / 1156.10 */
  --symbol-top: 1.3499%;      /* 1.75 / 129.64    */
  --symbol-width: 9.3418%;    /* 108 / 1156.10    */
  --symbol-height: 77.1367%;  /* 100 / 129.64     */

  --zoom: 1.95;
  --shift-x: -88.39%;         /* (0.5 - 0.95329) x 1.95 */
  --shift-y: 19.52%;          /* (0.5 - 0.39988) x 1.95 */
}

/* Narrow screens: the lockup is smaller, so the symbol needs more zoom to
   hold the screen on its own. */
@media (max-width: 40rem) {
  .intro--en { --zoom: 3.2; --shift-x: 147.83%; }
  .intro--ar { --zoom: 2.6;  --shift-x: -117.86%; --shift-y: 26.03%; }
}

/* The printed lockup: the real artwork, faded in once the strokes have drawn. */
.intro__full {
  display: block;
  width: 100%;
  height: auto;
  opacity: 0;
}

/* The animated symbol, laid exactly over the printed one. Physical offsets,
   not logical: the Arabic artwork already places the symbol on its right, and
   mirroring it again would move it off the mark. */
.intro__symbol {
  position: absolute;
  left: var(--symbol-left);
  top: var(--symbol-top);
  width: var(--symbol-width);
  height: var(--symbol-height);
  overflow: hidden;
}

.intro__stroke {
  fill: var(--ink-inverse);
  /* Percentages resolve against the path's own bounding box, so 115% clears
     it completely whichever stroke it is. */
  transform-box: fill-box;
  transform-origin: 50% 100%;
  transform: translateY(115%);
}

/* While pending, the curtain is up and opaque but nothing inside it has
   moved. This is the frame the script measures. */
html[data-intro='pending'] .intro__full {
  opacity: 0;
}

/* ----------------------------------------------------------------------
   The sequence, 2.4s end to end.

        0ms  ┬ upright stroke wipes up
      160ms  ┆ slanted stroke follows, a beat behind
      840ms  ┆ lockup zooms out from the symbol to its resting size
      940ms  ┆ printed lockup fades in, drawn symbol steps aside
     1280ms  ┆ the whole lockup is on screen, centred
     1360ms  ┆ it flies onto the header's logo
     1820ms  ┆ it arrives, and holds
     1880ms  ┆ the curtain starts to rise
     1960ms  ┆ the ivory lockup dissolves, still over navy
     2400ms  ┴ the curtain is gone

   The one ordering constraint worth stating: the ivory lockup has to be gone
   before the curtain's lower edge clears the masthead, or it ghosts across
   the paper-coloured header it is handing off to. With this easing that edge
   passes the masthead at about 66% of the lift, near 2224ms, so the dissolve
   finishing at 2100ms leaves room to spare at any viewport height.
   ---------------------------------------------------------------------- */

html[data-intro='playing'] .intro__stroke--a {
  animation: introWipe 680ms var(--ease-out) both;
}

html[data-intro='playing'] .intro__stroke--b {
  /* The slanted stroke follows the upright one, a beat behind. */
  animation: introWipe 680ms var(--ease-out) 160ms both;
}

html[data-intro='playing'] .intro__settle {
  animation: introSettle 440ms var(--ease-out) 840ms both;
}

html[data-intro='playing'] .intro__full {
  animation: introReveal 340ms ease 940ms both;
}

html[data-intro='playing'] .intro__symbol {
  animation: introHandover 280ms ease 1000ms both;
}

html[data-intro='playing'] .intro__land {
  animation: introLand 460ms var(--ease-in-out) 1360ms both;
}

/* Timed against the curtain, not against the landing. See the note above. */
html[data-intro='playing'] .intro__stage {
  animation: introHandover 140ms ease 1960ms both;
}

html[data-intro='playing'] .intro__curtain {
  animation: introLift 520ms cubic-bezier(0.7, 0, 0.2, 1) 1880ms both;
}

@keyframes introWipe {
  from { transform: translateY(115%); }
  to   { transform: translateY(0); }
}

/* Zoomed and centred on the symbol, out to the lockup at rest. */
@keyframes introSettle {
  from { transform: translate(var(--shift-x), var(--shift-y)) scale(var(--zoom)); }
  to   { transform: none; }
}

@keyframes introReveal {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* The drawn symbol steps aside for the printed one. They occupy the same
   pixels, so the swap is invisible. */
@keyframes introHandover {
  from { opacity: 1; }
  to   { opacity: 0; }
}

/* First-last-invert onto the header logo. The fallbacks matter: if the
   measurement never ran, the lockup shrinks on the spot rather than jumping
   somewhere arbitrary. */
@keyframes introLand {
  from {
    transform: none;
  }
  to {
    transform:
      translate(var(--intro-land-x, 0px), var(--intro-land-y, -38vh))
      scale(var(--intro-land-scale, 0.22));
  }
}

@keyframes introLift {
  from { transform: translateY(0); }
  to   { transform: translateY(-100%); }
}

/* ==========================================================================
   Scroll reveals

   Armed by the head script before first paint, so nothing flashes. Only ever
   applied inside <main>: the masthead and footer are chrome, and hiding them
   until scroll would be worse than leaving them alone.
   ========================================================================== */

html[data-motion='on'] main [data-reveal] {
  opacity: 0;
  transform: translateY(var(--reveal-distance));
  transition:
    opacity var(--reveal-duration) ease,
    transform var(--reveal-duration) var(--ease-out);
  /* Hints the compositor without leaving layers behind: cleared below once
     the element has arrived. */
  will-change: opacity, transform;
}

html[data-motion='on'] main [data-reveal][data-revealed] {
  opacity: 1;
  transform: none;
  will-change: auto;
}

/*
   Staggering.

   The delay comes from nth-child rather than a custom property written per
   element, because a per-element value would mean a style attribute in the
   markup, which the Content-Security-Policy forbids. Eight steps is more
   than the largest group on the page, the six investment services, needs.
*/
html[data-motion='on'] [data-reveal-stagger] > :nth-child(1) { transition-delay: 0ms; }
html[data-motion='on'] [data-reveal-stagger] > :nth-child(2) { transition-delay: 60ms; }
html[data-motion='on'] [data-reveal-stagger] > :nth-child(3) { transition-delay: 120ms; }
html[data-motion='on'] [data-reveal-stagger] > :nth-child(4) { transition-delay: 180ms; }
html[data-motion='on'] [data-reveal-stagger] > :nth-child(5) { transition-delay: 240ms; }
html[data-motion='on'] [data-reveal-stagger] > :nth-child(6) { transition-delay: 300ms; }
html[data-motion='on'] [data-reveal-stagger] > :nth-child(7) { transition-delay: 360ms; }
html[data-motion='on'] [data-reveal-stagger] > :nth-child(8) { transition-delay: 420ms; }

/* ==========================================================================
   Interaction polish

   Hover and focus states that move. These are plain transitions on elements
   that are already visible, so they need no JavaScript gate and work with the
   script blocked.
   ========================================================================== */

/* ---- the arrow that means "onward" ---- */

.arrow-link svg {
  transition: translate 200ms var(--ease-out);
}

.arrow-link:hover svg,
.arrow-link:focus-visible svg {
  translate: 4px 0;
}

/* The arrow is mirrored in RTL, so "onward" is the other way. */
[dir='rtl'] .arrow-link:hover svg,
[dir='rtl'] .arrow-link:focus-visible svg {
  translate: -4px 0;
}

/* ---- navigation underline ---- */

/*
   The underline is a pseudo-element rather than the border, so it can wipe in
   from the leading edge instead of appearing all at once. The border stays as
   a transparent spacer, which keeps the link's height identical in both
   states and stops the row shifting on hover.
*/
.nav__link {
  position: relative;
}

.nav__link::after {
  content: '';
  position: absolute;
  inset-inline: 0;
  bottom: -2px;
  height: var(--stroke-rule);
  background: var(--brand-700);
  transform-origin: left;
  scale: 0 1;
  transition: scale 220ms var(--ease-out);
}

[dir='rtl'] .nav__link::after {
  transform-origin: right;
}

.nav__link:hover::after,
.nav__link:focus-visible::after,
.nav__link[aria-current='page']::after {
  scale: 1 1;
}

/* In the collapsed menu each link is a row with a real divider, and an
   underline sliding under it would just read as noise. */
@media (max-width: 59.99rem) {
  .nav__link::after {
    display: none;
  }
}

/* ---- industries and their services ---- */

.sector,
.sector-jump__link {
  transition: border-color 250ms ease;
}

.sector:hover {
  border-inline-color: var(--border-strong);
  border-block-end-color: var(--border-strong);
}

.marker {
  transition: scale 250ms var(--ease-out);
}

.sector:hover .sector__title .marker,
.sector-jump__link:hover .marker,
.hero-index__link:hover .marker {
  scale: 1.3;
}

/* The service being pointed at, or tabbed to, takes the brand colour, so it
   is plain which one its Request link belongs to. */
.offer__title {
  transition: color 180ms ease;
}

.offer:hover .offer__title,
.offer:focus-within .offer__title {
  color: var(--brand-700);
}

/* ---- controls ---- */

.button {
  transition:
    background-color 180ms ease,
    color 180ms ease,
    transform 120ms ease;
}

.button:active {
  /* A press that answers. Two pixels is enough to feel it. */
  transform: translateY(1px);
}

.field__control {
  transition:
    border-color 150ms ease,
    background-color 150ms ease;
}

.field__control:hover {
  border-color: var(--ink-600);
}

.hero-index__link {
  transition: background-color 180ms ease;
}

.footer-col a {
  transition: color 180ms ease;
}

.service__icon {
  transition: color 250ms ease;
}

.service:hover .service__icon {
  color: var(--accent-600);
}

/* ==========================================================================
   Reduced motion

   The head script never arms any of the above when the preference is set, so
   these rules are the belt to that braces: even if the attributes were
   somehow present, nothing would move and nothing would be hidden.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  .intro {
    display: none !important;
  }

  html[data-motion='on'] main [data-reveal] {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }

  .arrow-link svg,
  .nav__link::after,
  .sector,
  .sector-jump__link,
  .offer__title,
  .marker,
  .button,
  .field__control,
  .service__icon {
    transition: none !important;
  }

  .nav__link::after {
    scale: 1 1;
  }

  .nav__link:not(:hover):not(:focus-visible):not([aria-current='page'])::after {
    opacity: 0;
  }
}
