/* ==========================================================================
   QuantAim — motion layer. Added 2026-09-11.

   WHAT THIS IS FOR
   ----------------
   Two effects, used everywhere, replacing nothing that already works.

     1. Scroll reveal. Blocks rise and fade as they enter the viewport. This
        is the single technique that most separates a site that feels built
        from one that feels assembled, and it was the only thing genuinely
        missing — styles.css already carries 28 keyframes and 81 transforms.

     2. Count-up numbers. The live content counts on the schools page are the
        most persuasive thing on it, and they currently just sit there.
        A number that climbs is read; a number that is already there is
        skimmed past.

   WHAT THIS DELIBERATELY IS NOT
   ----------------------------
   Not particles, not aurora backgrounds, not magnetic cursors. The audience
   is a head of department deciding whether to trust a small company with
   their students' coursework. Restraint reads as competence here, and a
   landing page that behaves like a product demo reads as the opposite.

   CONTENT IS NEVER HIDDEN BY CSS ALONE
   ------------------------------------
   The hiding rules are scoped to .qa-motion, a class JavaScript adds to
   <html> at runtime. If the script fails, never loads, or is blocked, that
   class is absent and every element renders normally. A page whose text is
   invisible without JavaScript is a broken page, not an animated one — and
   that failure mode is invisible in testing because the developer's JS
   always works.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. SCROLL REVEAL
   -------------------------------------------------------------------------- */

.qa-motion .qa-reveal {
    opacity: 0;
    transform: translateY(18px);
    /* Long enough to be perceived, short enough not to be waited on. The
       ease-out curve matters more than the duration: it arrives quickly and
       settles, rather than drifting in at a constant speed. */
    transition: opacity .62s cubic-bezier(.16, 1, .3, 1),
                transform .62s cubic-bezier(.16, 1, .3, 1);
    will-change: opacity, transform;
}

.qa-motion .qa-reveal.qa-in {
    opacity: 1;
    transform: none;
}

/* Once it has arrived, stop claiming a compositor layer for it. */
.qa-motion .qa-reveal.qa-done {
    will-change: auto;
}

/* Staggering. Applied by the observer to children of a [data-qa-stagger]
   container so a row of cards arrives in sequence rather than as one slab.
   Capped at six: past that the last card arrives late enough to feel broken
   rather than choreographed. */
.qa-motion .qa-reveal[style*="--qa-delay"] {
    transition-delay: var(--qa-delay, 0ms);
}

/* --------------------------------------------------------------------------
   2. COUNT-UP
   -------------------------------------------------------------------------- */

/* Digits must not reflow while counting, or the whole row jitters as the
   number widens. Tabular figures make every digit the same width. */
.qa-countup {
    font-variant-numeric: tabular-nums;
}

/* --------------------------------------------------------------------------
   3. REDUCED MOTION
   --------------------------------------------------------------------------
   Not a courtesy. For a vestibular-disorder sufferer, parallax and rise
   animations cause real nausea, and this is a site teenagers are required to
   use for a graded course — they cannot simply leave.

   Everything resolves to its final state instantly. Nothing is hidden.
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
    .qa-motion .qa-reveal,
    .qa-motion .qa-reveal.qa-in {
        opacity: 1 !important;
        transform: none !important;
        transition: none !important;
        will-change: auto !important;
    }
}

/* ==========================================================================
   4. SPOTLIGHT CARD
   ==========================================================================
   A soft highlight that follows the pointer across a card. Borrowed from
   ReactBits' SpotlightCard, rebuilt in plain CSS driven by two custom
   properties the script sets on pointermove.

   Cheap in every sense: one extra pseudo-element, no layout, no repaint of
   the card's own content, and it costs nothing at all on touch devices where
   there is no pointer to follow.
   ========================================================================== */

.qa-spotlight {
    position: relative;
    isolation: isolate;
    overflow: hidden;
}

.qa-spotlight::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    opacity: 0;
    transition: opacity .3s ease;
    background: radial-gradient(
        360px circle at var(--qa-mx, 50%) var(--qa-my, 50%),
        rgba(56, 189, 248, .14),
        transparent 62%
    );
}

.qa-spotlight:hover::after { opacity: 1; }

/* Touch: there is no hover, and leaving it on would latch the highlight on
   after a tap and never clear it. */
@media (hover: none) {
    .qa-spotlight::after { display: none; }
}

/* ==========================================================================
   5. GRADIENT HEADLINE
   ==========================================================================
   ReactBits' GradientText and ShinyText, minus the sweep animation. The
   sweep draws the eye continuously, which is fine on a component gallery and
   wrong on a page someone is trying to read.

   Applied to one element per page at most. A gradient on every heading stops
   reading as emphasis and starts reading as a template.
   ========================================================================== */

.qa-gradient-text {
    background: linear-gradient(92deg, #0369a1 0%, #0ea5e9 46%, #22d3ee 100%);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    /* Fallback for anything that cannot clip a background to text: the
       declaration above is ignored and this colour shows instead, so the
       headline is never invisible. */
    -webkit-text-fill-color: transparent;
}

@supports not (background-clip: text) {
    .qa-gradient-text {
        color: #0369a1;
        -webkit-text-fill-color: #0369a1;
    }
}

@media (prefers-contrast: more) {
    .qa-gradient-text {
        background: none;
        color: #0c4a6e;
        -webkit-text-fill-color: #0c4a6e;
    }
}
