Skip to main content

Overview

The interactive gallery system is a core feature used across the Volunteer Program, Hospital Clown, and Partnerships sections. It implements 3D flip cards that rotate on click to reveal inspirational stories and messages.

System Architecture

The gallery system consists of three layers:
1

HTML Structure

Semantic markup defining the grid, items, and flip card containers
2

CSS Styling

3D transforms, animations, and responsive grid layouts
3

JavaScript Interactions

Click handlers to toggle flip states and manage active cards

HTML Structure

The gallery uses a responsive grid layout from index.html:62-176:
<section class="galeria-impacto">
    <h2>Historias que Transforman Vidas</h2>
    <p class="galeria-subtitulo">Cada momento cuenta. Cada sonrisa importa.</p>
    
    <div class="galeria-grid">
        <!-- Gallery items go here -->
    </div>
</section>

Flip Card Item Structure

Each gallery item contains a complete flip card:
<div class="galeria-item">
    <div class="galeria-flip-card">
        <!-- Front Face -->
        <div class="galeria-flip-front">
            <img src="./img voluntario/img2.png" alt="Voluntarios ayudando">
            <div class="galeria-overlay">
                <p>Juntos Somos Más Fuertes</p>
            </div>
        </div>
        
        <!-- Back Face -->
        <div class="galeria-flip-back">
            <div class="galeria-historia">
                <p class="historia-titulo">🤝 Comunidad</p>
                <p class="historia-texto">"Un voluntario es fuerte, pero una comunidad de voluntarios es invencible..."</p>
                <p class="historia-autor">— Misión Siloé</p>
            </div>
        </div>
    </div>
</div>
For featured content, add the galeria-item-grande class:
<div class="galeria-item galeria-item-grande">
    <!-- Same flip card structure -->
</div>
On desktop (≥900px), large items span 2 columns and 2 rows. On mobile, they display the same size as regular items.

CSS Implementation

Grid Layout

The gallery grid from style.css:1356-1362:
.galeria-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
    gap: 20px;
    max-width: 1400px;
    margin: 0 auto;
}
How it works:
  • auto-fit: Creates as many columns as will fit
  • minmax(280px, 1fr): Minimum 280px width, grows to fill available space
  • gap: 20px: 20px spacing between items
  • max-width: 1400px: Container max width for large screens
Base styling for each gallery item from style.css:1365-1373:
.galeria-item {
    position: relative;
    overflow: hidden;
    border-radius: 15px;
    aspect-ratio: 1;
    cursor: pointer;
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
    transition: all 0.4s ease;
}

Overlay Effect

The overlay appears on hover before clicking from style.css:1398-1422:
style.css:1398-1408
.galeria-overlay {
    position: absolute;
    inset: 0;
    background: linear-gradient(135deg, rgba(41, 171, 226, 0.85), rgba(255, 107, 180, 0.85));
    display: flex;
    align-items: center;
    justify-content: center;
    opacity: 0;
    transition: opacity 0.4s ease;
    padding: 20px;
}
The overlay uses inset: 0 which is shorthand for top: 0; right: 0; bottom: 0; left: 0, positioning it to cover the entire image.

3D Flip Card CSS

The flip card uses CSS 3D transforms for the rotation effect.

Flip Card Container

The main flip container from style.css:1440-1447:
.galeria-flip-card {
    width: 100%;
    height: 100%;
    position: relative;
    transition: transform 0.6s;
    transform-style: preserve-3d;
    cursor: pointer;
}
Key properties:
  • transform-style: preserve-3d: Enables 3D space for child elements
  • transition: transform 0.6s: Smooth 0.6-second rotation
  • position: relative: Positions front/back faces absolutely within

Perspective Setup

Perspective for depth perception from style.css:1521-1527:
.galeria-item {
    perspective: 1000px;
}

.galeria-flip-card {
    perspective: 1000px;
}
The perspective property is crucial for 3D effects. A value of 1000px creates a moderate depth effect. Lower values (e.g., 500px) create more dramatic perspective.

Flipped State

When the .flipped class is added via JavaScript from style.css:1450-1452:
.galeria-flip-card.flipped {
    transform: rotateY(180deg);
}
This rotates the entire card 180 degrees around the Y-axis (vertical axis).

Front Face

The front face styling from style.css:1455-1460:
.galeria-flip-front {
    width: 100%;
    height: 100%;
    position: absolute;
    backface-visibility: hidden;
}
backface-visibility: hidden is critical - it hides the front face when rotated away from the viewer.

Back Face

The back face is pre-rotated 180 degrees from style.css:1463-1476:
.galeria-flip-back {
    width: 100%;
    height: 100%;
    position: absolute;
    backface-visibility: hidden;
    transform: rotateY(180deg);
    background: linear-gradient(135deg, var(--azul-siloe), var(--rosa-vibrante));
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 20px;
    border-radius: 15px;
    overflow: hidden;
}
How it works:
  1. Initially rotated 180deg (facing away)
  2. Hidden due to backface-visibility: hidden
  3. When card flips 180deg, back face ends up at 0deg (facing viewer)
  4. Front face ends up at 180deg (hidden)

Story Content Styling

The text content on the back face from style.css:1479-1518:
.galeria-historia {
    text-align: center;
    color: white;
    height: 100%;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    gap: 12px;
    padding: 15px;
    overflow: hidden;
    max-height: 100%;
}
The flexbox layout with flex-grow: 1 on .historia-texto allows the main text to expand and become scrollable if needed, while keeping the title and author fixed in size.

JavaScript Implementation

The flip interaction is controlled by JavaScript from index.html:831-854:

Complete Code

// Select all flip cards
const flipCards = document.querySelectorAll('.galeria-flip-card');
let currentFlipped = null; // Variable to track currently flipped card

// Add event listener to each card
flipCards.forEach(card => {
    card.addEventListener('click', function() {
        // If there's a card already flipped and it's different from this one
        if (currentFlipped && currentFlipped !== this) {
            // Flip the previous one back
            currentFlipped.classList.remove('flipped');
        }
        
        // Toggle the 'flipped' class on the current card
        this.classList.toggle('flipped');
        
        // Update the reference to the flipped card
        if (this.classList.contains('flipped')) {
            currentFlipped = this;
        } else {
            currentFlipped = null;
        }
    });
});

Code Breakdown

1

Select all flip cards

const flipCards = document.querySelectorAll('.galeria-flip-card');
Gets all elements with class .galeria-flip-card across all gallery sections.
2

Track current flipped card

let currentFlipped = null;
Stores reference to the currently flipped card to ensure only one is flipped at a time.
3

Add click listeners

flipCards.forEach(card => {
    card.addEventListener('click', function() {
        // Handler logic
    });
});
Attaches click event to each card.
4

Flip back previous card

if (currentFlipped && currentFlipped !== this) {
    currentFlipped.classList.remove('flipped');
}
If another card is flipped, flip it back before flipping the new one.
5

Toggle current card

this.classList.toggle('flipped');
Adds .flipped class if not present, removes it if present.
6

Update tracker

if (this.classList.contains('flipped')) {
    currentFlipped = this;
} else {
    currentFlipped = null;
}
Updates the currentFlipped reference for the next click.

Interaction Flow

Section Toggling

The gallery sections are shown/hidden using a navigation system. From index.html:22-37, the navigation buttons:
<div class="nav-buttons">
    <button id="btn-nosotros" class="nav-btn nav-btn-active">
        ℹ️ Nosotros
    </button>
    <button id="btn-voluntarios" class="nav-btn">
        👥 Voluntariado Siloé
    </button>
    <button id="btn-clow" class="nav-btn">
        🤡 Clow de Siloé
    </button>
    <button id="btn-aliados" class="nav-btn">
        🤝 Nuestros Aliados
    </button>
</div>

Section Visibility Classes

Sections are toggled using these classes from style.css:84-102:
#seccion-voluntariado.seccion-activa,
#seccion-clow.seccion-activa,
#seccion-aliados.seccion-activa,
#seccion-nosotros.seccion-activa {
    display: block !important;
    opacity: 1 !important;
    visibility: visible !important;
}

JavaScript Section Switching

From index.html:754-793, the section switching function:
function mostrarSeccion(nombre) {
    console.log('Mostrando sección:', nombre);
    
    // Hide all sections
    seccionVoluntariado.classList.remove('seccion-activa');
    seccionVoluntariado.classList.add('seccion-inactiva');
    seccionClow.classList.remove('seccion-activa');
    seccionClow.classList.add('seccion-inactiva');
    seccionAliados.classList.remove('seccion-activa');
    seccionAliados.classList.add('seccion-inactiva');
    seccionNosotros.classList.remove('seccion-activa');
    seccionNosotros.classList.add('seccion-inactiva');
    
    // Remove active state from all buttons
    btnVoluntarios.classList.remove('nav-btn-active');
    btnClow.classList.remove('nav-btn-active');
    btnAliados.classList.remove('nav-btn-active');
    btnNosotros.classList.remove('nav-btn-active');
    
    // Show selected section
    if (nombre === 'voluntarios') {
        seccionVoluntariado.classList.remove('seccion-inactiva');
        seccionVoluntariado.classList.add('seccion-activa');
        btnVoluntarios.classList.add('nav-btn-active');
    } else if (nombre === 'clow') {
        seccionClow.classList.remove('seccion-inactiva');
        seccionClow.classList.add('seccion-activa');
        btnClow.classList.add('nav-btn-active');
    } else if (nombre === 'aliados') {
        seccionAliados.classList.remove('seccion-inactiva');
        seccionAliados.classList.add('seccion-activa');
        btnAliados.classList.add('nav-btn-active');
    } else if (nombre === 'nosotros') {
        seccionNosotros.classList.remove('seccion-inactiva');
        seccionNosotros.classList.add('seccion-activa');
        btnNosotros.classList.add('nav-btn-active');
    }
    
    // Scroll to top smoothly
    window.scrollTo({ top: 0, behavior: 'smooth' });
}
The gallery system has three visual variants: Default styling with blue-pink gradient. Warm color scheme from style.css:1424-1431:
.galeria-clow {
    background: linear-gradient(135deg, #fff4e6, #ffe8cc);
}

.galeria-clow h2 {
    color: var(--rosa-vibrante);
}
Professional blue-purple gradient from style.css:966-972:
.galeria-aliados {
    background: linear-gradient(135deg, #e8f4f8, #f0e8f8);
}

.galeria-aliados h2 {
    color: var(--azul-siloe);
}
Follow this complete workflow:
1

Prepare your content

Gather:
  • Image file (recommended: 800x800px, JPG or PNG)
  • Overlay title (short, 2-5 words)
  • Story emoji and title
  • Story text (keep under 100 words)
  • Author attribution
2

Upload image

Place image in the appropriate folder:
  • Volunteer: ./img voluntario/
  • Clown: ./img clow/
  • Partnership: ./img aliados/
3

Copy template code

Use this template:
<div class="galeria-item">
    <div class="galeria-flip-card">
        <div class="galeria-flip-front">
            <img src="./img voluntario/your-image.jpg" alt="Description">
            <div class="galeria-overlay">
                <p>Your Title</p>
            </div>
        </div>
        <div class="galeria-flip-back">
            <div class="galeria-historia">
                <p class="historia-titulo">🎯 Category</p>
                <p class="historia-texto">"Your inspiring story..."</p>
                <p class="historia-autor">— Author</p>
            </div>
        </div>
    </div>
</div>
4

Customize content

Replace placeholders with your content.
5

Choose size (optional)

For featured content, add galeria-item-grande class:
<div class="galeria-item galeria-item-grande">
6

Insert into gallery

Add your HTML inside the .galeria-grid container in the appropriate section.
7

Test

  • Image displays correctly
  • Overlay appears on hover
  • Card flips on click
  • Story is readable
  • Only one card flips at a time

Troubleshooting

Possible causes:
  • Missing .galeria-flip-card class on the card container
  • JavaScript not loaded (check browser console for errors)
  • CSS transform-style: preserve-3d not applied
Solution: Verify all classes are present and JavaScript is running after DOM loads.
Cause: Missing backface-visibility: hidden on front/back faces.Solution: Ensure both .galeria-flip-front and .galeria-flip-back have:
backface-visibility: hidden;
Cause: JavaScript currentFlipped tracking is broken.Solution: Verify the click handler logic checks currentFlipped before toggling.
Cause: Text content too long for card height.Solution: The .historia-texto has overflow-y: auto to enable scrolling. Keep text under 100 words for best UX.

Performance Considerations

Image Optimization

Compress images to 800x800px or smaller to reduce load time. Use JPG for photos, PNG for graphics with transparency.

CSS Transitions

The 0.6s transition duration provides smooth animation without feeling sluggish. Avoid durations over 1 second.

JavaScript Efficiency

Using querySelectorAll once and storing references is more efficient than repeated DOM queries.

Mobile Performance

3D transforms are GPU-accelerated on most devices, providing smooth animations even on mobile.

Browser Compatibility

The gallery system uses modern CSS features:
FeatureChromeFirefoxSafariEdge
CSS Grid57+52+10.1+16+
3D Transforms36+16+9+12+
Flexbox29+28+9+12+
backface-visibility36+16+9+12+
All features are supported in modern browsers (2017+). For legacy browser support, consider adding vendor prefixes using a tool like Autoprefixer.

Build docs developers (and LLMs) love