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:
HTML Structure
Semantic markup defining the grid, items, and flip card containers
CSS Styling
3D transforms, animations, and responsive grid layouts
JavaScript Interactions
Click handlers to toggle flip states and manage active cards
HTML Structure
Gallery Container
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 >
Large Gallery Items
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 ( 280 px , 1 fr ));
gap : 20 px ;
max-width : 1400 px ;
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
Gallery Item Styling
Base styling for each gallery item from style.css:1365-1373:
Base Item (style.css:1365-1373)
Large Item Layout (style.css:1375-1384)
Image Scaling (style.css:1386-1395)
.galeria-item {
position : relative ;
overflow : hidden ;
border-radius : 15 px ;
aspect-ratio : 1 ;
cursor : pointer ;
box-shadow : 0 10 px 30 px rgba ( 0 , 0 , 0 , 0.15 );
transition : all 0.4 s ease ;
}
Overlay Effect
The overlay appears on hover before clicking from style.css:1398-1422:
Overlay Structure
Overlay Hover
Overlay Text
.galeria-overlay {
position : absolute ;
inset : 0 ;
background : linear-gradient ( 135 deg , 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.4 s ease ;
padding : 20 px ;
}
.galeria-item:hover .galeria-overlay {
opacity : 1 ;
}
.galeria-overlay p {
color : white ;
font-size : 1.3 rem ;
font-weight : bold ;
text-shadow : 2 px 2 px 4 px rgba ( 0 , 0 , 0 , 0.3 );
text-align : center ;
margin : 0 ;
line-height : 1.4 ;
}
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.6 s ;
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 : 1000 px ;
}
.galeria-flip-card {
perspective : 1000 px ;
}
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 ( 180 deg );
}
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 ( 180 deg );
background : linear-gradient ( 135 deg , var ( --azul-siloe ), var ( --rosa-vibrante ));
display : flex ;
align-items : center ;
justify-content : center ;
padding : 20 px ;
border-radius : 15 px ;
overflow : hidden ;
}
How it works:
Initially rotated 180deg (facing away)
Hidden due to backface-visibility: hidden
When card flips 180deg, back face ends up at 0deg (facing viewer)
Front face ends up at 180deg (hidden)
Story Content Styling
The text content on the back face from style.css:1479-1518:
Container (style.css:1479-1491)
Title (style.css:1493-1499)
Text Content (style.css:1501-1510)
Author (style.css:1512-1518)
.galeria-historia {
text-align : center ;
color : white ;
height : 100 % ;
display : flex ;
flex-direction : column ;
justify-content : center ;
align-items : center ;
gap : 12 px ;
padding : 15 px ;
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
Select all flip cards
const flipCards = document . querySelectorAll ( '.galeria-flip-card' );
Gets all elements with class .galeria-flip-card across all gallery sections.
Track current flipped card
let currentFlipped = null ;
Stores reference to the currently flipped card to ensure only one is flipped at a time.
Add click listeners
flipCards . forEach ( card => {
card . addEventListener ( 'click' , function () {
// Handler logic
});
});
Attaches click event to each card.
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.
Toggle current card
this . classList . toggle ( 'flipped' );
Adds .flipped class if not present, removes it if present.
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:
Active Section (style.css:85-92)
Inactive Section (style.css:95-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' });
}
Gallery Variants
The gallery system has three visual variants:
Volunteer Gallery
Default styling with blue-pink gradient.
Clown Gallery
Warm color scheme from style.css:1424-1431:
.galeria-clow {
background : linear-gradient ( 135 deg , #fff4e6 , #ffe8cc );
}
.galeria-clow h2 {
color : var ( --rosa-vibrante );
}
Partnership Gallery
Professional blue-purple gradient from style.css:966-972:
.galeria-aliados {
background : linear-gradient ( 135 deg , #e8f4f8 , #f0e8f8 );
}
.galeria-aliados h2 {
color : var ( --azul-siloe );
}
Adding New Gallery Items
Follow this complete workflow:
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
Upload image
Place image in the appropriate folder:
Volunteer: ./img voluntario/
Clown: ./img clow/
Partnership: ./img aliados/
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 >
Customize content
Replace placeholders with your content.
Choose size (optional)
For featured content, add galeria-item-grande class: < div class = "galeria-item galeria-item-grande" >
Insert into gallery
Add your HTML inside the .galeria-grid container in the appropriate section.
Test
Image displays correctly
Overlay appears on hover
Card flips on click
Story is readable
Only one card flips at a time
Troubleshooting
Card doesn't flip on click
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.
Back face shows backwards text
Cause: Missing backface-visibility: hidden on front/back faces.Solution:
Ensure both .galeria-flip-front and .galeria-flip-back have:backface-visibility : hidden;
Multiple cards flip at once
Cause: JavaScript currentFlipped tracking is broken.Solution:
Verify the click handler logic checks currentFlipped before toggling.
Gallery items don't align properly
Cause: Grid container not properly configured.Solution:
Ensure parent has .galeria-grid class with proper CSS:display: grid;
grid-template-columns : repeat( auto-fit , minmax(280px, 1fr));
Story text overflows card
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.
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:
Feature Chrome Firefox Safari Edge CSS Grid 57+ 52+ 10.1+ 16+ 3D Transforms 36+ 16+ 9+ 12+ Flexbox 29+ 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.