+ {shapes.map((shape, index) => (
+
{
+ if (el) elementsRef.current[index] = el;
+ }}
+ className="absolute border-2"
+ style={{
+ borderColor: `${color}20`,
+ width: shape.size,
+ height: shape.size,
+ left: `${shape.x}%`,
+ top: `${shape.y}%`,
+ borderRadius: shape.type === 'circle' ? '50%' : '0'
+ }}
+ />
+ ))}
+
+ );
+}
+
+export default GSAPAnimation;
\ No newline at end of file
diff --git a/src/components/effects/mesh-gradient.tsx b/src/components/effects/mesh-gradient.tsx
new file mode 100644
index 0000000..a3e41ed
--- /dev/null
+++ b/src/components/effects/mesh-gradient.tsx
@@ -0,0 +1,96 @@
+'use client';
+
+import { motion } from 'framer-motion';
+
+interface MeshGradientProps {
+ className?: string;
+}
+
+export function MeshGradient({
+ className = ''
+}: MeshGradientProps) {
+ return (
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default MeshGradient;
\ No newline at end of file
diff --git a/src/components/effects/parallax-effect.tsx b/src/components/effects/parallax-effect.tsx
new file mode 100644
index 0000000..5788f5d
--- /dev/null
+++ b/src/components/effects/parallax-effect.tsx
@@ -0,0 +1,77 @@
+'use client';
+
+import { useEffect, useRef, useState } from 'react';
+import { motion } from 'framer-motion';
+
+interface ParallaxEffectProps {
+ className?: string;
+ color?: string;
+ sensitivity?: number;
+}
+
+export function ParallaxEffect({
+ className = '',
+ color = '#C41E3A',
+ sensitivity = 0.05
+}: ParallaxEffectProps) {
+ const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });
+ const containerRef = useRef
(null);
+
+ useEffect(() => {
+ const handleMouseMove = (e: MouseEvent) => {
+ if (!containerRef.current) return;
+
+ const rect = containerRef.current.getBoundingClientRect();
+ const centerX = rect.width / 2;
+ const centerY = rect.height / 2;
+
+ const x = (e.clientX - rect.left - centerX) * sensitivity;
+ const y = (e.clientY - rect.top - centerY) * sensitivity;
+
+ setMousePosition({ x, y });
+ };
+
+ const container = containerRef.current;
+ container?.addEventListener('mousemove', handleMouseMove);
+
+ return () => {
+ container?.removeEventListener('mousemove', handleMouseMove);
+ };
+ }, [sensitivity]);
+
+ const layers = [
+ { size: 300, x: 10, y: 15, factor: 1 },
+ { size: 200, x: 70, y: 20, factor: 1.5 },
+ { size: 150, x: 60, y: 60, factor: 2 },
+ { size: 100, x: 15, y: 65, factor: 2.5 }
+ ];
+
+ return (
+
+ {layers.map((layer, index) => (
+
+ ))}
+
+ );
+}
+
+export default ParallaxEffect;
\ No newline at end of file
diff --git a/src/components/effects/particle-galaxy.tsx b/src/components/effects/particle-galaxy.tsx
new file mode 100644
index 0000000..bd6ce1b
--- /dev/null
+++ b/src/components/effects/particle-galaxy.tsx
@@ -0,0 +1,225 @@
+'use client';
+
+import { useEffect, useRef, useState, useCallback } from 'react';
+import { motion, useMotionValue, useTransform } from 'framer-motion';
+
+interface Particle {
+ x: number;
+ y: number;
+ vx: number;
+ vy: number;
+ size: number;
+ opacity: number;
+}
+
+interface ParticleGalaxyProps {
+ particleCount?: number;
+ connectionDistance?: number;
+ mouseRadius?: number;
+ particleColor?: string;
+ lineColor?: string;
+ className?: string;
+}
+
+export function ParticleGalaxy({
+ particleCount = 100,
+ connectionDistance = 150,
+ mouseRadius = 150,
+ particleColor = '196, 30, 58',
+ lineColor = '196, 30, 58',
+ className = ''
+}: ParticleGalaxyProps) {
+ const canvasRef = useRef(null);
+ const particlesRef = useRef([]);
+ const animationRef = useRef();
+ const [isVisible, setIsVisible] = useState(false);
+
+ const mouseX = useMotionValue(0);
+ const mouseY = useMotionValue(0);
+ const mouseInCanvas = useMotionValue(false);
+
+ const createParticle = useCallback((width: number, height: number): Particle => {
+ return {
+ x: Math.random() * width,
+ y: Math.random() * height,
+ vx: (Math.random() - 0.5) * 0.8,
+ vy: (Math.random() - 0.5) * 0.8,
+ size: Math.random() * 2 + 1,
+ opacity: Math.random() * 0.5 + 0.2
+ };
+ }, []);
+
+ const initParticles = useCallback((width: number, height: number) => {
+ particlesRef.current = Array.from({ length: particleCount }, () =>
+ createParticle(width, height)
+ );
+ }, [particleCount, createParticle]);
+
+ const drawParticles = useCallback((ctx: CanvasRenderingContext2D, width: number, height: number) => {
+ const mx = mouseX.get();
+ const my = mouseY.get();
+ const inCanvas = mouseInCanvas.get();
+
+ ctx.clearRect(0, 0, width, height);
+
+ const particles = particlesRef.current;
+
+ particles.forEach((particle, i) => {
+ let { x, y, vx, vy, size, opacity } = particle;
+
+ if (inCanvas) {
+ const dx = x - mx;
+ const dy = y - my;
+ const distance = Math.sqrt(dx * dx + dy * dy);
+
+ if (distance < mouseRadius) {
+ const force = (mouseRadius - distance) / mouseRadius;
+ const angle = Math.atan2(dy, dx);
+ vx += Math.cos(angle) * force * 0.5;
+ vy += Math.sin(angle) * force * 0.5;
+ }
+ }
+
+ x += vx;
+ y += vy;
+
+ if (x < 0 || x > width) vx *= -1;
+ if (y < 0 || y > height) vy *= -1;
+
+ x = Math.max(0, Math.min(width, x));
+ y = Math.max(0, Math.min(height, y));
+
+ vx *= 0.99;
+ vy *= 0.99;
+
+ const speed = Math.sqrt(vx * vx + vy * vy);
+ if (speed < 0.1) {
+ vx += (Math.random() - 0.5) * 0.1;
+ vy += (Math.random() - 0.5) * 0.1;
+ }
+
+ particlesRef.current[i] = { x, y, vx, vy, size, opacity };
+
+ ctx.beginPath();
+ ctx.arc(x, y, size, 0, Math.PI * 2);
+ ctx.fillStyle = `rgba(${particleColor}, ${opacity})`;
+ ctx.fill();
+ });
+
+ for (let i = 0; i < particles.length; i++) {
+ for (let j = i + 1; j < particles.length; j++) {
+ const dx = particles[i].x - particles[j].x;
+ const dy = particles[i].y - particles[j].y;
+ const distance = Math.sqrt(dx * dx + dy * dy);
+
+ if (distance < connectionDistance) {
+ const opacity = (1 - distance / connectionDistance) * 0.3;
+ ctx.beginPath();
+ ctx.moveTo(particles[i].x, particles[i].y);
+ ctx.lineTo(particles[j].x, particles[j].y);
+ ctx.strokeStyle = `rgba(${lineColor}, ${opacity})`;
+ ctx.lineWidth = 0.5;
+ ctx.stroke();
+ }
+ }
+ }
+ }, [mouseX, mouseY, mouseInCanvas, mouseRadius, particleColor, lineColor, connectionDistance]);
+
+ const animate = useCallback(() => {
+ const canvas = canvasRef.current;
+ if (!canvas) return;
+
+ const ctx = canvas.getContext('2d');
+ if (!ctx) return;
+
+ drawParticles(ctx, canvas.width, canvas.height);
+ animationRef.current = requestAnimationFrame(animate);
+ }, [drawParticles]);
+
+ const handleResize = useCallback(() => {
+ const canvas = canvasRef.current;
+ if (!canvas) return;
+
+ const container = canvas.parentElement;
+ if (!container) return;
+
+ canvas.width = container.clientWidth;
+ canvas.height = container.clientHeight;
+
+ initParticles(canvas.width, canvas.height);
+ }, [initParticles]);
+
+ useEffect(() => {
+ const canvas = canvasRef.current;
+ if (!canvas) return;
+
+ const container = canvas.parentElement;
+ if (!container) return;
+
+ canvas.width = container.clientWidth;
+ canvas.height = container.clientHeight;
+
+ initParticles(canvas.width, canvas.height);
+
+ const observer = new IntersectionObserver(
+ (entries) => {
+ entries.forEach((entry) => {
+ setIsVisible(entry.isIntersecting);
+ });
+ },
+ { threshold: 0.1 }
+ );
+
+ observer.observe(canvas);
+
+ if (isVisible) {
+ animate();
+ }
+
+ const handleResizeWithDebounce = () => {
+ setTimeout(handleResize, 250);
+ };
+
+ window.addEventListener('resize', handleResizeWithDebounce);
+
+ return () => {
+ observer.disconnect();
+ window.removeEventListener('resize', handleResizeWithDebounce);
+ if (animationRef.current) {
+ cancelAnimationFrame(animationRef.current);
+ }
+ };
+ }, [isVisible, animate, initParticles, handleResize]);
+
+ useEffect(() => {
+ if (isVisible) {
+ animate();
+ } else if (animationRef.current) {
+ cancelAnimationFrame(animationRef.current);
+ }
+ }, [isVisible, animate]);
+
+ return (
+
+
+ );
+}
+
+export default ParticleGalaxy;
\ No newline at end of file
diff --git a/src/components/effects/subtle-dots.tsx b/src/components/effects/subtle-dots.tsx
new file mode 100644
index 0000000..2f5e15b
--- /dev/null
+++ b/src/components/effects/subtle-dots.tsx
@@ -0,0 +1,71 @@
+'use client';
+
+import { motion } from 'framer-motion';
+import { useState, useEffect } from 'react';
+
+interface SubtleDotsProps {
+ className?: string;
+ color?: string;
+ count?: number;
+}
+
+export function SubtleDots({
+ className = '',
+ color = '#C41E3A',
+ count = 12
+}: SubtleDotsProps) {
+ const [dots, setDots] = useState>([]);
+
+ useEffect(() => {
+ const generatedDots = Array.from({ length: count }, (_, i) => ({
+ id: i,
+ x: 10 + Math.random() * 80,
+ y: 10 + Math.random() * 80,
+ size: 2 + Math.random() * 3,
+ delay: i * 0.3
+ }));
+ setDots(generatedDots);
+ }, [count]);
+
+ if (dots.length === 0) {
+ return ;
+ }
+
+ return (
+
+ {dots.map((dot) => (
+
+ ))}
+
+ );
+}
+
+export default SubtleDots;
\ No newline at end of file
diff --git a/src/components/effects/subtle-particles.tsx b/src/components/effects/subtle-particles.tsx
new file mode 100644
index 0000000..f2c14f4
--- /dev/null
+++ b/src/components/effects/subtle-particles.tsx
@@ -0,0 +1,74 @@
+'use client';
+
+import { motion } from 'framer-motion';
+import { useState, useEffect } from 'react';
+
+interface SubtleParticleProps {
+ count?: number;
+ size?: number;
+ color?: string;
+ className?: string;
+}
+
+export function SubtleParticles({
+ count = 20,
+ size = 3,
+ color = '#C41E3A',
+ className = ''
+}: SubtleParticleProps) {
+ const [particles, setParticles] = useState>([]);
+
+ useEffect(() => {
+ const generatedParticles = Array.from({ length: count }, (_, i) => ({
+ id: i,
+ x: Math.random() * 100,
+ y: Math.random() * 100,
+ delay: Math.random() * 5,
+ duration: 8 + Math.random() * 4
+ }));
+ setParticles(generatedParticles);
+ }, [count]);
+
+ if (particles.length === 0) {
+ return ;
+ }
+
+ return (
+
+ {particles.map((particle) => (
+
+ ))}
+
+ );
+}
+
+export default SubtleParticles;
\ No newline at end of file
diff --git a/src/components/layout/header.tsx b/src/components/layout/header.tsx
index 080342f..c20c244 100644
--- a/src/components/layout/header.tsx
+++ b/src/components/layout/header.tsx
@@ -104,7 +104,7 @@ export function Header() {
/>
-