feat: set up basic project structure and initial styling
This commit initializes the project with a basic Next.js setup, including: - Integration of `tailwindcss` and `tw-animate-css` for styling. - Definition of a comprehensive color palette using CSS variables for both light and dark themes, supporting a theming system. - Implementation of a custom `aurora` keyframe animation. - Configuration of global CSS styles for `border` and `body` elements. - Setup of `next.config.ts` with image optimization, performance enhancements, and security headers (X-Frame-Options, X-Content-Type-Options, Referrer-Policy). - Update of `package.json` to include new dependencies for UI components (`class-variance-authority`, `clsx`, `lucide-react`, `motion`, `tailwind-merge`, `tailwindcss-animate`) and development tools (`@biomejs/biome`, `@tailwindcss/postcss`, `tw-animate-css`). - Refinement of `app/layout.tsx` to include comprehensive metadata for SEO (title, description, keywords, authors, Open Graph, Twitter cards, robots), preconnect/dns-prefetch links for external resources, favicon, manifest, and mobile app meta tags. - Restructuring of `app/page.tsx` to use dynamic imports for various sections (Hero, About, Skills, Experience, Projects, Contact, WorkModalities) to improve performance, and integration of `Squares` component for background effects. This foundational commit prepares the application for further development by establishing a robust and performant base.
This commit is contained in:
1 parent
ca9e70a53e
commit
90cf3c73ad
37 files changed
+3898
-118
No files matched your search
@@ -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 (
|
||||
<section id="about" className="py-20 px-4">
|
||||
<div className="max-w-6xl 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 font-bold mb-6">Sobre Mim</h2>
|
||||
</motion.div>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-12 items-center">
|
||||
{/* Main Description */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: -30 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.2 }}
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
<div className="p-8 space-y-6">
|
||||
<TextReveal>
|
||||
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.
|
||||
</TextReveal>
|
||||
|
||||
<TextReveal>
|
||||
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.
|
||||
</TextReveal>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Soft Skills */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: 30 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.4 }}
|
||||
viewport={{ once: true }}
|
||||
className="space-y-6"
|
||||
>
|
||||
<h3 className="text-2xl font-semibold mb-6 text-cyan-400">
|
||||
Soft Skills
|
||||
</h3>
|
||||
|
||||
<div className="space-y-4">
|
||||
{[
|
||||
{ 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) => (
|
||||
<div key={item.skill} className="space-y-2">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-gray-300">{item.skill}</span>
|
||||
<span className="text-cyan-400">{item.level}%</span>
|
||||
</div>
|
||||
<div className="w-full bg-white/10 rounded-full h-2">
|
||||
<div
|
||||
className="bg-gradient-to-r from-cyan-400 to-blue-500 h-2 rounded-full transition-all duration-1000"
|
||||
style={{ width: `${item.level}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="mt-8">
|
||||
<h4 className="text-lg font-semibold mb-4 text-blue-400">
|
||||
Destaques
|
||||
</h4>
|
||||
<ul className="space-y-2 text-gray-300">
|
||||
<li className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 bg-blue-400 rounded-full" />
|
||||
3+ anos de experiência
|
||||
</li>
|
||||
<li className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 bg-blue-400 rounded-full" />
|
||||
Engenheiro da Computação
|
||||
</li>
|
||||
<li className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 bg-blue-400 rounded-full" />
|
||||
Conhecimento em Cloud e IA
|
||||
</li>
|
||||
<li className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 bg-blue-400 rounded-full" />
|
||||
Ampla gama de conhecimentos
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -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<HTMLElement>(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 (
|
||||
<section
|
||||
id="contact"
|
||||
ref={contactSectionRef}
|
||||
className="py-20 px-4 bg-gradient-to-t from-black/20 to-transparent"
|
||||
>
|
||||
<div className="max-w-6xl 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 font-bold mb-6">
|
||||
Vamos Conversar
|
||||
</h2>
|
||||
<p className="text-xl text-gray-300 max-w-2xl mx-auto">
|
||||
Estou sempre interessado em novos desafios e oportunidades. Vamos
|
||||
discutir como posso ajudar seu projeto?
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-8 items-start mt-16">
|
||||
{/* Contact Info */}
|
||||
<ContactInfo onWhatsAppClick={handleWhatsAppClick} />
|
||||
|
||||
{/* Contact Options and Map */}
|
||||
<div className="flex flex-col gap-8">
|
||||
<ContactForm formType={formType} setFormType={setFormType} />{" "}
|
||||
{/* Usar o novo componente ContactForm */}
|
||||
{/* Mapa */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: 30 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.6 }}
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
<h3 className="text-2xl font-semibold mb-4 text-blue-400">
|
||||
Onde me encontrar
|
||||
</h3>
|
||||
<div className="overflow-hidden rounded-lg shadow-lg">
|
||||
<iframe
|
||||
src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3557.1999999999996!2d-48.77891484804123!3d-26.087801635807004!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x0%3A0x0!2zMjbCsDA1JzE2LjEiUyA0OMKwNDYnNDQuMSJX!5e0!3m2!1spt-BR!2sbr!4v1700000000000!5m2!1spt-BR!2sbr"
|
||||
width="100%"
|
||||
height="450"
|
||||
style={{ border: 0 }}
|
||||
allowFullScreen={true}
|
||||
loading="lazy"
|
||||
referrerPolicy="no-referrer-when-downgrade"
|
||||
></iframe>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -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<string>("");
|
||||
const [userEmail, setUserEmail] = useState<string>("");
|
||||
const [userPhone, setUserPhone] = useState<string>("");
|
||||
const [userCompany, setUserCompany] = useState<string>("");
|
||||
const [howFound, setHowFound] = useState<string>("");
|
||||
const [projectType, setProjectType] = useState<string>("");
|
||||
const [otherProjectType, setOtherProjectType] = useState<string>(""); // Novo estado para "Outro" tipo de projeto
|
||||
const [desiredDeadline, setDesiredDeadline] = useState<string>("");
|
||||
const [budgetValue, setBudgetValue] = useState<string>(""); // Novo estado para o valor do orçamento
|
||||
const [technicalComplexity, setTechnicalComplexity] = useState<string>("");
|
||||
const [hasInternalTeam, setHasInternalTeam] = useState<string>("");
|
||||
const [customMessage, setCustomMessage] = useState<string>("");
|
||||
const [isDecisor, setIsDecisor] = useState<string>("");
|
||||
const [projectApproved, setProjectApproved] = useState<string>("");
|
||||
const [urgency, setUrgency] = useState<string>("");
|
||||
const [preferredModality, setPreferredModality] = useState<string>("");
|
||||
const [isExpanded, setIsExpanded] = useState<boolean>(false); // Novo estado para controlar a expansão do formulário avançado
|
||||
const [nameError, setNameError] = useState<string | null>(null);
|
||||
const [emailError, setEmailError] = useState<string | null>(null);
|
||||
const [phoneError, setPhoneError] = useState<string | null>(null);
|
||||
const [otherProjectTypeError, setOtherProjectTypeError] = useState<
|
||||
string | null
|
||||
>(null); // Novo estado de erro
|
||||
const [honeypot, setHoneypot] = useState<string>(""); // Estado para o campo honeypot
|
||||
const [startTime, setStartTime] = useState<number>(0); // Estado para o tempo de início do formulário
|
||||
const [nameFocused, setNameFocused] = useState<boolean>(false);
|
||||
const [emailFocused, setEmailFocused] = useState<boolean>(false);
|
||||
const [phoneFocused, setPhoneFocused] = useState<boolean>(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<HTMLInputElement>) => {
|
||||
setUserName(e.target.value);
|
||||
if (e.target.value.trim() !== "") setNameError(null);
|
||||
};
|
||||
|
||||
const handleEmailChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
setUserEmail(e.target.value);
|
||||
if (e.target.value.trim() !== "") setEmailError(null);
|
||||
};
|
||||
|
||||
const handlePhoneChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
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<HTMLInputElement>) => {
|
||||
setUserCompany(e.target.value);
|
||||
};
|
||||
|
||||
const handleHowFoundChange = (e: ChangeEvent<HTMLSelectElement>) => {
|
||||
setHowFound(e.target.value);
|
||||
};
|
||||
|
||||
const handleProjectTypeChange = (e: ChangeEvent<HTMLSelectElement>) => {
|
||||
setProjectType(e.target.value);
|
||||
};
|
||||
|
||||
const handleDesiredDeadlineChange = (e: ChangeEvent<HTMLSelectElement>) => {
|
||||
setDesiredDeadline(e.target.value);
|
||||
};
|
||||
|
||||
const handleBudgetValueChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
setBudgetValue(e.target.value);
|
||||
};
|
||||
|
||||
const handleTechnicalComplexityChange = (
|
||||
e: ChangeEvent<HTMLSelectElement>
|
||||
) => {
|
||||
setTechnicalComplexity(e.target.value);
|
||||
};
|
||||
|
||||
const handleHasInternalTeamChange = (e: ChangeEvent<HTMLSelectElement>) => {
|
||||
setHasInternalTeam(e.target.value);
|
||||
};
|
||||
|
||||
const handleMessageChange = (e: ChangeEvent<HTMLTextAreaElement>) => {
|
||||
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 (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: 30 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.4 }}
|
||||
viewport={{ once: true }}
|
||||
className="space-y-4"
|
||||
>
|
||||
<h3 className="text-2xl font-semibold mb-6 text-blue-primary">
|
||||
Preencha o Formulário
|
||||
</h3>
|
||||
|
||||
<div className="flex justify-center gap-4 mb-8">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setFormType("simple")}
|
||||
className={`py-2 px-4 rounded-lg font-semibold transition-all duration-300 ${
|
||||
formType === "simple"
|
||||
? "bg-blue-primary text-white shadow-lg shadow-blue-primary/25 border-2 border-blue-500"
|
||||
: "bg-gray-700 text-gray-300 opacity-50 cursor-pointer"
|
||||
}`}
|
||||
>
|
||||
Formulário Simples
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setFormType("advanced")}
|
||||
className={`py-2 px-4 rounded-lg font-semibold transition-all duration-300 ${
|
||||
formType === "advanced"
|
||||
? "bg-blue-primary text-white shadow-lg shadow-blue-primary/25 border-2 border-blue-500"
|
||||
: "bg-gray-700 text-gray-300 opacity-50 cursor-pointer"
|
||||
}`}
|
||||
>
|
||||
Formulário Avançado
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{formType === "simple" ? (
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
{/* Campo Honeypot */}
|
||||
<div style={{ display: "none" }}>
|
||||
<label htmlFor="honeypot">Não preencha este campo</label>
|
||||
<input
|
||||
type="text"
|
||||
id="honeypot"
|
||||
name="honeypot"
|
||||
value={honeypot}
|
||||
onChange={(e) => setHoneypot(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Formulário Simples: Apenas Nome */}
|
||||
<div>
|
||||
<label
|
||||
htmlFor="simple-user-name"
|
||||
className="block text-sm font-medium text-gray-300 mb-2"
|
||||
>
|
||||
Seu Nome:
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="simple-user-name"
|
||||
className={`w-full p-3 rounded-lg bg-white/5 border ${
|
||||
nameError ? "border-red-500" : "border-white/10"
|
||||
} text-white placeholder-gray-500 focus:ring-2 focus:ring-blue-primary focus:border-transparent`}
|
||||
placeholder="Seu nome"
|
||||
value={userName}
|
||||
onChange={handleNameChange}
|
||||
onFocus={handleNameFocus}
|
||||
required
|
||||
/>
|
||||
{nameError && (
|
||||
<p className="text-red-400 text-xs mt-1">{nameError}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Botão de Contato para o formulário simples */}
|
||||
<motion.div whileHover={{ scale: 1.02 }} whileTap={{ scale: 0.98 }}>
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full py-4 px-6 rounded-lg font-semibold text-lg transition-all duration-300 bg-green-600 hover:bg-green-700 text-white shadow-lg shadow-green-600/25"
|
||||
onClick={() => window.open(generateWhatsAppUrl(), "_blank")}
|
||||
>
|
||||
Enviar Mensagem via WhatsApp
|
||||
</button>
|
||||
</motion.div>
|
||||
</form>
|
||||
) : (
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
{/* Campo Honeypot */}
|
||||
<div style={{ display: "none" }}>
|
||||
<label htmlFor="honeypot">Não preencha este campo</label>
|
||||
<input
|
||||
type="text"
|
||||
id="honeypot"
|
||||
name="honeypot"
|
||||
value={honeypot}
|
||||
onChange={(e) => setHoneypot(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Dados Básicos */}
|
||||
<div>
|
||||
<h4 className="text-xl font-semibold mb-4 text-gray-200">
|
||||
Dados Básicos
|
||||
</h4>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label
|
||||
htmlFor="user-name"
|
||||
className="block text-sm font-medium text-gray-300 mb-2"
|
||||
>
|
||||
Nome Completo:
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="user-name"
|
||||
className={`w-full p-3 rounded-lg bg-white/5 border ${
|
||||
nameError ? "border-red-500" : "border-white/10"
|
||||
} text-white placeholder-gray-500 focus:ring-2 focus:ring-blue-primary focus:border-transparent`}
|
||||
placeholder="Seu nome completo"
|
||||
value={userName}
|
||||
onChange={handleNameChange}
|
||||
onFocus={handleNameFocus}
|
||||
required
|
||||
/>
|
||||
{nameError && (
|
||||
<p className="text-red-400 text-xs mt-1">{nameError}</p>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
htmlFor="user-email"
|
||||
className="block text-sm font-medium text-gray-300 mb-2"
|
||||
>
|
||||
Email Corporativo:
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
id="user-email"
|
||||
className={`w-full p-3 rounded-lg bg-white/5 border ${
|
||||
emailError ? "border-red-500" : "border-white/10"
|
||||
} text-white placeholder-gray-500 focus:ring-2 focus:ring-blue-primary focus:border-transparent`}
|
||||
placeholder="[email protected]"
|
||||
value={userEmail}
|
||||
onChange={handleEmailChange}
|
||||
onFocus={handleEmailFocus}
|
||||
required
|
||||
/>
|
||||
{emailError && (
|
||||
<p className="text-red-400 text-xs mt-1">{emailError}</p>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
htmlFor="user-phone"
|
||||
className="block text-sm font-medium text-gray-300 mb-2"
|
||||
>
|
||||
WhatsApp/Telefone:
|
||||
</label>
|
||||
<input
|
||||
type="tel"
|
||||
id="user-phone"
|
||||
className={`w-full p-3 rounded-lg bg-white/5 border ${
|
||||
phoneError ? "border-red-500" : "border-white/10"
|
||||
} text-white placeholder-gray-500 focus:ring-2 focus:ring-blue-primary focus:border-transparent`}
|
||||
placeholder="(XX) XXXXX-XXXX"
|
||||
value={userPhone}
|
||||
onChange={handlePhoneChange}
|
||||
onFocus={handlePhoneFocus}
|
||||
required
|
||||
/>
|
||||
{phoneError && (
|
||||
<p className="text-red-400 text-xs mt-1">{phoneError}</p>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
htmlFor="user-company"
|
||||
className="block text-sm font-medium text-gray-300 mb-2"
|
||||
>
|
||||
Empresa/Posição:
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="user-company"
|
||||
className="w-full p-3 rounded-lg bg-white/5 border border-white/10 text-white placeholder-gray-500 focus:ring-2 focus:ring-blue-primary focus:border-transparent"
|
||||
placeholder="Nome da empresa, sua posição"
|
||||
value={userCompany}
|
||||
onChange={handleCompanyChange}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
htmlFor="how-found"
|
||||
className="block text-sm font-medium text-gray-300 mb-2"
|
||||
>
|
||||
Como conheceu meu trabalho?
|
||||
</label>
|
||||
<select
|
||||
id="how-found"
|
||||
className="w-full p-3 rounded-lg bg-white/5 border border-white/10 text-white focus:ring-2 focus:ring-blue-primary focus:border-transparent"
|
||||
value={howFound}
|
||||
onChange={handleHowFoundChange}
|
||||
>
|
||||
<option value="">Selecione uma opção</option>
|
||||
<option value="linkedin">LinkedIn</option>
|
||||
<option value="github">GitHub</option>
|
||||
<option value="indicacao">Indicação</option>
|
||||
<option value="pesquisa">Pesquisa Online</option>
|
||||
<option value="outro">Outro</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Tile de Expandir/Recolher */}
|
||||
<div
|
||||
onClick={() => 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"
|
||||
>
|
||||
<h4 className="text-xl font-semibold text-gray-200">
|
||||
{isExpanded
|
||||
? "Detalhes do Projeto (Recolher)"
|
||||
: "Detalhes do Projeto (Expandir)"}
|
||||
</h4>
|
||||
<svg
|
||||
className={`w-6 h-6 transition-transform duration-300 ${
|
||||
isExpanded ? "rotate-180" : ""
|
||||
}`}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M19 9l-7 7-7-7"
|
||||
></path>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={`overflow-hidden transition-all duration-500 ease-in-out ${
|
||||
isExpanded ? "max-h-[1000px] opacity-100" : "max-h-0 opacity-0"
|
||||
} space-y-6`}
|
||||
>
|
||||
{/* Conteúdo do Formulário Avançado */}
|
||||
{/* Sobre o Projeto */}
|
||||
<div>
|
||||
<h4 className="text-xl font-semibold mb-4 text-gray-200">
|
||||
Sobre o Projeto
|
||||
</h4>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label
|
||||
htmlFor="project-type"
|
||||
className="block text-sm font-medium text-gray-300 mb-2"
|
||||
>
|
||||
Tipo de Projeto:
|
||||
</label>
|
||||
<select
|
||||
id="project-type"
|
||||
className="w-full p-3 rounded-lg bg-white/5 border border-white/10 text-white focus:ring-2 focus:ring-blue-primary focus:border-transparent"
|
||||
value={projectType}
|
||||
onChange={handleProjectTypeChange}
|
||||
>
|
||||
<option value="">Selecione o tipo</option>
|
||||
<option value="web-app">Aplicação Web Completa</option>
|
||||
<option value="landing-page">Landing Page</option>
|
||||
<option value="mvp">MVP (Produto Mínimo Viável)</option>
|
||||
<option value="iot-app">Aplicação IoT</option>
|
||||
<option value="integracao">Integração de Sistemas</option>
|
||||
<option value="manutencao">Manutenção/Ajustes</option>
|
||||
<option value="consultoria">Consultoria/Mentoria</option>
|
||||
<option value="outro">Outro</option>
|
||||
</select>
|
||||
</div>
|
||||
{projectType === "outro" && (
|
||||
<div>
|
||||
<label
|
||||
htmlFor="other-project-type"
|
||||
className="block text-sm font-medium text-gray-300 mb-2"
|
||||
>
|
||||
Qual outro tipo de projeto?
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="other-project-type"
|
||||
className={`w-full p-3 rounded-lg bg-white/5 border ${
|
||||
otherProjectTypeError
|
||||
? "border-red-500"
|
||||
: "border-white/10"
|
||||
} text-white placeholder-gray-500 focus:ring-2 focus:ring-blue-primary focus:border-transparent`}
|
||||
placeholder="Ex: Desenvolvimento de Plugin, Automação de Processos"
|
||||
value={otherProjectType}
|
||||
onChange={(e) => {
|
||||
setOtherProjectType(e.target.value);
|
||||
if (e.target.value.trim() !== "")
|
||||
setOtherProjectTypeError(null);
|
||||
}}
|
||||
required
|
||||
/>
|
||||
{otherProjectTypeError && (
|
||||
<p className="text-red-400 text-xs mt-1">
|
||||
{otherProjectTypeError}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<label
|
||||
htmlFor="desired-deadline"
|
||||
className="block text-sm font-medium text-gray-300 mb-2"
|
||||
>
|
||||
Prazo Desejado:
|
||||
</label>
|
||||
<select
|
||||
id="desired-deadline"
|
||||
className="w-full p-3 rounded-lg bg-white/5 border border-white/10 text-white focus:ring-2 focus:ring-blue-primary focus:border-transparent"
|
||||
value={desiredDeadline}
|
||||
onChange={handleDesiredDeadlineChange}
|
||||
>
|
||||
<option value="">Selecione o prazo</option>
|
||||
<option value="1-3-weeks">1-3 Semanas</option>
|
||||
<option value="1-2-months">1-2 Meses</option>
|
||||
<option value="2-4-months">2-4 Meses</option>
|
||||
<option value="4-6-months">4-6 Meses</option>
|
||||
<option value="6-plus-months">Mais de 6 Meses</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
htmlFor="budget-value"
|
||||
className="block text-sm font-medium text-gray-300 mb-2"
|
||||
>
|
||||
Orçamento Disponível (R$):
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="budget-value"
|
||||
className="w-full p-3 rounded-lg bg-white/5 border border-white/10 text-white placeholder-gray-500 focus:ring-2 focus:ring-blue-primary focus:border-transparent"
|
||||
placeholder="Ex: 5000, 10000, A Combinar"
|
||||
value={budgetValue}
|
||||
onChange={handleBudgetValueChange}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
htmlFor="technical-complexity"
|
||||
className="block text-sm font-medium text-gray-300 mb-2"
|
||||
>
|
||||
Complexidade Técnica Estimada:
|
||||
</label>
|
||||
<select
|
||||
id="technical-complexity"
|
||||
className="w-full p-3 rounded-lg bg-white/5 border border-white/10 text-white focus:ring-2 focus:ring-blue-primary focus:border-transparent"
|
||||
value={technicalComplexity}
|
||||
onChange={handleTechnicalComplexityChange}
|
||||
>
|
||||
<option value="">Selecione a complexidade</option>
|
||||
<option value="baixa">Baixa</option>
|
||||
<option value="media">Média</option>
|
||||
<option value="alta">Alta</option>
|
||||
<option value="muito-alta">Muito Alta</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
htmlFor="has-internal-team"
|
||||
className="block text-sm font-medium text-gray-300 mb-2"
|
||||
>
|
||||
Já tem equipe técnica interna?
|
||||
</label>
|
||||
<select
|
||||
id="has-internal-team"
|
||||
className="w-full p-3 rounded-lg bg-white/5 border border-white/10 text-white focus:ring-2 focus:ring-blue-primary focus:border-transparent"
|
||||
value={hasInternalTeam}
|
||||
onChange={handleHasInternalTeamChange}
|
||||
>
|
||||
<option value="">Selecione</option>
|
||||
<option value="sim">Sim</option>
|
||||
<option value="nao">Não</option>
|
||||
<option value="parcial">Parcialmente</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Custom Message Input */}
|
||||
<div className="mb-6">
|
||||
<label
|
||||
htmlFor="custom-message"
|
||||
className="block text-sm font-medium text-gray-300 mb-2"
|
||||
>
|
||||
Mensagem Adicional (Opcional):
|
||||
</label>
|
||||
<textarea
|
||||
id="custom-message"
|
||||
rows={4}
|
||||
className="w-full p-3 rounded-lg bg-white/5 border border-white/10 text-white placeholder-gray-500 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
placeholder="Descreva seu projeto ou suas necessidades aqui..."
|
||||
value={customMessage}
|
||||
onChange={handleMessageChange}
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Contact Button */}
|
||||
<motion.div whileHover={{ scale: 1.02 }} whileTap={{ scale: 0.98 }}>
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full py-4 px-6 rounded-lg font-semibold text-lg transition-all duration-300 bg-blue-primary hover:bg-blue-primary/90 text-white shadow-lg shadow-blue-primary/25 border-2 border-blue-500"
|
||||
>
|
||||
{getCtaMessage()}
|
||||
</button>
|
||||
</motion.div>
|
||||
{projectType && desiredDeadline && budgetValue && (
|
||||
<p className="text-center text-lg font-semibold text-blue-400 mt-4">
|
||||
{getEstimatedValue()}
|
||||
</p>
|
||||
)}
|
||||
</form>
|
||||
)}
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: -30 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.2 }}
|
||||
viewport={{ once: true }}
|
||||
className="space-y-4"
|
||||
>
|
||||
<div>
|
||||
<h3 className="text-2xl font-semibold mb-6 text-blue-400">
|
||||
Entre em Contato
|
||||
</h3>
|
||||
<div className="space-y-4">
|
||||
<a
|
||||
href={`mailto:${data.contact.email}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center gap-4 p-4 hover:bg-white/5 rounded-lg transition-colors"
|
||||
>
|
||||
<div className="p-3 rounded-full bg-blue-500/20">
|
||||
<Mail className="w-6 h-6 text-blue-400" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-white font-medium">Email</p>
|
||||
<p className="text-gray-400">{data.contact.email}</p>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<button
|
||||
onClick={onWhatsAppClick}
|
||||
className="flex items-center gap-4 p-4 hover:bg-white/5 rounded-lg transition-colors w-full text-left"
|
||||
>
|
||||
<div className="p-3 rounded-full bg-green-500/20">
|
||||
<MessageCircle className="w-6 h-6 text-green-400" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-white font-medium">WhatsApp</p>
|
||||
<p className="text-gray-400">{data.contact.whatsapp}</p>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<a
|
||||
href={data.contact.linkedin}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center gap-4 p-4 hover:bg-white/5 rounded-lg transition-colors"
|
||||
>
|
||||
<div className="p-3 rounded-full bg-blue-500/20">
|
||||
<svg
|
||||
className="w-6 h-6 text-blue-400"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-white font-medium">LinkedIn</p>
|
||||
<p className="text-gray-400">
|
||||
{data.contact.linkedin.replace("https://www.", "")}
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a
|
||||
href={data.contact.github}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center gap-4 p-4 hover:bg-white/5 rounded-lg transition-colors"
|
||||
>
|
||||
<div className="p-3 rounded-full bg-gray-500/20">
|
||||
<svg
|
||||
className="w-6 h-6 text-gray-400"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-white font-medium">GitHub</p>
|
||||
<p className="text-gray-400">
|
||||
{data.contact.github.replace("https://github.com/", "")}
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="text-2xl font-semibold mb-4 text-blue-400">
|
||||
Localização
|
||||
</h3>
|
||||
<p className="text-gray-300">📍 {data.contact.location}</p>
|
||||
<p className="text-gray-400 mt-2">
|
||||
Disponível para projetos remotos e trabalho presencial na região.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Timeline de Resposta (Placeholder) */}
|
||||
<div>
|
||||
<h3 className="text-2xl font-semibold mb-4 text-blue-400">
|
||||
Timeline de Resposta
|
||||
</h3>
|
||||
<p className="text-gray-300">
|
||||
Em breve, uma timeline detalhada do processo de contato e resposta.
|
||||
</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
<section id="experience" className="py-20 px-4">
|
||||
<div className="max-w-6xl 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="experience" className="py-20 px-4">
|
||||
<div className="max-w-6xl mx-auto text-center">
|
||||
<p className="text-red-400">Erro ao carregar dados de experiência.</p>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
const experiences = data.experiences.filter((exp) => exp.featured);
|
||||
|
||||
return (
|
||||
<section
|
||||
id="experience"
|
||||
className="py-20 px-4 bg-gradient-to-b from-transparent to-black/20"
|
||||
>
|
||||
<div className="max-w-6xl 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 font-bold mb-6">
|
||||
Experiência Profissional
|
||||
</h2>
|
||||
<p className="text-xl text-gray-300 max-w-2xl mx-auto">
|
||||
Minha jornada profissional em desenvolvimento de software e soluções
|
||||
tecnológicas
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<BentoGrid className="gap-6">
|
||||
{experiences.map((exp, index) => (
|
||||
<BentoGridItem
|
||||
key={exp.id}
|
||||
colSpan={index === 0 ? 2 : 1}
|
||||
className="group hover:scale-[1.02] transition-all duration-300 hover:shadow-2xl hover:shadow-blue-500/10"
|
||||
>
|
||||
<div className="space-y-4">
|
||||
{/* Header */}
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1">
|
||||
<h3 className="text-xl font-semibold text-white mb-1">
|
||||
{exp.title}
|
||||
</h3>
|
||||
<p className="text-blue-400 font-medium">{exp.company}</p>
|
||||
<div className="flex flex-wrap items-center gap-2 text-sm text-gray-400 mt-2">
|
||||
<div className="flex items-center gap-1">
|
||||
<Calendar className="w-4 h-4" />
|
||||
{exp.period}
|
||||
</div>
|
||||
{exp.location && (
|
||||
<div className="flex items-center gap-1">
|
||||
<MapPin className="w-4 h-4" />
|
||||
{exp.location}
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center gap-1">
|
||||
<Briefcase className="w-4 h-4" />
|
||||
{exp.type}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Description */}
|
||||
<p className="text-gray-300 leading-relaxed">
|
||||
{exp.description}
|
||||
</p>
|
||||
|
||||
{/* Achievements */}
|
||||
{exp.achievements.length > 0 && (
|
||||
<div>
|
||||
<h4 className="text-sm font-semibold text-blue-400 mb-2">
|
||||
Principais Conquistas:
|
||||
</h4>
|
||||
<ul className="space-y-1">
|
||||
{exp.achievements.map((achievement, idx) => (
|
||||
<li
|
||||
key={idx}
|
||||
className="text-sm text-gray-400 flex items-center gap-2"
|
||||
>
|
||||
<div className="w-1.5 h-1.5 bg-blue-400 rounded-full" />
|
||||
{achievement}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</BentoGridItem>
|
||||
))}
|
||||
</BentoGrid>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -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<SVGElement>;
|
||||
|
||||
const Icons = {
|
||||
calendar: (props: IconProps) => <CalendarIcon {...props} />,
|
||||
email: (props: IconProps) => <MailIcon {...props} />,
|
||||
linkedin: (props: IconProps) => (
|
||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<title>LinkedIn</title>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
x: (props: IconProps) => (
|
||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<title>X</title>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M18.901 1.153h3.68l-8.04 9.19L24 22.846h-7.406l-5.8-7.584-6.638 7.584H.474l8.6-9.83L0 1.154h7.594l5.243 6.932ZM17.61 20.644h2.039L6.486 3.24H4.298Z"
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
youtube: (props: IconProps) => (
|
||||
<svg
|
||||
width="32px"
|
||||
height="32px"
|
||||
viewBox="0 0 32 32"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<title>youtube</title>
|
||||
<path d="M29.41,9.26a3.5,3.5,0,0,0-2.47-2.47C24.76,6.2,16,6.2,16,6.2s-8.76,0-10.94.59A3.5,3.5,0,0,0,2.59,9.26,36.13,36.13,0,0,0,2,16a36.13,36.13,0,0,0,.59,6.74,3.5,3.5,0,0,0,2.47,2.47C7.24,25.8,16,25.8,16,25.8s8.76,0,10.94-.59a3.5,3.5,0,0,0,2.47-2.47A36.13,36.13,0,0,0,30,16,36.13,36.13,0,0,0,29.41,9.26ZM13.2,20.2V11.8L20.47,16Z" />
|
||||
</svg>
|
||||
),
|
||||
github: (props: IconProps) => (
|
||||
<svg viewBox="0 0 438.549 438.549" {...props}>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M409.132 114.573c-19.608-33.596-46.205-60.194-79.798-79.8-33.598-19.607-70.277-29.408-110.063-29.408-39.781 0-76.472 9.804-110.063 29.408-33.596 19.605-60.192 46.204-79.8 79.8C9.803 148.168 0 184.854 0 224.63c0 47.78 13.94 90.745 41.827 128.906 27.884 38.164 63.906 64.572 108.063 79.227 5.14.954 8.945.283 11.419-1.996 2.475-2.282 3.711-5.14 3.711-8.562 0-.571-.049-5.708-.144-15.417a2549.81 2549.81 0 01-.144-25.406l-6.567 1.136c-4.187.767-9.469 1.092-15.846 1-6.374-.089-12.991-.757-19.842-1.999-6.854-1.231-13.229-4.086-19.13-8.559-5.898-4.473-10.085-10.328-12.56-17.556l-2.855-6.57c-1.903-4.374-4.899-9.233-8.992-14.559-4.093-5.331-8.232-8.945-12.419-10.848l-1.999-1.431c-1.332-.951-2.568-2.098-3.711-3.429-1.142-1.331-1.997-2.663-2.568-3.997-.572-1.335-.098-2.43 1.427-3.289 1.525-.859 4.281-1.276 8.28-1.276l5.708.853c3.807.763 8.516 3.042 14.133 6.851 5.614 3.806 10.229 8.754 13.846 14.842 4.38 7.806 9.657 13.754 15.846 17.847 6.184 4.093 12.419 6.136 18.699 6.136 6.28 0 11.704-.476 16.274-1.423 4.565-.952 8.848-2.383 12.847-4.285 1.713-12.758 6.377-22.559 13.988-29.41-10.848-1.14-20.601-2.857-29.264-5.14-8.658-2.286-17.605-5.996-26.835-11.14-9.235-5.137-16.896-11.516-22.985-19.126-6.09-7.614-11.088-17.61-14.987-29.979-3.901-12.374-5.852-26.648-5.852-42.826 0-23.035 7.52-42.637 22.557-58.817-7.044-17.318-6.379-36.732 1.997-58.24 5.52-1.715 13.706-.428 24.554 3.853 10.85 4.283 18.794 7.952 23.84 10.994 5.046 3.041 9.089 5.618 12.135 7.708 17.705-4.947 35.976-7.421 54.818-7.421s37.117 2.474 54.823 7.421l10.849-6.849c7.419-4.57 16.18-8.758 26.262-12.565 10.088-3.805 17.802-4.853 23.134-3.138 8.562 21.509 9.325 40.922 2.279 58.24 15.036 16.18 22.559 35.787 22.559 58.817 0 16.178-1.958 30.497-5.853 42.966-3.9 12.471-8.941 22.457-15.125 29.979-6.191 7.521-13.901 13.85-23.131 18.986-9.232 5.14-18.182 8.85-26.84 11.136-8.662 2.286-18.415 4.004-29.263 5.146 9.894 8.562 14.842 22.077 14.842 40.539v60.237c0 3.422 1.19 6.279 3.572 8.562 2.379 2.279 6.136 2.95 11.276 1.995 44.163-14.653 80.185-41.062 108.068-79.226 27.88-38.161 41.825-81.126 41.825-128.906-.01-39.771-9.818-76.454-29.414-110.049z"
|
||||
></path>
|
||||
</svg>
|
||||
),
|
||||
};
|
||||
|
||||
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 (
|
||||
<motion.header
|
||||
initial={{ y: -100, opacity: 0 }}
|
||||
animate={{ y: 0, opacity: 1 }}
|
||||
transition={{ duration: 0.8, ease: "easeOut" }}
|
||||
className="fixed top-0 w-full z-[100] bg-black/60 backdrop-blur-md"
|
||||
>
|
||||
<nav className="max-w-6xl mx-auto px-4 py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.2 }}
|
||||
className="text-xl font-bold"
|
||||
>
|
||||
<span className="bg-gradient-to-r from-cyan-400 to-blue-500 bg-clip-text text-transparent">
|
||||
Evandro.dev
|
||||
</span>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: 20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.3 }}
|
||||
className="flex items-center space-x-8"
|
||||
>
|
||||
{[
|
||||
{ href: "#", label: "Home" },
|
||||
{ href: "#about", label: "Sobre" },
|
||||
{ href: "#skills", label: "Habilidades" },
|
||||
{ href: "#experience", label: "Experiência" },
|
||||
{ href: "#projects", label: "Projetos" },
|
||||
{ href: "#contact", label: "Contato" },
|
||||
].map((item, index) => (
|
||||
<motion.a
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
initial={{ opacity: 0, y: -10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5, delay: 0.4 + index * 0.1 }}
|
||||
className="text-gray-300 hover:text-white transition-all duration-300 hover:scale-105 relative group"
|
||||
>
|
||||
{item.label}
|
||||
<span className="absolute -bottom-1 left-0 w-0 h-0.5 bg-gradient-to-r from-cyan-400 to-blue-500 group-hover:w-full transition-all duration-300"></span>
|
||||
</motion.a>
|
||||
))}
|
||||
|
||||
<motion.a
|
||||
initial={{ opacity: 0, scale: 0.8 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 0.5, delay: 1 }}
|
||||
href="/cv.pdf"
|
||||
className="hidden bg-gradient-to-r from-blue-600 to-cyan-600 hover:from-blue-700 hover:to-cyan-700 text-white px-4 py-2 rounded-lg transition-all duration-300 hover:scale-105 hover:shadow-lg hover:shadow-blue-500/25"
|
||||
download
|
||||
>
|
||||
Download CV
|
||||
</motion.a>
|
||||
</motion.div>
|
||||
</div>
|
||||
</nav>
|
||||
</motion.header>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
<section className="relative min-h-screen flex items-center justify-center px-4">
|
||||
{/* Background Pattern */}
|
||||
<div className="absolute inset-0 opacity-20"></div>
|
||||
|
||||
<div className="relative z-10 text-center max-w-4xl mx-auto">
|
||||
{/* Profile Image */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.5 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 0.8 }}
|
||||
className="mb-8"
|
||||
>
|
||||
<div className="relative w-48 h-48 mx-auto mb-8 mt-10">
|
||||
<Image
|
||||
src="/edev.jpeg"
|
||||
alt="Evandro Fonseca Junior - Desenvolvedor Full-Stack"
|
||||
fill
|
||||
className="rounded-lg object-cover"
|
||||
priority
|
||||
sizes="(max-width: 768px) 192px, 192px"
|
||||
quality={85}
|
||||
placeholder="blur"
|
||||
blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAAIAAoDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAv/xAAhEAACAQMDBQAAAAAAAAAAAAABAgMABAUGIWGRkqGx0f/EABUBAQEAAAAAAAAAAAAAAAAAAAMF/8QAGhEAAgIDAAAAAAAAAAAAAAAAAAECEgMRkf/aAAwDAQACEQMRAD8AltJagyeH0AthI5xdrLcNM91BF5pX2HaH9bcfaSXWGaRmknyJckliyjqTzSlT54b6bk+h0R+IRjWjBqO6O2mhP//Z"
|
||||
/>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Name and Title */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.2 }}
|
||||
className="mb-8"
|
||||
>
|
||||
<h1 className="text-3xl md:text-5xl lg:text-6xl font-black mb-6 leading-tight">
|
||||
<AuroraText>Evandro Fonseca Junior</AuroraText>
|
||||
</h1>
|
||||
<AnimatedGradientText className="text-xl md:text-2xl lg:text-3xl font-bold">
|
||||
Desenvolvedor Full-Stack
|
||||
</AnimatedGradientText>
|
||||
</motion.div>
|
||||
|
||||
{/* Description */}
|
||||
<motion.p
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.4 }}
|
||||
className="text-xl md:text-2xl lg:text-3xl text-gray-300 max-w-3xl mx-auto mb-12 leading-relaxed"
|
||||
>
|
||||
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.
|
||||
</motion.p>
|
||||
|
||||
{/* Location */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.6 }}
|
||||
className="text-gray-400 mb-8"
|
||||
>
|
||||
📍 Garuva, Santa Catarina, Brasil
|
||||
</motion.div>
|
||||
|
||||
{/* Social Icons */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.8 }}
|
||||
className="flex justify-center space-x-6 mb-8"
|
||||
>
|
||||
<a
|
||||
href="https://www.linkedin.com/in/evandro-fonseca-junior-0969aa1b0/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-gray-400 hover:text-blue-400 transition-colors"
|
||||
aria-label="LinkedIn"
|
||||
>
|
||||
<svg className="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z" />
|
||||
</svg>
|
||||
</a>
|
||||
<a
|
||||
href="https://github.com/evandrodevbr"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-gray-400 hover:text-white transition-colors"
|
||||
aria-label="GitHub"
|
||||
>
|
||||
<svg className="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
|
||||
</svg>
|
||||
</a>
|
||||
<a
|
||||
href="#contact"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
scrollToContact();
|
||||
}}
|
||||
className="text-gray-400 hover:text-green-400 transition-colors"
|
||||
aria-label="WhatsApp"
|
||||
>
|
||||
<svg className="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893A11.821 11.821 0 0020.885 3.488" />
|
||||
</svg>
|
||||
</a>
|
||||
<a
|
||||
href="#contact"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
scrollToContact();
|
||||
}}
|
||||
className="text-gray-400 hover:text-blue-400 transition-colors"
|
||||
aria-label="Email"
|
||||
>
|
||||
<svg
|
||||
className="w-6 h-6"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M3 8l7.89 4.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</motion.div>
|
||||
|
||||
{/* CTA Button */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 1 }}
|
||||
className="flex justify-center mb-8"
|
||||
>
|
||||
<a
|
||||
href="#contact"
|
||||
className="bg-blue-600 hover:bg-blue-700 text-white px-8 py-3 rounded-lg font-semibold transition-colors"
|
||||
>
|
||||
Vamos Trabalhar Juntos
|
||||
</a>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{/* Scroll Indicator */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.8, delay: 1.2 }}
|
||||
className="absolute bottom-8 left-1/2 transform -translate-x-1/2"
|
||||
>
|
||||
<div className="w-6 h-10 border-2 border-white/30 rounded-full flex justify-center">
|
||||
<motion.div
|
||||
animate={{ y: [0, 12, 0] }}
|
||||
transition={{ duration: 1.5, repeat: Infinity }}
|
||||
className="w-1 h-3 bg-white/50 rounded-full mt-2"
|
||||
/>
|
||||
</div>
|
||||
</motion.div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
<section id="projects" className="py-20 px-4">
|
||||
<div className="max-w-6xl 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-6xl 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">
|
||||
<div className="max-w-6xl 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 font-bold mb-6">
|
||||
Projetos em Destaque
|
||||
</h2>
|
||||
<p className="text-xl text-gray-300 max-w-2xl mx-auto">
|
||||
Alguns dos projetos que desenvolvi, demonstrando minhas habilidades
|
||||
e paixão por tecnologia
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<BentoGrid className="gap-6">
|
||||
{projects.map((project, index) => (
|
||||
<BentoGridItem
|
||||
key={project.id}
|
||||
colSpan={index === 0 ? 2 : 1}
|
||||
className="group hover:scale-[1.02] transition-all duration-300 hover:shadow-2xl hover:shadow-blue-500/10"
|
||||
>
|
||||
<div className="space-y-4">
|
||||
{/* Header */}
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1">
|
||||
<h3 className="text-xl font-semibold text-white mb-2">
|
||||
{project.title}
|
||||
</h3>
|
||||
<div className="flex flex-wrap gap-2 mb-3">
|
||||
{project.technologies.map((tech) => (
|
||||
<span
|
||||
key={tech}
|
||||
className="px-2 py-1 bg-blue-500/20 text-blue-400 text-xs rounded-full border border-blue-500/30"
|
||||
>
|
||||
{tech}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<a
|
||||
href={project.demoUrl}
|
||||
className="p-2 rounded-lg bg-white/10 hover:bg-white/20 transition-colors"
|
||||
aria-label="Ver demo"
|
||||
>
|
||||
<ExternalLink className="w-4 h-4" />
|
||||
</a>
|
||||
<a
|
||||
href={project.githubUrl}
|
||||
className="p-2 rounded-lg bg-white/10 hover:bg-white/20 transition-colors"
|
||||
aria-label="Ver código"
|
||||
>
|
||||
<Github className="w-4 h-4" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Description */}
|
||||
<p className="text-gray-300 leading-relaxed">
|
||||
{project.description}
|
||||
</p>
|
||||
|
||||
{/* Features */}
|
||||
<div>
|
||||
<h4 className="text-sm font-semibold text-blue-400 mb-2">
|
||||
Funcionalidades:
|
||||
</h4>
|
||||
<ul className="space-y-1">
|
||||
{project.features.map((feature, idx) => (
|
||||
<li
|
||||
key={idx}
|
||||
className="text-sm text-gray-400 flex items-center gap-2"
|
||||
>
|
||||
<div className="w-1.5 h-1.5 bg-blue-400 rounded-full" />
|
||||
{feature}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Status */}
|
||||
<div className="pt-2 border-t border-white/10">
|
||||
<span className="text-xs text-blue-400 font-medium">
|
||||
{project.status}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</BentoGridItem>
|
||||
))}
|
||||
</BentoGrid>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
<section id="skills" className="py-20 px-4">
|
||||
<div className="max-w-6xl 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="skills" className="py-20 px-4">
|
||||
<div className="max-w-6xl mx-auto text-center">
|
||||
<p className="text-red-400">Erro ao carregar dados de habilidades.</p>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
const skillCategories = data.skills.technical;
|
||||
|
||||
return (
|
||||
<section id="skills" className="py-20 px-4">
|
||||
<div className="max-w-6xl 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 font-bold mb-6">
|
||||
Habilidades Técnicas
|
||||
</h2>
|
||||
<p className="text-xl text-gray-300 max-w-2xl mx-auto">
|
||||
Tecnologias e ferramentas que utilizo para criar soluções inovadoras
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
{skillCategories.map((category, index) => {
|
||||
const IconComponent =
|
||||
iconMap[category.icon as keyof typeof iconMap] || Code;
|
||||
return (
|
||||
<motion.div
|
||||
key={category.category}
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: index * 0.1 }}
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
<div className="p-6 space-y-4">
|
||||
{/* Header */}
|
||||
<div className="flex items-center gap-3">
|
||||
<div className={`p-3 rounded-lg ${category.bgColor}`}>
|
||||
<IconComponent className={`w-6 h-6 ${category.color}`} />
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold">
|
||||
{category.category}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
{/* Skills List */}
|
||||
<div className="space-y-2">
|
||||
{category.skills.map((skill, skillIndex) => (
|
||||
<div
|
||||
key={skillIndex}
|
||||
className="flex items-center gap-2 text-gray-300"
|
||||
>
|
||||
<div
|
||||
className={`w-2 h-2 rounded-full ${
|
||||
colorMap[category.color as keyof typeof colorMap] ||
|
||||
"bg-gray-400"
|
||||
}`}
|
||||
/>
|
||||
<span className="text-sm">{skill}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
<section className="py-16 px-4">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8 }}
|
||||
viewport={{ once: true }}
|
||||
className="text-center mb-12"
|
||||
>
|
||||
<h2 className="text-4xl md:text-5xl font-bold mb-4">
|
||||
Minhas Modalidades de Trabalho
|
||||
</h2>
|
||||
<p className="text-xl text-gray-300 max-w-2xl mx-auto">
|
||||
Escolha a opção que melhor se encaixa nas suas necessidades.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 max-w-6xl mx-auto">
|
||||
{WorkModalitiesData.map((modality, index) => (
|
||||
<motion.div
|
||||
key={modality.title}
|
||||
initial={{ opacity: 0, scale: 0.9 }}
|
||||
whileInView={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 0.5, delay: index * 0.1 }}
|
||||
viewport={{ once: true }}
|
||||
className={`bg-white/5 p-6 rounded-lg shadow-lg border border-white/10 ${
|
||||
modality.color === "blue" ? "hover:border-blue-500" : ""
|
||||
} ${modality.color === "green" ? "hover:border-green-500" : ""} ${
|
||||
modality.color === "purple" ? "hover:border-purple-500" : ""
|
||||
} ${modality.color === "orange" ? "hover:border-orange-500" : ""} ${
|
||||
modality.color === "red" ? "hover:border-red-500" : ""
|
||||
} transition-all duration-300 cursor-pointer`}
|
||||
>
|
||||
<div
|
||||
className={`p-3 rounded-full ${
|
||||
modality.color === "blue" ? "bg-blue-500/20" : ""
|
||||
} ${modality.color === "green" ? "bg-green-500/20" : ""} ${
|
||||
modality.color === "purple" ? "bg-purple-500/20" : ""
|
||||
} ${modality.color === "orange" ? "bg-orange-500/20" : ""} ${
|
||||
modality.color === "red" ? "bg-red-500/20" : ""
|
||||
} inline-block mb-4`}
|
||||
>
|
||||
<modality.icon
|
||||
className={`w-8 h-8 ${
|
||||
modality.color === "blue" ? "text-blue-400" : ""
|
||||
} ${modality.color === "green" ? "text-green-400" : ""} ${
|
||||
modality.color === "purple" ? "text-purple-400" : ""
|
||||
} ${modality.color === "orange" ? "text-orange-400" : ""} ${
|
||||
modality.color === "red" ? "text-red-400" : ""
|
||||
}`}
|
||||
/>
|
||||
</div>
|
||||
<h3 className="text-2xl font-semibold mb-2">{modality.title}</h3>
|
||||
<p className="text-lg font-medium text-gray-200 mb-2">
|
||||
{modality.value}
|
||||
</p>
|
||||
<p className="text-gray-400 mb-4">{modality.description}</p>
|
||||
<p className="text-sm text-gray-500">
|
||||
<span className="font-semibold">Ideal para:</span>{" "}
|
||||
{modality.idealFor}
|
||||
</p>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
+155
-14
@@ -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 */
|
||||
}
|
||||
+98
-3
@@ -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 (
|
||||
<html lang="en">
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
{/* Preconnect to external origins for better performance */}
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link
|
||||
rel="preconnect"
|
||||
href="https://fonts.gstatic.com"
|
||||
crossOrigin="anonymous"
|
||||
/>
|
||||
<link rel="preconnect" href="https://wa.me" />
|
||||
<link rel="preconnect" href="https://www.linkedin.com" />
|
||||
<link rel="preconnect" href="https://github.com" />
|
||||
|
||||
{/* DNS prefetch for additional performance */}
|
||||
<link rel="dns-prefetch" href="//fonts.googleapis.com" />
|
||||
<link rel="dns-prefetch" href="//fonts.gstatic.com" />
|
||||
<link rel="dns-prefetch" href="//wa.me" />
|
||||
<link rel="dns-prefetch" href="//www.linkedin.com" />
|
||||
<link rel="dns-prefetch" href="//github.com" />
|
||||
|
||||
{/* Favicon and icons */}
|
||||
<link rel="icon" href="/favicon.ico" sizes="any" />
|
||||
<link rel="apple-touch-icon" href="/favicon.ico" />
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
|
||||
{/* SEO and Crawling */}
|
||||
<link rel="canonical" href="https://evandrofonsecajunior.dev" />
|
||||
|
||||
{/* Theme color for mobile browsers */}
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta name="msapplication-TileColor" content="#000000" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta
|
||||
name="apple-mobile-web-app-status-bar-style"
|
||||
content="black-translucent"
|
||||
/>
|
||||
<meta name="apple-mobile-web-app-title" content="Evandro.dev" />
|
||||
|
||||
{/* Performance hints */}
|
||||
<link rel="preload" href="/edev.jpeg" as="image" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, maximum-scale=5"
|
||||
/>
|
||||
</head>
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||
>
|
||||
|
||||
+71
-96
@@ -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: () => (
|
||||
<div className="min-h-screen flex items-center justify-center">
|
||||
<div className="animate-pulse text-white">Carregando...</div>
|
||||
</div>
|
||||
),
|
||||
});
|
||||
|
||||
const About = dynamic(() => import("./components/About"), {
|
||||
loading: () => (
|
||||
<div className="py-20 px-4 text-center">
|
||||
<div className="animate-pulse text-white">Carregando seção...</div>
|
||||
</div>
|
||||
),
|
||||
});
|
||||
|
||||
const Skills = dynamic(() => import("./components/Skills"), {
|
||||
loading: () => (
|
||||
<div className="py-20 px-4 text-center">
|
||||
<div className="animate-pulse text-white">Carregando habilidades...</div>
|
||||
</div>
|
||||
),
|
||||
});
|
||||
|
||||
const Experience = dynamic(() => import("./components/Experience"), {
|
||||
loading: () => (
|
||||
<div className="py-20 px-4 text-center">
|
||||
<div className="animate-pulse text-white">Carregando experiência...</div>
|
||||
</div>
|
||||
),
|
||||
});
|
||||
|
||||
const Projects = dynamic(() => import("./components/Projects"), {
|
||||
loading: () => (
|
||||
<div className="py-20 px-4 text-center">
|
||||
<div className="animate-pulse text-white">Carregando projetos...</div>
|
||||
</div>
|
||||
),
|
||||
});
|
||||
|
||||
const Contact = dynamic(() => import("./components/Contact"), {
|
||||
loading: () => (
|
||||
<div className="py-20 px-4 text-center">
|
||||
<div className="animate-pulse text-white">Carregando contato...</div>
|
||||
</div>
|
||||
),
|
||||
});
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="font-sans grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20">
|
||||
<main className="flex flex-col gap-[32px] row-start-2 items-center sm:items-start">
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/next.svg"
|
||||
alt="Next.js logo"
|
||||
width={180}
|
||||
height={38}
|
||||
priority
|
||||
/>
|
||||
<ol className="font-mono list-inside list-decimal text-sm/6 text-center sm:text-left">
|
||||
<li className="mb-2 tracking-[-.01em]">
|
||||
Get started by editing{" "}
|
||||
<code className="bg-black/[.05] dark:bg-white/[.06] font-mono font-semibold px-1 py-0.5 rounded">
|
||||
app/page.tsx
|
||||
</code>
|
||||
.
|
||||
</li>
|
||||
<li className="tracking-[-.01em]">
|
||||
Save and see your changes instantly.
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<div className="flex gap-4 items-center flex-col sm:flex-row">
|
||||
<a
|
||||
className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:w-auto"
|
||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/vercel.svg"
|
||||
alt="Vercel logomark"
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
Deploy now
|
||||
</a>
|
||||
<a
|
||||
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 w-full sm:w-auto md:w-[158px]"
|
||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Read our docs
|
||||
</a>
|
||||
</div>
|
||||
<div className="min-h-screen bg-black text-white relative">
|
||||
<Squares
|
||||
direction="diagonal"
|
||||
speed={0.5}
|
||||
borderColor="#374151"
|
||||
hoverFillColor="#1e40af"
|
||||
squareSize={30}
|
||||
className="absolute inset-0 opacity-30"
|
||||
/>
|
||||
<Header />
|
||||
<main className="relative z-10">
|
||||
<Hero />
|
||||
<About />
|
||||
<Skills />
|
||||
<Experience />
|
||||
<Projects />
|
||||
<WorkModalities /> {/* Adicionar WorkModalities aqui */}
|
||||
<Contact />
|
||||
</main>
|
||||
<footer className="row-start-3 flex gap-[24px] flex-wrap items-center justify-center">
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/file.svg"
|
||||
alt="File icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Learn
|
||||
</a>
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/window.svg"
|
||||
alt="Window icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Examples
|
||||
</a>
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/globe.svg"
|
||||
alt="Globe icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Go to nextjs.org →
|
||||
</a>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user