/* ============================================================================
   RSL Animations Library
   ============================================================================
   A collection of reusable CSS animations for modals, alerts, scroll effects,
   and general UI transitions.

   Categories:
   - Modal animations (fadeIn, fadeOut, zoom, rotate, slide)
   - Inlay alert animations (slide, fade, zoom, spin)
   - Scroll animations (card-reveal, fade-slide-up)
   - Progress bar animations
   ============================================================================ */

/* ──────────────────────────────────────────────
   Modal Animations
   ────────────────────────────────────────────── */

/* Default modal fade */
.modal {
    animation: fadeOut 0.3s;
}

.modal-container.show-modal .modal {
    animation: fadeIn 0.3s;
}

/* Fade In */
@keyframes fadeIn {
    from { opacity: 0; }
    to { opacity: 1; }
}

/* Fade Out */
@keyframes fadeOut {
    from { opacity: 1; }
    to { opacity: 0; }
}

/* Slide from Bottom */
@keyframes fadeSlideFromBottom {
    from {
        transform: translateY(100%);
        opacity: 0;
    }
    to {
        transform: translateY(0);
        opacity: 1;
    }
}

/* Slide from Top */
@keyframes fadeSlideFromTop {
    from {
        transform: translateY(-100%);
        opacity: 0;
    }
    to {
        transform: translateY(0);
        opacity: 1;
    }
}

/* Zoom In */
@keyframes zoomIn {
    from {
        transform: scale(0);
        opacity: 0;
    }
    to {
        transform: scale(1);
        opacity: 1;
    }
}

/* Zoom Out */
@keyframes zoomOut {
    from {
        transform: scale(1);
        opacity: 1;
    }
    to {
        transform: scale(0);
        opacity: 0;
    }
}

/* Rotate In */
@keyframes rotateIn {
    from {
        transform: rotate(-360deg);
        opacity: 0;
    }
    to {
        transform: rotate(0);
        opacity: 1;
    }
}

/* Rotate Out */
@keyframes rotateOut {
    from {
        transform: rotate(0);
        opacity: 1;
    }
    to {
        transform: rotate(360deg);
        opacity: 0;
    }
}

/* Fade Out to Top */
@keyframes fadeOutToTop {
    from {
        transform: translateY(0);
        opacity: 1;
    }
    to {
        transform: translateY(-100%);
        opacity: 0;
    }
}

/* Fade Out to Bottom */
@keyframes fadeOutToBottom {
    from {
        transform: translateY(0);
        opacity: 1;
    }
    to {
        transform: translateY(100%);
        opacity: 0;
    }
}

/* ──────────────────────────────────────────────
   Inlay Alert Animations
   ────────────────────────────────────────────── */

/* Slide in from Right */
@keyframes slide-in-right {
    from {
        transform: translateX(100%);
        opacity: 0;
    }
    to {
        transform: translateX(0);
        opacity: 1;
    }
}

.slide-in-right {
    animation: slide-in-right 0.5s ease-out forwards;
}

/* ──────────────────────────────────────────────
   ANIMATION POLISH PASS (2026-05-16)
   ──────────────────────────────────────────────
   Issue: cards and content reveal felt "glitchy" across multiple AI-builder
   sites. Diagnosis:
     1. ease-out 0.5s is too fast — SaaS-template default, jarring on content
     2. card-reveal used spring curve cubic-bezier(0.34, 1.56, 0.64, 1) — the
        1.56 overshoot caused visible bouncing/wobble
     3. No will-change hints → GPU layers not pre-promoted → first-paint jank
     4. translateY distances of 50-100px were too large — exaggerated motion
   Fix: studio-grade curve cubic-bezier(0.55, 0, 0.1, 1) (heavy ease-out, slow
   start fast finish) at 0.9s, smaller translate distances, will-change set,
   no spring overshoot. Old rules preserved as comments below in case we need
   to revert per-component or revive any of them.
   ────────────────────────────────────────────── */

/* Fade In */
@keyframes fade-in {
    from { opacity: 0; }
    to { opacity: 1; }
}

/* OLD (pre-polish): .fade-in { animation: fade-in 0.5s ease-in forwards; } */
.fade-in {
    animation: fade-in 0.9s cubic-bezier(0.55, 0, 0.1, 1) forwards;
    will-change: opacity;
}

/* Fade from Top */
@keyframes fade-from-top {
    from {
        transform: translateY(-30px);
        opacity: 0;
    }
    to {
        transform: translateY(0);
        opacity: 1;
    }
}

/* OLD (pre-polish): translateY(-100%) at 0.5s ease-out — overshoots far above viewport */
.fade-from-top {
    animation: fade-from-top 0.9s cubic-bezier(0.55, 0, 0.1, 1) forwards;
    will-change: transform, opacity;
}

/* Fade from Bottom */
@keyframes fade-from-bottom {
    from {
        transform: translateY(30px);
        opacity: 0;
    }
    to {
        transform: translateY(0);
        opacity: 1;
    }
}

/* OLD (pre-polish): translateY(100%) at 0.5s ease-out — too large, jarring */
.fade-from-bottom {
    animation: fade-from-bottom 0.9s cubic-bezier(0.55, 0, 0.1, 1) forwards;
    will-change: transform, opacity;
}

/* Fade from Left */
@keyframes fade-from-left {
    from {
        transform: translateX(-30px);
        opacity: 0;
    }
    to {
        transform: translateX(0);
        opacity: 1;
    }
}

/* OLD (pre-polish): translateX(-50px) at 0.5s ease-out */
.fade-from-left {
    animation: fade-from-left 0.9s cubic-bezier(0.55, 0, 0.1, 1) forwards;
    will-change: transform, opacity;
}

/* Fade from Right */
@keyframes fade-from-right {
    from {
        transform: translateX(30px);
        opacity: 0;
    }
    to {
        transform: translateX(0);
        opacity: 1;
    }
}

/* OLD (pre-polish): translateX(50px) at 0.5s ease-out */
.fade-from-right {
    animation: fade-from-right 0.9s cubic-bezier(0.55, 0, 0.1, 1) forwards;
    will-change: transform, opacity;
}

/* Fade Zoom In */
@keyframes fade-zoom-in {
    from {
        transform: scale(0.92);
        opacity: 0;
    }
    to {
        transform: scale(1);
        opacity: 1;
    }
}

/* OLD (pre-polish): scale(0) at 0.5s ease-out — too dramatic, content "exploded" in */
.fade-zoom-in {
    animation: fade-zoom-in 0.9s cubic-bezier(0.55, 0, 0.1, 1) forwards;
    will-change: transform, opacity;
}

/* Spin In */
@keyframes spin-in {
    from {
        transform: rotate(-360deg);
        opacity: 0;
    }
    to {
        transform: rotate(0);
        opacity: 1;
    }
}

/* OLD (pre-polish): 0.5s ease-out — kept timing same here, full rotation reads OK */
.spin-in {
    animation: spin-in 0.9s cubic-bezier(0.55, 0, 0.1, 1) forwards;
    will-change: transform, opacity;
}

/* Fade Slide Up */
@keyframes fade-slide-up {
    from {
        transform: translateY(30px);
        opacity: 0;
    }
    to {
        transform: translateY(0);
        opacity: 1;
    }
}

/* OLD (pre-polish): translateY(50px) at 0.5s ease-out */
.fade-slide-up {
    animation: fade-slide-up 0.9s cubic-bezier(0.55, 0, 0.1, 1) forwards;
    will-change: transform, opacity;
}

/* Fade Slide Down */
@keyframes fade-slide-down {
    from {
        transform: translateY(-30px);
        opacity: 0;
    }
    to {
        transform: translateY(0);
        opacity: 1;
    }
}

/* OLD (pre-polish): translateY(-50px) at 0.5s ease-out */
.fade-slide-down {
    animation: fade-slide-down 0.9s cubic-bezier(0.55, 0, 0.1, 1) forwards;
    will-change: transform, opacity;
}

/* Blink In */
@keyframes blink-in {
    0% { opacity: 0; }
    25% { opacity: 1; }
    50% { opacity: 0; }
    75% { opacity: 1; }
    100% { opacity: 1; }
}

.blink-in {
    animation: blink-in 0.5s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}

/* Expand Open */
@keyframes expand-open {
    from {
        transform: scaleY(0);
        opacity: 0;
    }
    to {
        transform: scaleY(1);
        opacity: 1;
    }
}

.expand-open {
    animation: expand-open 0.5s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}

/* ──────────────────────────────────────────────
   Progress Bar Animation
   ────────────────────────────────────────────── */

.progress-bar {
    position: absolute;
    bottom: 0;
    left: 0;
    height: 2px;
    background-color: #FFA500;
    width: 0;
    transition: none;
    animation-name: fill;
    animation-timing-function: linear;
    animation-fill-mode: forwards;
}

@keyframes fill {
    from { width: 0; }
    to { width: 100%; }
}

/* ──────────────────────────────────────────────
   Scroll Animations (Animate on Scroll)
   ────────────────────────────────────────────── */

/* Card Reveal - Custom animation for cards on scroll
   POLISHED 2026-05-16: was the main "glitchy card animation" culprit.
   Old curve cubic-bezier(0.34, 1.56, 0.64, 1) had 1.56 overshoot — cards
   bounced past final position and wobbled back. Combined with simultaneous
   translateY + scale + blur, it read as jank. Fix: heavier ease-out curve
   matching the studio-grade animations elsewhere, smaller translate, lighter
   blur, longer duration so motion reads considered rather than abrupt. */
@keyframes card-reveal {
    from {
        opacity: 0;
        transform: translateY(20px) scale(0.98);
        filter: blur(2px);
    }
    to {
        opacity: 1;
        transform: translateY(0) scale(1);
        filter: blur(0);
    }
}

/* OLD (pre-polish):
   @keyframes card-reveal {
       from { opacity: 0; transform: translateY(30px) scale(0.95); filter: blur(4px); }
       to   { opacity: 1; transform: translateY(0) scale(1); filter: blur(0); }
   }
   .card-reveal { animation: card-reveal 0.6s cubic-bezier(0.34, 1.56, 0.64, 1) forwards; }
*/
.card-reveal {
    animation: card-reveal 0.9s cubic-bezier(0.55, 0, 0.1, 1) forwards;
    will-change: transform, opacity, filter;
}

/* Base class for elements waiting to animate.
   Content is visible by default — the JS opts-in to hiding it by stamping
   'js-anim-armed' on <html>. This prevents content staying invisible forever
   if the IntersectionObserver never fires (sticky, clipped, or JS error).
   Pattern sourced from v186 production CSS (nandi-ghee.in). */
.rsl-animate-pending {
    opacity: 1;
}
html.js-anim-armed .rsl-animate-pending {
    opacity: 0;
}

/* ──────────────────────────────────────────────
   Per-Character Blur Reveal (Studio-pillow / premium typography move)
   Stamped on headings by V2 _renderHeading when config.revealStyle === 'per-char-blur'.
   Each character is wrapped in <span class="rsl-char">; staggered via --rsl-char-index.
   Reference: Lusion Oryzo, Espacio La Nube, Novu headlines.
   ────────────────────────────────────────────── */

/* The parent heading's pending opacity rule clashes with per-char reveal
   (children invisible because parent is invisible). When per-char-blur is
   set, let the children control their own visibility instead. */
[data-animate="per-char-blur"].rsl-animate-pending {
    opacity: 1;
}

.rsl-char {
    display: inline-block;
    opacity: 0;
    filter: blur(8px);
    transform: translateY(0.1em);
    transition:
        opacity 0.6s cubic-bezier(0.55, 0, 0.1, 1),
        filter 0.6s cubic-bezier(0.55, 0, 0.1, 1),
        transform 0.6s cubic-bezier(0.55, 0, 0.1, 1);
    transition-delay: calc(var(--rsl-char-index, 0) * 30ms);
    will-change: opacity, filter, transform;
}

.rsl-char.revealed {
    opacity: 1;
    filter: blur(0);
    transform: translateY(0);
}

/* ──────────────────────────────────────────────
   Reduced Motion Support
   ────────────────────────────────────────────── */

@media (prefers-reduced-motion: reduce) {
    .rsl-animate-pending {
        opacity: 1 !important;
    }

    /* Disable all animations for accessibility */
    .fade-in,
    .fade-from-top,
    .fade-from-bottom,
    .fade-from-left,
    .fade-from-right,
    .fade-zoom-in,
    .slide-in-right,
    .fade-slide-up,
    .fade-slide-down,
    .spin-in,
    .expand-open,
    .blink-in,
    .card-reveal {
        animation: none !important;
        opacity: 1 !important;
        transform: none !important;
        filter: none !important;
    }

    /* Per-char-blur: skip the staggered reveal, show all characters immediately. */
    .rsl-char {
        opacity: 1 !important;
        filter: none !important;
        transform: none !important;
        transition: none !important;
    }
}
