Files
portifolio-evandrodevbr/app/components/Experience.tsx
T
Evandro Fonseca JuniorandClaude Sonnet 5 d76bbf5974 feat: revamp portfolio with case studies, curriculo page, and local SEO
Adds dynamic case study and curriculo routes, project health monitoring
for live demo status, and strengthens local SEO (structured data,
service-area targeting for Garuva/Joinville/Curitiba, a dedicated
"criação de sites" service page) so search results better match the
professional title (Engenheiro da Computação) and experience (5 anos).

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-07-15 01:31:46 -03:00

110 lines
3.4 KiB
TypeScript

"use client";
import type { PortfolioExperience } from "@/lib/portfolio-data";
import { motion, type Variants, useReducedMotion } from "framer-motion";
import { Section } from "./Section";
import { DrawLine } from "./animations/DrawLine";
export default function Experience({
experiences,
}: {
experiences: PortfolioExperience[];
}) {
const shouldReduceMotion = useReducedMotion();
const sortedExperiences = [...experiences]
.filter((experience) => experience.featured)
.sort((a, b) => {
const yearA = Number.parseInt(a.period.match(/\d{4}/)?.[0] || "0", 10);
const yearB = Number.parseInt(b.period.match(/\d{4}/)?.[0] || "0", 10);
return yearB - yearA;
});
const listVariants: Variants = {
hidden: {},
visible: {
transition: {
delayChildren: 0.08,
staggerChildren: 0.08,
},
},
};
const itemVariants: Variants = {
hidden: shouldReduceMotion
? { opacity: 0 }
: { opacity: 0, y: 20, clipPath: "inset(100% 0 0 0)" },
visible: {
opacity: 1,
y: 0,
clipPath: "inset(0% 0 0 0)",
transition: shouldReduceMotion
? { duration: 0.25 }
: { duration: 0.8, ease: [0.16, 1, 0.3, 1] },
},
};
return (
<Section
id="experience"
eyebrow="Selected experience"
title="Experiencia em engenharia de software e sistemas criticos"
description="Projetos e contextos em que combinei arquitetura de software, integracoes complexas e entrega com responsabilidade operacional."
disableChildrenAnimation
>
<motion.div
className="space-y-sp-6"
initial="hidden"
whileInView="visible"
viewport={{ once: true, amount: 0.18 }}
variants={listVariants}
>
{sortedExperiences.map((exp) => (
<motion.article
key={exp.id}
className="ds-card h-full"
variants={itemVariants}
whileHover={
shouldReduceMotion
? undefined
: {
y: -4,
boxShadow: "0 16px 36px rgba(10,10,10,0.15)",
}
}
whileTap={shouldReduceMotion ? undefined : { scale: 0.995 }}
transition={{ type: "spring", stiffness: 170, damping: 20 }}
>
<div className="flex flex-col gap-sp-4 lg:flex-row lg:items-start lg:justify-between">
<div className="max-w-[720px]">
<div className="ds-kicker">{exp.period}</div>
<DrawLine delay={0.04} />
<h3 className="ds-card-title">{exp.title}</h3>
<p className="mt-sp-3 text-[13px] leading-6 text-[rgba(10,10,10,.64)] dark:text-[rgba(255,255,255,.6)]">
{exp.company} • {exp.type}
</p>
</div>
<div className="ds-metric">{exp.location}</div>
</div>
<p className="ds-copy">{exp.description}</p>
<ul className="ds-list">
{exp.achievements?.map((achievement) => (
<li key={achievement}>{achievement}</li>
))}
</ul>
<ul className="ds-tags mt-sp-6">
{exp.technologies?.slice(0, 6).map((tech) => (
<li key={tech} className="ds-tag">
{tech}
</li>
))}
</ul>
</motion.article>
))}
</motion.div>
</Section>
);
}