/*
 * Homepage motion layer. Loaded by inc/motion.php on the front page only, and
 * only when the motion switch is on (body.aib-motion).
 *
 * Three rules hold everywhere in this file:
 *  1. Only transform and opacity move. No layout property animates, nothing
 *     blurs, nothing delays the first paint of the hero text.
 *  2. Any starting state that hides something sits behind `.js-motion`, which
 *     motion.js adds to <html> once it is running. No script, no hiding.
 *  3. All of it lives inside `prefers-reduced-motion: no-preference`. Someone
 *     who asked for less motion gets the page in its final state, which is
 *     simply today's page.
 *
 * The section numbers match the table in tasks/redesign.md. Where an effect in
 * that table was left out or made quieter, the reason is beside the number.
 */

/* ---------------------------------------------------------------- static
   Not motion, so outside the media query: the founder cut-out's frame. The
   figure stands on the bottom edge of the card and its head clears the top,
   which is what makes a cut-out read as a cut-out rather than a cropped photo. */

.aib-motion .instructor .photo.has-cut {
  overflow: visible;
  box-shadow: 10px 10px 0 rgba(31, 75, 255, .32);
  margin-top: 40px;
}
.aib-motion .instructor .photo img.founder-cut {
  position: absolute;
  left: 50%;
  bottom: -8%;
  width: auto;
  height: 120%;
  max-width: none;
  object-fit: contain;
  transform: translateX(-50%);
  /* the painting's lower edge is ragged; sink it below the card and trim it
     there, so the shirt meets the card's edge cleanly */
  clip-path: inset(0 0 7% 0);
}

/* 9 · the drawn arc and its orbiting dot, coloured like the arc they replace */
.aib-motion .cyc-draw { stroke: var(--gada); }
.aib-motion .cyc-orbit circle { fill: var(--gada); }

@media (prefers-reduced-motion: no-preference) {

/* ---------------------------------------------------------------- shared */

@keyframes aib-up    { from { opacity: 0; transform: translateY(14px); } }
@keyframes aib-in    { from { opacity: 0; } }

/* Site-wide: a button answers the press. */
.aib-motion .btn:active { transform: translateY(1px); box-shadow: none; }

/* Header: a soft shadow once the page has moved, no shrink (that would shift
   the layout under it) and no backdrop blur (main.css explains why it is
   opaque). The shadow is a pseudo-element so only its opacity changes. */
.aib-motion .site-head::after {
  content: "";
  position: absolute;
  left: 0; right: 0; top: 100%;
  height: 14px;
  background: linear-gradient(rgba(13, 18, 41, .08), transparent);
  opacity: 0;
  transition: opacity .3s ease;
  pointer-events: none;
}
.aib-motion .site-head.m-scrolled::after { opacity: 1; }

/* Grids that stagger their children (main.css) keep the stagger as a
   transition-delay, which then also delays every hover. motion.js adds
   .m-done once the entrance is over. */
.aib-motion .m-done > * { transition-delay: 0s !important; }

/* ---------------------------------------------------------------- 0 · admission banner */

@keyframes aib-drop { from { opacity: 0; transform: translateY(-8px); } }
@keyframes aib-shine { from { transform: translateX(-120%); } to { transform: translateX(120%); } }
.aib-motion .aib-admission-bar .wrap { animation: aib-drop .5s cubic-bezier(.2, .7, .2, 1) both; }
.aib-motion .aib-admission-bar b {
  position: relative;
  display: inline-block;
  overflow: hidden;
  vertical-align: bottom;
}
.aib-motion .aib-admission-bar b::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(100deg, transparent 30%, rgba(255, 255, 255, .85) 50%, transparent 70%);
  transform: translateX(-120%);
  animation: aib-shine 1.6s .7s ease-in-out 1 both;
  pointer-events: none;
}

/* ---------------------------------------------------------------- 1 · hero
   The headline is painted in the first frame: it only settles a few pixels,
   it never starts invisible. Mask-per-line was dropped: Bangla wraps
   differently on every width, and a mask that clips a matra looks broken.
   The highlighter sweep in main.css stays the signature moment. */

@keyframes aib-settle { from { transform: translateY(.22em); } }
.aib-motion .hero h1.rise {
  animation: aib-settle .9s cubic-bezier(.2, .7, .2, 1) both;
  animation-delay: 0s;
}

/* proof pill: the faces arrive one by one, 60ms apart */
@keyframes aib-face { from { opacity: 0; transform: translateX(-8px) scale(.7); } }
.aib-motion .proof-bar .faces > * { animation: aib-face .45s cubic-bezier(.2, .7, .2, 1) both; }
.aib-motion .proof-bar .faces > :nth-child(1) { animation-delay: .12s; }
.aib-motion .proof-bar .faces > :nth-child(2) { animation-delay: .18s; }
.aib-motion .proof-bar .faces > :nth-child(3) { animation-delay: .24s; }
.aib-motion .proof-bar .faces > :nth-child(4) { animation-delay: .30s; }
.aib-motion .proof-bar .faces > :nth-child(5) { animation-delay: .36s; }

/* ---------------------------------------------------------------- 2 · the fan
   Cards leave a single stack and spread into their places. The slot (.card)
   carries the spread; the face (.cardin) only fades, so the hover lift in
   main.css is untouched. Scroll parallax was left out: the fan already has a
   hover, a tilt and five looping mocks, and a fourth motion was noise. */

@keyframes aib-fan { from { transform: translateX(-50%) translateY(24px) rotate(0deg); } }
@keyframes aib-fade-up { from { opacity: 0; transform: translateY(10px) scale(.97); } }
.aib-motion .fan .card { animation: aib-fan 1s cubic-bezier(.2, .8, .2, 1) backwards; }
.aib-motion .fan .cardin { animation: aib-fade-up .5s ease-out both; }
.aib-motion .fan .c1, .aib-motion .fan .c1 .cardin { animation-delay: .35s; }
.aib-motion .fan .c2, .aib-motion .fan .c2 .cardin { animation-delay: .31s; }
.aib-motion .fan .c3, .aib-motion .fan .c3 .cardin { animation-delay: .27s; }
.aib-motion .fan .c4, .aib-motion .fan .c4 .cardin { animation-delay: .31s; }
.aib-motion .fan .c5, .aib-motion .fan .c5 .cardin { animation-delay: .35s; }

/* desktop only: the whole fan leans toward the cursor, 4 degrees at most */
@media (hover: hover) and (pointer: fine) {
  .aib-motion .fan {
    transform: perspective(1400px) rotateY(calc(var(--mx, 0) * 4deg)) rotateX(calc(var(--my, 0) * -3deg));
    transition: transform .6s cubic-bezier(.2, .7, .2, 1);
  }
}

/* ---------------------------------------------------------------- 4 · problem
   Each paragraph rises as it arrives, so the argument builds as it is read.
   The dash on each cost line draws itself, and the bridge slides in from the
   left. The border-drawing around the callout was dropped: the slide is
   enough, and a drawn border on a tinted box reads as a loading state. */

.js-motion .aib-motion .prob.reveal { opacity: 1; transform: none; }
.aib-motion [data-m] { transition: opacity .6s ease, transform .6s cubic-bezier(.2, .7, .2, 1); }
.js-motion .aib-motion [data-m]:not(.m-in) { opacity: 0; transform: translateY(16px); }
.js-motion .aib-motion .prob-bridge[data-m]:not(.m-in) { transform: translateX(-24px); }

.aib-motion .prob-cost li::before { transform-origin: left center; transition: transform .45s .25s cubic-bezier(.2, .7, .2, 1); }
.js-motion .aib-motion .prob-cost li:not(.m-in)::before { transform: scaleX(0); }

/* ---------------------------------------------------------------- 5 · quiz
   Forward slides in from the right, back from the left. The ripple was left
   out: the press is enough feedback and the screen changes at once anyway. */

@keyframes aib-q-next { from { opacity: 0; transform: translateX(28px); } }
@keyframes aib-q-back { from { opacity: 0; transform: translateX(-28px); } }
.aib-motion .quiz-screen { animation: aib-q-next .38s cubic-bezier(.2, .7, .2, 1) both; }
.aib-motion .quiz.m-back .quiz-screen { animation-name: aib-q-back; }
.aib-motion .quiz-opt:active { transform: translateY(2px); }

/* ---------------------------------------------------------------- 6 · what you walk out with
   Rows slide in and each market rate counts up. The strike-through and the
   running total from the plan were dropped: these figures are what a client
   pays the student, not prices being discounted, so striking them through
   would say the opposite of the copy beside them. */

.js-motion .aib-motion .ledger li[data-m]:not(.m-in) { transform: translateX(18px); }

/* ---------------------------------------------------------------- 7 · reviews
   The staggered wave is already in main.css. Hover lifts a card. The two
   speed column parallax was dropped: CSS columns cannot move separately
   without rebuilding the wall in JS. */

@media (hover: hover) {
  .aib-motion .quotes.reveal.in > .quote { transition: opacity .55s ease, transform .3s cubic-bezier(.2, .7, .2, 1), box-shadow .3s ease; }
  .aib-motion .quotes.reveal.in > .quote:hover { transform: translateY(-4px); box-shadow: 0 18px 34px -22px rgba(13, 18, 41, .35); }
}

/* ---------------------------------------------------------------- 8 · seven segments
   The cards arrive in a diagonal wave (row + column), and on desktop a soft
   light follows the cursor across the dark band. */

@media (min-width: 901px) {
  .aib-motion .segs.reveal.in > :nth-child(1) { transition-delay: 0s; }
  .aib-motion .segs.reveal.in > :nth-child(2),
  .aib-motion .segs.reveal.in > :nth-child(4) { transition-delay: .09s; }
  .aib-motion .segs.reveal.in > :nth-child(3),
  .aib-motion .segs.reveal.in > :nth-child(5),
  .aib-motion .segs.reveal.in > :nth-child(7) { transition-delay: .18s; }
  .aib-motion .segs.reveal.in > :nth-child(6),
  .aib-motion .segs.reveal.in > :nth-child(8) { transition-delay: .27s; }
}

.aib-motion .m-spot { position: relative; overflow: hidden; }
.aib-motion .m-spot-l {
  position: absolute;
  left: 0; top: 0;
  width: 640px; height: 640px;
  margin: -320px 0 0 -320px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(90, 123, 255, .13), rgba(90, 123, 255, .04) 40%, transparent 68%);
  opacity: 0;
  transition: opacity .4s ease;
  pointer-events: none;
  will-change: transform;
  z-index: 2;
}
.aib-motion .m-spot.m-lit .m-spot-l { opacity: 1; }

/* ---------------------------------------------------------------- 9 · the cycle
   The arc draws as the section scrolls (motion.js sets its dash offset), each
   step lights as the arc reaches its node, and once the loop is closed a dot
   keeps orbiting: the one loop on the page, paused when off screen. */

.js-motion .aib-motion .cyc-run { display: none; }
.js-motion .aib-motion .cyc-draw { opacity: 1; }
.js-motion .aib-motion .cycle.m-done .cyc-orbit { opacity: 1; transition: opacity .6s ease; }
@keyframes aib-orbit { to { transform: rotate(360deg); } }
.aib-motion .cyc-orbit {
  transform-box: view-box;
  transform-origin: 130px 130px;
  animation: aib-orbit 14s linear infinite;
  animation-play-state: paused;
}
.aib-motion .cycle.m-done.m-vis .cyc-orbit { animation-play-state: running; }

.aib-motion .mech-step::after {
  content: "";
  position: absolute;
  left: -1px; top: -1px; bottom: -1px;
  width: 3px;
  background: var(--gada);
  transform: scaleY(0);
  transform-origin: top;
  transition: transform .45s cubic-bezier(.2, .7, .2, 1);
}
.aib-motion .mech-step.m-on::after { transform: none; }

/* ---------------------------------------------------------------- 10 · founder
   The portrait rises a beat after its card, the credentials follow one by
   one, and on desktop the card leans toward the cursor. */

.aib-motion .instructor .photo.has-cut img.founder-cut { transition: opacity .7s .15s ease, transform .9s .15s cubic-bezier(.2, .7, .2, 1); }
.js-motion .aib-motion .instructor .photo.has-cut:not(.in) img.founder-cut { opacity: 0; transform: translate(-50%, 28px); }
@media (hover: hover) and (pointer: fine) {
  .aib-motion .instructor .photo.reveal.in {
    transform: perspective(900px) rotateY(calc(var(--mx, 0) * 5deg)) rotateX(calc(var(--my, 0) * -4deg));
  }
}

.aib-motion .instructor .cred { transition: opacity .45s ease, transform .45s cubic-bezier(.2, .7, .2, 1); }
.js-motion .aib-motion .instructor .reveal:not(.in) .cred { opacity: 0; transform: translateY(8px); }
.aib-motion .instructor .reveal.in .cred:nth-child(1) { transition-delay: .25s; }
.aib-motion .instructor .reveal.in .cred:nth-child(2) { transition-delay: .33s; }
.aib-motion .instructor .reveal.in .cred:nth-child(3) { transition-delay: .41s; }
.aib-motion .instructor .reveal.in .cred:nth-child(4) { transition-delay: .49s; }

/* ---------------------------------------------------------------- 11 · modules
   One module after another. The growing tint bar and the 01, 02 counter were
   left out: the rows carry no number today, and adding one only to animate it
   would be decoration. */

.js-motion .aib-motion .curric.reveal { opacity: 1; transform: none; }
.aib-motion .curric > details { transition: opacity .5s ease, transform .5s cubic-bezier(.2, .7, .2, 1); }
.js-motion .aib-motion .curric.reveal:not(.in) > details { opacity: 0; transform: translateY(14px); }
.aib-motion .curric.reveal.in > details:nth-child(2) { transition-delay: .07s; }
.aib-motion .curric.reveal.in > details:nth-child(3) { transition-delay: .14s; }
.aib-motion .curric.reveal.in > details:nth-child(4) { transition-delay: .21s; }
.aib-motion .curric.reveal.in > details:nth-child(5) { transition-delay: .28s; }

/* 12 · numbers: the bars and the count-up already live in main.css/main.js. */

/* ---------------------------------------------------------------- 13 · pricing
   The featured card sends out one ring as it arrives, once. The per-day count
   down was left out: the payback line is a sentence, not a figure. */

@keyframes aib-ring {
  0%   { opacity: 0; transform: scale(1); }
  25%  { opacity: 1; }
  100% { opacity: 0; transform: scale(1.06, 1.04); }
}
.aib-motion .tiers.reveal.in .tier.featured::after {
  content: "";
  position: absolute;
  inset: -1px;
  border-radius: inherit;
  box-shadow: 0 0 0 2px rgba(90, 123, 255, .6);
  opacity: 0;
  animation: aib-ring 1.5s .6s ease-out 1;
  pointer-events: none;
}

/* ---------------------------------------------------------------- 14 · freelancer comparison
   The cost rows stack up one by one, the totals land last, then the saving
   badge pops beside the bars. */

.aib-motion .vstack > *, .aib-motion .saving-badge { transition: opacity .45s ease, transform .45s cubic-bezier(.2, .7, .2, 1); }
.js-motion .aib-motion .vstack-grid.reveal:not(.in) .vstack > * { opacity: 0; transform: translateX(-12px); }
.js-motion .aib-motion .vstack-grid.reveal:not(.in) .saving-badge { opacity: 0; transform: scale(.9); }
.aib-motion .vstack-grid.in .vstack > :nth-child(2) { transition-delay: .07s; }
.aib-motion .vstack-grid.in .vstack > :nth-child(3) { transition-delay: .14s; }
.aib-motion .vstack-grid.in .vstack > :nth-child(4) { transition-delay: .21s; }
.aib-motion .vstack-grid.in .vstack > :nth-child(5) { transition-delay: .28s; }
.aib-motion .vstack-grid.in .vstack > :nth-child(6) { transition-delay: .45s; }
.aib-motion .vstack-grid.in .vstack > :nth-child(7) { transition-delay: .6s; }
.aib-motion .vstack-grid.in .saving-badge { transition-delay: .75s; }

/* ---------------------------------------------------------------- 15 · refund */

@keyframes aib-stamp {
  0%   { opacity: 0; transform: scale(1.35) rotate(-10deg); }
  60%  { opacity: 1; transform: scale(.94) rotate(0deg); }
  100% { opacity: 1; transform: none; }
}
.aib-motion .guarantee.reveal.in .seal { animation: aib-stamp .6s .2s cubic-bezier(.3, .7, .3, 1) both; }

/* ---------------------------------------------------------------- 16 · FAQ (and the module list)
   The answer opens smoothly where the browser can animate to auto height, and
   instantly elsewhere. The "+" turns into a cross. */

.aib-motion .faq summary::after,
.aib-motion .faq details[open] summary::after { content: "+"; display: inline-block; transition: transform .25s ease; }
.aib-motion .faq details[open] summary::after { transform: rotate(45deg); }

@supports (interpolate-size: allow-keywords) {
  .aib-motion .faq, .aib-motion .curric { interpolate-size: allow-keywords; }
  .aib-motion .faq details::details-content,
  .aib-motion .curric details::details-content {
    height: 0;
    overflow: clip;
    transition: height .32s cubic-bezier(.2, .7, .2, 1), content-visibility .32s allow-discrete;
  }
  .aib-motion .faq details[open]::details-content,
  .aib-motion .curric details[open]::details-content { height: auto; }
}

/* ---------------------------------------------------------------- 17 · close
   The closing lines rise one after another. The breathing glow on the button
   and the sticky phone buy bar were left out: admission is closed, so the
   page has nothing to buy right now, and a looping glow on a closed door is
   the wrong signal. Both are worth adding when admission reopens. */

.js-motion .aib-motion .close-cta.reveal { opacity: 1; transform: none; }
.aib-motion .close-cta > * { transition: opacity .6s ease, transform .6s cubic-bezier(.2, .7, .2, 1); }
.js-motion .aib-motion .close-cta.reveal:not(.in) > * { opacity: 0; transform: translateY(16px); }
.aib-motion .close-cta.reveal.in > :nth-child(2) { transition-delay: .1s; }
.aib-motion .close-cta.reveal.in > :nth-child(3) { transition-delay: .2s; }
.aib-motion .close-cta.reveal.in > :nth-child(4) { transition-delay: .3s; }

} /* prefers-reduced-motion: no-preference */
