wespark-frontend
v1.0.1
Published
React component library & animation hooks for portfolio and agency websites. Built on GSAP, Lenis, and split-type.
Maintainers
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-typegsap, 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>→</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="© 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 —
</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 scrollinguseTextReveal
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
