Documentation Index Fetch the complete documentation index at: https://mintlify.com/ThalysonRibeiro/my-portfolio-fullstack/llms.txt
Use this file to discover all available pages before exploring further.
Overview
The Projects component showcases portfolio work with featured project cards, image carousels, tech stack badges, and multiple link types (live demo, GitHub, case studies).
Component Location
src/components/projects.tsx
src/components/carousel-image-projects.tsx
Project Display Structure
The component renders three types of project displays:
Featured Cards Large featured projects like Vexiun with detailed metrics and impact highlights
Theme Projects Sub-projects (e.g., VSCode themes) with grid layouts and marketplace links
Standard Cards Regular project cards with carousel, description, and action buttons
Animation Configuration
const ANIMATION_CONFIG = {
container: {
hidden: { opacity: 0 },
visible: {
opacity: 1 ,
transition: {
staggerChildren: 0.1 ,
delayChildren: 0.2
}
}
},
item: {
hidden: { opacity: 0 , y: 30 },
visible: {
opacity: 1 ,
y: 0 ,
transition: {
type: "spring" ,
damping: 20 ,
stiffness: 100
}
}
}
} as const ;
Project Data Structure
Projects are defined in content.json with this structure:
{
"id" : "02" ,
"title" : "Equilibrium Center" ,
"projectType" : "Fullstack" ,
"category" : {
"ptBR" : "Gestão / Saúde" ,
"en" : "Management / Health"
},
"description" : {
"ptBR" : "Plataforma completa de gestão para massoterapeutas: agendamento, clientes e organização do atendimento." ,
"en" : "A complete management platform for massage therapists: scheduling, clients, and service organization."
},
"tech" : [
"Next.js" ,
"TypeScript" ,
"shadcn" ,
"TailwindCSS" ,
"React Hook Form" ,
"Zod" ,
"PostgreSQL (Neon)" ,
"Prisma" ,
"Vercel" ,
"Docker" ,
"Stripe" ,
"Cloudinary" ,
"Auth.js"
],
"links" : {
"github" : "https://github.com/ThalysonRibeiro/equilibrium-center" ,
"githubBackend" : "" ,
"app" : "" ,
"live" : "https://equilibrium-center.vercel.app"
},
"images" : [
{ "title" : "Equilibrium-Center-photo-1" , "image" : "/eq-center/1.webp" },
{ "title" : "Equilibrium-Center-photo-2" , "image" : "/eq-center/2.webp" },
{ "title" : "Equilibrium-Center-photo-3" , "image" : "/eq-center/3.webp" }
]
}
All project data is centralized in src/utils/content.json under the projectsData array.
Image Carousel Component
The carousel is a standalone component with auto-play and manual navigation:
Key Features
Auto-play with Pause
Automatically cycles through images every 3 seconds useEffect (() => {
if ( isPaused ) return ;
const interval = setInterval ( nextSlide , 3000 );
return () => clearInterval ( interval );
}, [ nextSlide , isPaused ]);
Image Preloading
Preloads all images to prevent flickering during transitions useEffect (() => {
const imagePromises = images . map (( item ) => {
return new Promise (( resolve , reject ) => {
const img = new window . Image ();
img . onload = resolve ;
img . onerror = reject ;
img . src = item . image ;
});
});
Promise . all ( imagePromises )
. then (() => setIsLoaded ( true ))
. catch (() => setIsLoaded ( true ));
}, [ images ]);
Pause on Hover/Focus
Pauses auto-play when user interacts with carousel < div
onMouseEnter = { () => setIsPaused ( true ) }
onMouseLeave = { () => setIsPaused ( false ) }
onFocus = { () => setIsPaused ( true ) }
onBlur = { () => setIsPaused ( false ) }
>
Project Card Component
The ProjectCard is a memoized component for performance:
const ProjectCard = memo (({ project , lang } : ProjectCardProps ) => {
const projectLinks = useMemo (() => getProjectLinks ( project , lang ), [ project , lang ]);
return (
< motion . article
variants = {ANIMATION_CONFIG. item }
className = "h-full group"
role = "article"
aria - labelledby = { `project-title- ${ project . id } ` }
>
< Card className = "h-full p-0 relative bg-zinc-950/20 hover:bg-zinc-900/60" >
{ /* Image Carousel */ }
< Carousel images = {project. images } />
{ /* Project Details */ }
< div className = "p-5 flex flex-col flex-1 space-y-4" >
< CardTitle >{project. title } </ CardTitle >
< CardDescription >{project.description [lang]}</CardDescription>
{ /* Tech Stack Badges */ }
<div className="flex flex-wrap gap-1.5">
{project.tech.map((tech) => (
<Badge key={tech} variant="secondary">{tech}</Badge>
))}
</div>
{ /* Action Buttons */ }
{projectLinks.map((link) => (
<Button asChild>
<Link href={link.href} target="_blank">
{link.label}
</Link>
</Button>
))}
</div>
</Card>
</motion.article>
);
});
The memo wrapper prevents unnecessary re-renders when parent component updates.
Dynamic Link Generation
The getProjectLinks function generates appropriate links based on available URLs:
function getProjectLinks ( project , lang ) : ProjectLink [] {
const links : ProjectLink [] = [
{
href: project . links . live ,
label: content . projects . labels . liveDemo [ lang ],
icon: Eye ,
ariaLabel: `Ver demonstração ao vivo do projeto ${ project . title } `
}
];
if ( project . links . github ) {
links . push ({
href: project . links . github ,
label: content . projects . labels . frontend [ lang ],
icon: FaGithub
});
}
if ( project . links . githubBackend ) {
links . push ({
href: project . links . githubBackend ,
label: content . projects . labels . backend [ lang ],
icon: FaGithub
});
}
if ( project . links . app ) {
links . push ({
href: project . links . app ,
label: content . projects . labels . downloadApp [ lang ],
icon: ArrowBigDown
});
}
return links ;
}
Featured Project: Vexiun
The VexiunCard component showcases a major project with enhanced visuals:
export function VexiunCard ({ lang } : { lang : Lang }) {
return (
< div className = "grid grid-cols-1 lg:grid-cols-2" >
{ /* Hero Image */ }
< div className = "relative aspect-video" >
< Image
src = "/vexiun.cap-1.webp"
alt = "Vexiun Dashboard Preview"
fill
className = "object-contain transition-transform duration-700 group-hover:scale-105"
/>
< div className = "absolute left-4 top-4" >
< span className = "flex items-center gap-1.5 bg-black/60 backdrop-blur-md px-3 py-1" >
< span className = "h-1.5 w-1.5 animate-pulse bg-primary" />
{ content . vexiunData . i18n [ lang ]. status }
</ span >
</ div >
</ div >
{ /* Impact Metrics */ }
< div className = "grid grid-cols-3 gap-4 mb-8" >
{ content . vexiunData . i18n [ lang ]. impactMetrics . map (( metric ) => (
< div key = { metric . value } >
< span className = "text-2xl font-bold text-primary" >
{ metric . value } < span className = "text-[10px]" > { metric . label } </ span >
</ span >
< span className = "text-[10px] text-zinc-400" > { metric . detail } </ span >
</ div >
)) }
</ div >
</ div >
);
}
How to Add New Projects
Add Images
Place project images in the public/ directory: public/
└── my-project/
├── 1.webp
├── 2.webp
└── 3.webp
Update content.json
Add a new entry to the projectsData array: {
"id" : "04" ,
"title" : "My New Project" ,
"projectType" : "Fullstack" ,
"category" : { "ptBR" : "Categoria" , "en" : "Category" },
"description" : {
"ptBR" : "Descrição em português" ,
"en" : "English description"
},
"tech" : [ "Next.js" , "TypeScript" , "PostgreSQL" ],
"links" : {
"github" : "https://github.com/username/repo" ,
"live" : "https://myproject.com"
},
"images" : [
{ "title" : "Screenshot 1" , "image" : "/my-project/1.webp" },
{ "title" : "Screenshot 2" , "image" : "/my-project/2.webp" }
]
}
Test Locally
The project will automatically appear in the grid: npm run dev
# Navigate to /#projects
Make sure all image paths are correct and images are optimized (WebP format recommended for best performance).
Customization Options
Modify the grid configuration in projects.tsx:102: < div className = "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6" >
Change the interval in carousel-image-projects.tsx:29: const interval = setInterval ( nextSlide , 3000 ); // 3 seconds
Customize Card Hover Effects
Edit the card className in projects.tsx:164: className = "bg-zinc-950/20 hover:bg-zinc-900/60 transition-all duration-300"
Accessibility
The Projects component includes:
Semantic HTML with <article> and role="list"
ARIA labels for all interactive elements
Keyboard-accessible carousel controls
Alt text for all images
Focus management for modal interactions