/*!
 * Noemi Slider Pro — Layer / Text Animations v1.0
 *
 * Entry animations for layers, including split-text (per-word / per-letter)
 * with staggered timing. Driven by:
 *   .nsp-layer.nsp-animate-in[data-entry="X"]
 *   .nsp-layer[data-split="words|letters"] → JS wraps content in .nsp-word / .nsp-char
 *
 * Stagger is applied via --nsp-stagger-i (index) set by JS on each fragment.
 */

/* ============================================================
   BASE
   ============================================================ */
.nsp-layer {
    --nsp-anim-dur:   800ms;
    --nsp-anim-delay: 0ms;
    --nsp-anim-ease:  cubic-bezier(0.22, 1, 0.36, 1);
    --nsp-stagger:    45ms; /* per-fragment delay for split text */
}

/* Layers start hidden until animated in (JS adds .nsp-animate-in).
   Gated behind .nsp-js so that if the slider script never runs, content
   stays visible instead of being permanently hidden. */
.nsp-js .nsp-layer[data-entry]:not([data-entry="none"]):not(.nsp-animate-in) {
    opacity: 0;
}

/* Whole-layer animations: apply to the layer box itself when NOT split */
.nsp-layer.nsp-animate-in:not([data-split="words"]):not([data-split="letters"]) {
    animation-duration: var(--nsp-anim-dur);
    animation-delay: var(--nsp-anim-delay);
    animation-timing-function: var(--nsp-anim-ease);
    animation-fill-mode: both;
}

/* ============================================================
   WHOLE-LAYER ENTRY ANIMATIONS (no split)
   These respect the center anchor via translate(-50%,-50%) base.
   We animate a wrapper-safe set: opacity + transform offsets.
   ============================================================ */
.nsp-layer.nsp-animate-in[data-entry="fade"]        { animation-name: nspLFade; }
.nsp-layer.nsp-animate-in[data-entry="slide-up"]    { animation-name: nspLSlideUp; }
.nsp-layer.nsp-animate-in[data-entry="slide-down"]  { animation-name: nspLSlideDown; }
.nsp-layer.nsp-animate-in[data-entry="slide-left"]  { animation-name: nspLSlideLeft; }
.nsp-layer.nsp-animate-in[data-entry="slide-right"] { animation-name: nspLSlideRight; }
.nsp-layer.nsp-animate-in[data-entry="zoom-in"]     { animation-name: nspLZoomIn; }
.nsp-layer.nsp-animate-in[data-entry="zoom-out"]    { animation-name: nspLZoomOut; }
.nsp-layer.nsp-animate-in[data-entry="blur-in"]     { animation-name: nspLBlurIn; }
.nsp-layer.nsp-animate-in[data-entry="rotate-in"]   { animation-name: nspLRotateIn; }
.nsp-layer.nsp-animate-in[data-entry="flip-in"]     { animation-name: nspLFlipIn; }
.nsp-layer.nsp-animate-in[data-entry="bounce-in"]   { animation-name: nspLBounceIn; animation-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1); }
.nsp-layer.nsp-animate-in[data-entry="skew-in"]     { animation-name: nspLSkewIn; }
.nsp-layer.nsp-animate-in[data-entry="none"]        { animation: none; opacity: 1; }

@keyframes nspLFade       { from { opacity: 0; } to { opacity: 1; } }
@keyframes nspLSlideUp    { from { opacity: 0; transform: translateY(40px); } to { opacity: 1; transform: translateY(0); } }
@keyframes nspLSlideDown  { from { opacity: 0; transform: translateY(-40px); } to { opacity: 1; transform: translateY(0); } }
@keyframes nspLSlideLeft  { from { opacity: 0; transform: translateX(60px); } to { opacity: 1; transform: translateX(0); } }
@keyframes nspLSlideRight { from { opacity: 0; transform: translateX(-60px); } to { opacity: 1; transform: translateX(0); } }
@keyframes nspLZoomIn     { from { opacity: 0; transform: scale(0.7); } to { opacity: 1; transform: scale(1); } }
@keyframes nspLZoomOut    { from { opacity: 0; transform: scale(1.3); } to { opacity: 1; transform: scale(1); } }
@keyframes nspLBlurIn     { from { opacity: 0; filter: blur(16px); } to { opacity: 1; filter: blur(0); } }
@keyframes nspLRotateIn   { from { opacity: 0; transform: rotate(-8deg) scale(0.9); } to { opacity: 1; transform: rotate(0) scale(1); } }
@keyframes nspLFlipIn     { from { opacity: 0; transform: perspective(600px) rotateX(-90deg); } to { opacity: 1; transform: perspective(600px) rotateX(0); } }
@keyframes nspLBounceIn   { 0% { opacity: 0; transform: scale(0.3); } 60% { opacity: 1; transform: scale(1.05); } 100% { transform: scale(1); } }
@keyframes nspLSkewIn     { from { opacity: 0; transform: skewX(12deg) translateX(40px); } to { opacity: 1; transform: skewX(0) translateX(0); } }

/* Center-anchored variants keep the translate(-50%,-50%) base */
.nsp-anchor--center.nsp-animate-in[data-entry="fade"]        { animation-name: nspLFadeC; }
.nsp-anchor--center.nsp-animate-in[data-entry="slide-up"]    { animation-name: nspLSlideUpC; }
.nsp-anchor--center.nsp-animate-in[data-entry="slide-down"]  { animation-name: nspLSlideDownC; }
.nsp-anchor--center.nsp-animate-in[data-entry="slide-left"]  { animation-name: nspLSlideLeftC; }
.nsp-anchor--center.nsp-animate-in[data-entry="slide-right"] { animation-name: nspLSlideRightC; }
.nsp-anchor--center.nsp-animate-in[data-entry="zoom-in"]     { animation-name: nspLZoomInC; }
.nsp-anchor--center.nsp-animate-in[data-entry="zoom-out"]    { animation-name: nspLZoomOutC; }
.nsp-anchor--center.nsp-animate-in[data-entry="blur-in"]     { animation-name: nspLBlurInC; }

@keyframes nspLFadeC       { from { opacity: 0; transform: translate(-50%,-50%); } to { opacity: 1; transform: translate(-50%,-50%); } }
@keyframes nspLSlideUpC    { from { opacity: 0; transform: translate(-50%, calc(-50% + 40px)); } to { opacity: 1; transform: translate(-50%,-50%); } }
@keyframes nspLSlideDownC  { from { opacity: 0; transform: translate(-50%, calc(-50% - 40px)); } to { opacity: 1; transform: translate(-50%,-50%); } }
@keyframes nspLSlideLeftC  { from { opacity: 0; transform: translate(calc(-50% + 60px), -50%); } to { opacity: 1; transform: translate(-50%,-50%); } }
@keyframes nspLSlideRightC { from { opacity: 0; transform: translate(calc(-50% - 60px), -50%); } to { opacity: 1; transform: translate(-50%,-50%); } }
@keyframes nspLZoomInC     { from { opacity: 0; transform: translate(-50%,-50%) scale(0.7); } to { opacity: 1; transform: translate(-50%,-50%) scale(1); } }
@keyframes nspLZoomOutC    { from { opacity: 0; transform: translate(-50%,-50%) scale(1.3); } to { opacity: 1; transform: translate(-50%,-50%) scale(1); } }
@keyframes nspLBlurInC     { from { opacity: 0; filter: blur(16px); transform: translate(-50%,-50%); } to { opacity: 1; filter: blur(0); transform: translate(-50%,-50%); } }

/* ============================================================
   MASK REVEAL (clip-path based, works on whole layer)
   ============================================================ */
.nsp-layer.nsp-animate-in[data-entry="mask-up"] { animation-name: nspLMaskUp; }
@keyframes nspLMaskUp {
    from { opacity: 1; clip-path: inset(100% 0 0 0); }
    to   { opacity: 1; clip-path: inset(0 0 0 0); }
}

/* ============================================================
   SPLIT TEXT — words & letters
   JS wraps each fragment: <span class="nsp-word"> / <span class="nsp-char">
   and sets style="--nsp-i: N" for stagger ordering.
   ============================================================ */
.nsp-js .nsp-layer[data-split="words"] .nsp-word,
.nsp-js .nsp-layer[data-split="letters"] .nsp-char {
    display: inline-block;
    opacity: 0;
    will-change: transform, opacity, filter;
    /* stagger: each fragment delayed by index * --nsp-stagger, plus base delay */
    animation-delay: calc(var(--nsp-anim-delay) + var(--nsp-i, 0) * var(--nsp-stagger));
    animation-duration: var(--nsp-anim-dur);
    animation-timing-function: var(--nsp-anim-ease);
    animation-fill-mode: both;
}
/* Fragments are inline-block regardless of JS so layout is stable */
.nsp-layer[data-split="words"] .nsp-word,
.nsp-layer[data-split="letters"] .nsp-char { display: inline-block; }
/* Preserve spaces between words */
.nsp-layer[data-split="words"] .nsp-word::after { content: '\00a0'; }
.nsp-layer[data-split="letters"] .nsp-char--space { width: 0.3em; }

/* Only run fragment animations once the layer is animate-in */
.nsp-js .nsp-layer:not(.nsp-animate-in)[data-split="words"] .nsp-word,
.nsp-js .nsp-layer:not(.nsp-animate-in)[data-split="letters"] .nsp-char {
    animation-play-state: paused;
}

/* SAFETY NET: once the animation has had time to finish, JS adds
   .nsp-anim-done which forces the final visible state. Guarantees text is
   never left invisible if an animation is interrupted or doesn't fire. */
.nsp-layer.nsp-anim-done,
.nsp-layer.nsp-anim-done .nsp-word,
.nsp-layer.nsp-anim-done .nsp-char {
    opacity: 1 !important;
    filter: none !important;
}

/* WORD animations */
.nsp-animate-in[data-entry="words-fade"] .nsp-word  { animation-name: nspFragFade; }
.nsp-animate-in[data-entry="words-up"]   .nsp-word  { animation-name: nspFragUp; }
.nsp-animate-in[data-entry="words-blur"] .nsp-word  { animation-name: nspFragBlur; }
.nsp-animate-in[data-entry="words-flip"] .nsp-word  { animation-name: nspFragFlip; }

/* LETTER animations */
.nsp-animate-in[data-entry="letters-fade"]  .nsp-char { animation-name: nspFragFade; }
.nsp-animate-in[data-entry="letters-up"]    .nsp-char { animation-name: nspFragUp; }
.nsp-animate-in[data-entry="letters-drop"]  .nsp-char { animation-name: nspFragDrop; }
.nsp-animate-in[data-entry="letters-wave"]  .nsp-char { animation-name: nspFragWave; animation-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1); }
.nsp-animate-in[data-entry="letters-scale"] .nsp-char { animation-name: nspFragScale; animation-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1); }

@keyframes nspFragFade  { from { opacity: 0; } to { opacity: 1; } }
@keyframes nspFragUp    { from { opacity: 0; transform: translateY(1.1em); } to { opacity: 1; transform: translateY(0); } }
@keyframes nspFragDrop  { from { opacity: 0; transform: translateY(-0.8em); } to { opacity: 1; transform: translateY(0); } }
@keyframes nspFragBlur  { from { opacity: 0; filter: blur(10px); transform: translateY(0.3em); } to { opacity: 1; filter: blur(0); transform: translateY(0); } }
@keyframes nspFragFlip  { from { opacity: 0; transform: perspective(400px) rotateX(-90deg); } to { opacity: 1; transform: perspective(400px) rotateX(0); } }
@keyframes nspFragWave  { 0% { opacity: 0; transform: translateY(0.8em); } 60% { opacity: 1; transform: translateY(-0.15em); } 100% { opacity: 1; transform: translateY(0); } }
@keyframes nspFragScale { from { opacity: 0; transform: scale(0); } to { opacity: 1; transform: scale(1); } }

/* ============================================================
   TYPEWRITER
   JS sets up the character reveal + caret; CSS provides the caret.
   ============================================================ */
.nsp-layer[data-entry="typewriter"] .nsp-tw-caret {
    display: inline-block;
    width: 0.08em;
    background: currentColor;
    margin-left: 0.04em;
    animation: nspCaretBlink 900ms steps(1) infinite;
    align-self: stretch;
    /* height matches line via inline-block + line-height */
}
.nsp-js .nsp-layer[data-entry="typewriter"] .nsp-char {
    opacity: 0;
}
.nsp-layer.nsp-animate-in[data-entry="typewriter"] .nsp-char.is-typed {
    opacity: 1;
}
@keyframes nspCaretBlink { 0%, 50% { opacity: 1; } 50.01%, 100% { opacity: 0; } }

/* ============================================================
   RTL — flip horizontal slide directions
   ============================================================ */
.nsp-rtl .nsp-layer.nsp-animate-in[data-entry="slide-left"]  { animation-name: nspLSlideRight; }
.nsp-rtl .nsp-layer.nsp-animate-in[data-entry="slide-right"] { animation-name: nspLSlideLeft; }

/* ============================================================
   REDUCED MOTION
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
    .nsp-layer.nsp-animate-in,
    .nsp-layer[data-split] .nsp-word,
    .nsp-layer[data-split] .nsp-char {
        animation-name: nspLFade !important;
        animation-duration: 300ms !important;
        animation-delay: 0ms !important;
    }
}
