Skip to main content
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

  1. Grid View: Projects are displayed in a 2-column responsive grid
  2. Hover Effects: Cards lift and highlight on hover with gradient overlay
  3. Quick Preview: Hover over project images to reveal “View Full” button
  4. 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:
  1. Click eye icon on any project image
  2. Hold down scroll button to navigate
  3. Release button to stop scrolling
  4. When reaching bottom, releasing button auto-scrolls to top
  5. 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>

3. Technology Tags

<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>

4. Action Buttons

<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>

Scroll Animations

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

Auto-Scroll Implementation

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);
  }
};

Scroll Reset

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

Build docs developers (and LLMs) love