// hero-slide.tsx
const HeroSlide = () => {
const ref = useRef(null)
useEffect(() => {
const ctx = gsap.context(() => {
gsap.from("[data-letter]", {
y: "100%",
duration: 0.9,
stagger: 0.06,
ease: "expo.out",
delay: 0.2,
})
gsap.from("[data-sub]", {
letterSpacing: "0.4em",
opacity: 0,
duration: 1.2,
ease: "power3.out",
delay: 0.8,
})
}, ref)
return () => ctx.revert()
}, [])
return (
<div ref={ref} className="hero">
<h1>
{"RiPage".split("").map((l, i) => (
<span key={i} data-letter
style={{ display: "inline-block" }}
>
{l}
</span>
))}
</h1>
<p data-sub>{tagline}</p>
</div>
)
}
// particle-background.tsx
const LAYERS = [
{
count: 40,
speed: 0.3,
size: [1, 2.5],
opacity: [0.08, 0.18],
},
{
count: 30,
speed: 0.65,
size: [2, 4],
opacity: [0.15, 0.3],
},
{
count: 18,
speed: 1.2,
size: [3.5, 6],
opacity: [0.25, 0.5],
},
]
const ParticleBg = () => {
const canvasRef = useRef(null)
useEffect(() => {
const canvas = canvasRef.current
const ctx = canvas.getContext("2d")
const particles = LAYERS.flatMap((l) =>
Array.from({ length: l.count }, () =>
createParticle(l)
)
)
let frame: number
const tick = () => {
ctx.clearRect(0, 0,
canvas.width, canvas.height
)
particles.forEach((p) => {
p.y -= p.speed
if (p.y < -10)
p.y = canvas.height + 10
ctx.beginPath()
ctx.arc(p.x, p.y, p.size,
0, Math.PI * 2
)
ctx.fillStyle =
`rgba(0,0,0,${p.opacity})`
ctx.fill()
})
frame = requestAnimationFrame(tick)
}
tick()
return () => cancelAnimationFrame(frame)
}, [])
return (
<canvas
ref={canvasRef}
className="absolute inset-0"
/>
)
}
// slide-wrapper.tsx
const SlideWrapper = ({ children, i }) => {
const ref = useRef(null)
useEffect(() => {
const ctx = gsap.context(() => {
gsap.from("[data-item]", {
opacity: 0,
y: 18,
duration: 0.9,
stagger: 0.07,
ease: "power3.out",
})
}, ref)
return () => ctx.revert()
}, [i])
return <div ref={ref}>{children}</div>
}
// hero-slide.tsx
const HeroSlide = () => {
const ref = useRef(null)
useEffect(() => {
const ctx = gsap.context(() => {
gsap.from("[data-letter]", {
y: "100%",
duration: 0.9,
stagger: 0.06,
ease: "expo.out",
delay: 0.2,
})
gsap.from("[data-sub]", {
letterSpacing: "0.4em",
opacity: 0,
duration: 1.2,
ease: "power3.out",
delay: 0.8,
})
}, ref)
return () => ctx.revert()
}, [])
return (
<div ref={ref} className="hero">
<h1>
{"RiPage".split("").map((l, i) => (
<span key={i} data-letter
style={{ display: "inline-block" }}
>
{l}
</span>
))}
</h1>
<p data-sub>{tagline}</p>
</div>
)
}
// particle-background.tsx
const LAYERS = [
{
count: 40,
speed: 0.3,
size: [1, 2.5],
opacity: [0.08, 0.18],
},
{
count: 30,
speed: 0.65,
size: [2, 4],
opacity: [0.15, 0.3],
},
{
count: 18,
speed: 1.2,
size: [3.5, 6],
opacity: [0.25, 0.5],
},
]
const ParticleBg = () => {
const canvasRef = useRef(null)
useEffect(() => {
const canvas = canvasRef.current
const ctx = canvas.getContext("2d")
const particles = LAYERS.flatMap((l) =>
Array.from({ length: l.count }, () =>
createParticle(l)
)
)
let frame: number
const tick = () => {
ctx.clearRect(0, 0,
canvas.width, canvas.height
)
particles.forEach((p) => {
p.y -= p.speed
if (p.y < -10)
p.y = canvas.height + 10
ctx.beginPath()
ctx.arc(p.x, p.y, p.size,
0, Math.PI * 2
)
ctx.fillStyle =
`rgba(0,0,0,${p.opacity})`
ctx.fill()
})
frame = requestAnimationFrame(tick)
}
tick()
return () => cancelAnimationFrame(frame)
}, [])
return (
<canvas
ref={canvasRef}
className="absolute inset-0"
/>
)
}
// slide-wrapper.tsx
const SlideWrapper = ({ children, i }) => {
const ref = useRef(null)
useEffect(() => {
const ctx = gsap.context(() => {
gsap.from("[data-item]", {
opacity: 0,
y: 18,
duration: 0.9,
stagger: 0.07,
ease: "power3.out",
})
}, ref)
return () => ctx.revert()
}, [i])
return <div ref={ref}>{children}</div>
}