The Projects Section displays a portfolio of work with rich project cards, image previews, and an innovative full-page image viewer with scrolling controls.
Overview
Located in src/components/ProjectsSection.tsx, this component provides:
Dynamic project loading from backend API
Interactive project cards with hover effects
Full-page image viewer with custom scroll controls
GitHub integration with direct repository links
Tag-based categorization for technologies used
Project Data Structure
Projects are fetched from the API with the following interface:
interface Project {
id : number ;
title : string ;
description : string ;
tags : string []; // Technology tags (e.g., "React", "TypeScript")
image : string | null ; // Screenshot URL
links : ProjectLink []; // External links (demo, docs, etc.)
github : string | null ; // GitHub repository URL
order : number ; // Display order
}
interface ProjectLink {
url : string ;
label : string ; // Link button text
}
User Workflows
Browsing Projects
Grid View : Projects are displayed in a 2-column responsive grid
Hover Effects : Cards lift and highlight on hover with gradient overlay
Quick Preview : Hover over project images to reveal “View Full” button
Tag Navigation : Technology tags help users identify project tech stacks
Project Card Features
Screenshot preview
Project title and description
Technology tags
Action buttons (Live Demo, GitHub, etc.)
Smooth hover animations
Full-Page Image Viewer
The standout feature is the custom full-page viewer for project screenshots:
This viewer is specifically designed for viewing long website screenshots (common in portfolio projects). Users can scroll through tall images using intuitive speed controls.
Viewer Controls
Scroll Speed Buttons:
Devagar (Slow) : Scrolls at 2px per frame - for detailed viewing
Rápido (Fast) : Scrolls at 8px per frame - for quick overview
Usage Pattern:
Click eye icon on any project image
Hold down scroll button to navigate
Release button to stop scrolling
When reaching bottom, releasing button auto-scrolls to top
Click X or outside image to close
const SLOW_SPEED = 2 ; // px per frame
const FAST_SPEED = 8 ; // px per frame
const startScroll = ( speed : number ) => {
speedRef . current = speed ;
const tick = () => {
scrollRef . current = Math . min (
scrollRef . current + speedRef . current ,
maxScrollRef . current
);
setCurrentScroll ( scrollRef . current );
if ( scrollRef . current < maxScrollRef . current ) {
animFrameRef . current = requestAnimationFrame ( tick );
}
};
animFrameRef . current = requestAnimationFrame ( tick );
};
Helpful UI Tips
On first opening the viewer, users see an instructional tooltip:
< div className = "bg-primary text-primary-foreground px-5 py-3 rounded-xl" >
< span >
Segure os botões abaixo para navegar pela página.
Ao chegar no final e soltar, volta ao topo.
</ span >
< button onClick = { () => setShowTip ( false ) } > X </ button >
</ div >
Users can dismiss this tip, and it won’t reappear during the session.
API Integration
Projects are loaded from the backend:
const API_URL = import . meta . env . VITE_API_URL || '/api' ;
const fetchProjects = async () => {
try {
const response = await fetch ( ` ${ API_URL } /projects` );
if ( ! response . ok ) throw new Error ( `HTTP ${ response . status } ` );
const data = await response . json ();
setProjects ( Array . isArray ( data ) ? data : []);
} catch ( error ) {
console . error ( 'Erro ao carregar projetos:' , error );
}
};
The component includes error handling for API failures. If projects can’t be loaded, users see a “Carregando projetos…” message rather than an error screen.
Project Card Rendering
Each project card includes:
1. Project Image
{ project . image && (
< div className = "mb-4 rounded-lg overflow-hidden border border-border/50 relative group/image" >
< img
src = { project . image }
alt = { `Screenshot de ${ project . title } ` }
className = "w-full h-60 object-cover object-top"
/>
< button onClick = { () => setOpenImage ( project . image ) } >
< Eye className = "w-8 h-8 text-primary" />
</ button >
</ div >
)}
2. Title & Description
< h3 className = "text-xl font-bold mb-3" > { project . title } </ h3 >
< p className = "text-muted-foreground mb-4" > { project . description } </ p >
< div className = "flex flex-wrap gap-2 mb-6" >
{ project . tags . map (( tag , j ) => (
< span key = { j } className = "px-3 py-1 text-xs font-mono bg-secondary rounded-full text-primary" >
{ tag }
</ span >
)) }
</ div >
< div className = "flex flex-wrap gap-3" >
{ project . links . map (( link , j ) => (
< Button key = { j } variant = "outline" size = "sm" asChild >
< a href = { link . url } target = "_blank" rel = "noopener noreferrer" >
< ExternalLink className = "w-4 h-4" />
{ link . label }
</ a >
</ Button >
)) }
{ project . github && (
< Button variant = "ghost" size = "sm" asChild >
< a href = { project . github } target = "_blank" rel = "noopener noreferrer" >
< Github className = "w-4 h-4" />
Código
</ a >
</ Button >
) }
</ div >
At the bottom of the section, a friendly CTA encourages exploration:
< div className = "text-center mt-12" >
< div className = "inline-flex items-center gap-4" >
< img src = { memojiFist } alt = "Memoji" className = "w-16 h-16" />
< div className = "text-left" >
< p className = "text-muted-foreground" > Quer ver mais projetos? </ p >
< Button variant = "link" asChild >
< a href = "https://github.com/AndreRuperto" target = "_blank" >
Acesse meu GitHub →
</ a >
</ Button >
</ div >
</ div >
</ div >
Projects use the ScrollReveal component for smooth entrance animations:
{ projects . map (( project , index ) => (
< ScrollReveal key = { project . id } delay = { index * 150 } >
< div className = "group relative bg-card rounded-2xl..." >
{ /* Project card content */ }
</ div >
</ ScrollReveal >
))}
Each card appears with a staggered delay (150ms between cards) as users scroll.
Responsive Behavior
Mobile (< 768px)
Single column layout
Full-width cards
Stacked action buttons
Compact image viewer controls
Tablet/Desktop (≥ 768px)
Two-column grid
Fixed max-width container (5xl)
Side-by-side button layout
Larger image viewer
Loading States
While projects are being fetched:
if ( loading ) {
return (
< section className = "py-24 bg-card/50" >
< div className = "container mx-auto px-4" >
< div className = "text-center" >
< p className = "text-muted-foreground" > Carregando projetos... </ p >
</ div >
</ div >
</ section >
);
}
Image Viewer Technical Details
The viewer uses requestAnimationFrame for smooth, performant scrolling:
const tick = () => {
scrollRef . current = Math . min (
scrollRef . current + speedRef . current ,
maxScrollRef . current
);
setCurrentScroll ( scrollRef . current );
setAtBottom ( scrollRef . current >= maxScrollRef . current );
if ( scrollRef . current < maxScrollRef . current && speedRef . current > 0 ) {
animFrameRef . current = requestAnimationFrame ( tick );
}
};
When users release the button at the bottom:
const stopScroll = () => {
speedRef . current = 0 ;
cancelAnimationFrame ( animFrameRef . current );
// Auto-reset to top when at bottom
if ( scrollRef . current >= maxScrollRef . current ) {
scrollRef . current = 0 ;
setCurrentScroll ( 0 );
setAtBottom ( false );
}
};
The viewer calculates maximum scroll distance based on image height vs. container height, ensuring images smaller than the viewport don’t show scroll controls.
Admin Features
While this component displays projects, they are managed through the admin panel. See Admin Panel for details on:
Adding new projects
Uploading project images
Managing project links
Reordering projects
Editing descriptions and tags