Spaces:
Running
Running
| /** | |
| * 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">×</button> | |
| <div class="lightbox-nav"> | |
| <button class="lightbox-prev">❮</button> | |
| <button class="lightbox-next">❯</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); |