HTML Image gallery with shine hover feature (easy for customer to update, pulls alt text)
1. Create a standard page with all the images you want to feature. Currently works best if divisible by 4.
2. Add the HTML code below to a page and replace the example URL with the URL of the page you just created.
NOTE: the URL that feeds the photos has to be consistent with the main domain... so youll need to update the URL when launching or updating the main domain.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Scoped Image Gallery with Modal Navigation</title>
<style>
/* Visually hidden class for screen readers */
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
border: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
}
/* Gallery styles */
.gallery-container * {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.gallery-container {
background: #fff;
padding: 20px;
font-family: Arial, sans-serif;
}
.gallery-container .gallery {
display: flex;
flex-wrap: wrap;
gap: 10px;
justify-content: center;
}
.gallery-container .gallery-item {
position: relative;
width: calc(25% - 10px); /* Adjust columns as desired */
height: 250px; /* Fixed height; images will crop */
overflow: hidden;
cursor: pointer;
}
.gallery-container .gallery-item img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.5s ease;
}
.gallery-container .gallery-item::after {
content: "";
position: absolute;
top: 0;
left: -100%;
width: 50%;
height: 100%;
background: rgba(255, 255, 255, 0.3);
transform: skewX(-25deg);
transition: left 0.5s ease;
}
.gallery-container .gallery-item:hover img {
transform: scale(1.1);
}
.gallery-container .gallery-item:hover::after {
left: 100%;
}
/* Modal overlay styles */
.modal {
display: none; /* hidden by default */
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0, 0, 0, 0.8);
}
.modal-content {
margin: 5% auto;
display: block;
max-width: 90%;
max-height: 80%;
}
.modal-content:focus {
outline: none;
}
.close-button {
position: absolute;
top: 20px;
right: 30px;
color: #fff;
font-size: 30px;
font-weight: bold;
background: transparent;
border: none;
cursor: pointer;
}
.close-button:focus {
outline: 2px solid #fff;
}
/* Navigation arrow buttons */
.nav-button {
position: absolute;
top: 50%;
transform: translateY(-50%);
color: #fff;
font-size: 40px;
background: transparent;
border: none;
cursor: pointer;
padding: 10px;
}
.nav-button:focus {
outline: 2px solid #fff;
}
.nav-left {
left: 20px;
}
.nav-right {
right: 20px;
}
</style>
</head>
<body>
<div class="gallery-container">
<div class="gallery" id="gallery"></div>
</div>
<!-- Modal for full screen view -->
<div id="modal" class="modal" role="dialog" aria-modal="true" aria-labelledby="modalTitle">
<h2 id="modalTitle" class="visually-hidden">Image preview</h2>
<button class="close-button" id="closeModal" aria-label="Close modal">×</button>
<button class="nav-button nav-left" id="prevBtn" aria-label="Previous image">❮</button>
<button class="nav-button nav-right" id="nextBtn" aria-label="Next image">❯</button>
<img class="modal-content" id="modalImg" tabindex="0" alt="">
</div>
<script>
let lastFocusedElement; // to restore focus after closing modal
let galleryImages = []; // global array to store images
let currentIndex = 0; // track the current image index
// Opens modal with the image at the given index
function openModal(index) {
const modal = document.getElementById("modal");
const modalImg = document.getElementById("modalImg");
// Store the element that triggered the modal
lastFocusedElement = document.activeElement;
currentIndex = index;
modalImg.src = galleryImages[currentIndex].src;
modalImg.alt = galleryImages[currentIndex].alt;
modal.style.display = "block";
modalImg.focus();
}
// Update modal content with currentIndex
function updateModal() {
const modalImg = document.getElementById("modalImg");
modalImg.src = galleryImages[currentIndex].src;
modalImg.alt = galleryImages[currentIndex].alt;
}
// Closes the modal and restores focus
function closeModal() {
const modal = document.getElementById("modal");
modal.style.display = "none";
if (lastFocusedElement) {
lastFocusedElement.focus();
}
}
// Navigate to the previous image
function showPrevious() {
currentIndex = (currentIndex - 1 + galleryImages.length) % galleryImages.length;
updateModal();
}
// Navigate to the next image
function showNext() {
currentIndex = (currentIndex + 1) % galleryImages.length;
updateModal();
}
// Close modal when clicking the close button
document.getElementById("closeModal").addEventListener("click", closeModal);
// Close modal when clicking outside the modal content (but not on navigation buttons)
document.getElementById("modal").addEventListener("click", function(e) {
if (e.target === this) {
closeModal();
}
});
// Event listeners for navigation buttons
document.getElementById("prevBtn").addEventListener("click", function(e) {
e.stopPropagation();
showPrevious();
});
document.getElementById("nextBtn").addEventListener("click", function(e) {
e.stopPropagation();
showNext();
});
// Keyboard accessibility for modal
document.addEventListener("keydown", function(e) {
const modal = document.getElementById("modal");
if (modal.style.display === "block") {
if (e.key === "Escape") {
closeModal();
} else if (e.key === "ArrowLeft") {
showPrevious();
} else if (e.key === "ArrowRight") {
showNext();
}
}
});
// Load gallery images and set up click/keyboard events
function loadGallery() {
fetch("https://izzysworld.specialdistrict.org/bucketlist-gallery-attempt")
.then(response => response.text())
.then(html => {
const parser = new DOMParser();
const doc = parser.parseFromString(html, "text/html");
// Find the container that holds <figure> or <img> elements
const container =
doc.querySelector("#poc > div") ||
doc.querySelector("#poc") ||
doc.querySelector(".body") ||
doc;
// Look for <figure> elements
const figures = container.querySelectorAll("figure");
console.log("Found", figures.length, "figures.");
// Build an array of objects: { src, alt }
galleryImages = Array.from(figures).map(figure => {
const img = figure.querySelector("img");
if (!img) return null;
const src = img.src;
const alt = img.getAttribute("alt") || "Gallery image";
return { src, alt };
}).filter(Boolean);
return galleryImages;
})
.then(images => {
console.log("Extracted images:", images);
const gallery = document.getElementById("gallery");
images.forEach((image, index) => {
const item = document.createElement("div");
item.className = "gallery-item";
// Make each item keyboard-focusable
item.setAttribute("tabindex", "0");
const img = document.createElement("img");
img.src = image.src;
img.alt = image.alt;
// Open modal on click or Enter key press
item.addEventListener("click", function() {
openModal(index);
});
item.addEventListener("keydown", function(e) {
if (e.key === "Enter") {
openModal(index);
}
});
item.appendChild(img);
gallery.appendChild(item);
});
})
.catch(error => {
console.error("Error loading gallery:", error);
});
}
loadGallery();
</script>
</body>
</html>
https://izzysworld.specialdistrict.org/amplify-homepage-preview