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.
This commit is contained in:
George Birikorang 2025-09-17 19:40:13 -07:00
parent 195286e6c8
commit 7295bcba7e
4 changed files with 533 additions and 785 deletions

View file

@ -222,10 +222,12 @@ function initSite() {
});
});
}
}
// Product detail page functionality
(async function initProductDetail() {
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 +235,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 +272,103 @@ 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"
/>
`;
console.log("Updated main image to:", product.image);
}
// 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">
<img
src="${img}"
alt="${product.name} ${index + 1}"
class="w-full h-full object-cover"
/>
</div>
`
)
.join("");
console.log("Updated thumbnails with", product.images.length, "images");
}
// 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 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("Description element:", descriptionEl);
if (descriptionEl) {
descriptionEl.textContent = product.description;
}
// 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 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"))
);
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,8 +395,7 @@ function initSite() {
categoryLabel.parentElement.querySelector("span:last-child");
if (categoryValue) {
categoryValue.textContent =
product.category.charAt(0).toUpperCase() +
product.category.slice(1);
product.category.charAt(0).toUpperCase() + product.category.slice(1);
}
}
@ -485,45 +429,133 @@ function initSite() {
}
// Update description content
if (product.descriptionLong) {
const descriptionContainer = document.querySelector(
".max-w-5xl.mx-auto.space-y-6"
);
if (descriptionContainer) {
const descriptionParagraphs =
descriptionContainer.querySelectorAll("p");
console.log("Description paragraphs found:", descriptionParagraphs);
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"
);
}
// 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);
if (galleryContainer && product.galleryPairs) {
// galleryPairs is now a simple array of image paths
const allGalleryImages = product.galleryPairs;
// Store gallery images for carousel
window.currentGalleryImages = allGalleryImages;
window.currentGalleryIndex = 0;
// 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";
}
// 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);
console.log(
"Gallery carousel:",
allGalleryImages.length,
"gallery images total (sliding)"
);
}
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;
}
function renderGalleryCarousel() {
if (!window.currentGalleryImages || !galleryContainer) return;
// Handle single image vs multiple images
if (window.currentGalleryImages.length === 1) {
// Single image - show only one image, hide arrows
const image1 = window.currentGalleryImages[0];
galleryContainer.innerHTML = `
<div class="rounded-[10px] bg-floral-white p-6 transition-all duration-1500 ease-out" id="gallery-image-1">
<img
src="${image1}"
alt="${product.name} gallery"
class="w-full h-80 object-contain mx-auto"
/>
</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];
galleryContainer.innerHTML = `
<div class="rounded-[10px] bg-floral-white p-6 transition-all duration-1500 ease-out" id="gallery-image-1">
<img
src="${image1}"
alt="${product.name} gallery"
class="w-full h-80 object-contain mx-auto"
/>
</div>
<div class="rounded-[10px] bg-floral-white p-6 transition-all duration-1500 ease-out" id="gallery-image-2">
<img
src="${image2}"
alt="${product.name} gallery"
class="w-full h-80 object-contain mx-auto"
/>
</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 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();
});
}
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 +640,6 @@ function initSite() {
} catch (error) {
console.error("Error loading product data:", error);
}
})();
}
// Product Comparison Page Functionality
@ -1001,6 +1032,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 +1042,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();
}
});
@ -1116,7 +1151,7 @@ function initHeroCarousel() {
// Ensure image always fits its container uniformly
heroImage.style.height = "100%";
heroImage.style.objectFit = "cover";
heroImage.style.objectFit = "cover";
// Fade in new image
heroImage.style.opacity = "1";