agentsclimarketplace

Animaciones gsap

Skill magallon/website-audit-toolkit/animaciones-gsap

Pre-production audit protocol for static websites — 10 sequential skills covering performance, accessibility, SEO, security, and more

Install
npx -y skills add magallon/website-audit-toolkit --skill animaciones-gsap

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Patrones de animación GSAP + ScrollTrigger en Vanilla JS para landing pages. Usa este skill cuando el usuario pida animaciones, transiciones, efectos al scroll, o interacciones avanzadas en una landing page. Incluye navbar con cambio de estado, fade-up, split text, parallax, typewriter, acordeón, baraja de cards y más.

SKILL.md

11.8 KB, as published. Nobody here has run it

Skill: Animaciones GSAP para Landing Pages (Vanilla JS)

Reglas Generales de Animación

  • Siempre carga GSAP y ScrollTrigger vía CDN al final del body con defer.
  • Todo el código de animación va en app.js. Nunca en archivos separados.
  • Usa gsap.context() para agrupar animaciones y facilitar cleanup.
  • Registra ScrollTrigger una sola vez al inicio: gsap.registerPlugin(ScrollTrigger);
  • Todas las animaciones deben ser sobrias. Duración mínima 0.4s. Ease recomendado: power2.out o power3.out.
  • Nunca uses animaciones "infantiles" (bounce exagerado, colores parpadeantes, zoom agresivo).
  • Respeta prefers-reduced-motion: desactiva animaciones si el usuario lo pide.

Estructura base de app.js

document.addEventListener('DOMContentLoaded', () => {
  gsap.registerPlugin(ScrollTrigger);

  const ctx = gsap.context(() => {
    // Aquí van todas las animaciones
    initNavbar();
    initHeroAnimations();
    initScrollAnimations();
    initInteractions();
  });

  // Cleanup si se necesita
  // ctx.revert();
});

Verificar prefers-reduced-motion

const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;

if (!prefersReducedMotion) {
  // Inicializar animaciones
}

Patrón 1: Navbar con cambio de estado al scroll

Navbar tipo píldora flotante que cambia de transparente a sólida al hacer scroll.

function initNavbar() {
  const navbar = document.querySelector('.navbar');

  ScrollTrigger.create({
    start: 'top -80',
    onUpdate: (self) => {
      if (self.direction === 1 && self.scroll() > 80) {
        navbar.classList.add('navbar-scrolled');
      }
      if (self.scroll() < 80) {
        navbar.classList.remove('navbar-scrolled');
      }
    }
  });
}

CSS necesario en styles.css:

.navbar {
  position: fixed;
  top: 1rem;
  left: 50%;
  transform: translateX(-50%);
  z-index: 1000;
  padding: 0.75rem 2rem;
  border-radius: 9999px;
  transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
  background: transparent;
}

.navbar-scrolled {
  background: rgba(255, 255, 255, 0.85);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  box-shadow: 0 4px 30px rgba(0, 0, 0, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.3);
}

Patrón 2: Fade-up escalonado (Hero)

Aparición escalonada de elementos del hero de abajo hacia arriba.

function initHeroAnimations() {
  const heroElements = gsap.utils.toArray('.hero-animate');

  gsap.from(heroElements, {
    y: 60,
    opacity: 0,
    duration: 0.8,
    stagger: 0.15,
    ease: 'power3.out',
    delay: 0.3
  });
}

HTML: añadir clase hero-animate a cada elemento que debe aparecer en secuencia.

<h1 class="hero-animate">Titular principal</h1>
<p class="hero-animate">Subtítulo descriptivo</p>
<div class="hero-animate">
  <a href="#" class="btn-cta">CTA Principal</a>
  <a href="#" class="btn-secondary">CTA Secundario</a>
</div>

Patrón 3: Aparición al scroll (Secciones generales)

Elementos que aparecen suavemente al entrar en el viewport.

function initScrollAnimations() {
  // Fade up general
  gsap.utils.toArray('.animate-on-scroll').forEach(el => {
    gsap.from(el, {
      y: 40,
      opacity: 0,
      duration: 0.7,
      ease: 'power2.out',
      scrollTrigger: {
        trigger: el,
        start: 'top 85%',
        toggleActions: 'play none none none'
      }
    });
  });

  // Entrada desde izquierda
  gsap.utils.toArray('.animate-from-left').forEach(el => {
    gsap.from(el, {
      x: -60,
      opacity: 0,
      duration: 0.7,
      ease: 'power2.out',
      scrollTrigger: {
        trigger: el,
        start: 'top 85%',
        toggleActions: 'play none none none'
      }
    });
  });

  // Entrada desde derecha
  gsap.utils.toArray('.animate-from-right').forEach(el => {
    gsap.from(el, {
      x: 60,
      opacity: 0,
      duration: 0.7,
      ease: 'power2.out',
      scrollTrigger: {
        trigger: el,
        start: 'top 85%',
        toggleActions: 'play none none none'
      }
    });
  });

  // Escalonado para grupos (cards, features, timeline steps)
  gsap.utils.toArray('.animate-stagger-group').forEach(group => {
    const items = group.querySelectorAll('.stagger-item');
    gsap.from(items, {
      y: 40,
      opacity: 0,
      duration: 0.6,
      stagger: 0.12,
      ease: 'power2.out',
      scrollTrigger: {
        trigger: group,
        start: 'top 80%',
        toggleActions: 'play none none none'
      }
    });
  });
}

Patrón 4: Split text (Texto que aparece por palabras/líneas)

Para secciones de manifiesto o titulares dramáticos.

function initSplitText(selector) {
  const elements = document.querySelectorAll(selector);

  elements.forEach(el => {
    const text = el.textContent;
    const words = text.split(' ');
    el.innerHTML = words.map(word =>
      `<span class="inline-block overflow-hidden"><span class="split-word inline-block">${word}</span></span>`
    ).join(' ');

    gsap.from(el.querySelectorAll('.split-word'), {
      y: '100%',
      opacity: 0,
      duration: 0.6,
      stagger: 0.05,
      ease: 'power3.out',
      scrollTrigger: {
        trigger: el,
        start: 'top 80%',
        toggleActions: 'play none none none'
      }
    });
  });
}

Patrón 5: Typewriter (Texto que se escribe solo)

Efecto de terminal/telemetría con cursor intermitente.

function initTypewriter(selector, messages, speed = 50) {
  const el = document.querySelector(selector);
  let messageIndex = 0;
  let charIndex = 0;
  let isDeleting = false;

  function type() {
    const currentMessage = messages[messageIndex];

    if (isDeleting) {
      el.textContent = currentMessage.substring(0, charIndex - 1);
      charIndex--;
    } else {
      el.textContent = currentMessage.substring(0, charIndex + 1);
      charIndex++;
    }

    let delay = isDeleting ? speed / 2 : speed;

    if (!isDeleting && charIndex === currentMessage.length) {
      delay = 2000; // Pausa al completar
      isDeleting = true;
    } else if (isDeleting && charIndex === 0) {
      isDeleting = false;
      messageIndex = (messageIndex + 1) % messages.length;
      delay = 500;
    }

    setTimeout(type, delay);
  }

  type();
}

// Uso:
// initTypewriter('.typewriter-text', [
//   'Optimizando tu embudo…',
//   'Generando landing…',
//   'Ajustando conversiones…'
// ]);

CSS para el cursor:

.typewriter-container::after {
  content: '|';
  animation: blink 0.8s infinite;
  color: var(--color-accent);
  font-weight: 300;
}

@keyframes blink {
  0%, 50% { opacity: 1; }
  51%, 100% { opacity: 0; }
}

Patrón 6: Acordeón (FAQ)

Acordeón con transición suave de altura.

function initAccordion() {
  const items = document.querySelectorAll('.accordion-item');

  items.forEach(item => {
    const trigger = item.querySelector('.accordion-trigger');
    const content = item.querySelector('.accordion-content');
    const icon = item.querySelector('.accordion-icon');

    // Iniciar cerrado
    gsap.set(content, { height: 0, overflow: 'hidden' });

    trigger.addEventListener('click', () => {
      const isOpen = item.classList.contains('is-open');

      // Cerrar todos los demás
      items.forEach(other => {
        if (other !== item && other.classList.contains('is-open')) {
          other.classList.remove('is-open');
          gsap.to(other.querySelector('.accordion-content'), {
            height: 0,
            duration: 0.4,
            ease: 'power2.inOut'
          });
          gsap.to(other.querySelector('.accordion-icon'), {
            rotation: 0,
            duration: 0.3
          });
        }
      });

      // Toggle actual
      if (isOpen) {
        item.classList.remove('is-open');
        gsap.to(content, { height: 0, duration: 0.4, ease: 'power2.inOut' });
        gsap.to(icon, { rotation: 0, duration: 0.3 });
      } else {
        item.classList.add('is-open');
        gsap.to(content, { height: 'auto', duration: 0.4, ease: 'power2.inOut' });
        gsap.to(icon, { rotation: 45, duration: 0.3 });
      }
    });
  });
}

Patrón 7: Parallax sutil en fondos

Movimiento sutil del fondo al hacer scroll para dar profundidad.

function initParallax() {
  gsap.utils.toArray('.parallax-bg').forEach(bg => {
    gsap.to(bg, {
      yPercent: -20,
      ease: 'none',
      scrollTrigger: {
        trigger: bg.parentElement,
        start: 'top bottom',
        end: 'bottom top',
        scrub: 1
      }
    });
  });
}

HTML necesario:

<section class="relative overflow-hidden">
  <div class="parallax-bg absolute inset-0 -top-[20%] -bottom-[20%]">
    <img src="imagen.jpg" class="w-full h-full object-cover" alt="Descripción">
  </div>
  <div class="relative z-10">
    <!-- Contenido de la sección -->
  </div>
</section>

Patrón 8: Counter animado (Números que suben)

Para secciones de métricas o prueba social.

function initCounters() {
  gsap.utils.toArray('.counter').forEach(counter => {
    const target = parseInt(counter.getAttribute('data-target'));
    const suffix = counter.getAttribute('data-suffix') || '';

    gsap.from(counter, {
      textContent: 0,
      duration: 2,
      ease: 'power1.out',
      snap: { textContent: 1 },
      scrollTrigger: {
        trigger: counter,
        start: 'top 85%',
        toggleActions: 'play none none none'
      },
      onUpdate: function() {
        counter.textContent = Math.ceil(parseFloat(counter.textContent)) + suffix;
      }
    });
  });
}

HTML:

<span class="counter" data-target="147" data-suffix="+">0</span>

Patrón 9: Menú hamburger móvil

Toggle de menú en móvil con animación.

function initMobileMenu() {
  const menuBtn = document.querySelector('.menu-toggle');
  const mobileMenu = document.querySelector('.mobile-menu');
  const menuLinks = mobileMenu.querySelectorAll('a');
  let isOpen = false;

  menuBtn.addEventListener('click', () => {
    isOpen = !isOpen;
    menuBtn.classList.toggle('is-active');

    if (isOpen) {
      gsap.to(mobileMenu, {
        clipPath: 'circle(150% at top right)',
        duration: 0.5,
        ease: 'power3.inOut'
      });
      gsap.from(menuLinks, {
        y: 20,
        opacity: 0,
        stagger: 0.08,
        duration: 0.4,
        delay: 0.2
      });
    } else {
      gsap.to(mobileMenu, {
        clipPath: 'circle(0% at top right)',
        duration: 0.4,
        ease: 'power2.in'
      });
    }
  });

  // Cerrar al hacer click en un link
  menuLinks.forEach(link => {
    link.addEventListener('click', () => {
      isOpen = false;
      menuBtn.classList.remove('is-active');
      gsap.to(mobileMenu, {
        clipPath: 'circle(0% at top right)',
        duration: 0.4,
        ease: 'power2.in'
      });
    });
  });
}

CSS inicial del menú:

.mobile-menu {
  clip-path: circle(0% at top right);
  position: fixed;
  inset: 0;
  z-index: 999;
}

Patrón 10: Punto "en vivo" pulsante

Indicador de estado tipo "sistema activo".

.pulse-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background-color: #22c55e;
  position: relative;
  display: inline-block;
}

.pulse-dot::before {
  content: '';
  position: absolute;
  inset: -3px;
  border-radius: 50%;
  background-color: #22c55e;
  animation: pulse-ring 2s ease-out infinite;
}

@keyframes pulse-ring {
  0% { transform: scale(1); opacity: 0.5; }
  100% { transform: scale(2.5); opacity: 0; }
}

HTML:

<span class="pulse-dot"></span> Sistema activo

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.