/* =============================================
   TEXT ENTRANCE

   Headings and paragraphs lift a short way into place the first time they
   reach the viewport. Sitewide, and deliberately small: the move should read
   as the page settling under the scroll, not as an animation being played at
   the visitor.

   The rule below is the *only* place that decides what animates.
   text-reveal.js finds its targets by reading --text-reveal back off the
   element rather than carrying a copy of this selector, so there is no second
   list to fall out of step with this one and leave a paragraph stranded at
   opacity 0.
   ============================================= */

/* Registered so the marker does not inherit: without this, a paragraph nested
   inside something the rule matched would report its parent's 1 and be picked
   up twice. Browsers without @property fall back to an inheriting property,
   which is harmless here — nothing the rule matches contains anything else it
   matches. */
@property --text-reveal {
    syntax: "<integer>";
    inherits: false;
    initial-value: 0;
}

:root {
    /* Short enough that the copy never reads as arriving from off screen —
       it starts a line's worth low and closes the gap. */
    --text-reveal-shift: 1rem;

    /* The fade lands before the slide does, so the text is legible while it
       is still travelling and the last of the move is what settles rather
       than what announces itself. */
    --text-reveal-fade: 0.55s;
    --text-reveal-slide: 0.9s;

    /* Nearly all of the distance is covered early and the tail is long: the
       curve is what makes a 16px move feel like it eased in rather than
       stopped. */
    --text-reveal-ease: cubic-bezier(0.22, 1, 0.36, 1);
}

/* Reduced-motion visitors are never armed by meowgical_text_reveal_arm(), so
   this is belt-and-braces for a preference that flips mid-session: without the
   hide rule there is nothing to bring back. */
@media (prefers-reduced-motion: no-preference) {

    /* The two :not()s hang off the tag list with no space in front of them:
       any whitespace there — a newline to keep the exclusions readable, even a
       comment — is a descendant combinator, and the rule would match the
       children of every heading instead of the headings.

       They exclude, in order, anything already holding its own copy back:
       - the pinned scrub sections, where main.js tweens the opacity and y of
         the heading and content itself and a second owner of either property
         is a fight neither wins. The survey's own content is left in: it sits
         behind a curtain that lifts off it, so it is already in place by the
         time anyone sees it;
       - panels opened by a click, which are never on their way into view and
         would animate on top of their own expansion the first time they were
         opened;
       - blocks read as one object whose text is mostly not h1-h6 or p: the
         testimonial deck (the quote is a blockquote, so only the byline and
         brand rose, and on cards being swiped at the time) and the capability
         matrix (headings rose, the lists beside them did not);
       - the process step cards, which bring their own header, copy and list
         in as each card arrives (see initProcessSteps), and fold on phones;
       - the inclusion list, for the same reason: each item brings its tick,
         title and copy in together (see initInclusionList), and folds on
         phones;
       - the contact card and its sent/error panels: the card is one form
         whose labels would otherwise rise one by one, and the panels are
         swapped in by contact-form.js, which fades them itself;
       - the referral coupon, whose lines are printed on the ticket art and
         would otherwise rise off it one at a time;
       - the intro overlay, and the two hero elements it holds back and hands
         over itself when the blob clears (see preloader.css). */
    html.text-reveal-armed
        :where(#content, .site-footer-stack)
        :where(h1, h2, h3, h4, h5, h6, p):not(:where(
            .works-transition,
            .quote-transition,
            .home-survey__transition,
            .home-survey__reveal,
            .home-faq__panel,
            .services-faq__panel,
            .faq-directory__panel,
            .services-capabilities-mobile-item__panel,
            .services-tier-modal,
            .services-testimonial-card,
            .services-capabilities__table,
            .process-card,
            .inclusion-list__items,
            .contact-form__card,
            .contact-form__panel,
            .referral-hero__coupon,
            .preloader
        ) *):not(:where(.home-hero__title, .home-hero__desc))
    {
        --text-reveal: 1;
        opacity: 0;
        transform: translateY(var(--text-reveal-shift));
        transition:
            opacity var(--text-reveal-fade) var(--text-reveal-ease),
            transform var(--text-reveal-slide) var(--text-reveal-ease);
    }
}

/* Only ever set on an element that reported the marker, so this cannot reach
   anything the rule above left alone. Outranks it on the class count. */
html.text-reveal-armed .text-reveal-in {
    --text-reveal: 0;
    opacity: 1;
    transform: none;
}
