192 lines
6.3 KiB
TypeScript
192 lines
6.3 KiB
TypeScript
"use client";
|
|
|
|
import { motion, type Variants } from "motion/react";
|
|
import { useReducedMotion } from "@/app/hooks/useReducedMotion";
|
|
import type { PortfolioProjectView } from "@/lib/portfolio-data";
|
|
import { caseStudies } from "@/lib/site-content";
|
|
import { DrawLine } from "./animations/DrawLine";
|
|
import MagneticButton from "./cinematic/MagneticButton";
|
|
import { Section } from "./Section";
|
|
|
|
export default function Projects({
|
|
projects,
|
|
}: {
|
|
projects: PortfolioProjectView[];
|
|
}) {
|
|
const shouldReduceMotion = useReducedMotion();
|
|
|
|
const gridVariants: Variants = {
|
|
hidden: {},
|
|
visible: {
|
|
transition: {
|
|
delayChildren: 0.08,
|
|
staggerChildren: 0.08,
|
|
},
|
|
},
|
|
};
|
|
|
|
const cardVariants: Variants = {
|
|
hidden: shouldReduceMotion
|
|
? { opacity: 0 }
|
|
: { opacity: 0, y: 24, 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="projects"
|
|
eyebrow="Projetos"
|
|
title="Veja o que já saiu do papel."
|
|
description="Sistemas e aplicações com contexto, decisões técnicas e limites documentados. Explore os cases para entender o problema e a solução, além da interface."
|
|
dark
|
|
disableChildrenAnimation
|
|
>
|
|
<motion.div
|
|
className="ds-grid-2"
|
|
initial="hidden"
|
|
whileInView="visible"
|
|
viewport={{ once: true, amount: 0.2 }}
|
|
variants={gridVariants}
|
|
>
|
|
{projects.map((project) => {
|
|
const caseStudy = caseStudies.find(
|
|
(entry) => entry.id === project.id,
|
|
);
|
|
return (
|
|
<motion.article
|
|
key={project.id}
|
|
className="ds-card h-full"
|
|
variants={cardVariants}
|
|
whileHover={
|
|
shouldReduceMotion
|
|
? undefined
|
|
: {
|
|
y: -4,
|
|
boxShadow: "0 20px 44px rgba(0, 0, 0, 0.42)",
|
|
}
|
|
}
|
|
whileTap={shouldReduceMotion ? undefined : { scale: 0.99 }}
|
|
transition={{ type: "spring", stiffness: 170, damping: 18 }}
|
|
>
|
|
<div className="flex flex-wrap items-start justify-between gap-sp-6">
|
|
<div className="max-w-[560px]">
|
|
<div className="ds-kicker">{project.category}</div>
|
|
<DrawLine delay={0.05} />
|
|
<h3 className="ds-card-title">{project.title}</h3>
|
|
{project.subtitle && (
|
|
<p className="mt-sp-3 text-[13px] leading-6 text-[rgba(255,255,255,.56)]">
|
|
{project.subtitle}
|
|
</p>
|
|
)}
|
|
</div>
|
|
<MagneticButton
|
|
as="span"
|
|
className="ds-tag"
|
|
strength={0.15}
|
|
wrapperClassName="shrink-0 max-sm:ml-auto"
|
|
>
|
|
{project.status}
|
|
</MagneticButton>
|
|
</div>
|
|
|
|
<p className="ds-copy">
|
|
{caseStudy?.summary ?? project.description}
|
|
</p>
|
|
{caseStudy && (
|
|
<div className="mt-sp-4 border-l border-[var(--slash)] pl-sp-4">
|
|
<p className="ds-kicker">O desafio</p>
|
|
<p className="ds-copy">{caseStudy.impact}</p>
|
|
</div>
|
|
)}
|
|
|
|
<ul className="ds-tags mt-sp-6" data-reel-stagger="fade">
|
|
{project.technologies.slice(0, 5).map((tech) => (
|
|
<li key={tech} className="ds-tag">
|
|
{tech}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
|
|
<div className="mt-sp-6 flex flex-wrap gap-sp-3" data-reel="top">
|
|
{caseStudy && (
|
|
<MagneticButton
|
|
as="a"
|
|
href={`/cases/${caseStudy.id}`}
|
|
className="ds-link ds-link-accent"
|
|
strength={0.2}
|
|
>
|
|
Entender a solução
|
|
</MagneticButton>
|
|
)}
|
|
{project.githubUrl && (
|
|
<MagneticButton
|
|
as="a"
|
|
href={project.githubUrl}
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
className="ds-link ds-link-muted"
|
|
strength={0.25}
|
|
>
|
|
Ver codigo
|
|
</MagneticButton>
|
|
)}
|
|
{project.demoUrl &&
|
|
(project.demoAvailability === "available" ||
|
|
project.demoAvailability === "unknown") && (
|
|
<MagneticButton
|
|
as="a"
|
|
href={project.demoUrl}
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
className="ds-link ds-link-accent"
|
|
strength={0.25}
|
|
>
|
|
Abrir projeto
|
|
{project.demoAvailability === "unknown" && (
|
|
<span className="sr-only">
|
|
{" "}
|
|
Disponibilidade não verificada
|
|
</span>
|
|
)}
|
|
</MagneticButton>
|
|
)}
|
|
{project.demoAvailability === "unavailable" && (
|
|
<span className="ds-link ds-link-accent" aria-disabled="true">
|
|
Demo indisponível na última verificação
|
|
</span>
|
|
)}
|
|
</div>
|
|
</motion.article>
|
|
);
|
|
})}
|
|
</motion.div>
|
|
|
|
<div className="mt-sp-8 flex flex-wrap gap-sp-3" data-reel-stagger="left">
|
|
<MagneticButton
|
|
as="a"
|
|
href="/cases"
|
|
className="ds-link ds-link-accent"
|
|
strength={0.2}
|
|
>
|
|
Explorar todos os cases
|
|
</MagneticButton>
|
|
<MagneticButton
|
|
as="a"
|
|
href="/servicos"
|
|
className="ds-link ds-link-muted"
|
|
strength={0.2}
|
|
>
|
|
Ver servicos relacionados
|
|
</MagneticButton>
|
|
</div>
|
|
</Section>
|
|
);
|
|
}
|