Compare commits

..

5 commits

Author SHA1 Message Date
a648abe2d0 refactor: streamline sizes array formatting in products.json for consistency and readability 2025-09-17 20:52:22 -07:00
18cf5c8ed3 Implement image enlargement modal and enhance product detail functionality
- Replaced static quantity controls with a modal for image enlargement on product detail and catalog pages.
- Added event listeners for image clicks to trigger modal display with enlarged images.
- Updated product detail page to dynamically load images and descriptions, improving user experience.
- Refactored JavaScript to streamline image handling and modal interactions.
- Enhanced CSS for modal styling and transitions, ensuring a smooth user experience.
2025-09-17 20:52:03 -07:00
6caa3ee6c4 Fix galleryPairs for all 88 products to ensure at least 2 images
- Updated products 1-88 to have minimum 2 images in galleryPairs array
- Fixed empty galleryPairs by copying first 2 images from images array
- Fixed single-image galleryPairs by adding first image from images array
- Ensures carousel functionality works properly for all products
- Special case: Product 84 (Sphere) only had 1 image, copied it twice
2025-09-17 19:56:17 -07:00
d84c6196d1 refactor: update products.json to enhance product galleries with new image pairs for various training tables and screens 2025-09-17 19:43:10 -07:00
7295bcba7e refactor: enhance product detail page with dynamic content loading and improved gallery functionality
- Updated product-detail.html to replace static content with dynamic placeholders for images, title, description, sizes, and colors.
- Modified main.js to implement dynamic loading of product details, including title, description, images, sizes, and colors.
- Improved gallery functionality to support a carousel view for product images, including navigation buttons.
- Adjusted CSS to add new transition durations and easing functions for smoother animations.
2025-09-17 19:40:13 -07:00
7 changed files with 1222 additions and 1046 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 417 KiB

File diff suppressed because it is too large Load diff

View file

@ -164,7 +164,7 @@
<!-- Background Image -->
<div class="absolute inset-0 w-full h-full">
<img
src="assets/images/potty.jpg"
src="assets/images/prod-catalog.jpg"
alt="Product catalog background"
class="w-full h-full object-cover object-center"
style="filter: blur(3px)"

View file

@ -194,71 +194,33 @@
<div
class="flex flex-row md:flex-col gap-3 md:gap-4 w-full md:w-32 overflow-x-auto md:overflow-visible"
>
<div
class="w-32 h-32 bg-floral-white rounded-lg overflow-hidden cursor-pointer"
>
<img
src="assets/images/outdoor_sofa_set.png"
alt="Outdoor sofa set"
class="w-full h-full object-cover"
/>
</div>
<div
class="w-32 h-32 bg-floral-white rounded-lg overflow-hidden cursor-pointer"
>
<img
src="assets/images/outdoor_sofa_set.png"
alt="Outdoor sofa set 2"
class="w-full h-full object-cover"
/>
</div>
<div
class="w-32 h-32 bg-floral-white rounded-lg overflow-hidden cursor-pointer"
>
<img
src="assets/images/outdoor_sofa_set.png"
alt="Stuart sofa"
class="w-full h-full object-cover"
/>
</div>
<div
class="w-32 h-32 bg-floral-white rounded-lg overflow-hidden cursor-pointer"
>
<img
src="assets/images/outdoor_sofa_set.png"
alt="Maya sofa three seater"
class="w-full h-full object-cover"
/>
</div>
<!-- Thumbnail images will be populated dynamically -->
</div>
<!-- Main Product Image -->
<div
class="w-full h-80 md:w-[500px] md:h-[500px] bg-floral-white rounded-lg overflow-hidden"
>
<img
src="assets/images/asgaard_sofa.png"
alt="Asgaard sofa"
class="w-full h-full object-cover"
/>
<!-- Main product image will be populated dynamically -->
</div>
</div>
<!-- Right Section - Product Details -->
<div class="w-full md:w-[500px]">
<!-- Product Title -->
<h1 class="font-playfair font-normal text-4xl text-black mb-8">
Asgaard sofa
<h1
class="font-playfair font-normal text-4xl text-black mb-8"
id="product-title"
>
<!-- Product title will be populated dynamically -->
</h1>
<!-- Product Description -->
<p
class="font-playfair font-normal text-sm text-black mb-12 max-w-md"
id="product-description"
>
Setting the bar as one of the loudest speakers in its class, the
Kilburn is a compact, stout-hearted hero with a well-balanced
audio which boasts a clear midrange and extended highs for a
sound.
<!-- Product description will be populated dynamically -->
</p>
<!-- Size Options -->
@ -269,36 +231,7 @@
Size
</h3>
<div class="grid grid-cols-6 gap-2" id="size-buttons-container">
<button
class="w-8 h-8 bg-uc-gold text-white font-playfair font-normal text-sm rounded flex items-center justify-center size-button"
>
L
</button>
<button
class="w-8 h-8 bg-floral-white text-black font-playfair font-normal text-sm rounded flex items-center justify-center size-button"
>
XL
</button>
<button
class="w-8 h-8 bg-floral-white text-black font-playfair font-normal text-sm rounded flex items-center justify-center size-button"
>
XS
</button>
<button
class="w-8 h-8 bg-floral-white text-black font-playfair font-normal text-sm rounded flex items-center justify-center size-button"
>
S
</button>
<button
class="w-8 h-8 bg-floral-white text-black font-playfair font-normal text-sm rounded flex items-center justify-center size-button"
>
M
</button>
<button
class="w-8 h-8 bg-floral-white text-black font-playfair font-normal text-sm rounded flex items-center justify-center size-button"
>
2XL
</button>
<!-- Size buttons will be populated dynamically -->
</div>
</div>
@ -309,42 +242,13 @@
>
Color
</h3>
<div class="flex gap-4">
<button
class="w-8 h-8 bg-purple-500 rounded-full border-2 border-black"
></button>
<button class="w-8 h-8 bg-black rounded-full"></button>
<button class="w-8 h-8 bg-uc-gold rounded-full"></button>
<div class="flex gap-4" id="color-buttons-container">
<!-- Color buttons will be populated dynamically -->
</div>
</div>
<!-- Quantity and Action Buttons -->
<div class="flex flex-col md:flex-row gap-4 md:gap-6 mb-8">
<!-- Quantity Selector -->
<div
class="inline-flex items-center justify-between w-[180px] h-[64px] min-h-[64px] bg-white border border-quick-silver rounded-[15px] px-4 box-border shadow-sm hover:shadow-md transition-all duration-200"
>
<button
id="qty-decr"
aria-label="Decrease quantity"
class="font-playfair font-light text-[20px] leading-none text-black w-8 h-8 flex items-center justify-center rounded-lg hover:bg-light-bg transition-colors cursor-pointer"
>
-
</button>
<span
id="qty-value"
class="font-playfair font-light text-[20px] leading-none text-black"
>1</span
>
<button
id="qty-incr"
aria-label="Increase quantity"
class="font-playfair font-light text-[20px] leading-none text-black w-8 h-8 flex items-center justify-center rounded-lg hover:bg-light-bg transition-colors cursor-pointer"
>
+
</button>
</div>
<!-- Action Buttons -->
<a
href="contact.html"
@ -365,62 +269,7 @@
<!-- Product Metadata -->
<div class="space-y-4">
<div class="flex items-center">
<span
class="font-playfair font-normal text-base text-quick-silver w-20"
>Model No.</span
>
<span
class="font-playfair font-medium text-base text-quick-silver mx-2"
>:</span
>
<span
class="font-playfair font-normal text-base text-quick-silver"
>SS001</span
>
</div>
<div class="flex items-center">
<span
class="font-playfair font-normal text-base text-quick-silver w-20"
>Category</span
>
<span
class="font-playfair font-medium text-base text-quick-silver mx-2"
>:</span
>
<span
class="font-playfair font-normal text-base text-quick-silver"
>Sofas</span
>
</div>
<div class="flex items-center">
<span
class="font-playfair font-normal text-base text-quick-silver w-20"
>Tags</span
>
<span
class="font-playfair font-medium text-base text-quick-silver mx-2"
>:</span
>
<span
class="font-playfair font-normal text-base text-quick-silver"
>Sofa, Chair, Home, Shop</span
>
</div>
<div class="flex items-center">
<span
class="font-playfair font-normal text-base text-quick-silver w-20"
>Dimension</span
>
<span
class="font-playfair font-medium text-base text-quick-silver mx-2"
>:</span
>
<span
class="font-playfair font-normal text-base text-quick-silver"
>180cm x 85cm x 75cm</span
>
</div>
<!-- Product metadata will be populated dynamically -->
</div>
</div>
</div>
@ -439,45 +288,61 @@
</div>
<!-- Copy -->
<div class="max-w-5xl mx-auto space-y-6">
<p
class="font-playfair text-base leading-6 text-[#333333] text-justify"
<div
class="max-w-5xl mx-auto space-y-6"
id="product-description-content"
>
Embodying the raw, wayward spirit of rock ‘n’ roll, the Kilburn
portable active stereo speaker takes the unmistakable look and
sound of Marshall, unplugs the chords, and takes the show on the
road.
</p>
<p
class="font-playfair text-base leading-6 text-[#333333] text-justify"
>
Weighing in under 7 pounds, the Kilburn is a lightweight piece of
vintage styled engineering. Setting the bar as one of the loudest
speakers in its class, the Kilburn is a compact, stout-hearted
hero with a well-balanced audio which boasts a clear midrange and
extended highs for a sound that is both articulate and pronounced.
The analogue knobs allow you to fine tune the controls to your
personal preferences while the guitar-influenced leather strap
enables easy and stylish travel.
</p>
<!-- Product description content will be populated dynamically -->
</div>
<!-- Images -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-8 mt-10">
<div class="rounded-[10px] bg-floral-white p-6">
<img
src="assets/images/asgaard_sofa.png"
alt="Sofa variant left"
class="w-full h-80 object-contain mx-auto"
/>
</div>
<div class="rounded-[10px] bg-floral-white p-6">
<img
src="assets/images/asgaard_sofa.png"
alt="Sofa variant right"
class="w-full h-80 object-contain mx-auto"
/>
<div class="relative mt-10">
<div
class="grid grid-cols-1 md:grid-cols-2 gap-8"
id="product-gallery-images"
>
<!-- Product gallery images will be populated dynamically -->
</div>
<!-- Back Arrow -->
<button
id="gallery-back-btn"
class="absolute top-1/2 left-0 transform -translate-y-1/2 bg-white border border-gray-300 rounded-full p-3 shadow-lg hover:shadow-xl transition-all duration-200 opacity-0 pointer-events-none"
style="display: none"
>
<svg
class="w-6 h-6 text-gray-600"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M15 19l-7-7 7-7"
></path>
</svg>
</button>
<!-- Forward Arrow -->
<button
id="gallery-next-btn"
class="absolute top-1/2 right-0 transform -translate-y-1/2 bg-white border border-gray-300 rounded-full p-3 shadow-lg hover:shadow-xl transition-all duration-200 opacity-0 pointer-events-none"
style="display: none"
>
<svg
class="w-6 h-6 text-gray-600"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 5l7 7-7 7"
></path>
</svg>
</button>
</div>
</div>
</section>
@ -648,6 +513,45 @@
</div>
</footer>
<!-- Image Enlargement Modal -->
<div
id="image-modal"
class="fixed inset-0 bg-black bg-opacity-90 z-50 hidden flex items-center justify-center p-4 transition-opacity duration-300"
>
<div
class="relative max-w-7xl max-h-full w-full h-full flex items-center justify-center"
>
<!-- Close Button -->
<button
id="modal-close-btn"
class="absolute top-4 right-4 z-10 bg-white bg-opacity-20 hover:bg-opacity-30 text-white rounded-full p-3 transition-all duration-200 backdrop-blur-sm"
aria-label="Close modal"
>
<svg
class="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18L18 6M6 6l12 12"
></path>
</svg>
</button>
<!-- Enlarged Image -->
<img
id="modal-image"
src=""
alt=""
class="w-[95vw] h-[95vh] object-contain rounded-lg shadow-2xl transform transition-transform duration-300 scale-95"
/>
</div>
</div>
<script src="scripts/main.js?v=3.5"></script>
</body>
</html>

View file

@ -55,27 +55,6 @@ function initSite() {
window.addEventListener("scroll", updateActiveLink);
updateActiveLink();
// Quantity controls on product detail page
(function initQuantityControls() {
const decr = document.getElementById("qty-decr");
const incr = document.getElementById("qty-incr");
const valueEl = document.getElementById("qty-value");
if (!decr || !incr || !valueEl) return; // Not on product detail page
function parseQty() {
const n = parseInt(valueEl.textContent || "1", 10);
return Number.isFinite(n) && n > 0 ? n : 1;
}
decr.addEventListener("click", () => {
const next = Math.max(1, parseQty() - 1);
valueEl.textContent = String(next);
});
incr.addEventListener("click", () => {
const next = parseQty() + 1;
valueEl.textContent = String(next);
});
})();
// Related products (dynamic)
(async function initRelated() {
const grid = document.getElementById("related-grid");
@ -222,10 +201,143 @@ function initSite() {
});
});
}
}
// Product detail page functionality
(async function initProductDetail() {
// Image enlargement modal functionality (global)
function addImageEnlargementListeners() {
// Target both main product image AND gallery carousel images
const mainProductImage = document.querySelector(
".w-full.h-80.md\\:w-\\[500px\\].md\\:h-\\[500px\\] img[data-enlarge-src]"
);
const galleryImages = document.querySelectorAll(
"#product-gallery-images img[data-enlarge-src]"
);
const modal = document.getElementById("image-modal");
const modalImage = document.getElementById("modal-image");
const modalCloseBtn = document.getElementById("modal-close-btn");
console.log("Adding image enlargement listeners...");
console.log("Found main product image:", !!mainProductImage);
console.log("Found gallery images:", galleryImages.length);
console.log("Modal elements:", {
modal: !!modal,
modalImage: !!modalImage,
modalCloseBtn: !!modalCloseBtn,
});
if (!modal || !modalImage || !modalCloseBtn) {
console.log("Modal elements not found, skipping image enlargement setup");
return;
}
// Add click listener to main product image
if (mainProductImage) {
console.log(
`Adding click listener to main product image:`,
mainProductImage.src
);
mainProductImage.removeEventListener("click", handleImageClick);
mainProductImage.addEventListener("click", handleImageClick);
} else {
console.log("Main product image not found");
}
// Add click listeners to gallery carousel images
galleryImages.forEach((img, index) => {
console.log(
`Adding click listener to gallery image ${index + 1}:`,
img.src
);
img.removeEventListener("click", handleImageClick);
img.addEventListener("click", handleImageClick);
});
function handleImageClick(e) {
console.log("Image clicked!", this.src);
const imageSrc = this.getAttribute("data-enlarge-src");
const imageAlt = this.getAttribute("alt");
console.log("Opening modal with image:", imageSrc);
modalImage.src = imageSrc;
modalImage.alt = imageAlt;
// Show modal with animation
modal.classList.remove("hidden");
document.body.style.overflow = "hidden"; // Prevent background scrolling
// Trigger animation after a brief delay
setTimeout(() => {
modalImage.classList.remove("scale-95");
modalImage.classList.add("scale-100");
}, 10);
}
// Close modal functionality
function closeModal() {
// Animate out
modalImage.classList.remove("scale-100");
modalImage.classList.add("scale-95");
// Hide modal after animation
setTimeout(() => {
modal.classList.add("hidden");
document.body.style.overflow = ""; // Restore scrolling
}, 300);
}
modalCloseBtn.addEventListener("click", closeModal);
// Close modal when clicking outside the image
modal.addEventListener("click", function (e) {
if (e.target === modal) {
closeModal();
}
});
// Close modal with Escape key
document.addEventListener("keydown", function (e) {
if (e.key === "Escape" && !modal.classList.contains("hidden")) {
closeModal();
}
});
}
// Thumbnail click functionality (global)
function addThumbnailClickListeners() {
const thumbnails = document.querySelectorAll("[data-thumbnail-src]");
const mainImageContainer = document.querySelector(
".w-full.h-80.md\\:w-\\[500px\\].md\\:h-\\[500px\\]"
);
console.log("Adding thumbnail click listeners...");
console.log("Found thumbnails:", thumbnails.length);
console.log("Main image container:", !!mainImageContainer);
if (!mainImageContainer) {
console.log("Main image container not found");
return;
}
thumbnails.forEach((thumbnail, index) => {
thumbnail.addEventListener("click", function () {
const imageSrc = this.getAttribute("data-thumbnail-src");
console.log(`Thumbnail ${index + 1} clicked, switching to:`, imageSrc);
// Update the main product image
const mainImg = mainImageContainer.querySelector("img");
if (mainImg) {
mainImg.src = imageSrc;
mainImg.setAttribute("data-enlarge-src", imageSrc);
console.log("Main image updated to:", imageSrc);
}
});
});
}
// Product detail page functionality
async function initProductDetail() {
console.log("Product detail script running...");
console.log("Current URL:", window.location.href);
// Check if we're on the product detail page
const productTitle = document.querySelector("h1");
@ -233,6 +345,7 @@ function initSite() {
console.log("No h1 found, not on product detail page");
return;
}
console.log("Found h1 element:", productTitle);
// Get product ID from URL
const urlParams = new URLSearchParams(window.location.search);
@ -269,161 +382,112 @@ function initSite() {
}
// Update product title (the main product title, not the breadcrumb)
const allH1s = document.querySelectorAll("h1");
console.log("All H1 elements:", allH1s);
if (allH1s.length > 1) {
const mainProductTitle = allH1s[1]; // The second h1 should be the product title
console.log("Main product title element:", mainProductTitle);
if (mainProductTitle) {
mainProductTitle.textContent = product.name;
}
const productTitle = document.getElementById("product-title");
console.log("Product title element:", productTitle);
if (productTitle) {
productTitle.textContent = product.name;
console.log("Updated product title to:", product.name);
}
// Update product description - find by text content
const allParagraphs = document.querySelectorAll("p");
console.log("All paragraphs:", allParagraphs);
const descriptionEl = Array.from(allParagraphs).find(
(p) =>
p.textContent &&
p.textContent.includes(
"Setting the bar as one of the loudest speakers"
// Update product description
const productDescription = document.getElementById("product-description");
console.log("Product description element:", productDescription);
if (productDescription) {
productDescription.textContent = product.description;
console.log("Updated product description to:", product.description);
}
// Update main product image
const mainImageContainer = document.querySelector(
".w-full.h-80.md\\:w-\\[500px\\].md\\:h-\\[500px\\]"
);
console.log("Main image container:", mainImageContainer);
if (mainImageContainer) {
mainImageContainer.innerHTML = `
<img
src="${product.image}"
alt="${product.alt || product.name}"
class="w-full h-full object-cover cursor-pointer hover:opacity-90 transition-opacity"
data-enlarge-src="${product.image}"
/>
`;
console.log("Updated main image to:", product.image);
// Add image enlargement functionality to the main product image
setTimeout(() => {
addImageEnlargementListeners();
}, 100);
}
// Update thumbnail images
const thumbnailContainer = document.querySelector(
".flex.flex-row.md\\:flex-col.gap-3.md\\:gap-4"
);
console.log("Thumbnail container:", thumbnailContainer);
if (thumbnailContainer && product.images) {
thumbnailContainer.innerHTML = product.images
.slice(0, 4)
.map(
(img, index) => `
<div class="w-32 h-32 bg-floral-white rounded-lg overflow-hidden cursor-pointer hover:opacity-80 transition-opacity" data-thumbnail-src="${img}">
<img
src="${img}"
alt="${product.name} ${index + 1}"
class="w-full h-full object-cover"
/>
</div>
`
)
);
console.log("Description element:", descriptionEl);
if (descriptionEl) {
descriptionEl.textContent = product.description;
.join("");
console.log("Updated thumbnails with", product.images.length, "images");
// Add click event listeners to thumbnails
addThumbnailClickListeners();
}
// Update main product image - find by alt text
const allImages = document.querySelectorAll("img");
console.log("All images:", allImages);
const mainImage = Array.from(allImages).find(
(img) => img.alt && img.alt.includes("Asgaard sofa")
);
console.log("Main image element:", mainImage);
if (mainImage) {
mainImage.src = product.image;
mainImage.alt = product.alt || product.name;
// Update size options
const sizeContainer = document.getElementById("size-buttons-container");
console.log("Size container:", sizeContainer);
if (sizeContainer && product.sizes) {
sizeContainer.innerHTML = product.sizes
.map(
(size, index) => `
<button
class="w-8 h-8 ${
index === 0
? "bg-uc-gold text-white"
: "bg-floral-white text-black"
} font-playfair font-normal text-sm rounded flex items-center justify-center size-button"
>
${size}
</button>
`
)
.join("");
console.log("Updated size options with", product.sizes.length, "sizes");
}
// Update thumbnail images - find by alt text
if (product.images && product.images.length > 0) {
const allImages = document.querySelectorAll("img");
const thumbnails = Array.from(allImages).filter(
(img) =>
img.alt &&
(img.alt.includes("Outdoor sofa set") ||
img.alt.includes("Stuart sofa") ||
img.alt.includes("Maya sofa"))
// Update color options
const colorContainer = document.getElementById("color-buttons-container");
console.log("Color container:", colorContainer);
if (colorContainer && product.colors) {
colorContainer.innerHTML = product.colors
.map(
(color, index) => `
<button
class="w-8 h-8 rounded-full ${
index === 0 ? "border-2 border-black" : ""
}"
style="background-color: ${color}"
></button>
`
)
.join("");
console.log(
"Updated color options with",
product.colors.length,
"colors"
);
console.log("Thumbnail images found:", thumbnails);
product.images.forEach((imageSrc, index) => {
if (thumbnails[index]) {
thumbnails[index].src = imageSrc;
thumbnails[index].alt = product.alt || product.name;
}
});
}
// Update size options - use the specific size-button class
if (product.sizes) {
const sizeButtons = document.querySelectorAll(".size-button");
console.log("Size buttons found:", sizeButtons);
// Hide all size buttons first
sizeButtons.forEach((button) => {
button.style.display = "none";
});
// Show and update only the buttons we need
product.sizes.forEach((size, index) => {
if (sizeButtons[index]) {
sizeButtons[index].style.display = "flex";
sizeButtons[index].textContent = size;
sizeButtons[index].setAttribute("data-size", size);
// Set selected size
if (size === product.selectedSize) {
sizeButtons[index].classList.remove(
"bg-floral-white",
"text-black"
);
sizeButtons[index].classList.add("bg-uc-gold", "text-white");
sizeButtons[index].classList.add("selected");
} else {
sizeButtons[index].classList.remove(
"bg-uc-gold",
"text-white",
"selected"
);
sizeButtons[index].classList.add("bg-floral-white", "text-black");
}
}
});
// Add click event listeners for size buttons
sizeButtons.forEach((button) => {
button.addEventListener("click", function () {
// Remove selected state from all size buttons
sizeButtons.forEach((btn) => {
btn.classList.remove("bg-uc-gold", "text-white", "selected");
btn.classList.add("bg-floral-white", "text-black");
});
// Add selected state to clicked button
this.classList.remove("bg-floral-white", "text-black");
this.classList.add("bg-uc-gold", "text-white", "selected");
});
});
}
// Update color options - find only the rounded color buttons, not size buttons
if (product.colors) {
const allButtons = document.querySelectorAll("button");
const colorButtons = Array.from(allButtons).filter(
(button) =>
button.classList.contains("w-8") &&
button.classList.contains("h-8") &&
button.classList.contains("rounded-full") &&
!button.textContent // Color buttons should not have text content
);
console.log("Color buttons found:", colorButtons);
product.colors.forEach((color, index) => {
if (colorButtons[index]) {
colorButtons[index].style.backgroundColor = color.value;
colorButtons[index].setAttribute(
"data-color",
color.name || color.value
);
// Set selected color
if (color.selected) {
colorButtons[index].classList.add(
"border-2",
"border-black",
"selected"
);
} else {
colorButtons[index].classList.remove(
"border-2",
"border-black",
"selected"
);
}
}
});
// Add click event listeners for color buttons
colorButtons.forEach((button) => {
button.addEventListener("click", function () {
// Remove selected state from all color buttons
colorButtons.forEach((btn) => {
btn.classList.remove("border-2", "border-black", "selected");
});
// Add selected state to clicked button
this.classList.add("border-2", "border-black", "selected");
});
});
}
// Update product metadata - find by text content
@ -450,20 +514,7 @@ function initSite() {
categoryLabel.parentElement.querySelector("span:last-child");
if (categoryValue) {
categoryValue.textContent =
product.category.charAt(0).toUpperCase() +
product.category.slice(1);
}
}
// Find and update Tags
const tagsLabel = Array.from(allSpans).find(
(span) => span.textContent === "Tags"
);
if (tagsLabel && product.tags) {
const tagsValue =
tagsLabel.parentElement.querySelector("span:last-child");
if (tagsValue) {
tagsValue.textContent = product.tags.join(", ");
product.category.charAt(0).toUpperCase() + product.category.slice(1);
}
}
@ -485,45 +536,161 @@ function initSite() {
}
// Update description content
if (product.descriptionLong) {
const descriptionContainer = document.querySelector(
".max-w-5xl.mx-auto.space-y-6"
const descriptionContainer = document.getElementById(
"product-description-content"
);
console.log("Description content container:", descriptionContainer);
if (descriptionContainer && product.descriptionLong) {
descriptionContainer.innerHTML = product.descriptionLong
.map(
(paragraph) => `
<p class="font-playfair text-base leading-6 text-[#333333] text-justify">
${paragraph}
</p>
`
)
.join("");
console.log(
"Updated description content with",
product.descriptionLong.length,
"paragraphs"
);
if (descriptionContainer) {
const descriptionParagraphs =
descriptionContainer.querySelectorAll("p");
console.log("Description paragraphs found:", descriptionParagraphs);
// Update each paragraph with the product's description content
product.descriptionLong.forEach((paragraph, index) => {
if (descriptionParagraphs[index]) {
descriptionParagraphs[index].textContent = paragraph;
}
// Update gallery images with carousel
const galleryContainer = document.getElementById("product-gallery-images");
const galleryBackBtn = document.getElementById("gallery-back-btn");
const galleryNextBtn = document.getElementById("gallery-next-btn");
console.log("Gallery container:", galleryContainer);
console.log("Product galleryPairs:", product.galleryPairs);
if (galleryContainer && product.galleryPairs) {
// galleryPairs is now a simple array of image paths
const allGalleryImages = product.galleryPairs;
console.log("All gallery images:", allGalleryImages);
// Store gallery images for carousel
window.currentGalleryImages = allGalleryImages;
window.currentGalleryIndex = 0;
console.log("About to call renderGalleryCarousel...");
// Render initial gallery view
renderGalleryCarousel();
// Show navigation arrows for looped carousel (always show if more than 2 images)
if (allGalleryImages.length > 2) {
galleryBackBtn.style.display = "block";
galleryNextBtn.style.display = "block";
} else {
galleryBackBtn.style.display = "none";
galleryNextBtn.style.display = "none";
}
console.log(
"Gallery carousel:",
allGalleryImages.length,
"gallery images total (sliding)"
);
}
function renderGalleryCarousel() {
console.log("renderGalleryCarousel called");
console.log("window.currentGalleryImages:", window.currentGalleryImages);
console.log("galleryContainer:", galleryContainer);
if (!window.currentGalleryImages || !galleryContainer) {
console.log(
"Early return: missing currentGalleryImages or galleryContainer"
);
return;
}
console.log(
"Rendering gallery carousel with",
window.currentGalleryImages.length,
"images"
);
// Handle single image vs multiple images
if (window.currentGalleryImages.length === 1) {
// Single image - show only one image, hide arrows
const image1 = window.currentGalleryImages[0];
console.log("Rendering single image:", image1);
galleryContainer.innerHTML = `
<div class="rounded-[10px] bg-floral-white p-6 transition-all duration-1500 ease-out cursor-pointer hover:opacity-90" id="gallery-image-1">
<img
src="${image1}"
alt="${product.name} gallery"
class="w-full h-80 object-contain mx-auto"
data-enlarge-src="${image1}"
/>
</div>
`;
// Hide arrows for single image
galleryBackBtn.style.display = "none";
galleryNextBtn.style.display = "none";
} else {
// Multiple images - show 2 images with carousel
const image1 = window.currentGalleryImages[window.currentGalleryIndex];
const image2Index =
(window.currentGalleryIndex + 1) % window.currentGalleryImages.length;
const image2 = window.currentGalleryImages[image2Index];
console.log("Rendering multiple images:", image1, image2);
galleryContainer.innerHTML = `
<div class="rounded-[10px] bg-floral-white p-6 transition-all duration-1500 ease-out cursor-pointer hover:opacity-90" id="gallery-image-1">
<img
src="${image1}"
alt="${product.name} gallery"
class="w-full h-80 object-contain mx-auto"
data-enlarge-src="${image1}"
/>
</div>
<div class="rounded-[10px] bg-floral-white p-6 transition-all duration-1500 ease-out cursor-pointer hover:opacity-90" id="gallery-image-2">
<img
src="${image2}"
alt="${product.name} gallery"
class="w-full h-80 object-contain mx-auto"
data-enlarge-src="${image2}"
/>
</div>
`;
// Show arrows for multiple images
galleryBackBtn.style.display = "block";
galleryBackBtn.style.opacity = "1";
galleryBackBtn.style.pointerEvents = "auto";
galleryNextBtn.style.display = "block";
galleryNextBtn.style.opacity = "1";
galleryNextBtn.style.pointerEvents = "auto";
}
// Add click event listeners to gallery images for enlargement
console.log("About to call addImageEnlargementListeners for gallery...");
addImageEnlargementListeners();
}
// Add click handlers for looped gallery carousel navigation
if (galleryBackBtn) {
galleryBackBtn.addEventListener("click", function () {
// Left arrow moves forward: if at last position, go to 0
window.currentGalleryIndex =
(window.currentGalleryIndex + 1) % window.currentGalleryImages.length;
renderGalleryCarousel();
});
}
}
// Update gallery images
if (product.galleryPairs) {
// Find gallery images by alt text
const allImages = document.querySelectorAll("img");
const galleryImages = Array.from(allImages).filter(
(img) =>
img.alt &&
(img.alt.includes("Sofa variant left") ||
img.alt.includes("Sofa variant right"))
);
console.log("Gallery images found:", galleryImages);
product.galleryPairs.forEach((pair, index) => {
if (galleryImages[index * 2]) {
galleryImages[index * 2].src = pair.left;
galleryImages[index * 2].alt = product.alt || product.name;
}
if (galleryImages[index * 2 + 1]) {
galleryImages[index * 2 + 1].src = pair.right;
galleryImages[index * 2 + 1].alt = product.alt || product.name;
}
if (galleryNextBtn) {
galleryNextBtn.addEventListener("click", function () {
// Right arrow moves backward: if at 0, go to last position
window.currentGalleryIndex =
window.currentGalleryIndex === 0
? window.currentGalleryImages.length - 1
: window.currentGalleryIndex - 1;
renderGalleryCarousel();
});
}
@ -608,7 +775,6 @@ function initSite() {
} catch (error) {
console.error("Error loading product data:", error);
}
})();
}
// Product Comparison Page Functionality
@ -1001,6 +1167,8 @@ if (window.location.pathname.includes("product-detail.html")) {
"Product detail page detected, initializing compare functionality immediately"
);
initProductDetailCompare();
// Also initialize product detail loading
initProductDetail();
}
// Also try to initialize on DOMContentLoaded
@ -1009,6 +1177,8 @@ document.addEventListener("DOMContentLoaded", function () {
if (window.location.pathname.includes("product-detail.html")) {
console.log("Product detail page detected in DOMContentLoaded");
initProductDetailCompare();
// Also initialize product detail loading
initProductDetail();
}
});

View file

@ -123,6 +123,11 @@ class ProductManager {
.join("");
this.updateResultsCount();
this.updatePagination();
// Re-add image enlargement listeners after products are rendered
setTimeout(() => {
addImageEnlargementListeners();
}, 50);
}
// Create individual product card HTML
@ -140,7 +145,8 @@ class ProductManager {
<img
src="${product.image}"
alt="${product.alt}"
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300 cursor-pointer"
data-enlarge-src="${product.image}"
/>
<!-- Hover Overlay -->
<div class="absolute inset-0 bg-dark-charcoal bg-opacity-70 opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex items-center justify-center">
@ -454,7 +460,76 @@ class ProductManager {
// Initialize product manager
const productManager = new ProductManager();
// Image enlargement modal functionality
function addImageEnlargementListeners() {
const productImages = document.querySelectorAll(
"#product-grid img[data-enlarge-src]"
);
const modal = document.getElementById("image-modal");
const modalImage = document.getElementById("modal-image");
const modalCloseBtn = document.getElementById("modal-close-btn");
if (!modal || !modalImage || !modalCloseBtn) return;
productImages.forEach((img) => {
img.addEventListener("click", function (e) {
e.preventDefault(); // Prevent any default behavior
e.stopPropagation(); // Stop event bubbling to parent elements
const imageSrc = this.getAttribute("data-enlarge-src");
const imageAlt = this.getAttribute("alt");
modalImage.src = imageSrc;
modalImage.alt = imageAlt;
// Show modal with animation
modal.classList.remove("hidden");
document.body.style.overflow = "hidden"; // Prevent background scrolling
// Trigger animation after a brief delay
setTimeout(() => {
modalImage.classList.remove("scale-95");
modalImage.classList.add("scale-100");
}, 10);
});
});
// Close modal functionality
function closeModal() {
// Animate out
modalImage.classList.remove("scale-100");
modalImage.classList.add("scale-95");
// Hide modal after animation
setTimeout(() => {
modal.classList.add("hidden");
document.body.style.overflow = ""; // Restore scrolling
}, 300);
}
modalCloseBtn.addEventListener("click", closeModal);
// Close modal when clicking outside the image
modal.addEventListener("click", function (e) {
if (e.target === modal) {
closeModal();
}
});
// Close modal with Escape key
document.addEventListener("keydown", function (e) {
if (e.key === "Escape" && !modal.classList.contains("hidden")) {
closeModal();
}
});
}
// Load products when DOM is ready
document.addEventListener("DOMContentLoaded", () => {
productManager.loadProducts();
// Add image enlargement listeners after products are loaded
setTimeout(() => {
addImageEnlargementListeners();
}, 100);
});

View file

@ -673,6 +673,10 @@ video {
top: 20rem;
}
.top-4 {
top: 1rem;
}
.z-10 {
z-index: 10;
}
@ -779,10 +783,6 @@ video {
margin-top: 2.5rem;
}
.mt-2 {
margin-top: 0.5rem;
}
.mt-20 {
margin-top: 5rem;
}
@ -791,10 +791,18 @@ video {
margin-top: 0.75rem;
}
.mt-12 {
margin-top: 3rem;
}
.mt-6 {
margin-top: 1.5rem;
}
.mt-2 {
margin-top: 0.5rem;
}
.box-border {
box-sizing: border-box;
}
@ -883,22 +891,22 @@ video {
height: 24rem;
}
.h-\[22vh\] {
height: 22vh;
.h-\[145px\] {
height: 145px;
}
.h-\[29vh\] {
height: 29vh;
.h-\[301px\] {
height: 301px;
}
.h-\[446px\] {
height: 446px;
}
.h-\[64px\] {
height: 64px;
}
.h-\[6vh\] {
height: 6vh;
}
.h-auto {
height: auto;
}
@ -915,6 +923,54 @@ video {
height: 100vh;
}
.h-\[320px\] {
height: 320px;
}
.h-\[220px\] {
height: 220px;
}
.h-\[100px\] {
height: 100px;
}
.h-\[25vh\] {
height: 25vh;
}
.h-\[18vh\] {
height: 18vh;
}
.h-\[7vh\] {
height: 7vh;
}
.h-\[30vh\] {
height: 30vh;
}
.h-\[22vh\] {
height: 22vh;
}
.h-\[8vh\] {
height: 8vh;
}
.h-\[29vh\] {
height: 29vh;
}
.h-\[21vh\] {
height: 21vh;
}
.h-\[6vh\] {
height: 6vh;
}
.h-\[31vh\] {
height: 31vh;
}
@ -927,8 +983,12 @@ video {
height: 32vh;
}
.h-\[25vh\] {
height: 25vh;
.h-\[95vh\] {
height: 95vh;
}
.max-h-full {
max-height: 100%;
}
.min-h-\[64px\] {
@ -1015,6 +1075,10 @@ video {
width: 100%;
}
.w-\[95vw\] {
width: 95vw;
}
.max-w-2xl {
max-width: 42rem;
}
@ -1047,6 +1111,14 @@ video {
max-width: 20rem;
}
.max-w-4xl {
max-width: 56rem;
}
.max-w-full {
max-width: 100%;
}
.flex-1 {
flex: 1 1 0%;
}
@ -1070,6 +1142,18 @@ video {
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.scale-95 {
--tw-scale-x: .95;
--tw-scale-y: .95;
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.scale-100 {
--tw-scale-x: 1;
--tw-scale-y: 1;
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.transform {
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
@ -1164,14 +1248,14 @@ video {
gap: 1.5rem;
}
.gap-7 {
gap: 1.75rem;
}
.gap-8 {
gap: 2rem;
}
.gap-7 {
gap: 1.75rem;
}
.space-x-10 > :not([hidden]) ~ :not([hidden]) {
--tw-space-x-reverse: 0;
margin-right: calc(2.5rem * var(--tw-space-x-reverse));
@ -1208,6 +1292,12 @@ video {
margin-bottom: calc(0.25rem * var(--tw-space-y-reverse));
}
.space-y-16 > :not([hidden]) ~ :not([hidden]) {
--tw-space-y-reverse: 0;
margin-top: calc(4rem * calc(1 - var(--tw-space-y-reverse)));
margin-bottom: calc(4rem * var(--tw-space-y-reverse));
}
.space-y-2 > :not([hidden]) ~ :not([hidden]) {
--tw-space-y-reverse: 0;
margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse)));
@ -1442,6 +1532,14 @@ video {
--tw-bg-opacity: 0.7;
}
.bg-opacity-20 {
--tw-bg-opacity: 0.2;
}
.bg-opacity-90 {
--tw-bg-opacity: 0.9;
}
.object-contain {
-o-object-fit: contain;
object-fit: contain;
@ -1590,6 +1688,10 @@ video {
padding-right: 1.5rem;
}
.pt-0 {
padding-top: 0px;
}
.pt-12 {
padding-top: 3rem;
}
@ -1860,6 +1962,12 @@ video {
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
}
.shadow-2xl {
--tw-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.25);
--tw-shadow-colored: 0 25px 50px -12px var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
}
.blur {
--tw-blur: blur(8px);
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
@ -1874,6 +1982,11 @@ video {
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
}
.backdrop-blur-sm {
--tw-backdrop-blur: blur(4px);
backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
}
.transition {
transition-property: color, background-color, border-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-text-decoration-color;
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
@ -1922,10 +2035,22 @@ video {
transition-duration: 300ms;
}
.duration-500 {
transition-duration: 500ms;
}
.duration-1000 {
transition-duration: 1000ms;
}
.ease-in-out {
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}
.ease-out {
transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
}
.hover\:-translate-y-0\.5:hover {
--tw-translate-y: -0.125rem;
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
@ -1956,6 +2081,10 @@ video {
--tw-bg-opacity: 0.9;
}
.hover\:bg-opacity-30:hover {
--tw-bg-opacity: 0.3;
}
.hover\:text-black:hover {
--tw-text-opacity: 1;
color: rgb(0 0 0 / var(--tw-text-opacity, 1));
@ -1985,6 +2114,10 @@ video {
opacity: 0.8;
}
.hover\:opacity-90:hover {
opacity: 0.9;
}
.hover\:shadow-lg:hover {
--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);
@ -2257,18 +2390,90 @@ video {
margin-left: 2rem;
}
.lg\:mt-16 {
margin-top: 4rem;
}
.lg\:mt-8 {
margin-top: 2rem;
}
.lg\:h-\[70vh\] {
height: 70vh;
}
.lg\:h-\[90vh\] {
height: 90vh;
}
.lg\:h-\[50vh\] {
height: 50vh;
}
.lg\:h-\[600px\] {
height: 600px;
}
.lg\:h-\[500px\] {
height: 500px;
}
.lg\:h-\[85vh\] {
height: 85vh;
}
.lg\:h-\[60vh\] {
height: 60vh;
}
.lg\:h-\[65vh\] {
height: 65vh;
}
.lg\:min-h-screen {
min-height: 100vh;
}
.lg\:min-h-\[90vh\] {
min-height: 90vh;
}
.lg\:min-h-\[100vh\] {
min-height: 100vh;
}
.lg\:min-h-\[150vh\] {
min-height: 150vh;
}
.lg\:min-h-\[900px\] {
min-height: 900px;
}
.lg\:min-h-\[800px\] {
min-height: 800px;
}
.lg\:min-h-\[700px\] {
min-height: 700px;
}
.lg\:min-h-\[600px\] {
min-height: 600px;
}
.lg\:min-h-\[60vh\] {
min-height: 60vh;
}
.lg\:min-h-\[65vh\] {
min-height: 65vh;
}
.lg\:min-h-\[80vh\] {
min-height: 80vh;
}
.lg\:w-36 {
width: 9rem;
}
@ -2305,6 +2510,18 @@ video {
gap: 2rem;
}
.lg\:space-y-10 > :not([hidden]) ~ :not([hidden]) {
--tw-space-y-reverse: 0;
margin-top: calc(2.5rem * calc(1 - var(--tw-space-y-reverse)));
margin-bottom: calc(2.5rem * var(--tw-space-y-reverse));
}
.lg\:space-y-6 > :not([hidden]) ~ :not([hidden]) {
--tw-space-y-reverse: 0;
margin-top: calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));
margin-bottom: calc(1.5rem * var(--tw-space-y-reverse));
}
.lg\:space-y-4 > :not([hidden]) ~ :not([hidden]) {
--tw-space-y-reverse: 0;
margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse)));
@ -2328,6 +2545,11 @@ video {
padding-top: 12rem;
}
.lg\:text-2xl {
font-size: 1.5rem;
line-height: 2rem;
}
.lg\:text-3xl {
font-size: 1.875rem;
line-height: 2.25rem;
@ -2348,6 +2570,11 @@ video {
line-height: 1.75rem;
}
.lg\:text-lg {
font-size: 1.125rem;
line-height: 1.75rem;
}
.lg\:leading-snug {
line-height: 1.375;
}