107 lines
4.1 KiB
TypeScript
107 lines
4.1 KiB
TypeScript
|
|
import { motion } from "framer-motion";
|
||
|
|
|
||
|
|
interface TechStackVisualizationProps {
|
||
|
|
stacks: Array<{
|
||
|
|
name: string;
|
||
|
|
technologies: string[];
|
||
|
|
color: string;
|
||
|
|
icon: string;
|
||
|
|
}>;
|
||
|
|
}
|
||
|
|
|
||
|
|
export const TechStackVisualization = ({ stacks }: TechStackVisualizationProps) => {
|
||
|
|
return (
|
||
|
|
<div className="relative w-full h-96 flex items-center justify-center">
|
||
|
|
{/* Central Hub */}
|
||
|
|
<motion.div
|
||
|
|
initial={{ opacity: 0, scale: 0 }}
|
||
|
|
animate={{ opacity: 1, scale: 1 }}
|
||
|
|
transition={{ duration: 0.8 }}
|
||
|
|
className="relative w-32 h-32 bg-gradient-to-br from-accent to-purple-600 rounded-full flex items-center justify-center z-10"
|
||
|
|
>
|
||
|
|
<div className="w-20 h-20 bg-white/20 rounded-full flex items-center justify-center backdrop-blur-sm">
|
||
|
|
<svg className="w-10 h-10 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 9.172V5L8 4z" />
|
||
|
|
</svg>
|
||
|
|
</div>
|
||
|
|
</motion.div>
|
||
|
|
|
||
|
|
{/* Tech Stack Orbits */}
|
||
|
|
{stacks.map((stack, stackIndex) => (
|
||
|
|
<motion.div
|
||
|
|
key={stack.name}
|
||
|
|
initial={{ opacity: 0 }}
|
||
|
|
animate={{ opacity: 1 }}
|
||
|
|
transition={{ duration: 0.6, delay: stackIndex * 0.3 }}
|
||
|
|
className="absolute"
|
||
|
|
>
|
||
|
|
{/* Stack Hub */}
|
||
|
|
<motion.div
|
||
|
|
animate={{
|
||
|
|
rotate: stackIndex % 2 === 0 ? 360 : -360
|
||
|
|
}}
|
||
|
|
transition={{
|
||
|
|
duration: 20 + stackIndex * 5,
|
||
|
|
repeat: Infinity,
|
||
|
|
ease: "linear"
|
||
|
|
}}
|
||
|
|
className="relative"
|
||
|
|
style={{
|
||
|
|
transform: `rotate(${stackIndex * (360 / stacks.length)}deg) translateX(${120 + stackIndex * 20}px) rotate(-${stackIndex * (360 / stacks.length)}deg)`
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
<div className={`w-16 h-16 bg-gradient-to-br ${stack.color} rounded-full flex items-center justify-center shadow-lg`}>
|
||
|
|
<span className="text-2xl">{stack.icon}</span>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Stack Label */}
|
||
|
|
<div className="absolute -bottom-8 left-1/2 transform -translate-x-1/2 text-white text-xs font-manrope whitespace-nowrap">
|
||
|
|
{stack.name}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Technologies around the stack */}
|
||
|
|
{stack.technologies.map((tech, techIndex) => (
|
||
|
|
<motion.div
|
||
|
|
key={tech}
|
||
|
|
initial={{ opacity: 0, scale: 0 }}
|
||
|
|
animate={{ opacity: 1, scale: 1 }}
|
||
|
|
transition={{ duration: 0.4, delay: stackIndex * 0.3 + techIndex * 0.1 + 0.5 }}
|
||
|
|
className="absolute w-8 h-8 bg-white/10 border border-white/20 rounded-full flex items-center justify-center text-xs font-manrope text-white backdrop-blur-sm"
|
||
|
|
style={{
|
||
|
|
transform: `rotate(${techIndex * (360 / stack.technologies.length)}deg) translateX(35px) rotate(-${techIndex * (360 / stack.technologies.length)}deg)`,
|
||
|
|
fontSize: '8px'
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
{tech.slice(0, 2)}
|
||
|
|
</motion.div>
|
||
|
|
))}
|
||
|
|
</motion.div>
|
||
|
|
</motion.div>
|
||
|
|
))}
|
||
|
|
|
||
|
|
{/* Animated Particles */}
|
||
|
|
{Array.from({ length: 8 }).map((_, index) => (
|
||
|
|
<motion.div
|
||
|
|
key={index}
|
||
|
|
className="absolute w-2 h-2 bg-accent/60 rounded-full"
|
||
|
|
animate={{
|
||
|
|
x: [0, Math.random() * 400 - 200],
|
||
|
|
y: [0, Math.random() * 400 - 200],
|
||
|
|
opacity: [0, 1, 0],
|
||
|
|
scale: [0, 1, 0]
|
||
|
|
}}
|
||
|
|
transition={{
|
||
|
|
duration: 3 + Math.random() * 2,
|
||
|
|
repeat: Infinity,
|
||
|
|
delay: index * 0.5,
|
||
|
|
ease: "easeInOut"
|
||
|
|
}}
|
||
|
|
style={{
|
||
|
|
left: '50%',
|
||
|
|
top: '50%'
|
||
|
|
}}
|
||
|
|
/>
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
};
|