- Added section IDs to About, Contact, and Hero components for improved navigation and accessibility. - Refactored Header component to utilize a structured navigation array, enhancing maintainability and responsiveness. - Updated Skills component to improve type handling and visual consistency, including a loading state and error handling. - Adjusted styling and animations across components for a smoother user experience and better visual appeal.
163 lines
6.1 KiB
TypeScript
163 lines
6.1 KiB
TypeScript
"use client";
|
|
|
|
import React, { useState, useEffect } from "react";
|
|
import { motion, AnimatePresence } from "framer-motion";
|
|
import { MenuIcon, XIcon, Layers } from "lucide-react";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
// Definição de Tipos para consistência de dados
|
|
interface NavItem {
|
|
href: string;
|
|
label: string;
|
|
}
|
|
|
|
const navItems: NavItem[] = [
|
|
{ href: "#home", label: "Home" },
|
|
{ href: "#about", label: "Sobre" },
|
|
{ href: "#skills", label: "Habilidades" },
|
|
{ href: "#experience", label: "Experiência" },
|
|
{ href: "#projects", label: "Projetos" },
|
|
{ href: "#contact", label: "Contato" },
|
|
];
|
|
|
|
export function Header() {
|
|
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
|
const [scrolled, setScrolled] = useState(false);
|
|
|
|
// Hook de detecção de scroll para mudança de estado do Header
|
|
useEffect(() => {
|
|
const handleScroll = () => {
|
|
setScrolled(window.scrollY > 20);
|
|
};
|
|
window.addEventListener("scroll", handleScroll);
|
|
return () => window.removeEventListener("scroll", handleScroll);
|
|
}, []);
|
|
|
|
return (
|
|
<>
|
|
<motion.header
|
|
initial={{ y: -100 }}
|
|
animate={{ y: 0 }}
|
|
className={cn(
|
|
"fixed top-0 left-0 right-0 z-[100] transition-all duration-500 ease-in-out px-6 py-4",
|
|
scrolled
|
|
? "bg-black/40 backdrop-blur-xl border-b border-white/5 py-3"
|
|
: "bg-transparent py-6"
|
|
)}
|
|
>
|
|
<nav className="max-w-7xl mx-auto flex items-center justify-between">
|
|
{/* Logo com Identidade Visual de Engenheiro */}
|
|
<motion.a
|
|
href="#home"
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
const element = document.querySelector("#home");
|
|
if (element) {
|
|
element.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
}
|
|
}}
|
|
whileHover={{ scale: 1.05 }}
|
|
className="flex items-center gap-2 cursor-pointer"
|
|
>
|
|
<div className="w-8 h-8 bg-blue-600 rounded-lg flex items-center justify-center shadow-[0_0_15px_rgba(37,99,235,0.4)]">
|
|
<Layers className="w-5 h-5 text-white" />
|
|
</div>
|
|
<span className="text-xl font-black tracking-tighter text-white">
|
|
EVANDRO<span className="text-blue-500">.DEV</span>
|
|
</span>
|
|
</motion.a>
|
|
|
|
{/* Desktop Navigation - Focada em Espaçamento Ótimo */}
|
|
<div className="hidden md:flex items-center gap-1 bg-white/5 p-1 rounded-full border border-white/10 backdrop-blur-md">
|
|
{navItems.map((item) => (
|
|
<a
|
|
key={item.href}
|
|
href={item.href}
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
const element = document.querySelector(item.href);
|
|
if (element) {
|
|
element.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
}
|
|
}}
|
|
className="px-5 py-2 text-xs font-bold uppercase tracking-widest text-slate-300 hover:text-white hover:bg-white/10 rounded-full transition-all duration-300 cursor-pointer"
|
|
>
|
|
{item.label}
|
|
</a>
|
|
))}
|
|
</div>
|
|
|
|
{/* Botão de Ação CTA (Call to Action) */}
|
|
<div className="hidden md:block">
|
|
<a
|
|
href="/Profile.pdf"
|
|
download="Evandro_Fonseca_Junior_CV.pdf"
|
|
className="px-6 py-2 bg-white text-black text-xs font-bold uppercase tracking-widest rounded-full hover:bg-blue-500 hover:text-white transition-all duration-500 inline-block"
|
|
>
|
|
Download CV
|
|
</a>
|
|
</div>
|
|
|
|
{/* Mobile Toggle */}
|
|
<button
|
|
onClick={() => setIsMenuOpen(!isMenuOpen)}
|
|
className="md:hidden p-2 text-white bg-white/5 rounded-lg border border-white/10"
|
|
>
|
|
{isMenuOpen ? <XIcon size={20} /> : <MenuIcon size={20} />}
|
|
</button>
|
|
</nav>
|
|
</motion.header>
|
|
|
|
{/* Mobile Menu Overlay com Framer Motion para fluidez */}
|
|
<AnimatePresence>
|
|
{isMenuOpen && (
|
|
<motion.div
|
|
initial={{ opacity: 0, x: "100%" }}
|
|
animate={{ opacity: 1, x: 0 }}
|
|
exit={{ opacity: 0, x: "100%" }}
|
|
transition={{ type: "spring", damping: 25, stiffness: 200 }}
|
|
className="fixed inset-0 z-[110] bg-black/95 backdrop-blur-2xl md:hidden flex flex-col items-center justify-center gap-8"
|
|
>
|
|
<button
|
|
onClick={() => setIsMenuOpen(false)}
|
|
className="absolute top-8 right-8 p-4 text-white/50 hover:text-white"
|
|
>
|
|
<XIcon size={32} />
|
|
</button>
|
|
|
|
{navItems.map((item, i) => (
|
|
<motion.a
|
|
key={item.href}
|
|
href={item.href}
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ delay: i * 0.1 }}
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
setIsMenuOpen(false);
|
|
const element = document.querySelector(item.href);
|
|
if (element) {
|
|
element.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
}
|
|
}}
|
|
className="text-3xl font-black text-white uppercase tracking-tighter hover:text-blue-500 transition-colors cursor-pointer"
|
|
>
|
|
{item.label}
|
|
</motion.a>
|
|
))}
|
|
<motion.a
|
|
href="/Profile.pdf"
|
|
download="Evandro_Fonseca_Junior_CV.pdf"
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ delay: navItems.length * 0.1 }}
|
|
className="mt-8 px-8 py-4 bg-white text-black text-xl font-black uppercase tracking-tighter rounded-full hover:bg-blue-500 hover:text-white transition-all duration-300"
|
|
>
|
|
Download CV
|
|
</motion.a>
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
</>
|
|
);
|
|
} |