npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

wespark-frontend

v1.0.1

Published

React component library & animation hooks for portfolio and agency websites. Built on GSAP, Lenis, and split-type.

Readme

wespark-frontend

React component library & animation hooks for portfolio and agency websites. Built on GSAP, Lenis, and split-type.

Install

npm install wespark-frontend gsap lenis split-type

gsap, lenis, and split-type are peer dependencies — you install them alongside the library.

Quick Start

import { WesparkProvider, Navbar, Hero, TextReveal } from 'wespark-frontend';
import 'wespark-frontend/styles';

function App() {
  return (
    <WesparkProvider smoothScroll={{ duration: 1.2, lerp: 0.1 }}>
      <Navbar
        brand="Studio"
        links={[
          { label: 'Work', href: '/work' },
          { label: 'About', href: '/about' },
          { label: 'Contact', href: '/contact' },
        ]}
        socials={[
          { label: 'Twitter', href: 'https://twitter.com' },
          { label: 'Instagram', href: 'https://instagram.com' },
        ]}
      />
      <Hero title="We build things" subtitle="A creative studio" />
      <TextReveal as="p" animation="lines">
        Some paragraph that reveals line by line on scroll.
      </TextReveal>
    </WesparkProvider>
  );
}

Theming

Override CSS variables to match your brand:

:root {
  --wespark-bg: #0a0a0a;
  --wespark-text: #f2ede6;
  --wespark-accent: #555;
  --wespark-font-primary: 'Neue Montreal', sans-serif;
  --wespark-font-mono: 'DM Mono', monospace;
  --wespark-nav-height: 60px;
  --wespark-transition-duration: 0.5s;
  --wespark-ease: cubic-bezier(0.76, 0, 0.24, 1);
  --wespark-border-color: rgba(255, 255, 255, 0.15);
  --wespark-radius: 0.5rem;
}

All components also accept a className prop for full style overrides.


Components

WesparkProvider

Wraps your app with Lenis smooth scrolling and context.

<WesparkProvider smoothScroll={{ duration: 1.2, lerp: 0.1 }}>
  {children}
</WesparkProvider>

| Prop | Type | Default | Description | |------|------|---------|-------------| | smoothScroll | SmoothScrollOptions \| false | {} | Lenis config. Pass false to disable. |

SmoothScrollOptions:

| Option | Type | Default | Description | |--------|------|---------|-------------| | duration | number | 1.2 | Scroll duration (desktop) | | lerp | number | 0.1 | Linear interpolation factor (desktop) | | smoothWheel | boolean | true | Smooth wheel scrolling | | syncTouch | boolean | true | Sync touch events | | touchMultiplier | number | 2 | Touch scroll multiplier (desktop) | | wheelMultiplier | number | 1 | Wheel scroll multiplier | | mobileDuration | number | 0.8 | Scroll duration (mobile) | | mobileLerp | number | 0.075 | Lerp factor (mobile) | | mobileTouchMultiplier | number | 1.5 | Touch multiplier (mobile) | | mobileBreakpoint | number | 1000 | Width threshold for mobile settings |


Navbar

Fixed navigation bar with full-screen animated menu overlay.

<Navbar
  brand="Studio"
  links={[
    { label: 'Work', href: '/work' },
    { label: 'About', href: '/about' },
  ]}
  socials={[
    { label: 'Twitter', href: 'https://twitter.com' },
  ]}
  hideOnScroll={true}
  blendMode={true}
/>

| Prop | Type | Default | Description | |------|------|---------|-------------| | brand | string | "" | Brand name / logo text | | links | NavbarLink[] | [] | Menu links ({ label, href }) | | socials | NavbarSocial[] | [] | Social links in menu footer | | className | string | "" | Custom class for the nav bar | | menuClassName | string | "" | Custom class for the menu overlay | | hideOnScroll | boolean | true | Hide navbar on scroll down | | blendMode | boolean | true | Apply mix-blend-mode: difference |

Animations: Hamburger rotates on toggle. Menu reveals with clip-path. Links stagger in with GSAP (power3.inOut, 0.075s stagger). Lenis scroll locks while menu is open.


Hero

Full-viewport hero section with animated title and subtitle.

<Hero
  title="We build things"
  subtitle="A creative studio"
  titleDelay={0.3}
/>

| Prop | Type | Default | Description | |------|------|---------|-------------| | title | string | required | Main heading text | | subtitle | string | — | Subtitle text | | className | string | "" | Custom class | | titleClassName | string | "" | Custom class for title | | subtitleClassName | string | "" | Custom class for subtitle | | animateTitle | boolean | true | Animate title with split-type | | animateSubtitle | boolean | true | Fade in subtitle | | titleDuration | number | 1 | Title animation duration | | titleStagger | number | 0.1 | Stagger between title lines | | titleEase | string | "power4.out" | GSAP easing | | titleDelay | number | 0.3 | Delay before animation starts | | children | ReactNode | — | Extra content inside hero |

Animations: Title splits into lines with split-type, each line slides up from y: 100% with overflow hidden masking. Subtitle fades in after title completes.


TextReveal

Text that reveals line-by-line, word-by-word, or character-by-character on scroll.

<TextReveal as="p" type="lines" stagger={0.1} scrollStart="top 85%">
  This paragraph reveals line by line as you scroll down.
</TextReveal>

<TextReveal as="h2" type="words" duration={0.8} ease="power3.out">
  Each word animates individually
</TextReveal>

| Prop | Type | Default | Description | |------|------|---------|-------------| | children | ReactNode | required | Text content | | as | string | "p" | HTML tag to render | | className | string | "" | Custom class | | type | "lines" \| "words" \| "chars" | "lines" | Split type | | duration | number | 1 | Animation duration | | stagger | number | 0.1 | Delay between each element | | ease | string | "power4.out" | GSAP easing | | delay | number | 0 | Initial delay | | direction | "bottom" \| "top" | "bottom" | Direction elements slide from | | animateOnScroll | boolean | true | Trigger on scroll (vs immediate) | | scrollStart | string | "top 85%" | ScrollTrigger start position | | once | boolean | true | Animate only once |


AnimatedButton

Button with hover effects. Three variants: fill, slide, and circle.

<AnimatedButton label="View Work" href="/work" variant="fill" />

<AnimatedButton
  label="Learn More"
  variant="circle"
  icon={<span>&#8594;</span>}
  onClick={() => console.log('clicked')}
/>

| Prop | Type | Default | Description | |------|------|---------|-------------| | label | string | required | Button text | | href | string | — | Renders as <a> if provided | | onClick | () => void | — | Click handler (renders as <button>) | | icon | ReactNode | — | Icon element | | className | string | "" | Custom class | | variant | "fill" \| "slide" \| "circle" | "fill" | Hover animation style | | animateOnScroll | boolean | true | Scale-in on scroll | | delay | number | 0 | Animation delay |

Variants:

  • fill — Background fills from left on hover
  • slide — Background slides up on hover
  • circle — Small circle expands to fill button on hover

Footer

Multi-column footer with staggered link reveal animations.

<Footer
  brand="Studio"
  columns={[
    {
      title: 'Pages',
      links: [
        { label: 'Home', href: '/' },
        { label: 'Work', href: '/work' },
        { label: 'About', href: '/about' },
      ],
    },
    {
      title: 'Social',
      links: [
        { label: 'Twitter', href: 'https://twitter.com' },
        { label: 'Instagram', href: 'https://instagram.com' },
      ],
    },
  ]}
  copyright="&copy; 2026 Studio. All rights reserved."
/>

| Prop | Type | Default | Description | |------|------|---------|-------------| | brand | string | — | Brand name | | columns | FooterColumn[] | [] | Column groups with title and links | | copyright | string | — | Copyright text | | className | string | "" | Custom class | | children | ReactNode | — | Extra content |


Preloader

Full-screen loading overlay with counter and progress bar.

<Preloader duration={3} onComplete={() => console.log('loaded')} />

| Prop | Type | Default | Description | |------|------|---------|-------------| | duration | number | 3 | Counter duration in seconds | | className | string | "" | Custom class | | onComplete | () => void | — | Callback when preloader finishes | | children | ReactNode | — | Custom preloader content (replaces default counter) |

Animations: Counter counts 0-100, progress bar scales from left, then preloader exits with clip-path animation. Lenis scroll is paused during loading.


ParallaxImage

Image with scroll-based parallax movement.

<ParallaxImage
  src="/images/hero.jpg"
  alt="Hero image"
  speed={0.2}
  scale={1.5}
/>

| Prop | Type | Default | Description | |------|------|---------|-------------| | src | string | required | Image source | | alt | string | "" | Alt text | | className | string | "" | Custom class for wrapper | | imgClassName | string | "" | Custom class for img element | | speed | number | 0.2 | Parallax speed (0-1) | | lerp | number | 0.1 | Smoothing factor | | scale | number | 1.5 | Image scale (to prevent gaps) | | mobileBreakpoint | number | 900 | Disable parallax below this width |


FAQ

Animated accordion component.

<FAQ
  items={[
    { question: 'What services do you offer?', answer: 'We offer web design, development, and branding.' },
    { question: 'What is your process?', answer: 'Discovery, design, development, and launch.' },
    { question: 'How long does a project take?', answer: 'Typically 4-8 weeks depending on scope.' },
  ]}
  allowMultiple={false}
/>

| Prop | Type | Default | Description | |------|------|---------|-------------| | items | FAQItem[] | required | Array of { question, answer } | | className | string | "" | Custom class | | allowMultiple | boolean | false | Allow multiple items open at once |

Animations: Icon rotates 45 degrees on open. Answer height and opacity animate with GSAP (power2.out).


Marquee

Infinite scrolling text/content strip that reverses direction on scroll.

<Marquee speed={15} reverseOnScroll={true} pauseOnHover={true}>
  <h1 style={{ fontSize: '5rem', paddingRight: '2em' }}>
    FEATURED WORK &mdash;
  </h1>
</Marquee>

| Prop | Type | Default | Description | |------|------|---------|-------------| | children | ReactNode | required | Content to repeat | | speed | number | 15 | Animation duration (higher = slower) | | direction | "left" \| "right" | "left" | Initial scroll direction | | pauseOnHover | boolean | false | Pause on mouse hover | | className | string | "" | Custom class | | reverseOnScroll | boolean | true | Reverse direction based on scroll |


PageTransition

Route transition overlay with clip-path animation. Uses render props pattern.

<PageTransition duration={0.8} color="#0a0a0a">
  {({ animateOut, animateIn, isTransitioning }) => (
    <button
      onClick={() =>
        animateOut(() => {
          // navigate to new page
          window.location.href = '/about';
        })
      }
      disabled={isTransitioning}
    >
      Go to About
    </button>
  )}
</PageTransition>

| Prop | Type | Default | Description | |------|------|---------|-------------| | className | string | "" | Custom class for overlay | | color | string | — | Background color (defaults to CSS var) | | duration | number | 0.8 | Transition duration | | ease | string | "power4.inOut" | GSAP easing | | onStart | () => void | — | Called when transition starts | | onComplete | () => void | — | Called when transition ends |


Hooks

All hooks can be used independently to build your own components.

useSmoothScroll

Access the Lenis instance from WesparkProvider.

const { lenis, scrollTo, stop, start } = useSmoothScroll();

scrollTo('#section', { duration: 1.5, offset: -100 });
stop();  // pause scrolling (e.g. when modal is open)
start(); // resume scrolling

useTextReveal

Attach split-type + GSAP text reveal animation to any element ref.

const ref = useTextReveal<HTMLParagraphElement>({
  type: 'lines',
  duration: 1,
  stagger: 0.1,
  ease: 'power4.out',
  animateOnScroll: true,
  scrollStart: 'top 85%',
});

return <p ref={ref}>This text will animate.</p>;

useParallax

Attach parallax scroll movement to any element ref.

const ref = useParallax<HTMLImageElement>({
  speed: 0.2,
  lerp: 0.1,
  scale: 1.5,
});

return <img ref={ref} src="/photo.jpg" alt="" />;

useScrollTrigger

Generic GSAP ScrollTrigger animation on any element ref.

const ref = useScrollTrigger<HTMLDivElement>({
  from: { opacity: 0, y: 60 },
  to: { opacity: 1, y: 0 },
  start: 'top 85%',
  once: true,
});

return <div ref={ref}>Fades in on scroll</div>;

useStaggerReveal

Stagger-animate child elements of a container.

const ref = useStaggerReveal<HTMLDivElement>({
  selector: '.card',
  from: { opacity: 0, y: 40 },
  to: { opacity: 1, y: 0 },
  stagger: 0.1,
  duration: 0.8,
});

return (
  <div ref={ref}>
    <div className="card">Card 1</div>
    <div className="card">Card 2</div>
    <div className="card">Card 3</div>
  </div>
);

usePageTransition

Manage route transition animations programmatically.

const { overlayRef, animateOut, animateIn, isTransitioning } = usePageTransition({
  duration: 0.8,
  ease: 'power4.inOut',
});

const navigate = (path: string) => {
  animateOut(() => {
    router.push(path);
    setTimeout(() => animateIn(), 100);
  });
};

return (
  <>
    <button onClick={() => navigate('/about')}>About</button>
    <div ref={overlayRef} className="transition-overlay" style={{ display: 'none' }} />
  </>
);

Peer Dependencies

| Package | Version | Required | |---------|---------|----------| | react | >= 18.0.0 | Yes | | react-dom | >= 18.0.0 | Yes | | gsap | >= 3.12.0 | Yes | | lenis | >= 1.1.0 | Yes | | split-type | >= 0.3.0 | Optional (needed for TextReveal and Hero) |

License

MIT