/**
 * Just Orthodontics — motion.
 *
 * §01  Entrance reveals
 * §02  Staggering
 * §03  The play control's radar pulse
 * §04  Reduced motion
 *
 * One rise, one fade, one easing curve for the whole site. The language is
 * deliberately narrow so sections read as arriving in the same way rather than
 * each performing separately — no flying elements, no rotation, no bounce.
 *
 * Nothing in this file hides anything except under `.jo-anim`, which the inline
 * bootstrap in functions.php adds before first paint and removes again if the
 * module never claims the page. With JavaScript off, or broken, or on a browser
 * without IntersectionObserver, the site is simply the site.
 */

/* ═══ §01 Entrance reveals ════════════════════════════════════ */

.jo-anim [data-reveal] {
    opacity: 0;
    transform: translate3d(0, var(--jo-reveal-y), 0);
    /**
     * Only opacity and transform are animated — both composited, neither
     * triggering layout — so a page full of reveals costs the main thread
     * nothing while it scrolls.
     */
    transition: opacity var(--jo-reveal-dur) var(--jo-ease),
                transform var(--jo-reveal-dur) var(--jo-ease);
    transition-delay: var(--jo-reveal-delay, 0ms);
    will-change: opacity, transform;
}

.jo-anim [data-reveal].is-revealed {
    opacity: 1;
    transform: none;
    /* Released once the element has arrived: leaving `will-change` set on
       everything would keep dozens of layers alive for no reason. */
    will-change: auto;
}

/**
 * The first screenful is faster.
 *
 * A reader is waiting on it rather than scrolling towards it, so the same
 * motion is played at a pace that does not hold the page up.
 */
.jo-anim .jo-hero [data-reveal],
.jo-anim .jo-tx-hero [data-reveal],
.jo-anim .jo-article__header [data-reveal] {
    transition-duration: var(--jo-reveal-dur-prompt);
}

/**
 * Content that arrived because someone asked for it.
 *
 * A filtered result or an appended batch fading in over a second and a half
 * would read as the site having stalled, not as polish.
 */
.jo-anim [data-reveal].is-quick { transition-duration: var(--jo-reveal-dur-quick); }

/* ═══ §02 Staggering ══════════════════════════════════════════ */

/**
 * A tasteful stagger across a row of cards.
 *
 * The step is applied by index within the row that crosses the reveal line
 * together — not across a whole grid, because everything below that row is
 * still waiting on its own arrival. That is what lets the delay be slow enough
 * to actually watch without the last card in a twelve-card grid arriving two
 * seconds late.
 */
.jo-anim [data-reveal-step="0"] { --jo-reveal-delay: 0ms; }
.jo-anim [data-reveal-step="1"] { --jo-reveal-delay: calc(var(--jo-stagger-step) * 1); }
.jo-anim [data-reveal-step="2"] { --jo-reveal-delay: calc(var(--jo-stagger-step) * 2); }
.jo-anim [data-reveal-step="3"] { --jo-reveal-delay: calc(var(--jo-stagger-step) * 3); }
.jo-anim [data-reveal-step="4"] { --jo-reveal-delay: calc(var(--jo-stagger-step) * 4); }
.jo-anim [data-reveal-step="5"] { --jo-reveal-delay: calc(var(--jo-stagger-step) * 5); }

/**
 * Past the sixth item the delay stops growing.
 *
 * A grid of twelve cards should not make its last card wait nearly two seconds:
 * beyond a rowful the stagger has done its work, and holding the step constant
 * keeps the rhythm without the tail dragging.
 */
.jo-anim [data-reveal-step="6"],
.jo-anim [data-reveal-step="7"],
.jo-anim [data-reveal-step="8"],
.jo-anim [data-reveal-step="9"],
.jo-anim [data-reveal-step="10"],
.jo-anim [data-reveal-step="11"] { --jo-reveal-delay: calc(var(--jo-stagger-step) * 5); }

/**
 * Within a section, the parts arrive in reading order.
 *
 * The eyebrow, then the heading, then the copy, then the buttons — which is the
 * order a reader's eye takes anyway, so the motion follows attention rather than
 * competing with it. Card grids get their own per-card step above.
 */
.jo-anim .jo-section-head__title { --jo-reveal-delay: 80ms; }
.jo-anim .jo-section-head__intro { --jo-reveal-delay: 160ms; }
.jo-anim .jo-btn-row            { --jo-reveal-delay: 240ms; }

/* ═══ §03 The play control's radar pulse ══════════════════════ */

/**
 * Two rings expanding out of the play button and fading as they go.
 *
 * Drawn on pseudo-siblings rather than on the button, so the pulse scales past
 * the control's bounds without changing its hit area or nudging anything around
 * it. The second ring is offset by half the cycle, which is what makes it read
 * as a repeating radar sweep rather than as a single throb.
 *
 * It is deliberately slow: at 2.8s it attracts the eye once and then stops
 * competing with the page.
 */
@keyframes jo-pulse {
    0%   { transform: scale(1);    opacity: .5; }
    70%  { transform: scale(1.85); opacity: 0; }
    100% { transform: scale(1.85); opacity: 0; }
}

.jo-play__ring {
    animation: jo-pulse var(--jo-pulse-dur) var(--jo-ease) infinite;
}

.jo-play__ring--2 { animation-delay: calc(var(--jo-pulse-dur) / 2); }

/* The pulse has done its job once the video is playing. */
.jo-video.is-playing .jo-play__ring { animation: none; }

/**
 * Paused is not an invitation.
 *
 * A visitor who has just paused the video knows where the control is and does
 * not need a radar sweep asking them to press it again. A video that has run to
 * the end is a different matter — there the pulse returns, because the panel is
 * back to offering something rather than waiting on a decision already made.
 */
.jo-video[data-video-state="paused"] .jo-play__ring {
    animation: none;
    opacity: 0;
}

/* ═══ §04 Reduced motion ══════════════════════════════════════ */

/**
 * `prefers-reduced-motion` is honoured completely, not partially.
 *
 * Every reveal resolves instantly to its final state — nothing is left hidden —
 * the pulse stops, the parallax stops (the module also refuses to bind), the
 * clinician panels stop rotating on a timer but remain steerable by their
 * controls, and hover transforms are removed.
 *
 * Reduced motion is a request to remove movement, not a request to remove
 * content or interactivity.
 */
@media (prefers-reduced-motion: reduce) {

    .jo-anim [data-reveal],
    .jo-anim [data-reveal].is-revealed {
        opacity: 1 !important;
        transform: none !important;
        transition: none !important;
        will-change: auto;
    }

    .jo-play__ring { animation: none !important; opacity: 0 !important; }

    /*
     * The video still changes state, and the cover still comes and goes — it
     * simply stops being a movement. Cutting rather than fading keeps every
     * state readable while removing the animation that carried it.
     */
    .jo-video__poster,
    .jo-video__stage iframe,
    .jo-video__stage video,
    .jo-play__icon,
    .jo-video .jo-play,
    .jo-video .jo-play__tip {
        transition-duration: 1ms !important;
        transition-delay: 0s !important;
    }

    /*
     * The mobile menu arrives and leaves without moving. It still opens, still
     * traps focus and still closes — only the fade and the rise are dropped, so
     * the panel is simply there and then simply gone.
     */
    .jo-mnav,
    .jo-mnav.is-open,
    .jo-mnav__head,
    .jo-mnav__nav > ul > li,
    .jo-mnav__body > :not(.jo-mnav__nav),
    .jo-mnav.is-open .jo-mnav__head,
    .jo-mnav.is-open .jo-mnav__nav > ul > li,
    .jo-mnav.is-open .jo-mnav__body > :not(.jo-mnav__nav) {
        transition: none !important;
        transform: none !important;
    }

    .jo-mnav.is-open,
    .jo-mnav.is-open .jo-mnav__head,
    .jo-mnav.is-open .jo-mnav__nav > ul > li,
    .jo-mnav.is-open .jo-mnav__body > :not(.jo-mnav__nav) { opacity: 1 !important; }

    .jo-photo-cta__media { transform: none !important; }

    /* The frames still cross-fade, but instantly — the panel changes without a
       movement to track. */
    .jo-flip__frame,
    .jo-lead__frame { transition-duration: 1ms !important; }

    .jo-card-photo__img,
    .jo-card-tx__img,
    .jo-card-news__img,
    .jo-featured__img,
    .jo-card-tx,
    .jo-card-news,
    .jo-card-feature,
    .jo-play,
    .jo-share__btn {
        transition: none !important;
        transform: none !important;
    }

    .jo-card-photo:hover .jo-card-photo__img,
    .jo-card-tx:hover .jo-card-tx__img,
    .jo-card-news:hover .jo-card-news__img,
    .jo-featured:hover .jo-featured__img,
    .jo-card-tx:hover,
    .jo-card-news:hover,
    .jo-card-feature:hover { transform: none !important; }

    /* Colour changes on hover are not motion, so they stay: a reader who has
       asked for less movement still needs to see what is interactive. */
}
