diff --git a/app/components/About.tsx b/app/components/About.tsx new file mode 100644 index 0000000..9d72a01 --- /dev/null +++ b/app/components/About.tsx @@ -0,0 +1,111 @@ +"use client"; + +import { motion } from "motion/react"; +import { MagicCard } from "@/components/magicui/magic-card"; +import { TextReveal } from "@/components/magicui/text-reveal"; + +export default function About() { + return ( +
+
+ +

Sobre Mim

+
+ +
+ {/* Main Description */} + +
+ + Desenvolvedor Full-Stack com 3+ anos de experiência, + especializado em soluções web modernas e inovadoras. Cursando + Engenharia da Computação, com conhecimentos em computação em + nuvem e inteligência artificial. Busco constantemente novos + desafios onde possa aplicar minha paixão por tecnologia e + resolver problemas de forma criativa. + + + + Meu objetivo é entregar soluções de alta qualidade, sempre + priorizando colaboração, ética e inovação. Acredito no poder da + tecnologia para fazer a diferença e estou comprometido com o + aprendizado contínuo e a excelência técnica. + +
+
+ + {/* Soft Skills */} + +

+ Soft Skills +

+ +
+ {[ + { skill: "Comunicação", level: 90 }, + { skill: "Trabalho em Equipe", level: 85 }, + // { skill: "Resolução de Problemas", level: 95 }, + { skill: "Aprendizado Contínuo", level: 90 }, + { skill: "Adaptabilidade", level: 85 }, + ].map((item) => ( +
+
+ {item.skill} + {item.level}% +
+
+
+
+
+ ))} +
+ +
+

+ Destaques +

+
    +
  • +
    + 3+ anos de experiência +
  • +
  • +
    + Engenheiro da Computação +
  • +
  • +
    + Conhecimento em Cloud e IA +
  • +
  • +
    + Ampla gama de conhecimentos +
  • +
+
+ +
+
+
+ ); +} diff --git a/app/components/Contact.tsx b/app/components/Contact.tsx new file mode 100644 index 0000000..5a6bd20 --- /dev/null +++ b/app/components/Contact.tsx @@ -0,0 +1,90 @@ +"use client"; + +import { motion } from "motion/react"; +import { ShimmerButton } from "@/components/magicui/shimmer-button"; +import { useRef, useState } from "react"; +import { usePortfolioData } from "@/lib/usePortfolioData"; +import { ContactForm } from "./ContactForm"; // Importar o novo componente +import { ContactInfo } from "./ContactInfo"; // Importar o novo componente + +export default function Contact() { + const { data } = usePortfolioData(); + const contactSectionRef = useRef(null); + const [formType, setFormType] = useState<"simple" | "advanced">("simple"); // Estado para controlar o tipo de formulário + + const handleWhatsAppClick = () => { + setFormType("simple"); // Redireciona para o formulário simples + if (contactSectionRef.current) { + contactSectionRef.current.scrollIntoView({ behavior: "smooth" }); + } + }; + + const scrollToContact = () => { + if (contactSectionRef.current) { + contactSectionRef.current.scrollIntoView({ behavior: "smooth" }); + } + }; + + if (!data) { + return null; // Ou um componente de carregamento/erro + } + + return ( +
+
+ +

+ Vamos Conversar +

+

+ Estou sempre interessado em novos desafios e oportunidades. Vamos + discutir como posso ajudar seu projeto? +

+
+ +
+ {/* Contact Info */} + + + {/* Contact Options and Map */} +
+ {" "} + {/* Usar o novo componente ContactForm */} + {/* Mapa */} + +

+ Onde me encontrar +

+
+ +
+
+
+
+
+
+ ); +} diff --git a/app/components/ContactForm.tsx b/app/components/ContactForm.tsx new file mode 100644 index 0000000..39e5ada --- /dev/null +++ b/app/components/ContactForm.tsx @@ -0,0 +1,769 @@ +"use client"; + +import { useState, ChangeEvent, FormEvent, useEffect } from "react"; +import { usePortfolioData } from "@/lib/usePortfolioData"; +import { motion } from "motion/react"; + +interface ContactFormProps { + formType: "simple" | "advanced"; + setFormType: (type: "simple" | "advanced") => void; +} + +export function ContactForm({ formType, setFormType }: ContactFormProps) { + const { data } = usePortfolioData(); + const [contactMethod, setContactMethod] = useState<"whatsapp" | "email">( + "whatsapp" + ); + const [userName, setUserName] = useState(""); + const [userEmail, setUserEmail] = useState(""); + const [userPhone, setUserPhone] = useState(""); + const [userCompany, setUserCompany] = useState(""); + const [howFound, setHowFound] = useState(""); + const [projectType, setProjectType] = useState(""); + const [otherProjectType, setOtherProjectType] = useState(""); // Novo estado para "Outro" tipo de projeto + const [desiredDeadline, setDesiredDeadline] = useState(""); + const [budgetValue, setBudgetValue] = useState(""); // Novo estado para o valor do orçamento + const [technicalComplexity, setTechnicalComplexity] = useState(""); + const [hasInternalTeam, setHasInternalTeam] = useState(""); + const [customMessage, setCustomMessage] = useState(""); + const [isDecisor, setIsDecisor] = useState(""); + const [projectApproved, setProjectApproved] = useState(""); + const [urgency, setUrgency] = useState(""); + const [preferredModality, setPreferredModality] = useState(""); + const [isExpanded, setIsExpanded] = useState(false); // Novo estado para controlar a expansão do formulário avançado + const [nameError, setNameError] = useState(null); + const [emailError, setEmailError] = useState(null); + const [phoneError, setPhoneError] = useState(null); + const [otherProjectTypeError, setOtherProjectTypeError] = useState< + string | null + >(null); // Novo estado de erro + const [honeypot, setHoneypot] = useState(""); // Estado para o campo honeypot + const [startTime, setStartTime] = useState(0); // Estado para o tempo de início do formulário + const [nameFocused, setNameFocused] = useState(false); + const [emailFocused, setEmailFocused] = useState(false); + const [phoneFocused, setPhoneFocused] = useState(false); + + useEffect(() => { + setStartTime(Date.now()); // Registra o tempo de carregamento do componente + }, []); + + if (!data) { + return null; // Ou um componente de carregamento/erro + } + + const getGreetingMessageWithUserName = () => { + const hour = new Date().getHours(); + + const baseMessage = (name: string) => { + if (hour >= 8 && hour < 12) { + // Manhã + return `Bom dia Evandro, meu nome é ${name}. Vi seu portfólio e estou interessado nos seus serviços. Podemos conversar?`; + } else if (hour >= 13 && hour < 18) { + // Tarde + return `Boa tarde Evandro, meu nome é ${name}. Vi seu portfólio e estou interessado nos seus serviços. Podemos conversar?`; + } else { + // Noite + return `Olá Evandro, meu nome é ${name}. Vi seu portfólio e estou interessado nos seus serviços. Podemos conversar quando estiver disponível?`; + } + }; + return baseMessage(userName); + }; + + const generateAdvancedMessage = () => { + if (customMessage) return customMessage; + + let message = `Olá Evandro,\n\n`; + message += `Meu nome é ${userName}`; + if (userCompany) { + message += ` e sou de ${userCompany}`; + } + if (howFound) { + message += `. Conheci seu trabalho via ${howFound}`; + } + message += `.\n\n`; + + message += `Estou entrando em contato para discutir um projeto de `; + if (projectType === "outro" && otherProjectType) { + message += `${otherProjectType}`; + } else if (projectType) { + message += `${projectType}`; + } else { + message += `[Tipo de Projeto Não Especificado]`; + } + message += `.\n`; + + if (desiredDeadline) { + message += `O prazo desejado é ${desiredDeadline}.\n`; + } + if (budgetValue) { + message += `O orçamento disponível é ${budgetValue}.\n`; + } + if (technicalComplexity) { + message += `A complexidade técnica estimada é ${technicalComplexity}.\n`; + } + if (hasInternalTeam) { + message += `Já possuímos uma equipe técnica ${hasInternalTeam}.\n`; + } + + message += `\nMeus contatos:\n`; + if (userEmail) { + message += `Email: ${userEmail}\n`; + } + if (userPhone) { + message += `Telefone: ${userPhone}\n`; + } + + message += `\nAguardamos seu retorno para discutir os próximos passos.`; + + return message; + }; + + const handleNameChange = (e: ChangeEvent) => { + setUserName(e.target.value); + if (e.target.value.trim() !== "") setNameError(null); + }; + + const handleEmailChange = (e: ChangeEvent) => { + setUserEmail(e.target.value); + if (e.target.value.trim() !== "") setEmailError(null); + }; + + const handlePhoneChange = (e: ChangeEvent) => { + setUserPhone(e.target.value); + if (e.target.value.trim() !== "") setPhoneError(null); + }; + + const handleNameFocus = () => setNameFocused(true); + const handleEmailFocus = () => setEmailFocused(true); + const handlePhoneFocus = () => setPhoneFocused(true); + + const handleCompanyChange = (e: ChangeEvent) => { + setUserCompany(e.target.value); + }; + + const handleHowFoundChange = (e: ChangeEvent) => { + setHowFound(e.target.value); + }; + + const handleProjectTypeChange = (e: ChangeEvent) => { + setProjectType(e.target.value); + }; + + const handleDesiredDeadlineChange = (e: ChangeEvent) => { + setDesiredDeadline(e.target.value); + }; + + const handleBudgetValueChange = (e: ChangeEvent) => { + setBudgetValue(e.target.value); + }; + + const handleTechnicalComplexityChange = ( + e: ChangeEvent + ) => { + setTechnicalComplexity(e.target.value); + }; + + const handleHasInternalTeamChange = (e: ChangeEvent) => { + setHasInternalTeam(e.target.value); + }; + + const handleMessageChange = (e: ChangeEvent) => { + setCustomMessage(e.target.value); + }; + + const generateWhatsAppUrl = () => { + const message = encodeURIComponent( + formType === "advanced" + ? generateAdvancedMessage() + : customMessage || getGreetingMessageWithUserName() + ); + const phone = data.contact.whatsapp.replace(/\D/g, ""); // Remove caracteres não numéricos + return `https://wa.me/${phone}?text=${message}`; + }; + + const generateEmailUrl = () => { + const subject = encodeURIComponent("Proposta de Projeto - Portfólio"); + const body = encodeURIComponent( + formType === "advanced" + ? generateAdvancedMessage() + : `Nome: ${userName}\nEmail: ${userEmail}\nTelefone: ${userPhone}\nEmpresa/Posição: ${userCompany}\nComo conheceu: ${howFound}\n\nTipo de Projeto: ${projectType}\nPrazo Desejado: ${desiredDeadline}\nOrçamento Disponível: ${budgetValue}\nComplexidade Técnica: ${technicalComplexity}\nTem equipe interna: ${hasInternalTeam}\n\nMensagem: ${ + customMessage || getGreetingMessageWithUserName() + }` + ); + return `mailto:${data.contact.email}?subject=${subject}&body=${body}`; + }; + + const handleSubmit = (e: FormEvent) => { + e.preventDefault(); + + let hasError = false; + if (userName.trim() === "") { + setNameError("Por favor, insira seu nome."); + hasError = true; + } else { + setNameError(null); + } + + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(userEmail)) { + setEmailError("Por favor, insira um email válido."); + hasError = true; + } else { + setEmailError(null); + } + + if (userPhone.trim() === "") { + setPhoneError("Por favor, insira seu telefone."); + hasError = true; + } else { + setPhoneError(null); + } + + if (projectType === "outro" && otherProjectType.trim() === "") { + setOtherProjectTypeError("Por favor, especifique o tipo de projeto."); + hasError = true; + } else { + setOtherProjectTypeError(null); + } + + // Validação Honeypot + if (honeypot) { + console.log("Honeypot preenchido, provável bot."); + return; // Ignora a submissão + } + + // Validação de tempo de preenchimento (mínimo de 3 segundos) + const timeElapsed = Date.now() - startTime; + if (timeElapsed < 3000) { + console.log("Formulário preenchido muito rápido, provável bot."); + return; // Ignora a submissão + } + + if (hasError) return; + + // Verificação de interação mínima + if (!nameFocused || !emailFocused || !phoneFocused) { + console.log("Campos principais não foram focados, provável bot."); + // Opcional: exibir uma mensagem de erro para o usuário + return; + } + + if (contactMethod === "whatsapp") { + window.open(generateWhatsAppUrl(), "_blank"); + } else { + window.open(generateEmailUrl(), "_blank"); + } + }; + + const getCtaMessage = () => { + if (projectType === "consultoria") { + return "Agendar Consultoria Gratuita"; + } + if (budgetValue && parseFloat(budgetValue.replace(/\D/g, "")) < 2000) { + return "Discutir Soluções de Baixo Custo"; + } + if (projectType && desiredDeadline && budgetValue) { + return "Receber Proposta Personalizada"; + } + return "Enviar Formulário"; + }; + + const getEstimatedValue = () => { + let value = 0; + // Lógica de estimativa baseada nas respostas + switch (projectType) { + case "web-app": + value += 10000; + break; + case "landing-page": + value += 2000; + break; + case "mvp": + value += 5000; + break; + case "iot-app": + value += 15000; + break; + case "integracao": + value += 7000; + break; + case "manutencao": + value += 1000; + break; + case "consultoria": + value += 500; + break; + default: + break; + } + + switch (desiredDeadline) { + case "1-3-weeks": + value *= 1.2; // Urgência aumenta o valor + break; + case "1-2-months": + value *= 1.1; + break; + case "6-plus-months": + value *= 0.9; // Prazo longo pode reduzir o valor + break; + default: + break; + } + + switch (technicalComplexity) { + case "alta": + value *= 1.3; + break; + case "muito-alta": + value *= 1.5; + break; + default: + break; + } + + if (hasInternalTeam === "nao") { + value *= 1.1; // Sem equipe interna pode indicar mais trabalho + } + + // Ajustar pelo valor do orçamento, se aplicável + if (budgetValue) { + const parsedBudget = parseFloat(budgetValue.replace(/\D/g, "")); + if (!isNaN(parsedBudget)) { + // Se o valor estimado for maior que o orçamento fornecido, ajusta para o orçamento + value = Math.min(value, parsedBudget); + } + } + + return value > 0 ? `Estimativa: R$ ${value.toFixed(2)}` : "Estimativa: N/A"; + }; + + return ( + +

+ Preencha o Formulário +

+ +
+ + +
+ + {formType === "simple" ? ( +
+ {/* Campo Honeypot */} +
+ + setHoneypot(e.target.value)} + /> +
+ + {/* Formulário Simples: Apenas Nome */} +
+ + + {nameError && ( +

{nameError}

+ )} +
+ + {/* Botão de Contato para o formulário simples */} + + + +
+ ) : ( +
+ {/* Campo Honeypot */} +
+ + setHoneypot(e.target.value)} + /> +
+ + {/* Dados Básicos */} +
+

+ Dados Básicos +

+
+
+ + + {nameError && ( +

{nameError}

+ )} +
+
+ + + {emailError && ( +

{emailError}

+ )} +
+
+ + + {phoneError && ( +

{phoneError}

+ )} +
+
+ + +
+
+ + +
+
+
+ + {/* Tile de Expandir/Recolher */} +
setIsExpanded(!isExpanded)} + className="p-4 rounded-lg bg-white/5 border border-white/10 text-white cursor-pointer hover:bg-white/10 transition-colors duration-300 mb-6 flex justify-between items-center" + > +

+ {isExpanded + ? "Detalhes do Projeto (Recolher)" + : "Detalhes do Projeto (Expandir)"} +

+ + + +
+ +
+ {/* Conteúdo do Formulário Avançado */} + {/* Sobre o Projeto */} +
+

+ Sobre o Projeto +

+
+
+ + +
+ {projectType === "outro" && ( +
+ + { + setOtherProjectType(e.target.value); + if (e.target.value.trim() !== "") + setOtherProjectTypeError(null); + }} + required + /> + {otherProjectTypeError && ( +

+ {otherProjectTypeError} +

+ )} +
+ )} +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + {/* Custom Message Input */} +
+ + +
+
+ + {/* Contact Button */} + + + + {projectType && desiredDeadline && budgetValue && ( +

+ {getEstimatedValue()} +

+ )} +
+ )} +
+ ); +} diff --git a/app/components/ContactInfo.tsx b/app/components/ContactInfo.tsx new file mode 100644 index 0000000..7759495 --- /dev/null +++ b/app/components/ContactInfo.tsx @@ -0,0 +1,128 @@ +"use client"; + +import { motion } from "motion/react"; +import { Mail, MessageCircle } from "lucide-react"; +import { usePortfolioData } from "@/lib/usePortfolioData"; + +interface ContactInfoProps { + onWhatsAppClick: () => void; +} + +export function ContactInfo({ onWhatsAppClick }: ContactInfoProps) { + const { data } = usePortfolioData(); + + if (!data) { + return null; // Ou um componente de carregamento/erro + } + + return ( + + + +
+

+ Localização +

+

📍 {data.contact.location}

+

+ Disponível para projetos remotos e trabalho presencial na região. +

+
+ + {/* Timeline de Resposta (Placeholder) */} +
+

+ Timeline de Resposta +

+

+ Em breve, uma timeline detalhada do processo de contato e resposta. +

+
+
+ ); +} diff --git a/app/components/Experience.tsx b/app/components/Experience.tsx new file mode 100644 index 0000000..8a65dc9 --- /dev/null +++ b/app/components/Experience.tsx @@ -0,0 +1,123 @@ +"use client"; + +import { motion } from "motion/react"; +import { BentoGrid, BentoGridItem } from "@/components/magicui/bento-grid"; +import { Calendar, MapPin, Briefcase } from "lucide-react"; +import { usePortfolioData } from "@/lib/usePortfolioData"; + +export default function Experience() { + const { data, loading, error } = usePortfolioData(); + + if (loading) { + return ( +
+
+
+
+
+
+
+
+ ); + } + + if (error || !data) { + return ( +
+
+

Erro ao carregar dados de experiência.

+
+
+ ); + } + + const experiences = data.experiences.filter((exp) => exp.featured); + + return ( +
+
+ +

+ Experiência Profissional +

+

+ Minha jornada profissional em desenvolvimento de software e soluções + tecnológicas +

+
+ + + {experiences.map((exp, index) => ( + +
+ {/* Header */} +
+
+

+ {exp.title} +

+

{exp.company}

+
+
+ + {exp.period} +
+ {exp.location && ( +
+ + {exp.location} +
+ )} +
+ + {exp.type} +
+
+
+
+ + {/* Description */} +

+ {exp.description} +

+ + {/* Achievements */} + {exp.achievements.length > 0 && ( +
+

+ Principais Conquistas: +

+
    + {exp.achievements.map((achievement, idx) => ( +
  • +
    + {achievement} +
  • + ))} +
+
+ )} +
+
+ ))} +
+
+
+ ); +} diff --git a/app/components/Header.tsx b/app/components/Header.tsx new file mode 100644 index 0000000..fab0c09 --- /dev/null +++ b/app/components/Header.tsx @@ -0,0 +1,151 @@ +"use client"; + +import { motion } from "motion/react"; +import { CalendarIcon, HomeIcon, MailIcon, PencilIcon } from "lucide-react"; +import Link from "next/link"; +import React from "react"; + +import { cn } from "@/lib/utils"; +import { Dock, DockIcon } from "@/components/magicui/dock"; + +export type IconProps = React.HTMLAttributes; + +const Icons = { + calendar: (props: IconProps) => , + email: (props: IconProps) => , + linkedin: (props: IconProps) => ( + + LinkedIn + + + ), + x: (props: IconProps) => ( + + X + + + ), + youtube: (props: IconProps) => ( + + youtube + + + ), + github: (props: IconProps) => ( + + + + ), +}; + +const DATA = { + navbar: [ + { href: "#", icon: HomeIcon, label: "Home" }, + { href: "#", icon: PencilIcon, label: "Blog" }, + ], + contact: { + social: { + GitHub: { + name: "GitHub", + url: "#", + icon: Icons.github, + }, + LinkedIn: { + name: "LinkedIn", + url: "#", + icon: Icons.linkedin, + }, + X: { + name: "X", + url: "#", + icon: Icons.x, + }, + email: { + name: "Send Email", + url: "#", + icon: Icons.email, + }, + }, + }, +}; + +export function Header() { + return ( + + + + ); +} diff --git a/app/components/Hero.tsx b/app/components/Hero.tsx new file mode 100644 index 0000000..f50dbdb --- /dev/null +++ b/app/components/Hero.tsx @@ -0,0 +1,203 @@ +"use client"; + +import { motion } from "motion/react"; +import Image from "next/image"; +import { AuroraText } from "@/components/magicui/aurora-text"; +import { AnimatedGradientText } from "@/components/magicui/animated-gradient-text"; +import { useEffect } from "react"; + +export default function Hero() { + const getGreetingMessage = () => { + const hour = new Date().getHours(); + + if (hour >= 8 && hour < 12) { + // Manhã + return "Olá! Vi seu portfólio e estou interessado nos seus serviços. Podemos conversar?"; + } else if (hour >= 13 && hour < 18) { + // Tarde + return "Boa tarde! Vi seu portfólio e estou interessado nos seus serviços. Podemos conversar?"; + } else { + // Noite + return "Olá! Vi seu portfólio e estou interessado nos seus serviços. Podemos conversar quando estiver disponível?"; + } + }; + + const generateWhatsAppUrl = () => { + const message = encodeURIComponent(getGreetingMessage()); + const phone = "5547992742365"; // Número sem formatação + return `https://wa.me/${phone}?text=${message}`; + }; + + const scrollToContact = () => { + const contactSection = document.getElementById("contact"); + if (contactSection) { + contactSection.scrollIntoView({ behavior: "smooth" }); + } + }; + + return ( +
+ {/* Background Pattern */} +
+ +
+ {/* Profile Image */} + +
+ Evandro Fonseca Junior - Desenvolvedor Full-Stack +
+
+ + {/* Name and Title */} + +

+ Evandro Fonseca Junior +

+ + Desenvolvedor Full-Stack + +
+ + {/* Description */} + + Especialista em soluções web modernas e inovadoras. Formado em + Engenharia da Computação, com conhecimentos sólidos em computação em + nuvem e inteligência artificial. + + + {/* Location */} + + 📍 Garuva, Santa Catarina, Brasil + + + {/* Social Icons */} + + + + + + + + + + + + { + e.preventDefault(); + scrollToContact(); + }} + className="text-gray-400 hover:text-green-400 transition-colors" + aria-label="WhatsApp" + > + + + + + { + e.preventDefault(); + scrollToContact(); + }} + className="text-gray-400 hover:text-blue-400 transition-colors" + aria-label="Email" + > + + + + + + + {/* CTA Button */} + + + Vamos Trabalhar Juntos + + +
+ + {/* Scroll Indicator */} + +
+ +
+
+
+ ); +} diff --git a/app/components/Projects.tsx b/app/components/Projects.tsx new file mode 100644 index 0000000..fe96e4d --- /dev/null +++ b/app/components/Projects.tsx @@ -0,0 +1,134 @@ +"use client"; + +import { motion } from "motion/react"; +import { BentoGrid, BentoGridItem } from "@/components/magicui/bento-grid"; +import { ExternalLink, Github } from "lucide-react"; +import { usePortfolioData } from "@/lib/usePortfolioData"; + +export default function Projects() { + const { data, loading, error } = usePortfolioData(); + + if (loading) { + return ( +
+
+
+
+
+
+
+
+ ); + } + + if (error || !data) { + return ( +
+
+

Erro ao carregar dados de projetos.

+
+
+ ); + } + + const projects = data.projects.filter((project) => project.featured); + + return ( +
+
+ +

+ Projetos em Destaque +

+

+ Alguns dos projetos que desenvolvi, demonstrando minhas habilidades + e paixão por tecnologia +

+
+ + + {projects.map((project, index) => ( + +
+ {/* Header */} +
+
+

+ {project.title} +

+
+ {project.technologies.map((tech) => ( + + {tech} + + ))} +
+
+ +
+ + {/* Description */} +

+ {project.description} +

+ + {/* Features */} +
+

+ Funcionalidades: +

+
    + {project.features.map((feature, idx) => ( +
  • +
    + {feature} +
  • + ))} +
+
+ + {/* Status */} +
+ + {project.status} + +
+
+
+ ))} +
+
+
+ ); +} diff --git a/app/components/Skills.tsx b/app/components/Skills.tsx new file mode 100644 index 0000000..07077d7 --- /dev/null +++ b/app/components/Skills.tsx @@ -0,0 +1,135 @@ +"use client"; + +import { motion } from "motion/react"; +import { MagicCard } from "@/components/magicui/magic-card"; +import { + Code, + Database, + Cloud, + Cpu, + Smartphone, + Zap, + GitBranch, + TestTube, +} from "lucide-react"; +import { usePortfolioData } from "@/lib/usePortfolioData"; + +// Mapa de ícones para as categorias +const iconMap = { + Code: Code, + Database: Database, + Cloud: Cloud, + Cpu: Cpu, + Smartphone: Smartphone, + Zap: Zap, + GitBranch: GitBranch, + TestTube: TestTube, +}; + +// Mapa de cores para garantir que o Tailwind inclua as classes no bundle +const colorMap = { + "text-blue-440": "bg-blue-400", + "text-blue-400": "bg-blue-400", + "text-green-400": "bg-blue-400", + "text-purple-400": "bg-blue-400", + "text-red-400": "bg-blue-400", + "text-yellow-400": "bg-blue-400", + "text-pink-400": "bg-blue-400", + "text-indigo-400": "bg-blue-400", +}; + +export default function Skills() { + const { data, loading, error } = usePortfolioData(); + + if (loading) { + return ( +
+
+
+
+
+
+
+
+ ); + } + + if (error || !data) { + return ( +
+
+

Erro ao carregar dados de habilidades.

+
+
+ ); + } + + const skillCategories = data.skills.technical; + + return ( +
+
+ +

+ Habilidades Técnicas +

+

+ Tecnologias e ferramentas que utilizo para criar soluções inovadoras +

+
+ +
+ {skillCategories.map((category, index) => { + const IconComponent = + iconMap[category.icon as keyof typeof iconMap] || Code; + return ( + +
+ {/* Header */} +
+
+ +
+

+ {category.category} +

+
+ + {/* Skills List */} +
+ {category.skills.map((skill, skillIndex) => ( +
+
+ {skill} +
+ ))} +
+
+ + ); + })} +
+
+
+ ); +} diff --git a/app/components/WorkModalities.tsx b/app/components/WorkModalities.tsx new file mode 100644 index 0000000..6f41760 --- /dev/null +++ b/app/components/WorkModalities.tsx @@ -0,0 +1,126 @@ +"use client"; + +import { motion } from "motion/react"; +import { Briefcase, Clock, DollarSign, HardHat, Lightbulb } from "lucide-react"; + +const WorkModalitiesData = [ + { + title: "Desenvolvimento por Demanda", + value: "A partir de R$ 50/hora", + idealFor: + "Empresas que buscam otimização rápida, startups com necessidades urgentes ou projetos que exigem intervenção especializada e focada.", + description: + "Soluções ágeis e focadas para desafios específicos. Ideal para otimizar recursos e obter resultados rápidos sem compromissos de longo prazo. Sua visão, minha expertise, entregas pontuais.", + icon: Clock, + color: "blue", + }, + { + title: "Retainer Mensal", + value: "Sob consulta (pacotes personalizados)", + idealFor: + "Negócios que valorizam estabilidade, crescimento planejado e acesso contínuo a um especialista dedicado para impulsionar sua inovação.", + description: + "Parceria estratégica para evolução contínua e suporte prioritário. Garanta acesso exclusivo à minha expertise para manutenção, novas funcionalidades e consultoria estratégica, com um compromisso de longo prazo que otimiza seu investimento.", + icon: Briefcase, + color: "green", + }, + { + title: "Consultoria Especializada", + value: "Sob consulta (projetos de alto impacto)", + idealFor: + "Líderes de tecnologia e empresas visionárias que enfrentam gargalos técnicos ou buscam validação e aprimoramento de suas estratégias de desenvolvimento.", + description: + "Direcionamento estratégico e técnico para desafios complexos. Minha experiência em arquitetura de sistemas, IoT e otimização de performance é o diferencial para transformar suas ideias em soluções robustas e escaláveis. Ideal para quem busca excelência e inovação.", + icon: Lightbulb, + color: "purple", + }, + { + title: "Desenvolvimento Completo", + value: "A partir de R$ 5.000 (investimento inicial)", + idealFor: + "Empreendedores e empresas que precisam de um parceiro para construir soluções digitais do zero, com a garantia de um produto de alta qualidade e impacto.", + description: + "Da concepção à entrega, seu projeto ganha vida com uma solução completa e personalizada. Garanto um processo transparente, com foco em performance, segurança e escalabilidade, para que você tenha um produto final que realmente se destaque no mercado.", + icon: HardHat, + color: "orange", + }, + { + title: "Projetos Fechados", + value: "A partir de R$ 5.000 (soluções sob medida)", + idealFor: + "Clientes com necessidades claras e orçamentos específicos, que buscam uma entrega eficiente e sem surpresas para projetos de médio porte.", + description: + "Soluções com escopo e prazo definidos para resultados previsíveis. Ideal para quem busca agilidade e clareza no investimento, com a certeza de uma entrega que atende exatamente às suas expectativas e impulsiona seus objetivos.", + icon: DollarSign, + color: "red", + }, +]; + +export function WorkModalities() { + return ( +
+ +

+ Minhas Modalidades de Trabalho +

+

+ Escolha a opção que melhor se encaixa nas suas necessidades. +

+
+ +
+ {WorkModalitiesData.map((modality, index) => ( + +
+ +
+

{modality.title}

+

+ {modality.value} +

+

{modality.description}

+

+ Ideal para:{" "} + {modality.idealFor} +

+
+ ))} +
+
+ ); +} diff --git a/app/globals.css b/app/globals.css index a2dc41e..3c7d0b1 100644 --- a/app/globals.css +++ b/app/globals.css @@ -1,26 +1,167 @@ @import "tailwindcss"; +@import "tw-animate-css"; -:root { - --background: #ffffff; - --foreground: #171717; -} +@custom-variant dark (&:is(.dark *)); @theme inline { --color-background: var(--background); --color-foreground: var(--foreground); --font-sans: var(--font-geist-sans); --font-mono: var(--font-geist-mono); -} - -@media (prefers-color-scheme: dark) { - :root { - --background: #0a0a0a; - --foreground: #ededed; + --color-sidebar-ring: var(--sidebar-ring); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar: var(--sidebar); + --color-chart-5: var(--chart-5); + --color-chart-4: var(--chart-4); + --color-chart-3: var(--chart-3); + --color-chart-2: var(--chart-2); + --color-chart-1: var(--chart-1); + --color-ring: var(--ring); + --color-input: var(--input); + --color-border: var(--border); + --color-destructive: var(--destructive); + --color-accent-foreground: var(--accent-foreground); + --color-accent: var(--accent); + --color-muted-foreground: var(--muted-foreground); + --color-muted: var(--muted); + --color-secondary-foreground: var(--secondary-foreground); + --color-secondary: var(--secondary); + --color-primary-foreground: var(--primary-foreground); + --color-primary: var(--primary); + --color-popover-foreground: var(--popover-foreground); + --color-popover: var(--popover); + --color-card-foreground: var(--card-foreground); + --color-card: var(--card); + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); + --animate-aurora: aurora 8s ease-in-out infinite alternate; + @keyframes aurora { + 0% { + background-position: 0% 50%; + transform: rotate(-5deg) scale(0.9); + } + 25% { + background-position: 50% 100%; + transform: rotate(5deg) scale(1.1); + } + 50% { + background-position: 100% 50%; + transform: rotate(-3deg) scale(0.95); + } + 75% { + background-position: 50% 0%; + transform: rotate(3deg) scale(1.05); + } + 100% { + background-position: 0% 50%; + transform: rotate(-5deg) scale(0.9); + } } } -body { - background: var(--background); - color: var(--foreground); - font-family: Arial, Helvetica, sans-serif; +:root { + --radius: 0.625rem; + --background: oklch(1 0 0); + --foreground: oklch(0.145 0 0); + --card: oklch(1 0 0); + --card-foreground: oklch(0.145 0 0); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.145 0 0); + --primary: oklch(0.205 0 0); + --primary-foreground: oklch(0.985 0 0); + --secondary: oklch(0.97 0 0); + --secondary-foreground: oklch(0.205 0 0); + --muted: oklch(0.97 0 0); + --muted-foreground: oklch(0.556 0 0); + --accent: oklch(0.97 0 0); + --accent-foreground: oklch(0.205 0 0); + --destructive: oklch(0.577 0.245 27.325); + --border: oklch(0.922 0 0); + --input: oklch(0.922 0 0); + --ring: oklch(0.708 0 0); + --blue-primary: #4facfe; /* Nova cor azul primária */ + --chart-1: oklch(0.646 0.222 41.116); + --chart-2: oklch(0.6 0.118 184.704); + --chart-3: oklch(0.398 0.07 227.392); + --chart-4: oklch(0.828 0.189 84.429); + --chart-5: oklch(0.769 0.188 70.08); + --sidebar: oklch(0.985 0 0); + --sidebar-foreground: oklch(0.145 0 0); + --sidebar-primary: oklch(0.205 0 0); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.97 0 0); + --sidebar-accent-foreground: oklch(0.205 0 0); + --sidebar-border: oklch(0.922 0 0); + --sidebar-ring: oklch(0.708 0 0); +} + +.dark { + --background: oklch(0.145 0 0); + --foreground: oklch(0.985 0 0); + --card: oklch(0.205 0 0); + --card-foreground: oklch(0.985 0 0); + --popover: oklch(0.205 0 0); + --popover-foreground: oklch(0.985 0 0); + --primary: oklch(0.922 0 0); + --primary-foreground: oklch(0.205 0 0); + --secondary: oklch(0.269 0 0); + --secondary-foreground: oklch(0.985 0 0); + --muted: oklch(0.269 0 0); + --muted-foreground: oklch(0.708 0 0); + --accent: oklch(0.269 0 0); + --accent-foreground: oklch(0.985 0 0); + --destructive: oklch(0.704 0.191 22.216); + --border: oklch(1 0 0 / 10%); + --input: oklch(1 0 0 / 15%); + --ring: oklch(0.556 0 0); + --blue-primary: #4facfe; /* Nova cor azul primária */ + --chart-1: oklch(0.488 0.243 264.376); + --chart-2: oklch(0.696 0.17 162.48); + --chart-3: oklch(0.769 0.188 70.08); + --chart-4: oklch(0.627 0.265 303.9); + --chart-5: oklch(0.645 0.246 16.439); + --sidebar: oklch(0.205 0 0); + --sidebar-foreground: oklch(0.985 0 0); + --sidebar-primary: oklch(0.488 0.243 264.376); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.269 0 0); + --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-border: oklch(1 0 0 / 10%); + --sidebar-ring: oklch(0.556 0 0); +} + +@layer base { + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; + } +} + +@layer utilities { + .animate-shimmer { + animation: shimmer 2s linear infinite; + } + + @keyframes shimmer { + 0% { + transform: translateX(-100%); + } + 100% { + transform: translateX(100%); + } + } +} + +html { + scroll-behavior: smooth; /* Adicionar rolagem suave */ + scroll-padding-top: 64px; /* Altura do cabeçalho fixo */ } diff --git a/app/layout.tsx b/app/layout.tsx index f7fa87e..c2c578c 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -13,8 +13,59 @@ const geistMono = Geist_Mono({ }); export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", + title: "Evandro Fonseca Junior - Desenvolvedor Full-Stack", + description: + "Desenvolvedor Full-Stack especializado em soluções web modernas com Angular, React, Node.js e computação em nuvem. Experiência em energia renovável e IoT.", + keywords: [ + "desenvolvedor", + "full-stack", + "angular", + "react", + "node.js", + "typescript", + "computação em nuvem", + "iot", + "energia renovável", + ], + authors: [{ name: "Evandro Fonseca Junior" }], + creator: "Evandro Fonseca Junior", + publisher: "Evandro Fonseca Junior", + formatDetection: { + email: false, + address: false, + telephone: false, + }, + metadataBase: new URL("https://evandrofonsecajunior.dev"), + alternates: { + canonical: "/", + }, + openGraph: { + title: "Evandro Fonseca Junior - Desenvolvedor Full-Stack", + description: + "Desenvolvedor Full-Stack especializado em soluções web modernas com Angular, React, Node.js e computação em nuvem.", + url: "https://evandrofonsecajunior.dev", + siteName: "Evandro Fonseca Junior", + locale: "pt_BR", + type: "website", + }, + twitter: { + card: "summary_large_image", + title: "Evandro Fonseca Junior - Desenvolvedor Full-Stack", + description: + "Desenvolvedor Full-Stack especializado em soluções web modernas com Angular, React, Node.js e computação em nuvem.", + creator: "@evandrodevbr", + }, + robots: { + index: true, + follow: true, + googleBot: { + index: true, + follow: true, + "max-video-preview": -1, + "max-image-preview": "large", + "max-snippet": -1, + }, + }, }; export default function RootLayout({ @@ -23,7 +74,51 @@ export default function RootLayout({ children: React.ReactNode; }>) { return ( - + + + {/* Preconnect to external origins for better performance */} + + + + + + + {/* DNS prefetch for additional performance */} + + + + + + + {/* Favicon and icons */} + + + + + {/* SEO and Crawling */} + + + {/* Theme color for mobile browsers */} + + + + + + + {/* Performance hints */} + + + diff --git a/app/page.tsx b/app/page.tsx index 21b686d..36fb1df 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,103 +1,78 @@ -import Image from "next/image"; +import dynamic from "next/dynamic"; +import { Header } from "./components/Header"; +import { Squares } from "@/components/magicui/squares"; +import { WorkModalities } from "./components/WorkModalities"; // Importar WorkModalities + +// Lazy load components for better performance +const Hero = dynamic(() => import("./components/Hero"), { + loading: () => ( +
+
Carregando...
+
+ ), +}); + +const About = dynamic(() => import("./components/About"), { + loading: () => ( +
+
Carregando seção...
+
+ ), +}); + +const Skills = dynamic(() => import("./components/Skills"), { + loading: () => ( +
+
Carregando habilidades...
+
+ ), +}); + +const Experience = dynamic(() => import("./components/Experience"), { + loading: () => ( +
+
Carregando experiência...
+
+ ), +}); + +const Projects = dynamic(() => import("./components/Projects"), { + loading: () => ( +
+
Carregando projetos...
+
+ ), +}); + +const Contact = dynamic(() => import("./components/Contact"), { + loading: () => ( +
+
Carregando contato...
+
+ ), +}); export default function Home() { return ( -
-
- Next.js logo -
    -
  1. - Get started by editing{" "} - - app/page.tsx - - . -
  2. -
  3. - Save and see your changes instantly. -
  4. -
- - +
+ +
+
+ + + + + + {/* Adicionar WorkModalities aqui */} +
-
); } diff --git a/components.json b/components.json new file mode 100644 index 0000000..26ad91e --- /dev/null +++ b/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "", + "css": "app/globals.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "iconLibrary": "lucide", + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + } +} diff --git a/components/magicui/animated-gradient-text.tsx b/components/magicui/animated-gradient-text.tsx new file mode 100644 index 0000000..b2c05dd --- /dev/null +++ b/components/magicui/animated-gradient-text.tsx @@ -0,0 +1,36 @@ +"use client"; + +import { motion } from "motion/react"; +import { cn } from "@/lib/utils"; + +interface AnimatedGradientTextProps { + children: React.ReactNode; + className?: string; +} + +export function AnimatedGradientText({ + children, + className, +}: AnimatedGradientTextProps) { + return ( + + {children} + + ); +} diff --git a/components/magicui/aurora-text.tsx b/components/magicui/aurora-text.tsx new file mode 100644 index 0000000..f774f9b --- /dev/null +++ b/components/magicui/aurora-text.tsx @@ -0,0 +1,33 @@ +"use client"; + +import { motion } from "motion/react"; +import { cn } from "@/lib/utils"; + +interface AuroraTextProps { + children: React.ReactNode; + className?: string; +} + +export function AuroraText({ children, className }: AuroraTextProps) { + return ( + + {children} + + ); +} diff --git a/components/magicui/bento-grid.tsx b/components/magicui/bento-grid.tsx new file mode 100644 index 0000000..2e7dd2b --- /dev/null +++ b/components/magicui/bento-grid.tsx @@ -0,0 +1,54 @@ +"use client"; + +import { motion } from "motion/react"; +import { cn } from "@/lib/utils"; + +interface BentoGridProps { + children: React.ReactNode; + className?: string; +} + +interface BentoGridItemProps { + children: React.ReactNode; + className?: string; + colSpan?: number; + rowSpan?: number; +} + +export function BentoGrid({ children, className }: BentoGridProps) { + return ( +
+ {children} +
+ ); +} + +export function BentoGridItem({ + children, + className, + colSpan = 1, + rowSpan = 1, +}: BentoGridItemProps) { + return ( + +
+
{children}
+ + ); +} diff --git a/components/magicui/dock.tsx b/components/magicui/dock.tsx new file mode 100644 index 0000000..ca40afe --- /dev/null +++ b/components/magicui/dock.tsx @@ -0,0 +1,153 @@ +"use client"; + +import { cva, type VariantProps } from "class-variance-authority"; +import { + motion, + MotionValue, + useMotionValue, + useSpring, + useTransform, +} from "motion/react"; +import type { MotionProps } from "motion/react"; +import React, { PropsWithChildren, useRef } from "react"; + +import { cn } from "@/lib/utils"; + +export interface DockProps extends VariantProps { + className?: string; + iconSize?: number; + iconMagnification?: number; + disableMagnification?: boolean; + iconDistance?: number; + direction?: "top" | "middle" | "bottom"; + children: React.ReactNode; +} + +const DEFAULT_SIZE = 40; +const DEFAULT_MAGNIFICATION = 60; +const DEFAULT_DISTANCE = 140; +const DEFAULT_DISABLEMAGNIFICATION = false; + +const dockVariants = cva( + "supports-backdrop-blur:bg-white/10 supports-backdrop-blur:dark:bg-black/10 mx-auto mt-8 flex h-[58px] w-max items-center justify-center gap-2 rounded-2xl border p-2 backdrop-blur-md", +); + +const Dock = React.forwardRef( + ( + { + className, + children, + iconSize = DEFAULT_SIZE, + iconMagnification = DEFAULT_MAGNIFICATION, + disableMagnification = DEFAULT_DISABLEMAGNIFICATION, + iconDistance = DEFAULT_DISTANCE, + direction = "middle", + ...props + }, + ref, + ) => { + const mouseX = useMotionValue(Infinity); + + const renderChildren = () => { + return React.Children.map(children, (child) => { + if ( + React.isValidElement(child) && + child.type === DockIcon + ) { + return React.cloneElement(child, { + ...child.props, + mouseX: mouseX, + size: iconSize, + magnification: iconMagnification, + disableMagnification: disableMagnification, + distance: iconDistance, + }); + } + return child; + }); + }; + + return ( + mouseX.set(e.pageX)} + onMouseLeave={() => mouseX.set(Infinity)} + {...props} + className={cn(dockVariants({ className }), { + "items-start": direction === "top", + "items-center": direction === "middle", + "items-end": direction === "bottom", + })} + > + {renderChildren()} + + ); + }, +); + +Dock.displayName = "Dock"; + +export interface DockIconProps + extends Omit, "children"> { + size?: number; + magnification?: number; + disableMagnification?: boolean; + distance?: number; + mouseX?: MotionValue; + className?: string; + children?: React.ReactNode; + props?: PropsWithChildren; +} + +const DockIcon = ({ + size = DEFAULT_SIZE, + magnification = DEFAULT_MAGNIFICATION, + disableMagnification, + distance = DEFAULT_DISTANCE, + mouseX, + className, + children, + ...props +}: DockIconProps) => { + const ref = useRef(null); + const padding = Math.max(6, size * 0.2); + const defaultMouseX = useMotionValue(Infinity); + + const distanceCalc = useTransform(mouseX ?? defaultMouseX, (val: number) => { + const bounds = ref.current?.getBoundingClientRect() ?? { x: 0, width: 0 }; + return val - bounds.x - bounds.width / 2; + }); + + const targetSize = disableMagnification ? size : magnification; + + const sizeTransform = useTransform( + distanceCalc, + [-distance, 0, distance], + [size, targetSize, size], + ); + + const scaleSize = useSpring(sizeTransform, { + mass: 0.1, + stiffness: 150, + damping: 12, + }); + + return ( + +
{children}
+
+ ); +}; + +DockIcon.displayName = "DockIcon"; + +export { Dock, DockIcon, dockVariants }; diff --git a/components/magicui/dot-pattern.tsx b/components/magicui/dot-pattern.tsx new file mode 100644 index 0000000..f115a13 --- /dev/null +++ b/components/magicui/dot-pattern.tsx @@ -0,0 +1,158 @@ +"use client"; + +import { cn } from "@/lib/utils"; +import { motion } from "motion/react"; +import React, { useEffect, useId, useRef, useState } from "react"; + +/** + * DotPattern Component Props + * + * @param {number} [width=16] - The horizontal spacing between dots + * @param {number} [height=16] - The vertical spacing between dots + * @param {number} [x=0] - The x-offset of the entire pattern + * @param {number} [y=0] - The y-offset of the entire pattern + * @param {number} [cx=1] - The x-offset of individual dots + * @param {number} [cy=1] - The y-offset of individual dots + * @param {number} [cr=1] - The radius of each dot + * @param {string} [className] - Additional CSS classes to apply to the SVG container + * @param {boolean} [glow=false] - Whether dots should have a glowing animation effect + */ +interface DotPatternProps extends React.SVGProps { + width?: number; + height?: number; + x?: number; + y?: number; + cx?: number; + cy?: number; + cr?: number; + className?: string; + glow?: boolean; + [key: string]: unknown; +} + +/** + * DotPattern Component + * + * A React component that creates an animated or static dot pattern background using SVG. + * The pattern automatically adjusts to fill its container and can optionally display glowing dots. + * + * @component + * + * @see DotPatternProps for the props interface. + * + * @example + * // Basic usage + * + * + * // With glowing effect and custom spacing + * + * + * @notes + * - The component is client-side only ("use client") + * - Automatically responds to container size changes + * - When glow is enabled, dots will animate with random delays and durations + * - Uses Motion for animations + * - Dots color can be controlled via the text color utility classes + */ + +export function DotPattern({ + width = 16, + height = 16, + x = 0, + y = 0, + cx = 1, + cy = 1, + cr = 1, + className, + glow = false, + ...props +}: DotPatternProps) { + const id = useId(); + const containerRef = useRef(null); + const [dimensions, setDimensions] = useState({ width: 0, height: 0 }); + + useEffect(() => { + const updateDimensions = () => { + if (containerRef.current) { + const { width, height } = containerRef.current.getBoundingClientRect(); + setDimensions({ width, height }); + } + }; + + updateDimensions(); + window.addEventListener("resize", updateDimensions); + return () => window.removeEventListener("resize", updateDimensions); + }, []); + + const dots = Array.from( + { + length: + Math.ceil(dimensions.width / width) * + Math.ceil(dimensions.height / height), + }, + (_, i) => { + const col = i % Math.ceil(dimensions.width / width); + const row = Math.floor(i / Math.ceil(dimensions.width / width)); + return { + x: col * width + cx, + y: row * height + cy, + delay: Math.random() * 5, + duration: Math.random() * 3 + 2, + }; + }, + ); + + return ( + + ); +} diff --git a/components/magicui/globe.tsx b/components/magicui/globe.tsx new file mode 100644 index 0000000..4cd6e03 --- /dev/null +++ b/components/magicui/globe.tsx @@ -0,0 +1,128 @@ +"use client"; + +import createGlobe, { COBEOptions } from "cobe"; +import { useMotionValue, useSpring } from "motion/react"; +import { useEffect, useRef } from "react"; + +import { cn } from "@/lib/utils"; + +const MOVEMENT_DAMPING = 1400; + +const GLOBE_CONFIG: COBEOptions = { + width: 800, + height: 800, + onRender: () => {}, + devicePixelRatio: 2, + phi: 0, + theta: 0.3, + dark: 0, + diffuse: 0.4, + mapSamples: 16000, + mapBrightness: 1.2, + baseColor: [1, 1, 1], + markerColor: [251 / 255, 100 / 255, 21 / 255], + glowColor: [1, 1, 1], + markers: [ + { location: [14.5995, 120.9842], size: 0.03 }, + { location: [19.076, 72.8777], size: 0.1 }, + { location: [23.8103, 90.4125], size: 0.05 }, + { location: [30.0444, 31.2357], size: 0.07 }, + { location: [39.9042, 116.4074], size: 0.08 }, + { location: [-23.5505, -46.6333], size: 0.1 }, + { location: [19.4326, -99.1332], size: 0.1 }, + { location: [40.7128, -74.006], size: 0.1 }, + { location: [34.6937, 135.5022], size: 0.05 }, + { location: [41.0082, 28.9784], size: 0.06 }, + ], +}; + +export function Globe({ + className, + config = GLOBE_CONFIG, +}: { + className?: string; + config?: COBEOptions; +}) { + let phi = 0; + let width = 0; + const canvasRef = useRef(null); + const pointerInteracting = useRef(null); + const pointerInteractionMovement = useRef(0); + + const r = useMotionValue(0); + const rs = useSpring(r, { + mass: 1, + damping: 30, + stiffness: 100, + }); + + const updatePointerInteraction = (value: number | null) => { + pointerInteracting.current = value; + if (canvasRef.current) { + canvasRef.current.style.cursor = value !== null ? "grabbing" : "grab"; + } + }; + + const updateMovement = (clientX: number) => { + if (pointerInteracting.current !== null) { + const delta = clientX - pointerInteracting.current; + pointerInteractionMovement.current = delta; + r.set(r.get() + delta / MOVEMENT_DAMPING); + } + }; + + useEffect(() => { + const onResize = () => { + if (canvasRef.current) { + width = canvasRef.current.offsetWidth; + } + }; + + window.addEventListener("resize", onResize); + onResize(); + + const globe = createGlobe(canvasRef.current!, { + ...config, + width: width * 2, + height: width * 2, + onRender: (state) => { + if (!pointerInteracting.current) phi += 0.005; + state.phi = phi + rs.get(); + state.width = width * 2; + state.height = width * 2; + }, + }); + + setTimeout(() => (canvasRef.current!.style.opacity = "1"), 0); + return () => { + globe.destroy(); + window.removeEventListener("resize", onResize); + }; + }, [rs, config]); + + return ( +
+ { + pointerInteracting.current = e.clientX; + updatePointerInteraction(e.clientX); + }} + onPointerUp={() => updatePointerInteraction(null)} + onPointerOut={() => updatePointerInteraction(null)} + onMouseMove={(e) => updateMovement(e.clientX)} + onTouchMove={(e) => + e.touches[0] && updateMovement(e.touches[0].clientX) + } + /> +
+ ); +} diff --git a/components/magicui/magic-card.tsx b/components/magicui/magic-card.tsx new file mode 100644 index 0000000..adc52f0 --- /dev/null +++ b/components/magicui/magic-card.tsx @@ -0,0 +1,25 @@ +"use client"; + +import { motion } from "motion/react"; +import { cn } from "@/lib/utils"; + +interface MagicCardProps { + children: React.ReactNode; + className?: string; +} + +export function MagicCard({ children, className }: MagicCardProps) { + return ( + +
+ {children} + + ); +} diff --git a/components/magicui/shimmer-button.tsx b/components/magicui/shimmer-button.tsx new file mode 100644 index 0000000..8687238 --- /dev/null +++ b/components/magicui/shimmer-button.tsx @@ -0,0 +1,31 @@ +"use client"; + +import { motion } from "motion/react"; +import { cn } from "@/lib/utils"; + +interface ShimmerButtonProps { + children: React.ReactNode; + className?: string; + onClick?: () => void; +} + +export function ShimmerButton({ + children, + className, + onClick, +}: ShimmerButtonProps) { + return ( + +
+ {children} + + ); +} diff --git a/components/magicui/squares.tsx b/components/magicui/squares.tsx new file mode 100644 index 0000000..6f7b9e0 --- /dev/null +++ b/components/magicui/squares.tsx @@ -0,0 +1,181 @@ +"use client"; + +import { useRef, useEffect, useState } from "react"; + +interface SquaresProps { + direction?: "right" | "left" | "up" | "down" | "diagonal"; + speed?: number; + borderColor?: string; + squareSize?: number; + hoverFillColor?: string; + className?: string; +} + +export function Squares({ + direction = "right", + speed = 1, + borderColor = "#333", + squareSize = 40, + hoverFillColor = "#222", + className, +}: SquaresProps) { + const canvasRef = useRef(null); + const requestRef = useRef(undefined); + const numSquaresX = useRef(undefined); + const numSquaresY = useRef(undefined); + const gridOffset = useRef({ x: 0, y: 0 }); + const [hoveredSquare, setHoveredSquare] = useState<{ + x: number; + y: number; + } | null>(null); + + useEffect(() => { + const canvas = canvasRef.current; + if (!canvas) return; + + const ctx = canvas.getContext("2d"); + if (!ctx) return; + + // Set canvas background + canvas.style.background = "#060606"; + + const resizeCanvas = () => { + canvas.width = canvas.offsetWidth; + canvas.height = canvas.offsetHeight; + numSquaresX.current = Math.ceil(canvas.width / squareSize) + 1; + numSquaresY.current = Math.ceil(canvas.height / squareSize) + 1; + }; + + window.addEventListener("resize", resizeCanvas); + resizeCanvas(); + + const drawGrid = () => { + ctx.clearRect(0, 0, canvas.width, canvas.height); + + const startX = Math.floor(gridOffset.current.x / squareSize) * squareSize; + const startY = Math.floor(gridOffset.current.y / squareSize) * squareSize; + + ctx.lineWidth = 0.5; + + for (let x = startX; x < canvas.width + squareSize; x += squareSize) { + for (let y = startY; y < canvas.height + squareSize; y += squareSize) { + const squareX = x - (gridOffset.current.x % squareSize); + const squareY = y - (gridOffset.current.y % squareSize); + + if ( + hoveredSquare && + Math.floor((x - startX) / squareSize) === hoveredSquare.x && + Math.floor((y - startY) / squareSize) === hoveredSquare.y + ) { + ctx.fillStyle = hoverFillColor; + ctx.fillRect(squareX, squareY, squareSize, squareSize); + } + + ctx.strokeStyle = borderColor; + ctx.strokeRect(squareX, squareY, squareSize, squareSize); + } + } + + const gradient = ctx.createRadialGradient( + canvas.width / 2, + canvas.height / 2, + 0, + canvas.width / 2, + canvas.height / 2, + Math.sqrt(Math.pow(canvas.width, 2) + Math.pow(canvas.height, 2)) / 2 + ); + gradient.addColorStop(0, "rgba(6, 6, 6, 0)"); + gradient.addColorStop(1, "#060606"); + + ctx.fillStyle = gradient; + ctx.fillRect(0, 0, canvas.width, canvas.height); + }; + + const updateAnimation = () => { + const effectiveSpeed = Math.max(speed, 0.1); + + switch (direction) { + case "right": + gridOffset.current.x = + (gridOffset.current.x - effectiveSpeed + squareSize) % squareSize; + break; + case "left": + gridOffset.current.x = + (gridOffset.current.x + effectiveSpeed + squareSize) % squareSize; + break; + case "up": + gridOffset.current.y = + (gridOffset.current.y + effectiveSpeed + squareSize) % squareSize; + break; + case "down": + gridOffset.current.y = + (gridOffset.current.y - effectiveSpeed + squareSize) % squareSize; + break; + case "diagonal": + gridOffset.current.x = + (gridOffset.current.x - effectiveSpeed + squareSize) % squareSize; + gridOffset.current.y = + (gridOffset.current.y - effectiveSpeed + squareSize) % squareSize; + break; + } + + drawGrid(); + requestRef.current = requestAnimationFrame(updateAnimation); + }; + + const handleMouseMove = (event: MouseEvent) => { + const rect = canvas.getBoundingClientRect(); + const mouseX = event.clientX - rect.left; + const mouseY = event.clientY - rect.top; + + const startX = Math.floor(gridOffset.current.x / squareSize) * squareSize; + const startY = Math.floor(gridOffset.current.y / squareSize) * squareSize; + + const hoveredSquareX = Math.floor( + (mouseX + gridOffset.current.x - startX) / squareSize + ); + const hoveredSquareY = Math.floor( + (mouseY + gridOffset.current.y - startY) / squareSize + ); + + setHoveredSquare({ x: hoveredSquareX, y: hoveredSquareY }); + }; + + const handleMouseLeave = () => { + setHoveredSquare(null); + }; + + // Event listeners + window.addEventListener("resize", resizeCanvas); + canvas.addEventListener("mousemove", handleMouseMove); + canvas.addEventListener("mouseleave", handleMouseLeave); + + // Initial setup + resizeCanvas(); + requestRef.current = requestAnimationFrame(updateAnimation); + + // Cleanup + return () => { + window.removeEventListener("resize", resizeCanvas); + canvas.removeEventListener("mousemove", handleMouseMove); + canvas.removeEventListener("mouseleave", handleMouseLeave); + if (requestRef.current) { + cancelAnimationFrame(requestRef.current); + } + }; + }, [ + direction, + speed, + borderColor, + hoverFillColor, + hoveredSquare, + squareSize, + ]); + + return ( + + ); +} diff --git a/components/magicui/text-reveal.tsx b/components/magicui/text-reveal.tsx new file mode 100644 index 0000000..8a08565 --- /dev/null +++ b/components/magicui/text-reveal.tsx @@ -0,0 +1,23 @@ +"use client"; + +import { motion } from "motion/react"; +import { cn } from "@/lib/utils"; + +interface TextRevealProps { + children: React.ReactNode; + className?: string; +} + +export function TextReveal({ children, className }: TextRevealProps) { + return ( + + {children} + + ); +} diff --git a/dados.txt b/dados.txt new file mode 100644 index 0000000..64c9601 --- /dev/null +++ b/dados.txt @@ -0,0 +1,5 @@ +link meu portifólio atual:https://evandro.dev.br/ + +minha imagem:./imagens + +markdown linkedin: \ No newline at end of file diff --git a/lib/usePortfolioData.ts b/lib/usePortfolioData.ts new file mode 100644 index 0000000..032aff1 --- /dev/null +++ b/lib/usePortfolioData.ts @@ -0,0 +1,93 @@ +import { useState, useEffect } from "react"; + +interface PortfolioData { + personal: { + name: string; + title: string; + location: string; + email: string; + phone: string; + linkedin: string; + github: string; + summary: string; + }; + experiences: Array<{ + id: string; + title: string; + company: string; + period: string; + location: string; + type: string; + description: string; + achievements: string[]; + technologies: string[]; + featured: boolean; + }>; + projects: Array<{ + id: string; + title: string; + description: string; + longDescription: string; + technologies: string[]; + features: string[]; + status: string; + demoUrl: string; + githubUrl: string; + imageUrl: string; + featured: boolean; + category: string; + }>; + skills: { + technical: Array<{ + category: string; + icon: string; + color: string; + bgColor: string; + skills: string[]; + }>; + soft: Array<{ + skill: string; + level: number; + }>; + }; + contact: { + email: string; + phone: string; + whatsapp: string; + linkedin: string; + github: string; + location: string; + }; + social: Array<{ + name: string; + url: string; + icon: string; + }>; +} + +export function usePortfolioData() { + const [data, setData] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const loadData = async () => { + try { + const response = await fetch("/portfolio-data.json"); + if (!response.ok) { + throw new Error("Failed to load portfolio data"); + } + const portfolioData: PortfolioData = await response.json(); + setData(portfolioData); + } catch (err) { + setError(err instanceof Error ? err.message : "Unknown error"); + } finally { + setLoading(false); + } + }; + + loadData(); + }, []); + + return { data, loading, error }; +} diff --git a/lib/utils.ts b/lib/utils.ts new file mode 100644 index 0000000..bd0c391 --- /dev/null +++ b/lib/utils.ts @@ -0,0 +1,6 @@ +import { clsx, type ClassValue } from "clsx" +import { twMerge } from "tailwind-merge" + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} diff --git a/linkedin.md b/linkedin.md new file mode 100644 index 0000000..f6636b7 --- /dev/null +++ b/linkedin.md @@ -0,0 +1,175 @@ +# Evandro Fonseca Junior + +**Desenvolvedor Full-Stack Angular** + +📍 Garuva, Santa Catarina, Brasil +🔗 [LinkedIn](https://www.linkedin.com/in/evandro-fonseca-junior-0969aa1b0/) + +--- + +## Sobre Mim + +Desenvolvedor Full-Stack com 3+ anos de experiência, especializado em soluções web modernas e inovadoras. Formado em Engenharia da Computação, com conhecimentos sólidos em computação em nuvem e inteligência artificial. Busco constantemente novos desafios onde possa aplicar minha paixão por tecnologia e resolver problemas de forma criativa. + +Meu objetivo é entregar soluções de alta qualidade que superem expectativas, sempre priorizando colaboração, ética e inovação. Acredito no poder da tecnologia para fazer a diferença e estou comprometido com o aprendizado contínuo e a excelência técnica. + +--- + +## Experiência Profissional + +### **Desenvolvedor Full Stack | Innovo Tecnologia Da Informação Ltda.** + +_Dezembro 2024 - Fevereiro 2025 (3 meses) | Tempo Integral_ + +### **Desenvolvedor Full Stack | Julia Beauty Lashes** + +_Julho 2024 - Presente (1 ano 3 meses) | Freelance | Balneário Camboriú, SC_ + +- **Liderança Técnica**: Arquitetei e desenvolvi sistema web completo para estúdio de beleza especializado em extensão de cílios +- **Tecnologias**: Next.js, TypeScript, Tailwind CSS, Firebase, Jest, React Testing Library +- **Resultados Mensuráveis**: + - Redução de 40% no tempo de marcação de horários com sistema de agendamento automatizado + - Aumento de 25% na taxa de conversão através de interface responsiva otimizada + - Pontuação 90+ no Google Lighthouse para SEO e performance + - Cobertura de testes acima de 80% +- **Integração e Automação**: Implementei pipeline CI/CD com GitHub Actions, deploy automatizado na Vercel, e integração com APIs de pagamento e WhatsApp +- **Segurança**: Desenvolvimento de sistema robusto de autenticação e autorização com Firebase Authentication + +### **Desenvolvedor Full Stack | Apolo Energia** + +_Junho 2022 - Presente (3 anos 4 meses) | Autônomo | Curitiba, PR_ + +**Responsabilidades Principais**: + +- Desenvolvimento de aplicações web e mobile responsivas e escaláveis +- Criação e implementação de APIs RESTful eficientes +- Otimização de performance de aplicações e bancos de dados +- Implementação de práticas avançadas de segurança de dados +- Integração com sistemas terceiros e APIs externas +- Colaboração com equipes multidisciplinares (UX/UI, dados, IoT) +- Code reviews e garantia de qualidade de código + +**Especialização em Energia Renovável**: + +- Desenvolvimento de soluções para geração, armazenamento e gestão de energia +- Criação de sistemas de automação para otimização de eficiência industrial +- Implementação de soluções IoT para comunicação entre dispositivos +- Participação ativa no planejamento e arquitetura de projetos + +--- + +## Principais Competências Técnicas + +### **Frontend** + +- React.js & Next.js +- Angular +- TypeScript & JavaScript +- Tailwind CSS +- HTML5 & CSS3 +- UI/UX Design Responsivo + +### **Backend** + +- Node.js +- PHP +- APIs RESTful +- Firebase +- MySQL +- Arquitetura de microsserviços + +### **DevOps & Ferramentas** + +- GitHub Actions (CI/CD) +- Vercel +- Git & GitHub +- Jest & React Testing Library +- SEO e Performance Optimization + +### **Especialidades** + +- Desenvolvimento de software personalizado +- Computação em nuvem +- Inteligência artificial +- IoT e conectividade +- Energias renováveis +- Automação e controle de sistemas + +--- + +## Serviços Oferecidos + +- **Desenvolvimento Web**: Aplicações modernas e responsivas +- **Desenvolvimento de Software Personalizado**: Soluções sob medida +- **Consultoria de TI**: Arquitetura e planejamento de projetos +- **Suporte Técnico**: Redes, recuperação de dados e manutenção +- **Integração de Sistemas**: Conectividade entre plataformas + +--- + +## Projetos em Destaque + +### **Heaven Time Tracker** + +_Ferramenta moderna de gestão de tempo e produtividade_ + +- **Tecnologias**: Next.js 14, TypeScript, Tailwind CSS +- **Funcionalidades**: Dashboard personalizado, análises detalhadas, sistema de metas, relatórios avançados, integração com calendário +- **Características**: Interface intuitiva com suporte a tema claro/escuro, performance otimizada +- **Status**: Versão 1.0.0 em produção + +### **MinGW Downloader and Installer** + +_Aplicativo Python para automação de instalação de ambiente de desenvolvimento_ + +- **Tecnologias**: Python, Tkinter, Requests, Threading, Logging +- **Funcionalidades**: Download automatizado, verificação de compatibilidade, integração com PATH do sistema +- **Impacto**: Simplificação do processo de configuração para desenvolvedores Windows + +--- + +## Diferenciais Profissionais + +### **Inovação e Criatividade** + +- Desenvolvimento de soluções criativas e personalizadas +- Foco em experiência do usuário e interface intuitiva +- Implementação de tecnologias de ponta + +### **Eficiência e Performance** + +- Otimização constante de processos e aplicações +- Práticas de Clean Code e TDD +- Arquiteturas escaláveis e maintíveis + +### **Sustentabilidade e Impacto** + +- Especialização em soluções de energia renovável +- Desenvolvimento consciente com foco em redução de impacto ambiental +- Soluções que geram valor real para o negócio + +### **Comunicação e Colaboração** + +- Habilidade de traduzir conceitos técnicos para stakeholders não técnicos +- Trabalho efetivo em equipes multidisciplinares +- Mentoria e transferência de conhecimento + +--- + +## Status Atual + +**🔍 Em busca de oportunidades como:** + +- Engenheiro de Software +- Desenvolvedor Full-Stack +- Desenvolvedor Web + +**📊 Engajamento LinkedIn:** + +- 40 conexões profissionais +- 41 seguidores +- Publicações técnicas regulares sobre projetos e inovações + +--- + +_"Acredito no poder de levar ideias adiante e fazer a diferença de forma significativa através da tecnologia."_ diff --git a/next.config.ts b/next.config.ts index e9ffa30..b986ff4 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,7 +1,64 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { - /* config options here */ + // Otimização de imagens + images: { + formats: ["image/webp", "image/avif"], + deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840], + imageSizes: [16, 32, 48, 64, 96, 128, 256, 384], + minimumCacheTTL: 60, + }, + + // Otimizações de performance + experimental: { + scrollRestoration: true, + }, + + // Headers de cache e segurança + async headers(): Promise< + Array<{ source: string; headers: Array<{ key: string; value: string }> }> + > { + return [ + { + source: "/(.*)", + headers: [ + { + key: "X-Frame-Options", + value: "DENY", + }, + { + key: "X-Content-Type-Options", + value: "nosniff", + }, + { + key: "Referrer-Policy", + value: "origin-when-cross-origin", + }, + ], + }, + { + source: "/static/(.*)", + headers: [ + { + key: "Cache-Control", + value: "public, max-age=31536000, immutable", + }, + ], + }, + { + source: "/_next/image(.*)", + headers: [ + { + key: "Cache-Control", + value: "public, max-age=31536000, immutable", + }, + ], + }, + ]; + }, + + // Compressão + compress: true, }; export default nextConfig; diff --git a/package.json b/package.json index 9620a0f..19e335a 100644 --- a/package.json +++ b/package.json @@ -10,17 +10,25 @@ "format": "biome format --write" }, "dependencies": { + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "cobe": "^0.6.4", + "lucide-react": "^0.542.0", + "motion": "^12.23.12", + "next": "15.5.2", "react": "19.1.0", "react-dom": "19.1.0", - "next": "15.5.2" + "tailwind-merge": "^3.3.1", + "tailwindcss-animate": "^1.0.7" }, "devDependencies": { - "typescript": "^5", + "@biomejs/biome": "2.2.0", + "@tailwindcss/postcss": "^4", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", - "@tailwindcss/postcss": "^4", "tailwindcss": "^4", - "@biomejs/biome": "2.2.0" + "tw-animate-css": "^1.3.7", + "typescript": "^5" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4d648d3..9d84e11 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,21 @@ importers: .: dependencies: + class-variance-authority: + specifier: ^0.7.1 + version: 0.7.1 + clsx: + specifier: ^2.1.1 + version: 2.1.1 + cobe: + specifier: ^0.6.4 + version: 0.6.4 + lucide-react: + specifier: ^0.542.0 + version: 0.542.0(react@19.1.0) + motion: + specifier: ^12.23.12 + version: 12.23.12(react-dom@19.1.0(react@19.1.0))(react@19.1.0) next: specifier: 15.5.2 version: 15.5.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -17,6 +32,12 @@ importers: react-dom: specifier: 19.1.0 version: 19.1.0(react@19.1.0) + tailwind-merge: + specifier: ^3.3.1 + version: 3.3.1 + tailwindcss-animate: + specifier: ^1.0.7 + version: 1.0.7(tailwindcss@4.1.12) devDependencies: '@biomejs/biome': specifier: 2.2.0 @@ -36,6 +57,9 @@ importers: tailwindcss: specifier: ^4 version: 4.1.12 + tw-animate-css: + specifier: ^1.3.7 + version: 1.3.7 typescript: specifier: ^5 version: 5.9.2 @@ -404,9 +428,19 @@ packages: resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} engines: {node: '>=18'} + class-variance-authority@0.7.1: + resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} + client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + cobe@0.6.4: + resolution: {integrity: sha512-huuGFnDoXLy/tsCZYYa/H35BBRs9cxsS0XKJ3BXjRp699cQKuoEVrvKlAQMx0DKXG7+VUv4jsHVrS7yPbkLSkQ==} + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -432,6 +466,20 @@ packages: resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} engines: {node: '>=10.13.0'} + framer-motion@12.23.12: + resolution: {integrity: sha512-6e78rdVtnBvlEVgu6eFEAgG9v3wLnYEboM8I5O5EXvfKC8gxGQB8wXJdhkMy10iVcn05jl6CNw7/HTsTCfwcWg==} + peerDependencies: + '@emotion/is-prop-valid': '*' + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/is-prop-valid': + optional: true + react: + optional: true + react-dom: + optional: true + graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} @@ -506,6 +554,11 @@ packages: resolution: {integrity: sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==} engines: {node: '>= 12.0.0'} + lucide-react@0.542.0: + resolution: {integrity: sha512-w3hD8/SQB7+lzU2r4VdFyzzOzKnUjTZIF/MQJGSSvni7Llewni4vuViRppfRAa2guOsY5k4jZyxw/i9DQHv+dw==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + magic-string@0.30.18: resolution: {integrity: sha512-yi8swmWbO17qHhwIBNeeZxTceJMeBvWJaId6dyvTSOwTipqeHhMhOrz6513r1sOKnpvQ7zkhlG8tPrpilwTxHQ==} @@ -522,6 +575,26 @@ packages: engines: {node: '>=10'} hasBin: true + motion-dom@12.23.12: + resolution: {integrity: sha512-RcR4fvMCTESQBD/uKQe49D5RUeDOokkGRmz4ceaJKDBgHYtZtntC/s2vLvY38gqGaytinij/yi3hMcWVcEF5Kw==} + + motion-utils@12.23.6: + resolution: {integrity: sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==} + + motion@12.23.12: + resolution: {integrity: sha512-8jCD8uW5GD1csOoqh1WhH1A6j5APHVE15nuBkFeRiMzYBdRwyAHmSP/oXSuW0WJPZRXTFdBoG4hY9TFWNhhwng==} + peerDependencies: + '@emotion/is-prop-valid': '*' + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/is-prop-valid': + optional: true + react: + optional: true + react-dom: + optional: true + nanoid@3.3.11: resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -548,6 +621,9 @@ packages: sass: optional: true + phenomenon@1.6.0: + resolution: {integrity: sha512-7h9/fjPD3qNlgggzm88cY58l9sudZ6Ey+UmZsizfhtawO6E3srZQXywaNm2lBwT72TbpHYRPy7ytIHeBUD/G0A==} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -600,6 +676,14 @@ packages: babel-plugin-macros: optional: true + tailwind-merge@3.3.1: + resolution: {integrity: sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==} + + tailwindcss-animate@1.0.7: + resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==} + peerDependencies: + tailwindcss: '>=3.0.0 || insiders' + tailwindcss@4.1.12: resolution: {integrity: sha512-DzFtxOi+7NsFf7DBtI3BJsynR+0Yp6etH+nRPTbpWnS2pZBaSksv/JGctNwSWzbFjp0vxSqknaUylseZqMDGrA==} @@ -614,6 +698,9 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tw-animate-css@1.3.7: + resolution: {integrity: sha512-lvLb3hTIpB5oGsk8JmLoAjeCHV58nKa2zHYn8yWOoG5JJusH3bhJlF2DLAZ/5NmJ+jyH3ssiAx/2KmbhavJy/A==} + typescript@5.9.2: resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==} engines: {node: '>=14.17'} @@ -897,8 +984,18 @@ snapshots: chownr@3.0.0: {} + class-variance-authority@0.7.1: + dependencies: + clsx: 2.1.1 + client-only@0.0.1: {} + clsx@2.1.1: {} + + cobe@0.6.4: + dependencies: + phenomenon: 1.6.0 + color-convert@2.0.1: dependencies: color-name: 1.1.4 @@ -928,6 +1025,15 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.2.3 + framer-motion@12.23.12(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + motion-dom: 12.23.12 + motion-utils: 12.23.6 + tslib: 2.8.1 + optionalDependencies: + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + graceful-fs@4.2.11: {} is-arrayish@0.3.2: @@ -980,6 +1086,10 @@ snapshots: lightningcss-win32-arm64-msvc: 1.30.1 lightningcss-win32-x64-msvc: 1.30.1 + lucide-react@0.542.0(react@19.1.0): + dependencies: + react: 19.1.0 + magic-string@0.30.18: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -992,6 +1102,20 @@ snapshots: mkdirp@3.0.1: {} + motion-dom@12.23.12: + dependencies: + motion-utils: 12.23.6 + + motion-utils@12.23.6: {} + + motion@12.23.12(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + framer-motion: 12.23.12(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + tslib: 2.8.1 + optionalDependencies: + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + nanoid@3.3.11: {} next@15.5.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0): @@ -1017,6 +1141,8 @@ snapshots: - '@babel/core' - babel-plugin-macros + phenomenon@1.6.0: {} + picocolors@1.1.1: {} postcss@8.4.31: @@ -1085,6 +1211,12 @@ snapshots: client-only: 0.0.1 react: 19.1.0 + tailwind-merge@3.3.1: {} + + tailwindcss-animate@1.0.7(tailwindcss@4.1.12): + dependencies: + tailwindcss: 4.1.12 + tailwindcss@4.1.12: {} tapable@2.2.3: {} @@ -1100,6 +1232,8 @@ snapshots: tslib@2.8.1: {} + tw-animate-css@1.3.7: {} + typescript@5.9.2: {} undici-types@6.21.0: {} diff --git a/public/edev.jpeg b/public/edev.jpeg new file mode 100644 index 0000000..7dd685c Binary files /dev/null and b/public/edev.jpeg differ diff --git a/public/manifest.json b/public/manifest.json new file mode 100644 index 0000000..4d1afa6 --- /dev/null +++ b/public/manifest.json @@ -0,0 +1,20 @@ +{ + "name": "Evandro Fonseca Junior - Portfólio", + "short_name": "Evandro.dev", + "description": "Portfólio profissional de Evandro Fonseca Junior, Desenvolvedor Full-Stack", + "start_url": "/", + "display": "standalone", + "background_color": "#000000", + "theme_color": "#000000", + "orientation": "portrait-primary", + "categories": ["portfolio", "developer", "technology"], + "lang": "pt-BR", + "dir": "ltr", + "icons": [ + { + "src": "/favicon.ico", + "sizes": "any", + "type": "image/x-icon" + } + ] +} diff --git a/public/portfolio-data.json b/public/portfolio-data.json new file mode 100644 index 0000000..1e100e5 --- /dev/null +++ b/public/portfolio-data.json @@ -0,0 +1,169 @@ +{ + "personal": { + "name": "Evandro Fonseca Junior", + "title": "Desenvolvedor Full-Stack", + "location": "Garuva, Santa Catarina, Brasil", + "email": "evandrofonsecajunior@gmail.com", + "phone": "+55 47 99274-2365", + "linkedin": "https://www.linkedin.com/in/evandro-fonseca-junior-0969aa1b0/", + "github": "https://github.com/evandrodevbr", + "summary": "Desenvolvedor Full-Stack com 3+ anos de experiência, especializado em soluções web modernas e inovadoras. Formado em Engenharia da Computação, com conhecimentos sólidos em computação em nuvem e inteligência artificial. Busco constantemente novos desafios onde possa aplicar minha paixão por tecnologia e resolver problemas de forma criativa." + }, + "experiences": [ + { + "id": "julia-beauty", + "title": "Desenvolvedor Full Stack", + "company": "Julia Beauty Lashes", + "period": "Julho 2024 - Presente", + "location": "Balneário Camboriú, SC", + "type": "Freelance", + "description": "Sistema web completo para estúdio de beleza especializado em extensão de cílios. Tecnologias: Next.js, TypeScript, Tailwind CSS, Firebase.", + "achievements": [ + "Redução de 40% no tempo de marcação de horários", + "Aumento de 25% na taxa de conversão" + ], + "technologies": ["Next.js", "TypeScript", "Tailwind CSS", "Firebase"], + "featured": true + }, + { + "id": "apolo-energia", + "title": "Desenvolvedor Full Stack", + "company": "Apolo Energia", + "period": "Junho 2022 - Presente", + "location": "Curitiba, PR", + "type": "Autônomo", + "description": "Soluções em energia renovável e IoT. Desenvolvimento web/mobile responsivo.", + "achievements": ["Foco em sustentabilidade e automação industrial"], + "technologies": ["React", "Node.js", "IoT", "Cloud"], + "featured": true + }, + { + "id": "innovo-tecnologia", + "title": "Desenvolvedor Fullstack", + "company": "Innovo Tecnologia", + "period": "Dezembro 2024 - Fevereiro 2025", + "location": "", + "type": "Tempo Integral", + "description": "Desenvolvimento de soluções empresariais.", + "achievements": [], + "technologies": ["Angular", "PHP", "MySQL"], + "featured": false + } + ], + "projects": [ + { + "id": "heaven-time-tracker", + "title": "Heaven Time Tracker", + "description": "Ferramenta moderna de gestão de tempo e produtividade com dashboard personalizado, análises detalhadas e sistema de metas.", + "longDescription": "Uma aplicação completa para rastreamento de tempo com recursos avançados de análise e relatórios. Inclui dashboard interativo, sistema de metas personalizáveis e integração com ferramentas de produtividade.", + "technologies": ["Next.js 14", "TypeScript", "Tailwind CSS"], + "features": [ + "Dashboard personalizado", + "Análises detalhadas", + "Sistema de metas", + "Relatórios avançados", + "Interface claro/escuro" + ], + "status": "Versão 1.0.0 em produção", + "demoUrl": "#", + "githubUrl": "#", + "imageUrl": "/projects/heaven-time-tracker.jpg", + "featured": true, + "category": "produtividade" + }, + { + "id": "mingw-downloader", + "title": "MinGW Downloader and Installer", + "description": "Aplicativo Python para automação de instalação de ambiente de desenvolvimento Windows.", + "longDescription": "Ferramenta automatizada para desenvolvedores Windows que simplifica a instalação e configuração do ambiente MinGW. Inclui verificação de compatibilidade e integração automática com o PATH do sistema.", + "technologies": ["Python", "Tkinter", "Requests", "Threading"], + "features": [ + "Download automatizado", + "Verificação de compatibilidade", + "Integração com PATH", + "Interface gráfica intuitiva" + ], + "status": "Concluído", + "demoUrl": "#", + "githubUrl": "#", + "imageUrl": "/projects/mingw-installer.jpg", + "featured": true, + "category": "ferramenta" + } + ], + "skills": { + "technical": [ + { + "category": "Frontend", + "icon": "Code", + "color": "text-blue-400", + "bgColor": "bg-blue-500/20", + "skills": [ + "React.js & Next.js", + "Angular", + "TypeScript & JavaScript", + "Tailwind CSS", + "HTML5 & CSS3" + ] + }, + { + "category": "Backend", + "icon": "Database", + "color": "text-blue-400", + "bgColor": "bg-blue-500/20", + "skills": ["Node.js", "PHP", "MySQL", "APIs RESTful", "Firebase"] + }, + { + "category": "DevOps & Tools", + "icon": "GitBranch", + "color": "text-blue-400", + "bgColor": "bg-blue-500/20", + "skills": ["GitHub Actions", "Git", "Jest & Testing", "Vercel", "CI/CD"] + }, + { + "category": "Especialidades", + "icon": "Cpu", + "color": "text-blue-400", + "bgColor": "bg-blue-500/20", + "skills": [ + "Cloud Computing", + "Inteligência Artificial", + "IoT", + "Energia Renovável" + ] + } + ], + "soft": [ + { "skill": "Comunicação", "level": 90 }, + { "skill": "Trabalho em Equipe", "level": 85 }, + { "skill": "Resolução de Problemas", "level": 95 }, + { "skill": "Aprendizado Contínuo", "level": 90 }, + { "skill": "Adaptabilidade", "level": 85 } + ] + }, + "contact": { + "email": "evandrofonsecajunior@gmail.com", + "phone": "+55 47 99274-2365", + "whatsapp": "+55 47 99274-2365", + "linkedin": "https://www.linkedin.com/in/evandro-fonseca-junior-0969aa1b0/", + "github": "https://github.com/evandrodevbr", + "location": "Garuva, Santa Catarina, Brasil" + }, + "social": [ + { + "name": "LinkedIn", + "url": "https://www.linkedin.com/in/evandro-fonseca-junior-0969aa1b0/", + "icon": "linkedin" + }, + { + "name": "GitHub", + "url": "https://github.com/evandrodevbr", + "icon": "github" + }, + { + "name": "Email", + "url": "mailto:evandrofonsecajunior@gmail.com", + "icon": "mail" + } + ] +} diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..fdb754f --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,10 @@ +User-agent: * +Allow: / + +# Block access to sensitive files +Disallow: /api/ +Disallow: /_next/ +Disallow: /admin/ + +# Sitemap +Sitemap: https://evandrofonsecajunior.dev/sitemap.xml diff --git a/public/sitemap.xml b/public/sitemap.xml new file mode 100644 index 0000000..29065e4 --- /dev/null +++ b/public/sitemap.xml @@ -0,0 +1,39 @@ + + + + https://evandrofonsecajunior.dev + 2025-01-09 + monthly + 1.0 + + + https://evandrofonsecajunior.dev/#about + 2025-01-09 + monthly + 0.8 + + + https://evandrofonsecajunior.dev/#skills + 2025-01-09 + monthly + 0.8 + + + https://evandrofonsecajunior.dev/#experience + 2025-01-09 + monthly + 0.8 + + + https://evandrofonsecajunior.dev/#projects + 2025-01-09 + monthly + 0.8 + + + https://evandrofonsecajunior.dev/#contact + 2025-01-09 + monthly + 0.8 + + diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000..4383c1a --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,40 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + darkMode: ["class"], + content: [ + "./pages/**/*.{ts,tsx}", + "./components/**/*.{ts,tsx}", + "./app/**/*.{ts,tsx}", + "./src/**/*.{ts,tsx}", + ], + prefix: "", + theme: { + container: { + center: true, + padding: "2rem", + screens: { + "2xl": "1400px", + }, + }, + extend: { + colors: { + "blue-primary": "#4facfe", // Cor azul personalizada + }, + keyframes: { + "accordion-down": { + from: { height: "0" }, + to: { height: "var(--radix-accordion-content-height)" }, + }, + "accordion-up": { + from: { height: "var(--radix-accordion-content-height)" }, + to: { height: "0" }, + }, + }, + animation: { + "accordion-down": "accordion-down 0.2s ease-out", + "accordion-up": "accordion-up 0.2s ease-out", + }, + }, + }, + plugins: [require("tailwindcss-animate")], +};