pharma-see / components /image-gallery.js
steake's picture
improve prompt engineering for images
ca7232a verified
/**
* Image Gallery Web Component
* Displays a gallery of pharmaceutical products with lightbox functionality
*/
class ImageGallery extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.images = [];
this.currentIndex = 0;
}
connectedCallback() {
// Parse images from data attribute or children
const imagesData = this.getAttribute('data-images');
if (imagesData) {
try {
this.images = JSON.parse(imagesData);
} catch (e) {
console.error('Failed to parse images data', e);
}
}
// If no images data attribute, look for child elements
if (this.images.length === 0) {
this.querySelectorAll('img').forEach(img => {
this.images.push({
src: img.src,
alt: img.alt,
title: img.title || img.alt
});
});
}
this.render();
}
render() {
const columns = this.getAttribute('columns') || '4';
const gap = this.getAttribute('gap') || '4';
const showCaptions = this.hasAttribute('show-captions');
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
width: 100%;
}
.gallery-container {
display: grid;
grid-template-columns: repeat(${columns}, 1fr);
gap: ${gap}px;
margin: 0 auto;
}
@media (max-width: 1024px) {
.gallery-container {
grid-template-columns: repeat(3, 1fr);
}
}
@media (max-width: 768px) {
.gallery-container {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 480px) {
.gallery-container {
grid-template-columns: 1fr;
}
}
.gallery-item {
position: relative;
cursor: pointer;
border-radius: 8px;
overflow: hidden;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.gallery-item:hover {
transform: translateY(-4px);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
}
.gallery-image {
width: 100%;
height: 200px;
object-fit: cover;
display: block;
}
.image-caption {
padding: 12px;
background: white;
text-align: center;
font-size: 14px;
color: #333;
}
/* Lightbox styles */
.lightbox {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.9);
z-index: 1000;
align-items: center;
justify-content: center;
}
.lightbox.active {
display: flex;
}
.lightbox-content {
position: relative;
max-width: 90%;
max-height: 90%;
}
.lightbox-image {
max-width: 100%;
max-height: 80vh;
object-fit: contain;
}
.lightbox-caption {
color: white;
text-align: center;
padding: 15px;
font-size: 18px;
}
.lightbox-close {
position: absolute;
top: 20px;
right: 20px;
background: none;
border: none;
color: white;
font-size: 30px;
cursor: pointer;
z-index: 1001;
}
.lightbox-nav {
position: absolute;
top: 50%;
width: 100%;
display: flex;
justify-content: space-between;
transform: translateY(-50%);
padding: 0 20px;
}
.lightbox-prev,
.lightbox-next {
background: rgba(255, 255, 255, 0.2);
border: none;
color: white;
width: 50px;
height: 50px;
border-radius: 50%;
font-size: 24px;
cursor: pointer;
transition: background 0.3s;
}
.lightbox-prev:hover,
.lightbox-next:hover {
background: rgba(255, 255, 255, 0.4);
}
</style>
<div class="gallery-container">
${this.images.map((image, index) => `
<div class="gallery-item" data-index="${index}">
<optimized-image
src="${image.src}"
alt="${image.alt}"
height="200px"
lazy
class="gallery-image"
></optimized-image>
${showCaptions ? `<div class="image-caption">${image.title || image.alt}</div>` : ''}
</div>
`).join('')}
</div>
<!-- Lightbox -->
<div class="lightbox">
<button class="lightbox-close">&times;</button>
<div class="lightbox-nav">
<button class="lightbox-prev">&#10094;</button>
<button class="lightbox-next">&#10095;</button>
</div>
<div class="lightbox-content">
<img class="lightbox-image" src="" alt="">
<div class="lightbox-caption"></div>
</div>
</div>
`;
// Add event listeners
this.addEventListeners();
}
addEventListeners() {
const lightbox = this.shadowRoot.querySelector('.lightbox');
const lightboxImage = this.shadowRoot.querySelector('.lightbox-image');
const lightboxCaption = this.shadowRoot.querySelector('.lightbox-caption');
const closeBtn = this.shadowRoot.querySelector('.lightbox-close');
const prevBtn = this.shadowRoot.querySelector('.lightbox-prev');
const nextBtn = this.shadowRoot.querySelector('.lightbox-next');
const galleryItems = this.shadowRoot.querySelectorAll('.gallery-item');
// Open lightbox on image click
galleryItems.forEach(item => {
item.addEventListener('click', () => {
this.currentIndex = parseInt(item.dataset.index);
this.updateLightbox();
lightbox.classList.add('active');
document.body.style.overflow = 'hidden';
});
});
// Close lightbox
closeBtn.addEventListener('click', () => {
lightbox.classList.remove('active');
document.body.style.overflow = 'auto';
});
// Lightbox navigation
prevBtn.addEventListener('click', () => {
this.currentIndex = (this.currentIndex - 1 + this.images.length) % this.images.length;
this.updateLightbox();
});
nextBtn.addEventListener('click', () => {
this.currentIndex = (this.currentIndex + 1) % this.images.length;
this.updateLightbox();
});
// Keyboard navigation
document.addEventListener('keydown', (e) => {
if (!lightbox.classList.contains('active')) return;
switch (e.key) {
case 'Escape':
lightbox.classList.remove('active');
document.body.style.overflow = 'auto';
break;
case 'ArrowLeft':
this.currentIndex = (this.currentIndex - 1 + this.images.length) % this.images.length;
this.updateLightbox();
break;
case 'ArrowRight':
this.currentIndex = (this.currentIndex + 1) % this.images.length;
this.updateLightbox();
break;
}
});
// Close on background click
lightbox.addEventListener('click', (e) => {
if (e.target === lightbox) {
lightbox.classList.remove('active');
document.body.style.overflow = 'auto';
}
});
}
updateLightbox() {
const lightboxImage = this.shadowRoot.querySelector('.lightbox-image');
const lightboxCaption = this.shadowRoot.querySelector('.lightbox-caption');
const image = this.images[this.currentIndex];
lightboxImage.src = image.src;
lightboxImage.alt = image.alt;
lightboxCaption.textContent = image.title || image.alt;
}
}
customElements.define('image-gallery', ImageGallery);