/* ============================================================================================
   Novalie Pharma — motion
   Animation is used to explain change, never for decoration. Everything here is disabled under
   prefers-reduced-motion.
   ============================================================================================ */

@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }

@keyframes fade-up {
    from { opacity: 0; transform: translateY(1.25rem); }
    to { opacity: 1; transform: none; }
}

@keyframes fade-down {
    from { opacity: 0; transform: translateY(-.75rem); }
    to { opacity: 1; transform: none; }
}

@keyframes fade-in-start {
    from { opacity: 0; transform: translateX(-1.25rem); }
    to { opacity: 1; transform: none; }
}

@keyframes scale-in {
    from { opacity: 0; transform: scale(.96); }
    to { opacity: 1; transform: none; }
}

@keyframes modal-in {
    from { opacity: 0; transform: translateY(1.5rem) scale(.97); }
    to { opacity: 1; transform: none; }
}

@keyframes dropdown-in {
    from { opacity: 0; transform: translateY(-.375rem) scale(.985); }
    to { opacity: 1; transform: none; }
}

@keyframes toast-in {
    from { opacity: 0; transform: translateX(1.5rem) scale(.97); }
    to { opacity: 1; transform: none; }
}

@keyframes toast-out {
    to { opacity: 0; transform: translateX(1.5rem) scale(.97); }
}

@keyframes spin { to { transform: rotate(360deg); } }

@keyframes shimmer { 100% { transform: translateX(100%); } }
@keyframes shimmer-rtl { 100% { transform: translateX(-200%); } }

@keyframes marquee { to { transform: translateX(-50%); } }
@keyframes marquee-rtl { to { transform: translateX(50%); } }

@keyframes float-slow {
    0%, 100% { transform: translateY(0) rotate(0deg); }
    50% { transform: translateY(-1.5rem) rotate(2deg); }
}

@keyframes pulse-ring {
    0% { transform: scale(.85); opacity: .55; }
    70% { transform: scale(1.35); opacity: 0; }
    100% { opacity: 0; }
}

@keyframes progress-indeterminate {
    0% { transform: translateX(-100%) scaleX(.4); }
    50% { transform: translateX(20%) scaleX(.7); }
    100% { transform: translateX(120%) scaleX(.4); }
}

/* --------------------------------------------------------------------------------------------
   Scroll reveal
   Elements start hidden and are revealed by IntersectionObserver adding .is-visible.
   If JavaScript never runs, the .no-js fallback in site.js leaves them visible.
   -------------------------------------------------------------------------------------------- */

[data-reveal] {
    opacity: 0;
    transform: translateY(1.5rem);
    transition:
        opacity var(--duration-slower) var(--ease-out),
        transform var(--duration-slower) var(--ease-out);
    transition-delay: var(--reveal-delay, 0ms);
    /* No will-change here: a page holds dozens of reveal targets, and pre-promoting every one
       to its own compositor layer costs more memory than the one-off transition saves. */
}

[data-reveal="fade"] { transform: none; }
[data-reveal="start"] { transform: translateX(-1.75rem); }
[data-reveal="end"] { transform: translateX(1.75rem); }
[data-reveal="scale"] { transform: scale(.94); }

[data-reveal].is-visible {
    opacity: 1;
    transform: none;
}

/* Staggered children — the delay is set inline as --reveal-delay by the Razor markup. */
.stagger > * { transition-delay: calc(var(--stagger-step, 70ms) * var(--i, 0)); }

/* --------------------------------------------------------------------------------------------
   Named entrances used by components
   -------------------------------------------------------------------------------------------- */

.anim-fade-in { animation: fade-in var(--duration-slow) var(--ease-out) both; }
.anim-fade-up { animation: fade-up var(--duration-slower) var(--ease-out) both; }
.anim-scale-in { animation: scale-in var(--duration-slow) var(--ease-spring) both; }
.anim-fade-down { animation: fade-down var(--duration) var(--ease-out) both; }

.delay-1 { animation-delay: 80ms; }
.delay-2 { animation-delay: 160ms; }
.delay-3 { animation-delay: 240ms; }
.delay-4 { animation-delay: 320ms; }
.delay-5 { animation-delay: 400ms; }

/* --------------------------------------------------------------------------------------------
   Page transitions
   site.js adds .page-enter to <main> on each route change. Opacity only: a transform here would
   make <main> the containing block for any fixed-position overlay opened during the entrance.
   -------------------------------------------------------------------------------------------- */

@keyframes page-enter { from { opacity: 0; } to { opacity: 1; } }

.site-main.page-enter { animation: page-enter 480ms var(--ease-out) both; }

/* --------------------------------------------------------------------------------------------
   Off-screen pause
   site.js marks heroes and dark bands .is-offscreen once they leave the viewport, so their
   looping ambient motion stops costing frames while nobody can see it.
   -------------------------------------------------------------------------------------------- */

.is-offscreen,
.is-offscreen::before,
.is-offscreen::after,
.is-offscreen *,
.is-offscreen *::before,
.is-offscreen *::after { animation-play-state: paused !important; }

/* --------------------------------------------------------------------------------------------
   Scroll-driven hero depth
   Where the browser supports scroll timelines, the hero photograph drifts at a slower rate than
   the page and the copy lifts away a little faster, so the first screen reads as layered rather
   than flat. Runs on the compositor with no script; other browsers simply keep a static hero.
   -------------------------------------------------------------------------------------------- */

@keyframes hero-media-parallax { to { transform: translate3d(0, 28vh, 0); } }

@keyframes hero-copy-lift {
    to { opacity: 0; transform: translate3d(0, -7vh, 0); }
}

@supports (animation-timeline: scroll()) {
    @media (prefers-reduced-motion: no-preference) {
        .site-main > :first-child :is(
            .hero-slide-backgrounds,
            .page-header-media,
            .about-hero-media, .services-hero-media, .news-hero-media,
            .products-hero-media, .contact-hero-media, .gallery-hero-media) {
            animation: hero-media-parallax linear both;
            animation-timeline: scroll(root);
            animation-range: 0 100vh;
            animation-play-state: running !important; /* scroll-linked, never paused */
        }

        .site-main > :first-child:is(.hero, .page-header) > .container,
        .site-main > .home-experience:first-child > .hero:first-child > .hero-grid {
            animation: hero-copy-lift linear both;
            animation-timeline: scroll(root);
            animation-range: 0 75vh;
            animation-play-state: running !important;
        }
    }
}

/* --------------------------------------------------------------------------------------------
   Hero imagery
   One treatment for every page's hero photograph. It opens like an iris, from a rounded frame
   inset inside the hero out to full bleed, while settling from a close zoom; then it drifts very
   slowly (a Ken Burns move) for as long as the hero is on screen. The drift pauses off-screen
   with the rest of the ambient motion, and the scroll parallax runs on the parent container, so
   the three never compete for the same transform. Per-page colour grading (filter) and framing
   (object-position) are left to each page's stylesheet.
   -------------------------------------------------------------------------------------------- */

/* The frame opening (clip-path and fade) and the zoom settling run as two animations, because
   they read best on different curves: an even ease-in-out lets the iris visibly open, while the
   zoom lands on an expo-out and keeps easing long after the frame is full. */
@keyframes hero-image-iris {
    from { opacity: 0; clip-path: inset(10% 8% 10% 8% round 2.5rem); }
    30% { opacity: 1; }
    to { opacity: 1; clip-path: inset(0 0 0 0 round 0); }
}

@keyframes hero-image-settle {
    from { transform: scale(1.2); }
    to { transform: scale(1.06); }
}

@keyframes hero-image-drift {
    from { transform: scale(1.06) translate3d(0, 0, 0); }
    to { transform: scale(1.13) translate3d(-1.4%, -1%, 0); }
}

@media (prefers-reduced-motion: no-preference) {
    .site-main :is(
        .hero-slide-background,
        .page-header-media,
        .about-hero-media, .services-hero-media, .news-hero-media,
        .products-hero-media, .contact-hero-media, .gallery-hero-media) > img {
        transform-origin: 58% 45%;
        animation:
            hero-image-iris 1.5s cubic-bezier(.65, 0, .25, 1) both,
            hero-image-settle 2.2s cubic-bezier(.16, 1, .3, 1) both,
            hero-image-drift 28s var(--ease-in-out) 2.2s infinite alternate;
    }

    /* Held at the first frame (the hero's gradient shows through) until the photograph has
       actually arrived, so a slow connection never plays the reveal over an empty box.
       site.js adds .is-loaded, with a timeout so the image can never stay hidden. */
    :root[data-hero-hold] .site-main :is(
        .hero-slide-background,
        .page-header-media,
        .about-hero-media, .services-hero-media, .news-hero-media,
        .products-hero-media, .contact-hero-media, .gallery-hero-media) > img:not(.is-loaded) {
        animation-play-state: paused;
    }
}

/* --------------------------------------------------------------------------------------------
   Headline word reveal
   Titles rendered by <SplitWords> rise word by word from behind a clip line.
   -------------------------------------------------------------------------------------------- */

/* The words carry the entrance, so the heading opts out of any container fade around it. */
.split-heading { animation: none !important; opacity: 1 !important; transform: none !important; }

.split-word {
    display: inline-block;
    max-width: 100%;               /* a single long word (a drug name) may still wrap inside itself */
    overflow-wrap: anywhere;
    overflow: clip;
    overflow-clip-margin: .12em;   /* keeps descenders and diacritics */
    padding-bottom: .08em;
    margin-bottom: -.08em;
    vertical-align: top;
}

.split-word > span {
    display: inline-block;
    animation: word-rise 900ms cubic-bezier(.2, .9, .25, 1) both;
    animation-delay: calc(var(--split-delay, 120ms) + var(--w, 0) * 70ms);
}

@keyframes word-rise {
    from { transform: translate3d(0, 105%, 0); opacity: 0; }
    60% { opacity: 1; }
    to { transform: none; opacity: 1; }
}

/* --------------------------------------------------------------------------------------------
   Theme switch
   site.js runs the change as a view transition and grows the new theme out of the toggle with a
   clip-path circle; the browser's default crossfade would fight that, so it is switched off.
   -------------------------------------------------------------------------------------------- */

::view-transition-old(root),
::view-transition-new(root) {
    animation: none;
    mix-blend-mode: normal;
}

/* --------------------------------------------------------------------------------------------
   Blazor's own indicators
   -------------------------------------------------------------------------------------------- */

/* The thin bar that appears while an enhanced navigation is in flight. */
.page-progress {
    position: fixed;
    inset-block-start: 0;
    inset-inline: 0;
    height: 2px;
    z-index: calc(var(--z-toast) + 1);
    background: transparent;
    overflow: hidden;
    pointer-events: none;
}

.page-progress::after {
    content: "";
    display: block;
    height: 100%;
    background: linear-gradient(90deg, var(--primary), var(--accent), var(--brand));
    animation: progress-indeterminate 1.1s var(--ease-in-out) infinite;
}

/* --------------------------------------------------------------------------------------------
   Reduced motion
   Transitions are shortened rather than removed, so state changes stay perceivable.
   -------------------------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: .01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: .01ms !important;
        scroll-behavior: auto !important;
    }

    [data-reveal] {
        opacity: 1 !important;
        transform: none !important;
    }

    .hero-orb, .marquee-track { animation: none !important; }
    .page-progress::after { animation: none; transform: scaleX(.6); transform-origin: inline-start; }
}
