feat: optimize 3D floating particles and enhance parallax effects; improve visibility and interaction of UI elements
This commit is contained in:
194
src/App.tsx
194
src/App.tsx
@@ -145,17 +145,17 @@ function ParallaxContainer({ children, depth = 1, className = '' }: ParallaxCont
|
||||
|
||||
// Enhanced 3D Floating Particles Background
|
||||
function FloatingParticles() {
|
||||
const particles = Array.from({ length: 30 }, (_, i) => i)
|
||||
const particles = Array.from({ length: 20 }, (_, i) => i)
|
||||
|
||||
return (
|
||||
<div className="absolute inset-0 overflow-hidden pointer-events-none">
|
||||
{particles.map((i) => {
|
||||
const size = Math.random() * 3 + 1
|
||||
const depth = Math.random() * 4 + 1
|
||||
const size = Math.random() * 2 + 0.5
|
||||
const depth = Math.random() * 3 + 1
|
||||
return (
|
||||
<motion.div
|
||||
key={i}
|
||||
className="absolute rounded-full bg-gradient-to-br from-primary-400/10 to-secondary-400/10 backdrop-blur-sm"
|
||||
className="absolute rounded-full bg-gradient-to-br from-primary-300/8 to-secondary-300/8 backdrop-blur-sm"
|
||||
style={{
|
||||
width: `${size}px`,
|
||||
height: `${size}px`,
|
||||
@@ -164,16 +164,16 @@ function FloatingParticles() {
|
||||
transformStyle: 'preserve-3d'
|
||||
}}
|
||||
animate={{
|
||||
y: [-30, -120, -30],
|
||||
x: [-15, 15, -15],
|
||||
z: [0, depth * 20, 0],
|
||||
opacity: [0.3, 0.8, 0.3],
|
||||
scale: [0.8, 1.2, 0.8]
|
||||
y: [-25, -100, -25],
|
||||
x: [-10, 10, -10],
|
||||
z: [0, depth * 15, 0],
|
||||
opacity: [0.2, 0.6, 0.2],
|
||||
scale: [0.9, 1.1, 0.9]
|
||||
}}
|
||||
transition={{
|
||||
duration: Math.random() * 15 + 8,
|
||||
duration: Math.random() * 18 + 10,
|
||||
repeat: Infinity,
|
||||
delay: Math.random() * 3,
|
||||
delay: Math.random() * 4,
|
||||
ease: "easeInOut"
|
||||
}}
|
||||
/>
|
||||
@@ -551,21 +551,22 @@ function Hero() {
|
||||
|
||||
{/* Content Layer with Parallax */}
|
||||
<motion.div
|
||||
className="mx-auto grid max-w-7xl items-center gap-10 px-4 py-20 sm:px-6 lg:grid-cols-2 lg:gap-12 lg:py-28 lg:px-8 relative z-10"
|
||||
className="mx-auto grid max-w-7xl items-center gap-16 px-4 py-20 sm:px-6 lg:grid-cols-12 lg:gap-8 lg:py-28 lg:px-8 relative z-10"
|
||||
style={{ y: contentY }}
|
||||
>
|
||||
<ParallaxContainer depth={0.3}>
|
||||
<motion.h1
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.6 }}
|
||||
className="text-balance text-4xl font-bold tracking-tight sm:text-5xl md:text-6xl transform-gpu"
|
||||
style={{ transformStyle: 'preserve-3d' }}
|
||||
>
|
||||
Equipping kids for success—
|
||||
<span className="relative whitespace-pre gradient-text"> school supplies, clothing, & more</span>
|
||||
</motion.h1>
|
||||
<div className="lg:col-span-7">
|
||||
<ParallaxContainer depth={0.3}>
|
||||
<motion.h1
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.6 }}
|
||||
className="text-balance text-4xl font-bold tracking-tight sm:text-5xl md:text-6xl transform-gpu"
|
||||
style={{ transformStyle: 'preserve-3d' }}
|
||||
>
|
||||
Equipping kids for success—
|
||||
<span className="relative whitespace-pre gradient-text"> school supplies, clothing, & more</span>
|
||||
</motion.h1>
|
||||
<p className="mt-5 max-w-xl text-lg leading-7 text-neutral-700 dark:text-neutral-300">
|
||||
Miracles in Motion is a nonprofit providing students with the essentials they need to thrive: backpacks and notebooks, uniforms and shoes, and the everything-else fund for urgent needs.
|
||||
</p>
|
||||
@@ -587,52 +588,55 @@ function Hero() {
|
||||
<li className="flex items-center gap-2"><CheckCircle2 className="h-4 w-4 text-emerald-500"/> Donations tax-deductible</li>
|
||||
<li className="flex items-center gap-2"><CheckCircle2 className="h-4 w-4 text-emerald-500"/> Community-driven impact</li>
|
||||
</ul>
|
||||
</ParallaxContainer>
|
||||
</ParallaxContainer>
|
||||
</div>
|
||||
|
||||
<ParallaxContainer depth={0.5} className="relative">
|
||||
<HeroShowcase />
|
||||
</ParallaxContainer>
|
||||
<div className="lg:col-span-5">
|
||||
<ParallaxContainer depth={0.5} className="relative">
|
||||
<HeroShowcase />
|
||||
</ParallaxContainer>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* 3D Scroll Indicator */}
|
||||
<motion.div
|
||||
className="absolute bottom-8 left-1/2 -translate-x-1/2 hidden lg:block"
|
||||
className="absolute bottom-12 left-1/2 -translate-x-1/2 hidden lg:block z-20"
|
||||
animate={{
|
||||
y: [0, 10, 0],
|
||||
rotateX: [0, 15, 0]
|
||||
y: [0, 8, 0],
|
||||
rotateX: [0, 10, 0]
|
||||
}}
|
||||
transition={{ duration: 2, repeat: Infinity, ease: "easeInOut" }}
|
||||
transition={{ duration: 2.5, repeat: Infinity, ease: "easeInOut" }}
|
||||
style={{ transformStyle: 'preserve-3d' }}
|
||||
>
|
||||
<div className="flex flex-col items-center gap-2 text-neutral-400 dark:text-neutral-500">
|
||||
<span className="text-sm font-medium">Scroll to explore</span>
|
||||
<div className="flex flex-col items-center gap-2 text-neutral-500 dark:text-neutral-400">
|
||||
<span className="text-xs font-medium opacity-75">Scroll to explore</span>
|
||||
<motion.div
|
||||
animate={{ y: [0, 5, 0] }}
|
||||
transition={{ duration: 1.5, repeat: Infinity, ease: "easeInOut" }}
|
||||
animate={{ y: [0, 4, 0] }}
|
||||
transition={{ duration: 1.8, repeat: Infinity, ease: "easeInOut" }}
|
||||
>
|
||||
<ArrowRight className="h-5 w-5 rotate-90" />
|
||||
<ArrowRight className="h-4 w-4 rotate-90 opacity-60" />
|
||||
</motion.div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Enhanced 3D Icon Elements with More Visibility */}
|
||||
<div className="absolute inset-0 pointer-events-none z-0">
|
||||
{/* Top layer - more visible and engaging */}
|
||||
{/* Top layer - subtle and non-interfering */}
|
||||
<motion.div
|
||||
className="absolute top-1/4 left-[8%] text-primary-400/80 dark:text-primary-500/80 hidden lg:block"
|
||||
className="absolute top-1/4 left-[5%] text-primary-300/40 dark:text-primary-600/40 hidden xl:block"
|
||||
animate={{
|
||||
y: [-15, 15, -15],
|
||||
y: [-12, 12, -12],
|
||||
rotateY: [0, 360],
|
||||
z: [0, 50, 0],
|
||||
scale: [0.8, 1.2, 0.8]
|
||||
z: [0, 30, 0],
|
||||
scale: [0.9, 1.1, 0.9]
|
||||
}}
|
||||
transition={{ duration: 6, repeat: Infinity, ease: "easeInOut" }}
|
||||
transition={{ duration: 8, repeat: Infinity, ease: "easeInOut" }}
|
||||
style={{
|
||||
transformStyle: 'preserve-3d',
|
||||
filter: 'drop-shadow(0 4px 8px rgba(0,0,0,0.1))'
|
||||
filter: 'drop-shadow(0 2px 4px rgba(0,0,0,0.05))'
|
||||
}}
|
||||
>
|
||||
<Heart className="h-8 w-8" />
|
||||
<Heart className="h-6 w-6" />
|
||||
</motion.div>
|
||||
|
||||
{/* Additional floating heart */}
|
||||
@@ -649,22 +653,22 @@ function Hero() {
|
||||
<Heart className="h-5 w-5" />
|
||||
</motion.div>
|
||||
|
||||
{/* Right side - more prominent sparkles */}
|
||||
{/* Right side - subtle sparkles */}
|
||||
<motion.div
|
||||
className="absolute top-1/3 right-[12%] text-secondary-400/80 dark:text-secondary-500/80 hidden xl:block"
|
||||
className="absolute top-1/3 right-[8%] text-secondary-300/40 dark:text-secondary-600/40 hidden xl:block"
|
||||
animate={{
|
||||
y: [20, -20, 20],
|
||||
y: [15, -15, 15],
|
||||
rotateX: [0, 180, 360],
|
||||
z: [0, 40, 0],
|
||||
scale: [0.9, 1.4, 0.9]
|
||||
z: [0, 25, 0],
|
||||
scale: [0.95, 1.2, 0.95]
|
||||
}}
|
||||
transition={{ duration: 5, repeat: Infinity, ease: "easeInOut", delay: 1.5 }}
|
||||
transition={{ duration: 7, repeat: Infinity, ease: "easeInOut", delay: 1.5 }}
|
||||
style={{
|
||||
transformStyle: 'preserve-3d',
|
||||
filter: 'drop-shadow(0 2px 4px rgba(0,0,0,0.1))'
|
||||
filter: 'drop-shadow(0 1px 2px rgba(0,0,0,0.05))'
|
||||
}}
|
||||
>
|
||||
<Sparkles className="h-7 w-7" />
|
||||
<Sparkles className="h-5 w-5" />
|
||||
</motion.div>
|
||||
|
||||
{/* Additional floating sparkle */}
|
||||
@@ -731,9 +735,9 @@ function Hero() {
|
||||
|
||||
function HeroShowcase() {
|
||||
return (
|
||||
<div className="relative mx-auto max-w-md lg:max-w-none">
|
||||
<div className="absolute -inset-8 -z-10 rounded-3xl bg-gradient-to-tr from-primary-500/20 via-secondary-500/20 to-secondary-600/20 blur-3xl" />
|
||||
<div className="grid grid-cols-2 gap-4 sm:gap-6">
|
||||
<div className="relative mx-auto max-w-sm lg:max-w-md xl:max-w-lg">
|
||||
<div className="absolute -inset-6 -z-10 rounded-3xl bg-gradient-to-tr from-primary-500/15 via-secondary-500/15 to-secondary-600/15 blur-2xl" />
|
||||
<div className="grid grid-cols-2 gap-3 sm:gap-4">
|
||||
<TiltCard icon={Backpack} title="School Supplies" desc="Backpacks, notebooks, calculators" />
|
||||
<TiltCard icon={Shirt} title="School Clothing" desc="Uniforms, shoes, coats" />
|
||||
<TiltCard icon={Sparkles} title="Everything Else" desc="Glasses, fees, emergencies" />
|
||||
@@ -746,10 +750,11 @@ function HeroShowcase() {
|
||||
function TiltCard({ icon: Icon, title, desc }: TiltCardProps) {
|
||||
const x = useMotionValue(0)
|
||||
const y = useMotionValue(0)
|
||||
const rx = useTransform(y, [-50, 50], [8, -8])
|
||||
const ry = useTransform(x, [-50, 50], [-8, 8])
|
||||
const springX = useSpring(rx, { stiffness: 200, damping: 20 })
|
||||
const springY = useSpring(ry, { stiffness: 200, damping: 20 })
|
||||
const rx = useTransform(y, [-50, 50], [12, -12])
|
||||
const ry = useTransform(x, [-50, 50], [-12, 12])
|
||||
const springX = useSpring(rx, { stiffness: 150, damping: 15 })
|
||||
const springY = useSpring(ry, { stiffness: 150, damping: 15 })
|
||||
const scale = useSpring(1, { stiffness: 300, damping: 25 })
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
@@ -757,13 +762,28 @@ function TiltCard({ icon: Icon, title, desc }: TiltCardProps) {
|
||||
const rect = e.currentTarget.getBoundingClientRect()
|
||||
x.set(e.clientX - rect.left - rect.width / 2)
|
||||
y.set(e.clientY - rect.top - rect.height / 2)
|
||||
scale.set(1.05)
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
x.set(0)
|
||||
y.set(0)
|
||||
scale.set(1)
|
||||
}}
|
||||
style={{ rotateX: springX, rotateY: springY, transformStyle: "preserve-3d" }}
|
||||
className="group card card-hover will-change-transform"
|
||||
style={{
|
||||
rotateX: springX,
|
||||
rotateY: springY,
|
||||
scale: scale,
|
||||
transformStyle: "preserve-3d",
|
||||
boxShadow: useTransform(scale, [1, 1.05], [
|
||||
"0 4px 6px -1px rgba(0, 0, 0, 0.1)",
|
||||
"0 20px 25px -5px rgba(0, 0, 0, 0.15), 0 10px 10px -5px rgba(0, 0, 0, 0.04)"
|
||||
])
|
||||
}}
|
||||
className="group card card-hover will-change-transform cursor-pointer"
|
||||
whileHover={{
|
||||
z: 20
|
||||
}}
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
<div className="pointer-events-none absolute inset-0 -z-10 bg-[radial-gradient(150px_80px_at_var(--x)_var(--y),_rgba(255,255,255,0.35),_transparent)] opacity-0 transition-opacity duration-500 group-hover:opacity-100" />
|
||||
<div className="flex items-start gap-4" style={{ transform: "translateZ(40px)" }}>
|
||||
@@ -855,19 +875,53 @@ function Programs() {
|
||||
|
||||
function FeatureCard({ icon: Icon, title, body }: FeatureCardProps) {
|
||||
return (
|
||||
<div className="group card card-hover">
|
||||
<div className="absolute left-1/2 top-0 -z-10 h-40 w-40 -translate-x-1/2 rounded-full bg-gradient-to-br from-primary-500/20 to-secondary-600/20 blur-2xl" />
|
||||
<motion.div
|
||||
className="group card card-hover cursor-pointer"
|
||||
whileHover={{
|
||||
scale: 1.02,
|
||||
rotateY: 5,
|
||||
z: 30,
|
||||
boxShadow: "0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)"
|
||||
}}
|
||||
initial={{ rotateY: 0, z: 0 }}
|
||||
style={{ transformStyle: 'preserve-3d' }}
|
||||
transition={{ duration: 0.3, ease: "easeOut" }}
|
||||
>
|
||||
<motion.div
|
||||
className="absolute left-1/2 top-0 -z-10 h-40 w-40 -translate-x-1/2 rounded-full bg-gradient-to-br from-primary-500/20 to-secondary-600/20 blur-2xl"
|
||||
whileHover={{ scale: 1.2, opacity: 0.8 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
/>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="grid h-10 w-10 place-items-center rounded-xl bg-gradient-to-br from-primary-500 to-secondary-600 text-white shadow">
|
||||
<motion.div
|
||||
className="grid h-10 w-10 place-items-center rounded-xl bg-gradient-to-br from-primary-500 to-secondary-600 text-white shadow"
|
||||
whileHover={{
|
||||
scale: 1.1,
|
||||
rotateY: 180,
|
||||
boxShadow: "0 8px 16px rgba(0,0,0,0.2)"
|
||||
}}
|
||||
style={{ transformStyle: 'preserve-3d' }}
|
||||
transition={{ duration: 0.4 }}
|
||||
>
|
||||
<Icon className="h-5 w-5" />
|
||||
</div>
|
||||
</motion.div>
|
||||
<div className="font-semibold tracking-tight">{title}</div>
|
||||
</div>
|
||||
<p className="mt-3 text-sm leading-6 text-neutral-700 dark:text-neutral-300">{body}</p>
|
||||
<div className="mt-5 text-sm text-primary-700 transition group-hover:translate-x-1 dark:text-primary-300">
|
||||
Explore <ArrowRight className="ml-1 inline h-4 w-4" />
|
||||
</div>
|
||||
</div>
|
||||
<motion.div
|
||||
className="mt-5 text-sm text-primary-700 transition dark:text-primary-300 flex items-center"
|
||||
whileHover={{ x: 8 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
Explore
|
||||
<motion.div
|
||||
whileHover={{ x: 4 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
<ArrowRight className="ml-1 h-4 w-4" />
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user