Enhance the About page with new story sections detailing personal journey and core values, including icons and structured content for better user engagement and self-presentation.
302 lines
11 KiB
TypeScript
302 lines
11 KiB
TypeScript
"use client";
|
|
|
|
import { motion } from "motion/react";
|
|
import { BentoGrid, BentoGridItem } from "@/components/magicui/bento-grid";
|
|
import {
|
|
ExternalLink,
|
|
Github,
|
|
Star,
|
|
Zap,
|
|
Globe,
|
|
Cpu,
|
|
Code2,
|
|
} from "lucide-react";
|
|
import { usePortfolioData } from "@/lib/usePortfolioData";
|
|
|
|
// Configuração de cores e ícones por categoria
|
|
const categoryConfig = {
|
|
"full-stack": {
|
|
color: "from-blue-500 to-blue-600",
|
|
bgColor: "bg-blue-500/10",
|
|
borderColor: "border-blue-500/20",
|
|
icon: Globe,
|
|
iconColor: "text-blue-400",
|
|
},
|
|
iot: {
|
|
color: "from-purple-500 to-purple-600",
|
|
bgColor: "bg-purple-500/10",
|
|
borderColor: "border-purple-500/20",
|
|
icon: Cpu,
|
|
iconColor: "text-purple-400",
|
|
},
|
|
api: {
|
|
color: "from-orange-500 to-orange-600",
|
|
bgColor: "bg-orange-500/10",
|
|
borderColor: "border-orange-500/20",
|
|
icon: Code2,
|
|
iconColor: "text-orange-400",
|
|
},
|
|
web: {
|
|
color: "from-green-500 to-green-600",
|
|
bgColor: "bg-green-500/10",
|
|
borderColor: "border-green-500/20",
|
|
icon: Globe,
|
|
iconColor: "text-green-400",
|
|
},
|
|
desktop: {
|
|
color: "from-emerald-500 to-emerald-600",
|
|
bgColor: "bg-emerald-500/10",
|
|
borderColor: "border-emerald-500/20",
|
|
icon: Code2,
|
|
iconColor: "text-emerald-400",
|
|
},
|
|
};
|
|
|
|
// Função para gerar estrelas de complexidade
|
|
const ComplexityStars = ({ complexity }: { complexity: number }) => {
|
|
return (
|
|
<div className="flex items-center gap-1">
|
|
{[...Array(5)].map((_, i) => (
|
|
<Star
|
|
key={i}
|
|
className={`w-3 h-3 ${
|
|
i < complexity ? "text-yellow-400 fill-yellow-400" : "text-gray-600"
|
|
}`}
|
|
/>
|
|
))}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
// Componente de Badge
|
|
const ProjectBadge = ({ badge, index }: { badge: string; index: number }) => {
|
|
const badgeColors = [
|
|
"bg-blue-500/20 text-blue-400 border-blue-500/30",
|
|
"bg-green-500/20 text-green-400 border-green-500/30",
|
|
"bg-purple-500/20 text-purple-400 border-purple-500/30",
|
|
"bg-orange-500/20 text-orange-400 border-orange-500/30",
|
|
"bg-red-500/20 text-red-400 border-red-500/30",
|
|
];
|
|
|
|
return (
|
|
<span
|
|
className={`px-2 py-1 text-xs font-medium rounded-full border ${
|
|
badgeColors[index % badgeColors.length]
|
|
}`}
|
|
>
|
|
{badge}
|
|
</span>
|
|
);
|
|
};
|
|
|
|
export default function Projects() {
|
|
const { data, loading, error } = usePortfolioData();
|
|
|
|
if (loading) {
|
|
return (
|
|
<section id="projects" className="py-20 px-4">
|
|
<div className="max-w-7xl mx-auto text-center">
|
|
<div className="animate-pulse">
|
|
<div className="h-8 bg-gray-700 rounded w-1/3 mx-auto mb-4"></div>
|
|
<div className="h-4 bg-gray-700 rounded w-1/2 mx-auto"></div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|
|
|
|
if (error || !data) {
|
|
return (
|
|
<section id="projects" className="py-20 px-4">
|
|
<div className="max-w-7xl mx-auto text-center">
|
|
<p className="text-red-400">Erro ao carregar dados de projetos.</p>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|
|
|
|
const projects = data.projects.filter((project) => project.featured);
|
|
|
|
return (
|
|
<section id="projects" className="py-20 px-4 mx-auto">
|
|
<div className="max-w-7xl mx-auto">
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 30 }}
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.8 }}
|
|
viewport={{ once: true }}
|
|
className="text-center mb-16"
|
|
>
|
|
<h2 className="text-4xl md:text-5xl lg:text-6xl font-bold mb-6 bg-gradient-to-r from-cyan-400 to-blue-500 bg-clip-text text-transparent">
|
|
Projetos em Destaque
|
|
</h2>
|
|
<p className="text-xl text-gray-300 max-w-3xl mx-auto">
|
|
Uma seleção dos projetos que desenvolvi, demonstrando minha evolução
|
|
técnica e capacidade de entregar soluções completas em diferentes
|
|
domínios
|
|
</p>
|
|
</motion.div>
|
|
|
|
<BentoGrid className="gap-6">
|
|
{projects.map((project, index) => {
|
|
const config =
|
|
categoryConfig[project.category as keyof typeof categoryConfig] ||
|
|
categoryConfig["web"];
|
|
const IconComponent = config.icon;
|
|
|
|
return (
|
|
<BentoGridItem
|
|
key={project.id}
|
|
colSpan={index <= 1 ? 2 : 1} // NutriScan e Twins Lab em destaque
|
|
className={`group hover:scale-105 transition-all duration-300 hover:shadow-2xl ${config.borderColor} border-2 bg-white/5 backdrop-blur-sm`}
|
|
>
|
|
<motion.div
|
|
className="space-y-5 h-full"
|
|
initial={{ opacity: 0, y: 20 }}
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.6, delay: index * 0.1 }}
|
|
viewport={{ once: true }}
|
|
>
|
|
{/* Header com ícone e badges */}
|
|
<div className="flex items-start justify-between">
|
|
<div className="flex items-center gap-3">
|
|
<div className={`p-3 rounded-xl ${config.bgColor}`}>
|
|
<IconComponent
|
|
className={`w-6 h-6 ${config.iconColor}`}
|
|
/>
|
|
</div>
|
|
<div>
|
|
<h3 className="text-xl font-bold text-white mb-1">
|
|
{project.title}
|
|
</h3>
|
|
<p className="text-sm text-gray-400 font-medium">
|
|
{project.subtitle}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex gap-2">
|
|
{project.demoUrl && (
|
|
<a
|
|
href={project.demoUrl}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="p-2 rounded-lg bg-green-500/20 text-green-400 hover:bg-green-500/30 transition-all duration-300 hover:scale-110"
|
|
aria-label="Ver demo"
|
|
>
|
|
<ExternalLink className="w-4 h-4" />
|
|
</a>
|
|
)}
|
|
{project.githubUrl && (
|
|
<a
|
|
href={project.githubUrl}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="p-2 rounded-lg bg-gray-500/20 text-gray-400 hover:bg-gray-500/30 transition-all duration-300 hover:scale-110"
|
|
aria-label="Ver código"
|
|
>
|
|
<Github className="w-4 h-4" />
|
|
</a>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Badges */}
|
|
<div className="flex flex-wrap gap-2">
|
|
{project.badges.map((badge, idx) => (
|
|
<ProjectBadge key={badge} badge={badge} index={idx} />
|
|
))}
|
|
</div>
|
|
|
|
{/* Description */}
|
|
<p className="text-gray-300 leading-relaxed text-sm">
|
|
{project.description}
|
|
</p>
|
|
|
|
{/* Tecnologias principais */}
|
|
<div>
|
|
<h4 className="text-xs font-semibold text-gray-400 mb-2 uppercase tracking-wide">
|
|
Stack Principal
|
|
</h4>
|
|
<div className="flex flex-wrap gap-1">
|
|
{project.technologies.slice(0, 4).map((tech) => (
|
|
<span
|
|
key={tech}
|
|
className="px-2 py-1 bg-gray-800/50 text-gray-300 text-xs rounded border border-gray-700/50"
|
|
>
|
|
{tech}
|
|
</span>
|
|
))}
|
|
{project.technologies.length > 4 && (
|
|
<span className="px-2 py-1 bg-gray-800/30 text-gray-500 text-xs rounded">
|
|
+{project.technologies.length - 4}
|
|
</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Features principais */}
|
|
<div>
|
|
<h4 className="text-xs font-semibold text-gray-400 mb-2 uppercase tracking-wide">
|
|
Destaques
|
|
</h4>
|
|
<ul className="space-y-1">
|
|
{project.features.slice(0, 3).map((feature, idx) => (
|
|
<li
|
|
key={idx}
|
|
className="text-xs text-gray-400 flex items-start gap-2"
|
|
>
|
|
<Zap className="w-3 h-3 text-yellow-400 mt-0.5 flex-shrink-0" />
|
|
<span>{feature}</span>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
|
|
{/* Footer com complexidade e status */}
|
|
<div className="pt-4 border-t border-white/10 flex items-center justify-between">
|
|
<div className="flex items-center gap-2">
|
|
<span className="text-xs text-gray-500">
|
|
Complexidade:
|
|
</span>
|
|
<ComplexityStars
|
|
complexity={project.metrics?.complexity || 3}
|
|
/>
|
|
</div>
|
|
<span
|
|
className={`text-xs font-medium px-2 py-1 rounded-full ${
|
|
project.status === "Em Produção"
|
|
? "bg-green-500/20 text-green-400 border border-green-500/30"
|
|
: "bg-blue-500/20 text-blue-400 border border-blue-500/30"
|
|
}`}
|
|
>
|
|
{project.status}
|
|
</span>
|
|
</div>
|
|
</motion.div>
|
|
</BentoGridItem>
|
|
);
|
|
})}
|
|
</BentoGrid>
|
|
|
|
{/* Call to Action */}
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.8, delay: 0.5 }}
|
|
viewport={{ once: true }}
|
|
className="text-center mt-16"
|
|
>
|
|
<div className="inline-flex items-center gap-2 bg-gradient-to-r from-blue-500/10 to-purple-500/10 px-6 py-3 rounded-full border border-blue-500/20">
|
|
<div className="w-2 h-2 bg-blue-400 rounded-full animate-pulse"></div>
|
|
<span className="text-sm text-blue-300 font-medium">
|
|
Todos os projetos são desenvolvidos individualmente, demonstrando
|
|
capacidade full-stack completa
|
|
</span>
|
|
</div>
|
|
</motion.div>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|