Gallery
Responsive screenshot and thumbnail grids with subtle lift and optional caption overlays. This pattern was extracted from CV project screenshots and generalized for product galleries.
Source file: styles/components/gallery.css
Base Gallery
Use .gallery for the grid and .gallery-item for each tile.
The component enforces a consistent 16:10 image ratio and token-based surface styling.
<div class="gallery">
<div class="gallery-item">
<img src="screenshot-1.png" alt="Dashboard overview" />
</div>
<div class="gallery-item">
<img src="screenshot-2.png" alt="Subscription list" />
</div>
</div>
Caption Overlay
Add an optional .gallery-item-caption to reveal context on hover.
Useful for project names, environments, or release tags.
<div class="gallery-item">
<img src="project-shot.png" alt="Project screenshot" />
<span class="gallery-item-caption">Project screenshot</span>
</div>
Resume-Style Project Entry
The CV uses a project entry block with a title/date row, summary text, and a gallery grid of
<figure> tiles. This variant includes both screenshot figures and a Mermaid
diagram figure, each with persistent <figcaption> labels.
Full-stack monitoring surface with virtualized tables, URL-synced filters, and reusable component architecture.
<div class="entry-header">
<strong>Deal Monitoring UI</strong>
<span class="entry-date">Jan 2025 - Oct 2025</span>
</div>
<p>Full-stack monitoring surface with virtualized tables...</p>
<div class="gallery">
<figure class="gallery-item gallery-item-figure">
<img src="table-view.png" alt="Virtualized table view" />
<figcaption>Virtualized table view with reusable column config</figcaption>
</figure>
<figure class="gallery-item gallery-item-figure gallery-item-diagram">
<div class="mermaid">
flowchart LR
A[Data API] --> B[Adapter]
B --> C[View Model]
C --> D[Virtualized Table]
</div>
<figcaption>Data flow diagram used in architecture notes</figcaption>
</figure>
</div>
Gallery + Lightbox Setup
Add data-lightbox-trigger to clickable gallery figures and include a single
lightbox overlay at page root. This mirrors the resume interaction pattern where clicking
a screenshot opens a focused, full-size preview.
<figure
class="gallery-item gallery-item-figure"
data-lightbox-trigger
data-lightbox-src="/assets/deal-monitoring-table.png"
data-lightbox-alt="Deal Monitoring table view"
>
<img src="/assets/deal-monitoring-table-thumb.png" alt="Deal Monitoring table view" />
<figcaption>Click to open full-size table view</figcaption>
</figure>
<div class="gallery-lightbox-overlay" data-gallery-lightbox aria-hidden="true">
<button class="gallery-lightbox-close" type="button" aria-label="Close lightbox">×</button>
<img class="gallery-lightbox-img" src="" alt="" />
</div>
const overlay = document.querySelector('[data-gallery-lightbox]');
const lightboxImg = overlay.querySelector('.gallery-lightbox-img');
document.querySelectorAll('[data-lightbox-trigger]').forEach((node) => {
node.addEventListener('click', () => {
lightboxImg.src = node.dataset.lightboxSrc;
lightboxImg.alt = node.dataset.lightboxAlt || '';
overlay.classList.add('active');
overlay.setAttribute('aria-hidden', 'false');
});
});
overlay.addEventListener('click', (event) => {
if (event.target === overlay || event.target.closest('.gallery-lightbox-close')) {
overlay.classList.remove('active');
overlay.setAttribute('aria-hidden', 'true');
}
});
Paginated Lightbox Setup
For project sets, group figures with data-lightbox-group so the lightbox can
paginate between items using previous/next controls.
<figure
class="gallery-item gallery-item-figure"
data-lightbox-trigger
data-lightbox-group="deal-monitoring"
data-lightbox-src="/assets/deal-table.png"
data-lightbox-alt="Deal Monitoring table view"
>
<img src="/assets/deal-table-thumb.png" alt="Deal Monitoring table view" />
<figcaption>Slide 1 · Table overview</figcaption>
</figure>
<div class="gallery-lightbox-overlay" data-gallery-lightbox-paginated aria-hidden="true">
<button class="gallery-lightbox-close" type="button" aria-label="Close lightbox">×</button>
<button class="gallery-lightbox-nav gallery-lightbox-nav-prev" type="button" aria-label="Previous image">‹</button>
<img class="gallery-lightbox-img" src="" alt="" />
<button class="gallery-lightbox-nav gallery-lightbox-nav-next" type="button" aria-label="Next image">›</button>
<div class="gallery-lightbox-status" data-gallery-lightbox-status>1 / 1</div>
</div>
const overlay = document.querySelector('[data-gallery-lightbox-paginated]');
const img = overlay.querySelector('.gallery-lightbox-img');
const status = overlay.querySelector('[data-gallery-lightbox-status]');
const group = [...document.querySelectorAll('[data-lightbox-group="deal-monitoring"]')];
let index = 0;
function render() {
const node = group[index];
img.src = node.dataset.lightboxSrc;
img.alt = node.dataset.lightboxAlt || '';
status.textContent = `${index + 1} / ${group.length}`;
}
overlay.querySelector('.gallery-lightbox-nav-prev').addEventListener('click', () => {
index = (index - 1 + group.length) % group.length;
render();
});
overlay.querySelector('.gallery-lightbox-nav-next').addEventListener('click', () => {
index = (index + 1) % group.length;
render();
});
Placeholder Image Design
Use .gallery-placeholder inside .gallery-item to represent missing
screenshots without breaking layout rhythm. This keeps spacing and visual weight consistent
while teams wait for final assets.
Screenshot Pending
Asset In Review
Final Export Needed
<div class="gallery-item">
<div class="gallery-placeholder">
<div class="gallery-placeholder-content">
<p class="gallery-placeholder-title">Screenshot Pending</p>
<p class="gallery-placeholder-meta">Dashboard · Desktop</p>
</div>
</div>
<span class="gallery-item-caption">Planned view</span>
</div>