/* ─── ai-nate.com shared motion layer ────────────────────────────────
   One place for timing tokens and the site-wide motion rules that both
   page generations share (the legacy css/style.css family and the newer
   css/home.css family). Pairs with js/motion.js, which only ever adds
   classes and custom properties — with JS off nothing here hides content.

   Spec (frontend-motion skill, four layers — tool · trigger · effect · UX rule):
   - hero     · page load· fade-up 16px, once, 60ms stagger · --dur-hero · RM: static
                            (the LCP child rises without fading — see below)
   - blocks   · in view  · fade-up 14px, once, 60ms stagger · --dur-xl · RM: static
   - stats    · in view  · counter lands on the exact printed text · 900ms · RM: static
   - actions  · press    · scale .97                        · --dur-xs
   - actions  · focus    · visible ring, never removed      · —
   - cards    · hover    · lift, desktop pointers only      · --dur-sm · RM: off

   The reveal rules are scoped to `.mo-ready`, which js/motion.js sets only
   once it has wired itself up, so a blocked or broken motion.js leaves every
   page in its plain, fully visible state. */

:root {
    --dur-xs: 100ms;
    /* press, toggle */
    --dur-sm: 160ms;
    /* hover, tooltip */
    --dur-md: 220ms;
    /* dropdown, popover */
    --dur-lg: 320ms;
    /* modal, drawer, card enter */
    --dur-xl: 600ms;
    /* marketing reveal only */

    /* The hero entrance is the one reveal that sits on the LCP element, so it
       gets its own duration rather than --dur-xl. Measured in Chromium on the
       six landing pages: at --dur-xl the last hero child reached full opacity
       772–919ms after navigation start, which leaves no headroom once a real
       network pushes first paint out. At --dur-lg the same elements land
       490–620ms, and with --ease-out-strong they are visually settled inside
       the first third of that. Raise this back to --dur-xl only with the
       entrance timings re-measured. */
    --dur-hero: var(--dur-lg);
    --stagger: 60ms;
    --ease-out-strong: cubic-bezier(0.23, 1, 0.32, 1);
    --ease-drawer: cubic-bezier(0.32, 0.72, 0, 1);
}

/* ─── Press feedback ─────────────────────────────────────────────────
   Every primary action answers a tap. Covers both design generations. */
.btn,
.cta-button,
.course-cta,
.download-option,
.app-download,
.link-arrow,
.nav-link,
.nav__link {
    -webkit-tap-highlight-color: transparent;
}

.btn:active,
.cta-button:active,
.course-cta:active,
.download-option:active,
.app-download:active {
    transform: scale(0.97);
    transition-duration: var(--dur-xs);
}

/* ─── Focus ──────────────────────────────────────────────────────────
   Keyboard users get a ring on every interactive element. This is an
   addition, never a removal — nothing here sets outline: none. */
:is(a, button, summary, [role="button"]):focus-visible {
    outline: 3px solid var(--accent, var(--primary-color, #2c5f80));
    outline-offset: 3px;
    border-radius: 6px;
}

/* ─── Reveal on scroll ───────────────────────────────────────────────
   Only active once motion.js has marked the document ready. Pages in the
   newer family keep their own [data-reveal] system in css/home.css;
   motion.js stands down there so the two never fight. */
.mo-ready .mo-reveal {
    opacity: 0;
    transform: translateY(14px);
    /* motion-audit: allow slow-ui — one-time marketing scroll reveal, not UI feedback */
    transition: opacity var(--dur-xl) var(--ease-out-strong), transform var(--dur-xl) var(--ease-out-strong);
    transition-delay: calc(var(--mo-i, 0) * var(--stagger));
}

.mo-ready .mo-reveal.mo-in {
    opacity: 1;
    transform: none;
}

/* ─── Hero entrance ──────────────────────────────────────────────────
   spec: hero children · page load · staggered fade-up 16px · --dur-xl
         --stagger --ease-out-strong · end: the page's own styles ·
         RM: nothing moves, the hero is simply there.

   Pure CSS, gated on `.mo-ready` exactly like the scroll reveals. motion.js
   sets that class synchronously from <head>, before the body is parsed, so
   the entrance is settled at the first style resolution — it never waits on
   DOMContentLoaded, never hides something that was already painted, and with
   JS off, blocked or thrown it simply never applies.

   `animation-fill-mode: backwards` (not `both`) is deliberate: the from-frame
   covers the stagger delay, and once the animation is over it contributes
   nothing, so each element lands on its OWN authored style rather than being
   pinned to the keyframe's. That is why the keyframe has no `to` block — the
   implicit 100% frame is the element's underlying value, which keeps
   `.courses-hero p` at its authored opacity .95 and leaves mentoring's
   `.hero-cta:hover` free to translate again afterwards. */

@keyframes mo-hero-in {
    from {
        opacity: 0;
        transform: translateY(16px);
    }
}

/* Chromium does not count an element as painted while its opacity animation is
   still running, so fading the Largest Contentful Paint element costs LCP its
   whole delay plus duration: measured on this branch, /index.html went 64ms →
   624ms and /products, /intensive and /opc-journal went ~60ms → ~430ms. The LCP
   element rises without fading instead, which is honest (it really is readable
   from the first frame) and puts LCP back on its baseline. Starting it at a
   token opacity like .01 would also restore the number, because Chromium's
   check is opacity === 0 exactly — but it would report content as painted that
   nobody can see, so it is not done here.

   WHICH element that is depends on the viewport, so it is measured, not assumed.
   The h1 is NOT always the largest: at 390px the h1 shrinks faster than the body
   copy wraps, and /intensive and /opc-journal hand LCP to `p.ihero__lede`
   (36,102px² vs the h1's 20,738px²); at 768px /mentoring hands it to
   `p.subtitle` (52,051px² vs 38,156px²). Unmarked, those three pages measured
   52ms → 492ms, 52ms → 496ms and 124ms → 496ms — the element was unchanged, so
   an element-identity gate alone does not catch this; only measuring at more
   than one width does. Every hero child that wins LCP at any of 1440 / 768 /
   390px carries the marker. Re-run outputs/lcp-identity/lcp-identity.mjs after
   editing hero copy: a reflow moves the winner. */
@keyframes mo-hero-rise {
    from {
        transform: translateY(16px);
    }
}

.mo-ready [data-mo-hero] {
    /* motion-audit: allow slow-ui — one-time marketing hero entrance, not UI feedback */
    animation: mo-hero-in var(--dur-hero) var(--ease-out-strong) backwards;
}

.mo-ready [data-mo-lcp] {
    /* motion-audit: allow slow-ui — one-time marketing hero entrance, not UI feedback */
    animation-name: mo-hero-rise;
}

.mo-ready [data-mo-hero="2"] {
    animation-delay: calc(1 * var(--stagger));
}

.mo-ready [data-mo-hero="3"] {
    animation-delay: calc(2 * var(--stagger));
}

.mo-ready [data-mo-hero="4"] {
    animation-delay: calc(3 * var(--stagger));
}

.mo-ready [data-mo-hero="5"] {
    animation-delay: calc(4 * var(--stagger));
}

/* An inline box cannot be transformed. The hero eyebrows are <span>s that
   already sit alone on their line, so promoting them to blocks renders
   identically and makes them movable. Unconditional — never gated on
   .mo-ready — so the JS-off layout matches the JS-on one exactly. */
span[data-mo-hero] {
    display: block;
}

/* One shot per page load. motion.js seals the hero on a bfcache restore so a
   restored page can never replay the entrance and blank its own content. */
.mo-hero-done [data-mo-hero] {
    animation: none !important;
}

/* Counters must not reflow the layout while they run. */
.stat-number {
    font-variant-numeric: tabular-nums;
}

/* ─── Printing shows everything, animated or not ─────────────────── */
@media print {

    .mo-ready .mo-reveal {
        opacity: 1 !important;
        transform: none !important;
    }

    .mo-ready [data-mo-hero] {
        animation: none !important;
    }
}

/* ─── Reduced motion ─────────────────────────────────────────────────
   Keep state changes — colour, opacity, focus — drop travel and loops.
   Content is never hidden here: the reveal resolves to its end state. */
@media (prefers-reduced-motion: reduce) {

    *,
    *::before,
    *::after {
        animation-duration: 1ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 1ms !important;
        transition-delay: 0ms !important;
        scroll-behavior: auto !important;
    }

    .mo-ready .mo-reveal {
        opacity: 1;
        transform: none;
    }

    /* The hero is simply there — no entrance, no travel, nothing to wait for. */
    .mo-ready [data-mo-hero] {
        animation: none !important;
    }

    /* Loading spinners report progress, so they keep turning — reduced motion
       asks for a gentler animation, not a frozen one, and a stopped spinner
       reads as a hung page. Slowed down rather than switched off. */
    [class*="spinner"],
    [class*="loader"],
    [class*="loading"] {
        animation-duration: 1.5s !important;
        animation-iteration-count: infinite !important;
    }
}
