feat: Add initial portfolio UI components and skill definitions.
This commit is contained in:
1 parent
e8d9ef4697
commit
04923ccb2f
15 files changed
+1419
-64
No files matched your search
@@ -5,6 +5,7 @@ import { useState } from "react";
|
||||
import { motion, type Variants, useReducedMotion } from "framer-motion";
|
||||
import { Section } from "./Section";
|
||||
import { DrawLine } from "./animations/DrawLine";
|
||||
import MagneticButton from "./cinematic/MagneticButton";
|
||||
|
||||
const WHATSAPP_NUMBER = "5547992742365";
|
||||
|
||||
@@ -200,15 +201,14 @@ export default function Contact() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<motion.button
|
||||
<MagneticButton
|
||||
as="button"
|
||||
type="submit"
|
||||
className="ds-link ds-link-accent min-h-12 border-0"
|
||||
whileHover={shouldReduceMotion ? undefined : { scale: 1.03 }}
|
||||
whileTap={shouldReduceMotion ? undefined : { scale: 0.97 }}
|
||||
transition={{ type: "spring", stiffness: 260, damping: 20 }}
|
||||
strength={0.2}
|
||||
>
|
||||
Enviar briefing pelo WhatsApp
|
||||
</motion.button>
|
||||
</MagneticButton>
|
||||
</motion.form>
|
||||
</motion.div>
|
||||
</Section>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { motion } from "motion/react";
|
||||
import { Mail, MessageCircle } from "lucide-react";
|
||||
import { usePortfolioData } from "@/lib/usePortfolioData";
|
||||
import MagneticButton from "./cinematic/MagneticButton";
|
||||
|
||||
interface ContactInfoProps {
|
||||
onWhatsAppClick: () => void;
|
||||
@@ -28,11 +29,14 @@ export function ContactInfo({ onWhatsAppClick }: ContactInfoProps) {
|
||||
Entre em Contato
|
||||
</h3>
|
||||
<div className="space-y-4">
|
||||
<a
|
||||
<MagneticButton
|
||||
as="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"
|
||||
className="flex items-center gap-4 p-4 hover:bg-white/5 rounded-lg transition-colors w-full text-left"
|
||||
wrapperClassName="block w-full"
|
||||
strength={0.1}
|
||||
>
|
||||
<div className="p-3 rounded-full bg-blue-500/20">
|
||||
<Mail className="w-6 h-6 text-blue-400" />
|
||||
@@ -41,11 +45,13 @@ export function ContactInfo({ onWhatsAppClick }: ContactInfoProps) {
|
||||
<p className="text-white font-medium">Email</p>
|
||||
<p className="text-gray-400">{data.contact.email}</p>
|
||||
</div>
|
||||
</a>
|
||||
</MagneticButton>
|
||||
|
||||
<button
|
||||
<MagneticButton
|
||||
onClick={onWhatsAppClick}
|
||||
className="flex items-center gap-4 p-4 hover:bg-white/5 rounded-lg transition-colors w-full text-left"
|
||||
wrapperClassName="block w-full"
|
||||
strength={0.1}
|
||||
>
|
||||
<div className="p-3 rounded-full bg-green-500/20">
|
||||
<MessageCircle className="w-6 h-6 text-green-400" />
|
||||
@@ -54,13 +60,16 @@ export function ContactInfo({ onWhatsAppClick }: ContactInfoProps) {
|
||||
<p className="text-white font-medium">WhatsApp</p>
|
||||
<p className="text-gray-400">{data.contact.whatsapp}</p>
|
||||
</div>
|
||||
</button>
|
||||
</MagneticButton>
|
||||
|
||||
<a
|
||||
<MagneticButton
|
||||
as="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"
|
||||
className="flex items-center gap-4 p-4 hover:bg-white/5 rounded-lg transition-colors w-full text-left"
|
||||
wrapperClassName="block w-full"
|
||||
strength={0.1}
|
||||
>
|
||||
<div className="p-3 rounded-full bg-blue-500/20">
|
||||
<svg
|
||||
@@ -77,13 +86,16 @@ export function ContactInfo({ onWhatsAppClick }: ContactInfoProps) {
|
||||
{data.contact.linkedin.replace("https://www.", "")}
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
</MagneticButton>
|
||||
|
||||
<a
|
||||
<MagneticButton
|
||||
as="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"
|
||||
className="flex items-center gap-4 p-4 hover:bg-white/5 rounded-lg transition-colors w-full text-left"
|
||||
wrapperClassName="block w-full"
|
||||
strength={0.1}
|
||||
>
|
||||
<div className="p-3 rounded-full bg-gray-500/20">
|
||||
<svg
|
||||
@@ -100,7 +112,7 @@ export function ContactInfo({ onWhatsAppClick }: ContactInfoProps) {
|
||||
{data.contact.github.replace("https://github.com/", "")}
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
</MagneticButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
"use client";
|
||||
"use client";
|
||||
|
||||
import { useMemo } from "react";
|
||||
import { motion, type Variants, useReducedMotion } from "framer-motion";
|
||||
import { usePortfolioData } from "@/lib/usePortfolioData";
|
||||
import { Section } from "./Section";
|
||||
import { DrawLine } from "./animations/DrawLine";
|
||||
import MagneticButton from "./cinematic/MagneticButton";
|
||||
import { SectionLoading } from "./SectionLoading";
|
||||
|
||||
export default function Experience() {
|
||||
@@ -107,9 +108,9 @@ export default function Experience() {
|
||||
|
||||
<ul className="ds-tags mt-sp-6">
|
||||
{exp.technologies?.slice(0, 6).map((tech) => (
|
||||
<li key={tech} className="ds-tag">
|
||||
<MagneticButton key={tech} as="li" className="ds-tag" strength={0.15}>
|
||||
{tech}
|
||||
</li>
|
||||
</MagneticButton>
|
||||
))}
|
||||
</ul>
|
||||
</motion.article>
|
||||
|
||||
@@ -36,6 +36,8 @@ const quickLinks = [
|
||||
},
|
||||
];
|
||||
|
||||
import MagneticButton from "./cinematic/MagneticButton";
|
||||
|
||||
export default function Footer() {
|
||||
const currentYear = new Date().getFullYear();
|
||||
|
||||
@@ -60,12 +62,14 @@ export default function Footer() {
|
||||
<p className="mb-2 text-[12px] tracking-[0.02em] text-[var(--meta)]">
|
||||
E-mail direto
|
||||
</p>
|
||||
<a
|
||||
<MagneticButton
|
||||
as="a"
|
||||
href="mailto:[email protected]"
|
||||
className="inline-block text-[18px] font-bold text-[var(--ink)] transition-colors duration-200 hover:text-[var(--slash)]"
|
||||
strength={0.2}
|
||||
>
|
||||
evandro@dev.br
|
||||
</a>
|
||||
</MagneticButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -88,13 +92,15 @@ export default function Footer() {
|
||||
|
||||
<div className="mt-sp-6 flex flex-col gap-sp-4">
|
||||
{socialLinks.map((link) => (
|
||||
<a
|
||||
<MagneticButton
|
||||
key={link.name}
|
||||
as="a"
|
||||
href={link.href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label={link.ariaLabel}
|
||||
className="group flex items-center justify-between rounded-[8px] border border-[var(--border)] bg-[rgba(10,10,10,0.02)] p-sp-4 transition-all duration-300 hover:border-[var(--slash)] hover:bg-[rgba(10,10,10,0.04)]"
|
||||
strength={0.15}
|
||||
>
|
||||
<div className="flex items-center gap-sp-3">
|
||||
<svg
|
||||
@@ -112,7 +118,7 @@ export default function Footer() {
|
||||
<span className="text-[12px] tracking-[0.08em] text-[var(--meta)] transition-colors group-hover:text-[var(--slash)]">
|
||||
abrir
|
||||
</span>
|
||||
</a>
|
||||
</MagneticButton>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -120,13 +126,15 @@ export default function Footer() {
|
||||
<div className="ds-kicker">Explorar</div>
|
||||
<div className="mt-sp-4 grid gap-sp-3 md:grid-cols-2">
|
||||
{quickLinks.map((link) => (
|
||||
<a
|
||||
<MagneticButton
|
||||
key={link.href}
|
||||
as="a"
|
||||
href={link.href}
|
||||
className="rounded-[8px] border border-[var(--border)] bg-[rgba(10,10,10,0.02)] px-sp-4 py-sp-3 text-[13px] leading-6 text-[var(--ink-80)] transition-colors duration-200 hover:border-[var(--slash)] hover:text-[var(--ink)]"
|
||||
strength={0.15}
|
||||
>
|
||||
{link.label}
|
||||
</a>
|
||||
</MagneticButton>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { useState } from "react";
|
||||
import { Logo } from "./Logo";
|
||||
import MagneticButton from "./cinematic/MagneticButton";
|
||||
|
||||
const navItems = [
|
||||
{ href: "#home", label: "Home" },
|
||||
@@ -30,12 +31,14 @@ export function Header() {
|
||||
<ul className="flex list-none items-center gap-sp-2">
|
||||
{navItems.map((item) => (
|
||||
<li key={item.href}>
|
||||
<a
|
||||
<MagneticButton
|
||||
as="a"
|
||||
href={item.href}
|
||||
className="ds-link rounded-sm px-sp-3 py-sp-2 text-white/85 transition-colors hover:bg-white/10 hover:text-white"
|
||||
strength={0.15}
|
||||
>
|
||||
{item.label}
|
||||
</a>
|
||||
</MagneticButton>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
@@ -45,13 +48,15 @@ export function Header() {
|
||||
<span className="ds-metric hidden rounded-full border border-white/10 px-[10px] py-1 text-white/40 lg:inline-flex">
|
||||
v1.0 / 2026
|
||||
</span>
|
||||
<a
|
||||
<MagneticButton
|
||||
as="a"
|
||||
href="/Profile.pdf"
|
||||
download="Evandro_Fonseca_Junior_CV.pdf"
|
||||
className="ds-link ds-link-accent hidden rounded-sm font-semibold transition-shadow hover:shadow-[0_0_0_1px_rgba(232,255,0,.8),0_8px_24px_rgba(232,255,0,.35)] lg:inline-flex"
|
||||
strength={0.2}
|
||||
>
|
||||
Download CV
|
||||
</a>
|
||||
</MagneticButton>
|
||||
<button
|
||||
type="button"
|
||||
className="flex h-8 w-8 flex-col items-center justify-center gap-sp-2 border-none bg-transparent lg:hidden"
|
||||
@@ -80,23 +85,27 @@ export function Header() {
|
||||
<ul className="flex list-none flex-col gap-sp-2">
|
||||
{navItems.map((item) => (
|
||||
<li key={item.href}>
|
||||
<a
|
||||
<MagneticButton
|
||||
as="a"
|
||||
href={item.href}
|
||||
className="ds-link block rounded-sm py-sp-3 text-white/85 transition-all hover:pl-sp-2 hover:text-white"
|
||||
onClick={() => setMobileMenuOpen(false)}
|
||||
strength={0.15}
|
||||
>
|
||||
{item.label}
|
||||
</a>
|
||||
</MagneticButton>
|
||||
</li>
|
||||
))}
|
||||
<li>
|
||||
<a
|
||||
<MagneticButton
|
||||
as="a"
|
||||
href="/Profile.pdf"
|
||||
download="Evandro_Fonseca_Junior_CV.pdf"
|
||||
className="ds-link ds-link-accent mt-sp-2 inline-flex rounded-sm font-semibold"
|
||||
strength={0.15}
|
||||
>
|
||||
Download CV
|
||||
</a>
|
||||
</MagneticButton>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
+16
-17
@@ -4,6 +4,7 @@ import { useMemo } from "react";
|
||||
import { motion, type Variants, useReducedMotion } from "framer-motion";
|
||||
import { usePortfolioData } from "@/lib/usePortfolioData";
|
||||
import { Section } from "./Section";
|
||||
import MagneticButton from "./cinematic/MagneticButton";
|
||||
import { DrawLine } from "./animations/DrawLine";
|
||||
import { SectionLoading } from "./SectionLoading";
|
||||
|
||||
@@ -91,45 +92,43 @@ export default function Projects() {
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<span className="ds-tag">{project.status}</span>
|
||||
<MagneticButton as="span" className="ds-tag" strength={0.15}>{project.status}</MagneticButton>
|
||||
</div>
|
||||
|
||||
<p className="ds-copy">{project.description}</p>
|
||||
|
||||
<ul className="ds-tags mt-sp-6">
|
||||
{project.technologies.slice(0, 5).map((tech: string) => (
|
||||
<li key={tech} className="ds-tag">
|
||||
<MagneticButton key={tech} as="li" className="ds-tag" strength={0.15}>
|
||||
{tech}
|
||||
</li>
|
||||
</MagneticButton>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<div className="mt-sp-6 flex flex-wrap gap-sp-3">
|
||||
{project.githubUrl && (
|
||||
<motion.a
|
||||
<MagneticButton
|
||||
as="a"
|
||||
href={project.githubUrl}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="ds-link ds-link-muted"
|
||||
whileHover={shouldReduceMotion ? undefined : { scale: 1.03 }}
|
||||
whileTap={shouldReduceMotion ? undefined : { scale: 0.97 }}
|
||||
transition={{ type: "spring", stiffness: 260, damping: 20 }}
|
||||
strength={0.25}
|
||||
>
|
||||
Ver codigo
|
||||
</motion.a>
|
||||
</MagneticButton>
|
||||
)}
|
||||
{project.demoUrl && (
|
||||
<motion.a
|
||||
<MagneticButton
|
||||
as="a"
|
||||
href={project.demoUrl}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="ds-link ds-link-accent"
|
||||
whileHover={shouldReduceMotion ? undefined : { scale: 1.03 }}
|
||||
whileTap={shouldReduceMotion ? undefined : { scale: 0.97 }}
|
||||
transition={{ type: "spring", stiffness: 260, damping: 20 }}
|
||||
strength={0.25}
|
||||
>
|
||||
Abrir projeto
|
||||
</motion.a>
|
||||
</MagneticButton>
|
||||
)}
|
||||
</div>
|
||||
</motion.article>
|
||||
@@ -137,12 +136,12 @@ export default function Projects() {
|
||||
</motion.div>
|
||||
|
||||
<div className="mt-sp-8 flex flex-wrap gap-sp-3">
|
||||
<a href="/cases" className="ds-link ds-link-accent">
|
||||
<MagneticButton as="a" href="/cases" className="ds-link ds-link-accent" strength={0.2}>
|
||||
Explorar todos os cases
|
||||
</a>
|
||||
<a href="/servicos" className="ds-link ds-link-muted">
|
||||
</MagneticButton>
|
||||
<MagneticButton as="a" href="/servicos" className="ds-link ds-link-muted" strength={0.2}>
|
||||
Ver servicos relacionados
|
||||
</a>
|
||||
</MagneticButton>
|
||||
</div>
|
||||
</Section>
|
||||
);
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
"use client";
|
||||
"use client";
|
||||
|
||||
import { motion, type Variants, useReducedMotion } from "framer-motion";
|
||||
import { usePortfolioData } from "@/lib/usePortfolioData";
|
||||
import { Section } from "./Section";
|
||||
import MagneticButton from "./cinematic/MagneticButton";
|
||||
import { DrawLine } from "./animations/DrawLine";
|
||||
import { SectionLoading } from "./SectionLoading";
|
||||
|
||||
@@ -82,9 +83,9 @@ export default function Skills() {
|
||||
</p>
|
||||
<ul className="ds-tags">
|
||||
{category.skills.map((skill) => (
|
||||
<li key={skill} className="ds-tag">
|
||||
<MagneticButton key={skill} as="li" className="ds-tag" strength={0.15}>
|
||||
{skill}
|
||||
</li>
|
||||
</MagneticButton>
|
||||
))}
|
||||
</ul>
|
||||
</motion.article>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { motion, type Variants, useReducedMotion } from "framer-motion";
|
||||
import { serviceHubLinks } from "@/lib/site-content";
|
||||
import { Section } from "./Section";
|
||||
import { DrawLine } from "./animations/DrawLine";
|
||||
import MagneticButton from "./cinematic/MagneticButton";
|
||||
|
||||
interface Tier {
|
||||
id: string;
|
||||
@@ -147,17 +148,16 @@ function TierCard({ tier }: Readonly<{ tier: Tier }>) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<motion.a
|
||||
<MagneticButton
|
||||
as="a"
|
||||
href={tier.ctaHref}
|
||||
className={`ds-link mt-sp-6 min-h-[50px] w-full justify-center px-sp-4 py-sp-3 text-center text-[11px] leading-none ${
|
||||
tier.featured ? "ds-link-accent" : "ds-link-muted"
|
||||
}`}
|
||||
whileHover={shouldReduceMotion ? undefined : { scale: 1.02 }}
|
||||
whileTap={shouldReduceMotion ? undefined : { scale: 0.98 }}
|
||||
transition={{ type: "spring", stiffness: 260, damping: 20 }}
|
||||
strength={0.25}
|
||||
>
|
||||
{tier.ctaLabel}
|
||||
</motion.a>
|
||||
</MagneticButton>
|
||||
</motion.article>
|
||||
);
|
||||
}
|
||||
@@ -207,9 +207,9 @@ export function WorkModalities() {
|
||||
aqui esta o caminho mais direto para a pagina certa.
|
||||
</p>
|
||||
</div>
|
||||
<Link href="/servicos" className="ds-link ds-link-accent">
|
||||
<MagneticButton as="a" href="/servicos" className="ds-link ds-link-accent" strength={0.2}>
|
||||
Ver hub de servicos
|
||||
</Link>
|
||||
</MagneticButton>
|
||||
</div>
|
||||
|
||||
<div className="mt-sp-6 grid gap-sp-3 md:grid-cols-2 xl:grid-cols-3">
|
||||
|
||||
@@ -9,11 +9,13 @@ interface MagneticButtonProps {
|
||||
href?: string;
|
||||
onClick?: () => void;
|
||||
strength?: number;
|
||||
as?: "a" | "button";
|
||||
as?: "a" | "button" | "div" | "span" | "li";
|
||||
target?: string;
|
||||
rel?: string;
|
||||
download?: boolean;
|
||||
download?: boolean | string;
|
||||
"data-cursor"?: string;
|
||||
type?: "button" | "submit" | "reset";
|
||||
wrapperClassName?: string;
|
||||
}
|
||||
|
||||
export default function MagneticButton({
|
||||
@@ -27,6 +29,8 @@ export default function MagneticButton({
|
||||
rel,
|
||||
download,
|
||||
"data-cursor": dataCursor,
|
||||
type,
|
||||
wrapperClassName = "",
|
||||
}: MagneticButtonProps) {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const [position, setPosition] = useState({ x: 0, y: 0 });
|
||||
@@ -49,15 +53,16 @@ export default function MagneticButton({
|
||||
setPosition({ x: 0, y: 0 });
|
||||
};
|
||||
|
||||
const Tag = as === "a" ? motion.a : motion.button;
|
||||
const Tag = as === "a" ? motion.a : as === "button" ? motion.button : as === "div" ? motion.div : as === "span" ? motion.span : motion.li;
|
||||
const linkProps = as === "a" ? { href, target, rel, download } : {};
|
||||
const buttonProps = as === "button" ? { type } : {};
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
onMouseMove={handleMouse}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
className="inline-block"
|
||||
className={`inline-block ${wrapperClassName}`.trim()}
|
||||
>
|
||||
<Tag
|
||||
className={className}
|
||||
@@ -74,6 +79,7 @@ export default function MagneticButton({
|
||||
mass: 0.5,
|
||||
}}
|
||||
{...linkProps}
|
||||
{...buttonProps}
|
||||
>
|
||||
{children}
|
||||
</Tag>
|
||||
|
||||
Reference in new issue
Block a user