/* Nextstep4u — motion layer, ported from Animate UI (animate-ui.com).
 *
 * Animate UI ships React components built on Tailwind and Motion. This site is
 * fifty hand-written HTML pages with no build step, so none of that could be
 * installed. What was worth having was never the React: it was the motion
 * vocabulary -- a highlight that slides between items instead of blinking, a
 * number that rolls its digits instead of swapping them, text that arrives a
 * character at a time, a button that answers a press where the finger landed.
 * Those are transforms and keyframes, and they port.
 *
 * Everything here is opt-in through a data attribute, so no existing markup
 * changes behaviour until it asks to. Nothing animates a property that forces
 * layout: transform and opacity only, with two deliberate exceptions that are
 * marked where they occur.
 *
 * Colours: not one literal in this file. Every colour is an app.css token or
 * currentColor, which is both the palette rule and the reason a themed section
 * gets a matching highlight for free.
 *
 * `.am` is set on <html> by motion.js. Rules that hide something until it is
 * revealed hang off it, so a visitor with JS blocked sees the page, not gaps.
 *
 * The whole layer collapses under prefers-reduced-motion at the bottom.
 */

:root{
  /* Animate UI's springs, as the nearest honest bezier. A CSS transition
     cannot overshoot and settle the way a real spring does, so the two that
     matter are approximated and the rest fall back to the house --ease.
     --am-spring is the standard one; --am-pop overshoots harder and is for
     things a finger just touched. */
  --am-spring:cubic-bezier(.34,1.26,.64,1);
  --am-pop:cubic-bezier(.22,1.4,.36,1);
  --am-glide:cubic-bezier(.32,.72,0,1);

  --am-dur-fast:160ms;
  --am-dur:260ms;
  --am-dur-slow:420ms;
}

/* ── motion highlight ───────────────────────────────────────────────────────
   The signature Animate UI behaviour: one shared background that travels to
   whatever you are pointing at, rather than each item lighting its own. It
   reads as a single object moving, which is the whole trick -- two items
   cross-fading reads as two items.

   The pill is one absolutely-positioned child; motion.js measures the target
   and writes its box into custom properties. Transitioning one element is
   cheaper than touching fifty backgrounds, and it is what makes the travel
   continuous instead of stepped.

   The pill sits at z-index:-1 under `isolation:isolate` rather than pushing
   the items up to z-index:1. That matters on the header: the exam dropdown is
   a child of the nav and its panel sits at z-index:50 to clear the links
   beside it. Giving that child a z-index of its own would make it a stacking
   context, cap the panel at the parent's level, and let the links to its
   right paint over the open menu. Isolating the container and sinking only
   the pill leaves every existing z-index on this site exactly where it was. */
[data-am-highlight]{position:relative;isolation:isolate;}

.am-highlight{
  position:absolute;z-index:-1;
  top:0;left:0;
  width:var(--am-hl-w,0px);height:var(--am-hl-h,0px);
  transform:translate3d(var(--am-hl-x,0px),var(--am-hl-y,0px),0);
  border-radius:var(--am-hl-r,var(--r-sm));
  background:var(--chip);
  opacity:0;
  pointer-events:none;
  /* width/height are animated here on purpose. The alternative is a fixed box
     scaled on both axes, which distorts the corner radius as it travels -- a
     pill that squashes while moving between a narrow tab and a wide one. The
     element is a single bare div with no children and no text, so the layout
     it invalidates is its own. */
  transition:
    transform var(--am-dur) var(--am-glide),
    width var(--am-dur) var(--am-glide),
    height var(--am-dur) var(--am-glide),
    border-radius var(--am-dur) var(--am-glide),
    opacity var(--am-dur-fast) var(--ease);
}
.am-highlight.am-on{opacity:1;}

/* Before the first move there is nothing to travel from, so the pill fades in
   where it lands rather than flying in from the corner. */
.am-highlight.am-first{transition:opacity var(--am-dur-fast) var(--ease);}

/* Variants, so a highlight can read as a tab underline or a solid selection
   without a second mechanism. */
[data-am-highlight-style="line"] .am-highlight{
  background:var(--blue);
  height:2px;
  top:auto;bottom:0;
  border-radius:var(--r-sm);
}
[data-am-highlight-style="solid"] .am-highlight{background:var(--blue);}
[data-am-highlight-style="outline"] .am-highlight{
  background:transparent;
  box-shadow:inset 0 0 0 1px var(--line-3);
}
[data-am-highlight-style="amber"] .am-highlight{background:var(--amber-bg);}

/* ── sliding number ─────────────────────────────────────────────────────────
   An odometer. Each digit is a column of 0-9 that slides to show the one it
   should be, so 199 -> 200 rolls three wheels instead of blanking and
   reprinting. tabular-nums keeps the column from twitching as the digits
   change width, which is the same reason the rest of the site uses it. */
[data-am-number]{
  display:inline-flex;align-items:baseline;
  font-variant-numeric:tabular-nums;
  line-height:1;
}
.am-digit{
  display:inline-block;overflow:hidden;
  height:1em;
  vertical-align:bottom;
}
.am-digit-col{
  display:flex;flex-direction:column;
  transform:translateY(calc(var(--am-d,0) * -1em));
  transition:transform var(--am-dur-slow) var(--am-spring);
  transition-delay:var(--am-d-delay,0ms);
}
.am-digit-col > span{height:1em;display:block;}
/* Separators and the decimal point do not roll -- a comma has nowhere to go. */
.am-static{display:inline-block;}

/* ── text effects ───────────────────────────────────────────────────────────
   Splitting text is the one that earns its place on a landing page: the line
   assembles rather than appearing, and the blur is what stops it looking like
   fifty separate letters animating. */
[data-am-text] .am-char,
[data-am-text] .am-word{display:inline-block;white-space:pre;}

.am [data-am-text="splitting"] .am-char{
  opacity:0;
  transform:translateY(.4em);
  filter:blur(6px);
}
.am [data-am-text="splitting"].am-in .am-char{
  opacity:1;transform:none;filter:blur(0);
  transition:
    opacity var(--am-dur-slow) var(--ease),
    transform var(--am-dur-slow) var(--am-spring),
    filter var(--am-dur-slow) var(--ease);
  transition-delay:var(--am-i-delay,0ms);
}

.am [data-am-text="rolling"] .am-word{
  opacity:0;
  transform:translateY(.55em) rotate(2deg);
}
.am [data-am-text="rolling"].am-in .am-word{
  opacity:1;transform:none;
  transition:opacity var(--am-dur) var(--ease),transform var(--am-dur-slow) var(--am-spring);
  transition-delay:var(--am-i-delay,0ms);
}

/* Typing leaves a caret until the line finishes, then drops it -- a caret that
   blinks forever on a heading reads as a stuck page. */
[data-am-text="typing"]{white-space:pre-wrap;}
[data-am-text="typing"].am-typing::after{
  content:"";
  display:inline-block;
  width:.08em;height:1em;
  margin-left:.06em;
  vertical-align:-.12em;
  background:currentColor;
  animation:am-caret 1s steps(1,end) infinite;
}
@keyframes am-caret{0%,49%{opacity:1;}50%,100%{opacity:0;}}

/* A marker-pen swipe behind a phrase. Drawn with a gradient on the background
   so it sits behind the glyphs without a second element. */
[data-am-text="highlight"]{
  background-image:linear-gradient(transparent 58%,var(--amber-bg) 58%);
  background-repeat:no-repeat;
  background-size:0% 100%;
  transition:background-size var(--am-dur-slow) var(--am-glide);
  transition-delay:var(--am-i-delay,0ms);
}
.am [data-am-text="highlight"].am-in{background-size:100% 100%;}

/* Gradient text sweeps a lighter band across the fill. Used on one or two
   words, never a paragraph: it costs a repaint per frame. */
[data-am-text="gradient"]{
  background-image:linear-gradient(100deg,var(--blue) 20%,var(--green) 40%,var(--blue) 60%);
  background-size:300% 100%;
  -webkit-background-clip:text;background-clip:text;
  color:transparent;
  animation:am-gradient 5s linear infinite;
}
@keyframes am-gradient{to{background-position:-300% 0;}}

/* ── reveal ─────────────────────────────────────────────────────────────────
   The site already reveals on scroll through `.rv` in site.js, and this does
   not replace it. These are the directional and blur variants Animate UI uses
   for entrances, available where `.rv`'s single upward fade is not enough. */
.am [data-am-reveal]{opacity:0;}
.am [data-am-reveal="up"]{transform:translateY(16px);}
.am [data-am-reveal="down"]{transform:translateY(-16px);}
.am [data-am-reveal="left"]{transform:translateX(18px);}
.am [data-am-reveal="right"]{transform:translateX(-18px);}
.am [data-am-reveal="scale"]{transform:scale(.94);}
.am [data-am-reveal="blur"]{filter:blur(10px);transform:translateY(10px);}

.am [data-am-reveal].am-in{
  opacity:1;transform:none;filter:blur(0);
  transition:
    opacity var(--am-dur-slow) var(--ease),
    transform var(--am-dur-slow) var(--am-spring),
    filter var(--am-dur-slow) var(--ease);
  transition-delay:var(--am-i-delay,0ms);
}

/* ── ripple ─────────────────────────────────────────────────────────────────
   The ripple starts where the pointer landed, not at the centre. That is the
   entire point of it: it confirms the press was received *there*, which is
   worth something on a phone where the finger covers the target. */
[data-am-ripple]{position:relative;overflow:hidden;}
.am-ripple{
  position:absolute;
  left:var(--am-rx,50%);top:var(--am-ry,50%);
  width:var(--am-rs,10px);height:var(--am-rs,10px);
  margin-left:calc(var(--am-rs,10px) / -2);
  margin-top:calc(var(--am-rs,10px) / -2);
  border-radius:999px;
  background:currentColor;
  opacity:.22;
  transform:scale(0);
  pointer-events:none;
  animation:am-ripple 560ms var(--am-glide) forwards;
}
@keyframes am-ripple{to{transform:scale(1);opacity:0;}}

/* ── press ──────────────────────────────────────────────────────────────────
   A spring on the way back up. Pure CSS, no JS, and it makes every control
   feel answered.

   The other four properties are listed because this attribute lands on
   buttons that already declare their own transition, and `transition` is a
   shorthand: naming transform alone at equal specificity, from a later
   stylesheet, would silently drop the background and shadow transitions those
   buttons were relying on. The durations match what .btn and .v1-btn already
   use, so nothing changes except the curve the press itself travels on. */
[data-am-press]{
  transition:
    transform var(--am-dur-fast) var(--am-pop),
    background var(--am-dur-fast) var(--ease),
    box-shadow var(--am-dur-fast) var(--ease),
    border-color var(--am-dur-fast) var(--ease),
    color var(--am-dur-fast) var(--ease);
}
[data-am-press]:active{transform:scale(.96);}

/* ── flip button ────────────────────────────────────────────────────────────
   Two stacked labels on a rotating card. The back face is written in
   data-am-flip and built by motion.js. */
[data-am-flip]{position:relative;perspective:600px;}
[data-am-flip] .am-flip-inner{
  display:block;
  transform-style:preserve-3d;
  transition:transform var(--am-dur-slow) var(--am-spring);
}
[data-am-flip]:hover .am-flip-inner,
[data-am-flip]:focus-visible .am-flip-inner{transform:rotateX(180deg);}
[data-am-flip] .am-flip-back{
  position:absolute;inset:0;
  display:flex;align-items:center;justify-content:center;
  transform:rotateX(180deg);
}
[data-am-flip] .am-flip-front,
[data-am-flip] .am-flip-back{backface-visibility:hidden;}

/* ── collapsible ────────────────────────────────────────────────────────────
   Height is animated here, which the top of this file said would be marked.
   There is no way to animate to `height:auto` without measuring, and the
   measured pixel height is written by motion.js before each run. An accordion
   that jumps open is the single most common reason a FAQ reads as cheap. */
[data-am-collapse]{
  overflow:hidden;
  height:0;
  transition:height var(--am-dur) var(--am-glide);
}
[data-am-collapse][data-am-open]{height:var(--am-h,auto);}
[data-am-collapse-trigger] .am-chev,
[data-am-collapse-trigger] svg{transition:transform var(--am-dur) var(--am-spring);}
[data-am-collapse-trigger][aria-expanded="true"] .am-chev,
[data-am-collapse-trigger][aria-expanded="true"] svg{transform:rotate(180deg);}

/* ── tilt and magnetic ──────────────────────────────────────────────────────
   Both are pointer-driven and both are switched off entirely on a touch
   screen by motion.js: a tilt that needs hover does nothing on a phone except
   cost a listener on every move. */
[data-am-tilt]{
  transform:perspective(700px) rotateX(var(--am-tx,0deg)) rotateY(var(--am-ty,0deg));
  transition:transform var(--am-dur) var(--ease);
}
[data-am-tilt].am-tilting{transition:transform 60ms linear;}

[data-am-magnetic]{
  transform:translate3d(var(--am-mx,0px),var(--am-my,0px),0);
  transition:transform var(--am-dur) var(--am-spring);
}
[data-am-magnetic].am-pulling{transition:transform 80ms linear;}

/* ── spotlight ──────────────────────────────────────────────────────────────
   A soft light that follows the pointer across a card. Light-only product, so
   this is a pale blue bloom rather than the dark-mode glow the library ships. */
[data-am-spotlight]{position:relative;isolation:isolate;}
[data-am-spotlight]::before{
  content:"";position:absolute;inset:0;z-index:-1;
  border-radius:inherit;
  background:radial-gradient(
    240px circle at var(--am-sx,50%) var(--am-sy,50%),
    var(--blue-tint),transparent 70%);
  opacity:0;
  transition:opacity var(--am-dur) var(--ease);
}
[data-am-spotlight]:hover::before{opacity:1;}

/* ── spring progress ────────────────────────────────────────────────────────
   Same idea as the existing .bar, but it settles rather than stopping dead. */
[data-am-progress]{
  display:block;overflow:hidden;
  height:6px;border-radius:999px;
  background:var(--line-2);
}
[data-am-progress] > i{
  display:block;height:100%;border-radius:999px;
  background:var(--blue);
  width:0;
  transition:width var(--am-dur-slow) var(--am-spring);
  transition-delay:var(--am-i-delay,0ms);
}

/* ── shine ──────────────────────────────────────────────────────────────────
   One pass of light across a surface, on entry only. Looping it turns a
   feature card into an advertisement. */
.am [data-am-shine]{position:relative;overflow:hidden;}
.am [data-am-shine].am-in::after{
  content:"";position:absolute;top:0;bottom:0;left:-40%;
  width:40%;
  background:linear-gradient(100deg,transparent,var(--blue-tint),transparent);
  transform:skewX(-18deg);
  animation:am-shine 1.1s var(--am-glide) .2s 1;
}
@keyframes am-shine{to{left:120%;}}

/* ── the whole layer, off ───────────────────────────────────────────────────
   Not "faster" and not "subtler". A visitor who asked for reduced motion gets
   the finished state immediately, which is what the setting means. Anything
   that hides content until it is revealed must be un-hidden here or the page
   is blank for the people least able to tolerate that. */
@media (prefers-reduced-motion:reduce){
  .am-highlight,
  .am-digit-col,
  [data-am-collapse],
  [data-am-tilt],
  [data-am-magnetic],
  [data-am-press],
  [data-am-progress] > i,
  [data-am-spotlight]::before,
  [data-am-text="highlight"]{transition:none !important;}

  .am [data-am-reveal],
  .am [data-am-reveal].am-in,
  .am [data-am-text] .am-char,
  .am [data-am-text] .am-word{
    opacity:1 !important;
    transform:none !important;
    filter:none !important;
    transition:none !important;
  }

  .am [data-am-text="highlight"]{background-size:100% 100% !important;}
  [data-am-text="gradient"]{animation:none;}
  .am [data-am-shine].am-in::after{display:none;}
  .am-ripple{display:none;}
  [data-am-text="typing"].am-typing::after{animation:none;}
  [data-am-tilt]{transform:none !important;}
}
