// Global script initialization test console.log("=== MAIN.JS LOADED ==="); console.log("Current pathname:", window.location.pathname); console.log( "Is comparison page:", window.location.pathname.includes("product-comparison.html") ); // Update year in footer and handle smooth scrolling function initSite() { // Update footer year const footer = document.querySelector("footer p"); if (footer) { const currentYear = new Date().getFullYear(); footer.innerHTML = footer.innerHTML.replace("2024", currentYear); } // Smooth scrolling for navigation links const navLinks = document.querySelectorAll('.nav-link[href^="#"]'); navLinks.forEach((link) => { link.addEventListener("click", function (e) { e.preventDefault(); const targetId = this.getAttribute("href").substring(1); const targetElement = document.getElementById(targetId); if (targetElement) { const headerHeight = 112; const targetPosition = targetElement.offsetTop - headerHeight; window.scrollTo({ top: targetPosition, behavior: "smooth" }); } }); }); // Handle cross-page navigation links (links that point to other pages with anchors) const crossPageLinks = document.querySelectorAll('.nav-link[href*=".html#"]'); crossPageLinks.forEach((link) => { link.addEventListener("click", function () { // allow default navigation }); }); // Remove any lingering .active classes and avoid adding new ones const allNavLinks = document.querySelectorAll(".nav-link"); allNavLinks.forEach((a) => a.classList.remove("active")); // Do not add page-specific active state anymore // const currentPage = window.location.pathname.split("/").pop() || "index.html"; // const currentPageLink = document.querySelector(`.nav-link[href="${currentPage}"]`); // if (currentPageLink) currentPageLink.classList.add("active"); // Disable scroll-based active highlighting // (Keeping function for potential future use, but it does nothing now.) function updateActiveLink() { // no-op: active highlighting disabled globally } 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"); if (!grid) return; // Don't run this function on product detail pages - let the product detail function handle it const urlParams = new URLSearchParams(window.location.search); const productId = urlParams.get("id"); if (productId) return; try { const res = await fetch("data/products.json", { cache: "no-store" }); const data = await res.json(); const products = Array.isArray(data.products) ? data.products : []; // Determine current product's category from the title text (fallback seating) const title = document.querySelector("h1"); const currentName = (title?.textContent || "").trim(); const current = products.find( (p) => (p.name || "").trim() === currentName ); const category = current?.category || "seating"; // Filter related: same category but not the current product const related = products.filter( (p) => p.category === category && p.name !== currentName ); let page = 1; const pageSize = 4; function render() { grid.innerHTML = ""; const start = 0; const end = page * pageSize; // cumulative show-more related.slice(start, end).forEach((p) => { const card = document.createElement("article"); card.className = "rounded-lg overflow-hidden bg-white shadow-sm"; // Distinct image background for Asgaard sofa to match others const imageBgClass = (p.name || "").toLowerCase() === "asgaard sofa" ? "bg-linen" : "bg-white"; const panelBgClass = "bg-light-bg"; card.innerHTML = `
\"${

${ p.name }

`; grid.appendChild(card); }); // Toggle show-more visibility const btn = document.getElementById("related-show-more"); if (btn) { const hasMore = related.length > page * pageSize; btn.style.display = hasMore ? "inline-flex" : "none"; // Ensure centered label btn.classList.add("inline-flex", "items-center", "justify-center"); } } const btn = document.getElementById("related-show-more"); if (btn) { btn.addEventListener("click", () => { page += 1; render(); }); } render(); } catch (e) { console.error("Failed to load related products:", e); } })(); // Blog functionality (only runs on blog page) (async function initBlog() { const blogSearchInput = document.getElementById("blog-search-input"); const mainBlogContent = document.getElementById("main-blog-content"); const blogCategories = document.getElementById("blog-categories"); const recentPosts = document.getElementById("recent-posts"); const pagination = document.getElementById("blog-pagination"); const pageButtons = document.querySelectorAll( "#blog-pagination .blog-page-btn" ); const nextButton = document.getElementById("blog-next-btn"); if (!blogSearchInput || !mainBlogContent) return; let allPosts = []; let filteredPosts = []; let activeTag = ""; let currentPage = 1; const pageSize = 4; // Load blog data from JSON try { const response = await fetch("data/blog.json", { cache: "no-store" }); const data = await response.json(); allPosts = Array.isArray(data.posts) ? data.posts : []; // Sort posts by date (newest first) allPosts.sort((a, b) => new Date(b.date) - new Date(a.date)); console.log("Blog posts loaded:", allPosts.length); } catch (error) { console.error("Failed to load blog posts:", error); return; } function normalize(text) { return (text || "") .toLowerCase() .replace(/[^a-z0-9]+/g, " ") .trim(); } function formatDate(dateString) { const date = new Date(dateString); return date.toLocaleDateString("en-US", { day: "numeric", month: "short", year: "numeric", }); } function getFilteredPosts() { const query = normalize(blogSearchInput.value.trim()); return allPosts.filter((post) => { const title = post.title || ""; const excerpt = post.excerpt || ""; const haystack = normalize(`${title} ${excerpt}`); const matchesQuery = query === "" || haystack.includes(query); const matchesTag = activeTag === "" || post.tags.includes(activeTag); return matchesQuery && matchesTag; }); } function renderBlogPost(post) { const tagsHtml = post.tags .map( (tag) => `${ tag.charAt(0).toUpperCase() + tag.slice(1) }` ) .join(", "); const contentHtml = post.content .map((paragraph) => `

${paragraph}

`) .join(""); return `
${post.alt || post.title}
Admin ${post.author}
Calendar ${formatDate(post.date)}
Tag ${ post.tags[0] ? post.tags[0].charAt(0).toUpperCase() + post.tags[0].slice(1) : "Uncategorized" }

${post.title}

${post.excerpt}

`; } function renderBlogPosts() { const start = (currentPage - 1) * pageSize; const end = start + pageSize; const postsToShow = filteredPosts.slice(start, end); mainBlogContent.innerHTML = postsToShow .map((post) => renderBlogPost(post)) .join(""); // Re-initialize read more functionality initReadMore(); } function renderCategories() { const tagCounts = {}; allPosts.forEach((post) => { post.tags.forEach((tag) => { tagCounts[tag] = (tagCounts[tag] || 0) + 1; }); }); const categoriesHtml = Object.entries(tagCounts) .map( ([tag, count]) => ` ` ) .join(""); blogCategories.innerHTML = categoriesHtml; // Add event listeners to category buttons const categoryButtons = blogCategories.querySelectorAll(".tag-filter"); categoryButtons.forEach((btn) => { btn.addEventListener("click", () => { const clickedTag = btn.getAttribute("data-tag"); activeTag = activeTag === clickedTag ? "" : clickedTag; categoryButtons.forEach((b) => { b.classList.remove("border-uc-gold", "bg-gray-50", "text-uc-gold"); b.setAttribute("aria-pressed", "false"); }); if (activeTag) { btn.classList.add("border-uc-gold", "bg-gray-50", "text-uc-gold"); btn.setAttribute("aria-pressed", "true"); } currentPage = 1; filteredPosts = getFilteredPosts(); renderBlogPosts(); renderPagination(); }); }); } function renderRecentPosts() { const recentPostsHtml = allPosts .slice(0, 3) .map( (post) => `
${post.alt || post.title}

${post.title}

${formatDate(post.date)}

` ) .join(""); recentPosts.innerHTML = recentPostsHtml; } function renderPagination() { const total = filteredPosts.length; const totalPages = Math.max(1, Math.ceil(total / pageSize)); currentPage = Math.min(Math.max(1, currentPage), totalPages); if (pagination) { const hasQuery = blogSearchInput.value.trim().length > 0; pagination.style.display = hasQuery ? "none" : "flex"; pageButtons.forEach((btn) => { const p = Number(btn.getAttribute("data-page")); btn.classList.toggle("bg-uc-gold", p === currentPage); btn.classList.toggle("text-white", p === currentPage); btn.classList.toggle("bg-linen", p !== currentPage); btn.classList.toggle("text-black", p !== currentPage); btn.style.display = p <= totalPages ? "inline-flex" : "none"; }); if (nextButton) { nextButton.style.display = totalPages > 1 ? "inline-flex" : "none"; nextButton.disabled = currentPage >= totalPages; } } } function initReadMore() { const articles = document.querySelectorAll("#main-blog-content article"); articles.forEach((article) => { const excerptP = article.querySelector("p"); const readMoreBtn = article.querySelector(".read-more-toggle"); if (!excerptP || !readMoreBtn) return; const fullExcerpt = excerptP.textContent || ""; const { text: truncated, truncated: isTruncated } = truncateToWords( fullExcerpt, 80 ); if (isTruncated) { excerptP.dataset.excerptFull = fullExcerpt; excerptP.textContent = truncated; readMoreBtn.style.display = "inline-block"; } else { readMoreBtn.style.display = "none"; } readMoreBtn.addEventListener("click", (ev) => { ev.preventDefault(); if (excerptP.dataset.excerptFull) { excerptP.textContent = excerptP.dataset.excerptFull; delete excerptP.dataset.excerptFull; } const fullBlock = article.querySelector(".full-content"); if (fullBlock) fullBlock.classList.remove("hidden"); readMoreBtn.style.display = "none"; // Add "Show less" button at the end of the article const showLessBtn = document.createElement("button"); showLessBtn.type = "button"; showLessBtn.className = "show-less-toggle inline-block font-playfair font-normal text-base text-black border-b border-black hover:text-gray-600 hover:border-gray-600 transition-colors mt-4"; showLessBtn.textContent = "Show less"; showLessBtn.addEventListener("click", (ev) => { ev.preventDefault(); // Restore truncated excerpt excerptP.textContent = truncated; excerptP.dataset.excerptFull = fullExcerpt; // Hide full content if (fullBlock) fullBlock.classList.add("hidden"); // Show "Read more" button again readMoreBtn.style.display = "inline-block"; // Remove "Show less" button showLessBtn.remove(); }); // Insert at the end of the article article.appendChild(showLessBtn); }); }); } // Event listeners blogSearchInput.addEventListener("input", () => { currentPage = 1; filteredPosts = getFilteredPosts(); renderBlogPosts(); renderPagination(); }); pageButtons.forEach((btn) => btn.addEventListener("click", () => { currentPage = Number(btn.getAttribute("data-page")) || 1; renderBlogPosts(); renderPagination(); }) ); if (nextButton) { nextButton.addEventListener("click", () => { currentPage += 1; renderBlogPosts(); renderPagination(); }); } // Initialize everything filteredPosts = getFilteredPosts(); renderBlogPosts(); renderCategories(); renderRecentPosts(); renderPagination(); })(); // Inline "Read more" expansion for blog posts const articlesForExcerpt = document.querySelectorAll( "#main-blog-content article" ); function truncateToWords(text, maxWords) { const words = (text || "").trim().split(/\s+/); if (words.length <= maxWords) return { text, truncated: false }; return { text: words.slice(0, maxWords).join(" ") + "…", truncated: true }; } articlesForExcerpt.forEach((article) => { const excerptP = article.querySelector("p"); const readMoreBtn = article.querySelector(".read-more-toggle"); if (!excerptP || !readMoreBtn) return; const fullExcerpt = excerptP.textContent || ""; const { text: truncated, truncated: isTruncated } = truncateToWords( fullExcerpt, 80 ); if (isTruncated) { // Store full text and show truncated preview excerptP.dataset.excerptFull = fullExcerpt; excerptP.textContent = truncated; readMoreBtn.style.display = "inline-block"; } else { // Nothing to expand; hide the control readMoreBtn.style.display = "none"; } // Click to expand (delegated fallback added below as well) readMoreBtn.addEventListener("click", (ev) => { ev.preventDefault(); if (excerptP.dataset.excerptFull) { excerptP.textContent = excerptP.dataset.excerptFull; delete excerptP.dataset.excerptFull; } const fullBlock = article.querySelector(".full-content"); if (fullBlock) fullBlock.classList.remove("hidden"); readMoreBtn.style.display = "none"; // Add "Show less" button at the end of the article const showLessBtn = document.createElement("button"); showLessBtn.type = "button"; showLessBtn.className = "show-less-toggle inline-block font-playfair font-normal text-base text-black border-b border-black hover:text-gray-600 hover:border-gray-600 transition-colors mt-4"; showLessBtn.textContent = "Show less"; showLessBtn.addEventListener("click", (ev) => { ev.preventDefault(); // Restore truncated excerpt excerptP.textContent = truncated; excerptP.dataset.excerptFull = fullExcerpt; // Hide full content if (fullBlock) fullBlock.classList.add("hidden"); // Show "Read more" button again readMoreBtn.style.display = "inline-block"; // Remove "Show less" button showLessBtn.remove(); }); // Insert at the end of the article article.appendChild(showLessBtn); }); }); // Safety net: delegated handler in case markup changes document.addEventListener("click", (e) => { const trigger = e.target.closest(".read-more-toggle"); if (!trigger) return; e.preventDefault(); const article = trigger.closest("article"); if (!article) return; const excerptP = article.querySelector("p"); if (excerptP?.dataset?.excerptFull) { excerptP.textContent = excerptP.dataset.excerptFull; delete excerptP.dataset.excerptFull; } const fullBlock = article.querySelector(".full-content"); if (fullBlock) fullBlock.classList.remove("hidden"); trigger.style.display = "none"; }); // Footer newsletter subscribe validation and feedback (works on all pages) const siteFooter = document.querySelector("footer"); if (siteFooter) { const emailInputs = siteFooter.querySelectorAll('input[type="email"]'); emailInputs.forEach((emailInput) => { const inputRow = emailInput.closest(".flex") || emailInput.parentElement; let subscribeButton = inputRow ? inputRow.querySelector("button") : null; if (!subscribeButton) { subscribeButton = emailInput.parentElement?.querySelector("button"); } if (!subscribeButton) return; subscribeButton.type = "button"; let feedback = inputRow ? inputRow.nextElementSibling : null; if (!(feedback instanceof HTMLElement) || !feedback.dataset.feedback) { feedback = document.createElement("div"); feedback.dataset.feedback = "newsletter"; if (inputRow && inputRow.parentElement) { inputRow.parentElement.insertBefore(feedback, inputRow.nextSibling); } } feedback.className = "hidden mt-3 rounded-lg px-4 py-3 font-playfair text-base"; function showMessage(text, type) { feedback.textContent = text; feedback.className = "mt-3 rounded-lg px-4 py-3 font-playfair text-base"; if (type === "success") { feedback.classList.add( "bg-green-50", "border", "border-green-200", "text-green-800" ); } else { feedback.classList.add( "bg-red-50", "border", "border-red-200", "text-red-800" ); } feedback.classList.remove("hidden"); setTimeout(() => feedback.classList.add("hidden"), 3000); } subscribeButton.addEventListener("click", (e) => { e.preventDefault(); const value = (emailInput.value || "").trim(); const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; emailInput.classList.remove("border-red-500"); if (!emailRegex.test(value)) { emailInput.classList.add("border-red-500"); showMessage("Please enter a valid email address.", "error"); emailInput.focus(); return; } showMessage("Thank you for subscribing!", "success"); emailInput.value = ""; subscribeButton.disabled = true; setTimeout(() => (subscribeButton.disabled = false), 1200); }); }); } // Product detail page functionality (async function initProductDetail() { console.log("Product detail script running..."); // Check if we're on the product detail page const productTitle = document.querySelector("h1"); if (!productTitle) { console.log("No h1 found, not on product detail page"); return; } // Get product ID from URL const urlParams = new URLSearchParams(window.location.search); const productId = parseInt(urlParams.get("id")); console.log("Product ID from URL:", productId); if (!productId) { console.log("No product ID found in URL"); return; } try { // Load product data const response = await fetch("data/products.json"); const data = await response.json(); const product = data.products.find((p) => p.id === productId); if (!product) { console.error("Product not found:", productId); return; } console.log("Loading product:", product); // Update page title document.title = `${product.name} - KHY`; // Update the "Product Details" title with the actual product name const productDetailsTitle = document.getElementById( "product-details-title" ); if (productDetailsTitle) { productDetailsTitle.textContent = product.name; } // 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; } } // 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" ) ); 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 const allSpans = document.querySelectorAll("span"); // Find and update Model No. const modelNoLabel = Array.from(allSpans).find( (span) => span.textContent === "Model No." ); if (modelNoLabel && product.modelNo) { const modelNoValue = modelNoLabel.parentElement.querySelector("span:last-child"); if (modelNoValue) { modelNoValue.textContent = product.modelNo; } } // Find and update Category const categoryLabel = Array.from(allSpans).find( (span) => span.textContent === "Category" ); if (categoryLabel && product.category) { const categoryValue = 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(", "); } } // Find and update Dimensions const dimensionsLabel = Array.from(allSpans).find( (span) => span.textContent === "Dimension" ); console.log("Dimensions label found:", dimensionsLabel); console.log("Product dimensions:", product.dimensions); if (dimensionsLabel && product.dimensions) { const dimensionsValue = dimensionsLabel.parentElement.querySelector("span:last-child"); console.log("Dimensions value element:", dimensionsValue); if (dimensionsValue) { dimensionsValue.textContent = product.dimensions; console.log("Updated dimensions to:", product.dimensions); } } // 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); // Update each paragraph with the product's description content product.descriptionLong.forEach((paragraph, index) => { if (descriptionParagraphs[index]) { descriptionParagraphs[index].textContent = paragraph; } }); } } // 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; } }); } // Update related products section const relatedGrid = document.getElementById("related-grid"); if (relatedGrid && product.category) { console.log("Current product category:", product.category); console.log( "All products:", data.products.map((p) => ({ id: p.id, name: p.name, category: p.category, })) ); // Filter related products by category (excluding current product) const allRelatedProducts = data.products.filter( (p) => p.category === product.category && p.id !== product.id ); console.log("All related products found:", allRelatedProducts); let currentPage = 1; const pageSize = 4; function renderRelatedProducts() { const start = 0; const end = currentPage * pageSize; const productsToShow = allRelatedProducts.slice(start, end); relatedGrid.innerHTML = productsToShow .map( (p) => `
${
                p.alt || p.name
              }

${ p.name }

` ) .join(""); // Handle "Show More" button visibility const showMoreBtn = document.getElementById("related-show-more"); if (showMoreBtn) { const hasMore = allRelatedProducts.length > end; showMoreBtn.style.display = hasMore ? "inline-flex" : "none"; showMoreBtn.classList.add( "inline-flex", "items-center", "justify-center" ); } } // Add event listener for "Show More" button const showMoreBtn = document.getElementById("related-show-more"); if (showMoreBtn) { showMoreBtn.addEventListener("click", () => { currentPage += 1; renderRelatedProducts(); }); } // Initial render renderRelatedProducts(); } } catch (error) { console.error("Error loading product data:", error); } })(); } // Product Comparison Page Functionality function initProductComparison() { console.log("=== INITIALIZING PRODUCT COMPARISON ==="); // Prevent running twice (it's invoked in multiple places) if (window.__cmpInitialized) { console.log("[Comparison] Already initialized, skipping"); return; } window.__cmpInitialized = true; // Get product IDs from URL parameters const urlParams = new URLSearchParams(window.location.search); const product1Id = urlParams.get("product1"); const product2Id = urlParams.get("product2"); console.log("URL Parameters:", { product1Id, product2Id }); // Keep references to the two products for event handlers let cmpProduct1 = null; let cmpProduct2 = null; // Fetch product data fetch("data/products.json") .then((response) => { console.log("Fetch response status:", response.status); return response.json(); }) .then((data) => { console.log("Products data loaded:", data); console.log("Total products:", data.products.length); // Find products by ID const product1 = data.products.find((p) => p.id == product1Id); const product2 = data.products.find((p) => p.id == product2Id); console.log("Found products:", { product1, product2 }); // Update product cards if (product1) { console.log("Updating product card 1 with:", product1.name); cmpProduct1 = product1; updateProductCard(1, product1); updateComparisonTable(product1, 1); } if (product2) { console.log("Updating product card 2 with:", product2.name); cmpProduct2 = product2; updateProductCard(2, product2); updateComparisonTable(product2, 2); } // Populate dropdown populateProductDropdown(data.products); // Update View More link with current comparison state updateViewMoreLink(product1Id, product2Id); // Wire Add To Quote buttons (first -> product1, second -> product2) wireComparisonAddToQuote(cmpProduct1, cmpProduct2); }) .catch((error) => { console.error("Error loading products:", error); }); } // Bind the two Add To Quote buttons on the comparison page function wireComparisonAddToQuote(prod1, prod2) { try { const addButtons = Array.from(document.querySelectorAll("button")).filter( (b) => (b.textContent || "").trim() === "Add To Quote" ); if (addButtons.length === 0) { console.log("[Comparison] No Add To Quote buttons found"); return; } // Ensure deterministic order: the first encountered is for product 1 const btn1 = addButtons[0] || null; const btn2 = addButtons[1] || null; function toQuotePayload(p) { if (!p) return null; return { id: Number(p.id), name: p.name || "Product", image: p.image || "", color: (p.colors && p.colors[0] && (p.colors[0].name || p.colors[0].value)) || "Default", size: (p.sizes && p.sizes[0]) || "Standard", quantity: 1, }; } if (btn1) { btn1.type = btn1.getAttribute("type") || "button"; btn1.addEventListener("click", (e) => { e.preventDefault(); e.stopPropagation(); const payload = toQuotePayload(prod1); if (!payload) return; payload.quantity = 1; // force 1 for comparison adds if (typeof addToQuote === "function") { addToQuote(payload); } else { addToQuoteFallback(payload); } }); } if (btn2) { btn2.type = btn2.getAttribute("type") || "button"; btn2.addEventListener("click", (e) => { e.preventDefault(); e.stopPropagation(); const payload = toQuotePayload(prod2); if (!payload) return; payload.quantity = 1; // force 1 for comparison adds if (typeof addToQuote === "function") { addToQuote(payload); } else { addToQuoteFallback(payload); } }); } // Delegated fallback (in case DOM changes after load) document.addEventListener("click", (e) => { const t = e.target.closest && e.target.closest("button"); if (!t) return; const label = (t.textContent || "").trim(); if (label !== "Add To Quote") return; // Determine which button index this is relative to current NodeList const currentButtons = Array.from( document.querySelectorAll("button") ).filter((b) => (b.textContent || "").trim() === "Add To Quote"); const idx = currentButtons.indexOf(t); if (idx === 0) { const payload = toQuotePayload(prod1); if (payload) { payload.quantity = 1; (typeof addToQuote === "function" ? addToQuote : addToQuoteFallback)( payload ); } } else if (idx === 1) { const payload = toQuotePayload(prod2); if (payload) { payload.quantity = 1; (typeof addToQuote === "function" ? addToQuote : addToQuoteFallback)( payload ); } } }); } catch (err) { console.error("[Comparison] Failed to wire Add To Quote buttons:", err); } } function updateProductCard(slotNumber, product) { console.log(`=== UPDATING PRODUCT CARD ${slotNumber} ===`); console.log("Product data:", product); const cardContainer = document.querySelector(`.product-card-${slotNumber}`); console.log("Card container found:", cardContainer); if (cardContainer) { const imageContainer = cardContainer.querySelector("div"); const title = cardContainer.querySelector("p"); console.log("Image container found:", imageContainer); console.log("Title element found:", title); if (imageContainer) { console.log("Replacing image container with:", product.image); // Replace the placeholder div with an image imageContainer.innerHTML = `${product.name}`; } if (title) { console.log("Updating title to:", product.name); title.textContent = product.name; } } else { console.log(`Product card container .product-card-${slotNumber} not found`); } } function updateComparisonTable(product, slotNumber) { console.log(`=== UPDATING COMPARISON TABLE FOR SLOT ${slotNumber} ===`); console.log("Product:", product); // Update all sections const sections = ["general", "product", "dimensions", "warranty"]; sections.forEach((section) => { console.log(`Updating ${section} section...`); updateTableSection(section, product, slotNumber); }); } function updateTableSection(sectionName, product, slotNumber) { console.log( `=== UPDATING TABLE SECTION: ${sectionName} (SLOT ${slotNumber}) ===` ); const sectionData = getProductSectionData(product, sectionName); // Find the section header const section = document.querySelector( `.comparison-table .${sectionName}-section` ); if (!section) { console.log(`Section ${sectionName}-section not found`); return; } // Find the table that comes after this section header const sectionTable = section.nextElementSibling; if (!sectionTable) { console.log(`Table after ${sectionName}-section not found`); return; } // Find all flex rows and filter to only those with data columns const allRows = sectionTable.querySelectorAll(".flex.items-center"); const rows = Array.from(allRows).filter((row) => row.querySelector(".column-1") ); console.log( `Found ${rows.length} data rows in ${sectionName} section (out of ${allRows.length} total rows)` ); if (rows.length > 0 && sectionData) { console.log("Updating rows with data:", sectionData); // Update each row with the corresponding data rows.forEach((row, index) => { if (index < sectionData.length) { const column = row.querySelector(`.column-${slotNumber}`); if (column) { console.log( `Updating row ${index + 1} with data: ${sectionData[index]}` ); console.log(`Row element:`, row); console.log(`Column element:`, column); console.log(`Column text before update: "${column.textContent}"`); column.textContent = sectionData[index]; console.log(`Column text after update: "${column.textContent}"`); } else { console.log(`Column ${slotNumber} not found in row ${index + 1}`); } } }); console.log("Table section updated successfully."); } else { console.log("No rows found or section data is empty, skipping update."); console.log("Rows found:", rows.length); console.log("Section data:", sectionData); } } function getProductSectionData(product, sectionName) { console.log(`=== GETTING PRODUCT SECTION DATA: ${sectionName} ===`); console.log("Product:", product); let sectionData; switch (sectionName) { case "general": sectionData = [ product.salesPackage || "N/A", // Sales Package product.modelNo || "N/A", // Model Number product.additionalInformation?.Material || "N/A", // Secondary Material product.configuration || "N/A", // Configuration product.additionalInformation?.Upholstery || "N/A", // Upholstery Material product.colors?.[0]?.name || "N/A", // Upholstery Color ]; break; case "product": sectionData = [ product.fillingMaterial || "N/A", // Filling Material product.finishType || "N/A", // Finish Type product.adjustableHeadrest || "N/A", // Adjustable Headrest product.maxLoadCapacity || "N/A", // Maximum Load Capacity product.originOfManufacture || "N/A", // Origin of Manufacture ]; break; case "dimensions": const dims = product.dimensions?.split(" x ") || []; sectionData = [ dims[0] || "N/A", // Width dims[1] || "N/A", // Height dims[2] || "N/A", // Depth product.weight || "N/A", // Weight product.seatHeight || "N/A", // Seat Height product.legHeight || "N/A", // Leg Height ]; break; case "warranty": sectionData = [ product.additionalInformation?.Warranty || "N/A", // Warranty Summary product.warrantyServiceType || "N/A", // Warranty Service Type product.coveredInWarranty || "N/A", // Covered in Warranty product.notCoveredInWarranty || "N/A", // Not Covered in Warranty product.additionalInformation?.Warranty || "N/A", // Domestic Warranty ]; break; default: sectionData = []; } console.log(`Section data for ${sectionName}:`, sectionData); console.log(`Detailed breakdown for ${sectionName}:`); sectionData.forEach((item, index) => { console.log(` Item ${index + 1}: "${item}"`); }); return sectionData; } function populateProductDropdown(products) { const dropdown = document.getElementById("product-dropdown"); if (dropdown) { // Clear existing options except the first one while (dropdown.children.length > 1) { dropdown.removeChild(dropdown.lastChild); } // Add all products products.forEach((product) => { const option = document.createElement("option"); option.value = product.id; option.textContent = product.name; dropdown.appendChild(option); }); // Add event listener for product selection dropdown.addEventListener("change", function () { const selectedProductId = this.value; console.log("Product selected from dropdown:", selectedProductId); if (selectedProductId && selectedProductId !== "Choose a Product") { // Find the selected product const selectedProduct = products.find((p) => p.id == selectedProductId); if (selectedProduct) { console.log("Selected product found:", selectedProduct); // Update the second product slot updateProductCard(2, selectedProduct); updateComparisonTable(selectedProduct, 2); // Update URL to include the second product updateURLParameter("product2", selectedProductId); // Reset dropdown to default option this.value = "Choose a Product"; } } }); } } function updateURLParameter(param, value) { const url = new URL(window.location); url.searchParams.set(param, value); window.history.replaceState({}, "", url); } function updateViewMoreLink(product1Id, product2Id) { const viewMoreLink = document.querySelector( 'a[href*="product-catalog.html"]' ); if (viewMoreLink) { // Determine which slot is available (1 or 2) let availableSlot = 1; if (!product1Id) { availableSlot = 1; } else if (!product2Id) { availableSlot = 2; } else { // Both slots are filled, default to slot 2 for replacement availableSlot = 2; } let newHref = `product-catalog.html?returnTo=comparison&slot=${availableSlot}`; if (product1Id) newHref += `&product1=${product1Id}`; if (product2Id) newHref += `&product2=${product2Id}`; viewMoreLink.href = newHref; console.log("Updated View More link:", newHref); } } // Initialize product comparison if on comparison page console.log("=== CHECKING IF COMPARISON PAGE INITIALIZATION SHOULD RUN ==="); console.log( "Pathname includes product-comparison.html:", window.location.pathname.includes("product-comparison.html") ); if (window.location.pathname.includes("product-comparison.html")) { console.log("Product comparison page detected, initializing immediately"); initProductComparison(); // Also try on DOMContentLoaded as backup document.addEventListener("DOMContentLoaded", function () { console.log("Product comparison page DOMContentLoaded backup"); initProductComparison(); }); } // Product Detail Page - Compare Products functionality function initProductDetailCompare() { console.log("=== INITIALIZING PRODUCT DETAIL COMPARE ==="); console.log("Current URL:", window.location.href); console.log("Pathname:", window.location.pathname); // Try to find the button immediately let compareButton = document.getElementById("compare-products-btn"); console.log("Button found by ID:", compareButton); if (!compareButton) { console.log("Button not found by ID, trying text search..."); const buttons = document.querySelectorAll("button"); console.log("Total buttons found:", buttons.length); buttons.forEach((button, index) => { console.log(`Button ${index}: "${button.textContent.trim()}"`); }); compareButton = Array.from(buttons).find( (button) => button.textContent.trim() === "Compare Products" ); console.log("Button found by text search:", compareButton); } if (compareButton) { console.log("=== ADDING CLICK LISTENER ==="); // Remove any existing listeners first compareButton.removeEventListener("click", handleCompareClick); compareButton.addEventListener("click", handleCompareClick); // Also add a direct onclick handler as backup compareButton.onclick = handleCompareClick; console.log("Click listener added successfully"); console.log("Button element:", compareButton); console.log("Button text content:", compareButton.textContent); } else { console.log("=== BUTTON NOT FOUND ==="); console.log("Compare Products button not found"); } } // Separate function for the click handler function handleCompareClick(event) { console.log("Compare Products button clicked"); event.preventDefault(); event.stopPropagation(); // Get current product ID from URL const urlParams = new URLSearchParams(window.location.search); const productId = urlParams.get("id"); console.log("Product ID from URL:", productId); if (productId) { // Navigate to comparison page with current product as product1 const comparisonUrl = `product-comparison.html?product1=${productId}`; console.log("Navigating to:", comparisonUrl); window.location.href = comparisonUrl; } else { // If no product ID, just go to comparison page console.log("No product ID found, navigating to comparison page"); window.location.href = "product-comparison.html"; } } // Initialize immediately if we're on the product detail page if (window.location.pathname.includes("product-detail.html")) { console.log( "Product detail page detected, initializing compare functionality immediately" ); initProductDetailCompare(); } // Also try to initialize on DOMContentLoaded document.addEventListener("DOMContentLoaded", function () { console.log("DOMContentLoaded event fired"); if (window.location.pathname.includes("product-detail.html")) { console.log("Product detail page detected in DOMContentLoaded"); initProductDetailCompare(); } }); // Initialize product comparison if on comparison page if (window.location.pathname.includes("product-comparison.html")) { document.addEventListener("DOMContentLoaded", function () { initProductComparison(); }); } // Initialize comparison functionality if on product catalog page if (window.location.pathname.includes("product-catalog.html")) { console.log("=== PRODUCT CATALOG PAGE DETECTED ==="); document.addEventListener("DOMContentLoaded", function () { console.log( "=== DOM CONTENT LOADED - INITIALIZING PRODUCT CATALOG COMPARISON ===" ); initProductCatalogComparison(); }); } // Product Catalog - Handle comparison page returns function initProductCatalogComparison() { const urlParams = new URLSearchParams(window.location.search); const returnTo = urlParams.get("returnTo"); const slot = urlParams.get("slot"); const product1Id = urlParams.get("product1"); const product2Id = urlParams.get("product2"); console.log("Product catalog comparison params:", { returnTo, slot, product1Id, product2Id, }); if (returnTo === "comparison" && slot) { console.log("Setting up comparison return functionality for slot:", slot); // Note: Product card click handling is now done in products.js viewProduct method console.log( "Comparison mode activated - View buttons will navigate to comparison page" ); } } // Replace Poppins with Playfair in all font references function updateFontClasses() { // Find all elements with font-poppins class and replace with font-playfair const poppinsElements = document.querySelectorAll(".font-poppins"); poppinsElements.forEach((element) => { element.classList.remove("font-poppins"); element.classList.add("font-playfair"); }); } // Run font update on page load document.addEventListener("DOMContentLoaded", function () { updateFontClasses(); }); // Initialize quote badge on all pages function initQuoteBadge() { // Load quote items from localStorage const storageKey = "khy_quote_items"; let quoteItems = []; try { const stored = localStorage.getItem(storageKey); quoteItems = stored ? JSON.parse(stored) : []; } catch (error) { console.error("Error loading quote items:", error); } // Calculate total count const count = quoteItems.reduce((total, item) => total + item.quantity, 0); // Update quote badge on all quote links (desktop nav, mobile button, mobile menu) const quoteLinks = document.querySelectorAll('a[href="quote.html"]'); quoteLinks.forEach((quoteLink) => { // Remove existing badge const existingBadge = quoteLink.querySelector(".quote-badge"); if (existingBadge) { existingBadge.remove(); } // Add new badge if there are items if (count > 0) { const badge = document.createElement("span"); badge.className = "quote-badge absolute -top-2 -right-2 bg-uc-gold text-white text-xs rounded-full w-5 h-5 flex items-center justify-center font-semibold"; badge.textContent = count > 99 ? "99+" : count; quoteLink.style.position = "relative"; quoteLink.appendChild(badge); } }); } // Initialize Add To Quote functionality on product detail pages function initAddToQuote() { const addToQuoteBtn = document.getElementById("add-to-quote-btn"); if (addToQuoteBtn) { try { // Ensure button is not treated as a submit in case inside a form if (!addToQuoteBtn.getAttribute("type")) { addToQuoteBtn.setAttribute("type", "button"); } addToQuoteBtn.addEventListener("click", function (e) { e.preventDefault(); e.stopPropagation(); console.log("[AddToQuote] Direct click captured"); const productData = getProductDataFromPage(); console.log("[AddToQuote] productData:", productData); if (productData) { if (typeof addToQuote === "function") { addToQuote(productData); } else { addToQuoteFallback(productData); } } else { console.warn( "[AddToQuote] No product data found. Check URL id and DOM." ); } }); } catch (err) { console.error("[AddToQuote] Failed to bind direct listener:", err); } } // Delegated fallback in case the button is replaced dynamically document.addEventListener("click", function (e) { const btn = e.target.closest && e.target.closest("#add-to-quote-btn"); if (!btn) return; e.preventDefault(); e.stopPropagation(); console.log("[AddToQuote] Delegated click captured"); try { const productData = getProductDataFromPage(); console.log("[AddToQuote][delegated] productData:", productData); if (productData) { if (typeof addToQuote === "function") { addToQuote(productData); } else { addToQuoteFallback(productData); } } else { console.warn("[AddToQuote][delegated] No product data found."); } } catch (err) { console.error("[AddToQuote][delegated] Error handling click:", err); } }); } // Get product data from the current page function getProductDataFromPage() { // Get product ID from URL const urlParams = new URLSearchParams(window.location.search); const productId = urlParams.get("id"); if (!productId) { console.error("No product ID found in URL"); return null; } // Get selected color and size const selectedColor = getSelectedColor(); const selectedSize = getSelectedSize(); const selectedQuantity = getSelectedQuantity(); // Get product name and a best-effort product image const productName = document.querySelector("h1")?.textContent?.trim() || "Product"; let productImage = ""; // Helper to guard against invalid selectors (e.g., unescaped brackets) function qsSafe(selector) { try { return document.querySelector(selector); } catch (e) { return null; } } // Try a series of selectors safely const imageCandidates = [ ".w-\\[500px\\].h-\\[500px\\] img", ".w-[500px].h-[500px] img", ".bg-floral-white img", "section img[alt]", ]; for (const sel of imageCandidates) { const el = qsSafe(sel); if (el && el.src) { productImage = el.src; break; } } if (!productImage) { const anyImg = qsSafe("img[alt]") || qsSafe("section img") || qsSafe("img"); productImage = anyImg?.src || ""; } return { id: parseInt(productId), name: productName, image: productImage, color: selectedColor, size: selectedSize, quantity: selectedQuantity, }; } // Get selected color from the page function getSelectedColor() { const colorButtons = document.querySelectorAll("button[data-color]"); for (let button of colorButtons) { if ( button.classList.contains("selected") || button.classList.contains("border-black") ) { return button.getAttribute("data-color") || "Default"; } } return "Default"; } // Get selected size from the page function getSelectedSize() { const sizeButtons = document.querySelectorAll("button[data-size]"); for (let button of sizeButtons) { if ( button.classList.contains("selected") || button.classList.contains("bg-uc-gold") ) { return button.getAttribute("data-size") || "Standard"; } } return "Standard"; } // Get selected quantity from the page function getSelectedQuantity() { const quantitySpan = document.getElementById("qty-value"); if (quantitySpan) { return parseInt(quantitySpan.textContent) || 1; } return 1; } // Fallback function to add to quote directly function addToQuoteFallback(productData) { const storageKey = "khy_quote_items"; let quoteItems = []; try { const stored = localStorage.getItem(storageKey); quoteItems = stored ? JSON.parse(stored) : []; } catch (error) { console.error("Error loading quote items:", error); quoteItems = []; } // Check if item already exists with same specifications const existingItemIndex = quoteItems.findIndex( (item) => item.id === productData.id && item.color === productData.color && item.size === productData.size ); if (existingItemIndex !== -1) { // Update quantity of existing item quoteItems[existingItemIndex].quantity += productData.quantity; } else { // Add new item const newItem = { ...productData, timestamp: new Date().toISOString(), }; quoteItems.push(newItem); } // Save to localStorage try { localStorage.setItem(storageKey, JSON.stringify(quoteItems)); // Update quote badge initQuoteBadge(); // Show success message showAddToQuoteSuccess(); } catch (error) { console.error("Error saving quote items:", error); } } // Show success message when item is added function showAddToQuoteSuccess() { // Create success notification const notification = document.createElement("div"); notification.className = "fixed top-24 right-4 bg-green-500 text-white px-6 py-3 rounded-lg shadow-lg z-50 transform transition-all duration-300 translate-x-full"; notification.innerHTML = `
Added to quote!
`; document.body.appendChild(notification); // Animate in setTimeout(() => { notification.classList.remove("translate-x-full"); }, 100); // Remove after 3 seconds setTimeout(() => { notification.classList.add("translate-x-full"); setTimeout(() => { if (document.body.contains(notification)) { document.body.removeChild(notification); } }, 300); }, 3000); } // Initialize quote badge immediately if DOM is already loaded if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", initQuoteBadge); } else { initQuoteBadge(); } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", initSite); } else { initSite(); } // Initialize quote badge on page load document.addEventListener("DOMContentLoaded", function () { initQuoteBadge(); initAddToQuote(); initHeroCarousel(); initMobileMenu(); }); // Hero Carousel Functionality function initHeroCarousel() { const arrowButton = document.getElementById("story-arrow-button"); const heroImage = document.querySelector("#hero-image img"); if (!arrowButton || !heroImage) return; // Carousel images array const carouselImages = [ "assets/images/our_story.jpg", "assets/images/first_homepage.jpg", "assets/images/conference_room.jpg", "assets/images/lounge_chair.jpg", "assets/images/kitchen.JPG", ]; let currentImageIndex = 0; let originalHeight = null; // Function to capture original image height on first load function captureOriginalHeight() { if (!originalHeight) { originalHeight = heroImage.offsetHeight; console.log("Original image height captured:", originalHeight + "px"); } } // Function to update image with fade transition function updateImage(newIndex) { const newImage = new Image(); newImage.onload = function () { // Fade out current image heroImage.style.opacity = "0"; setTimeout(() => { // Change image source heroImage.src = carouselImages[newIndex]; heroImage.alt = `Carousel image ${newIndex + 1}`; // Apply original height to maintain uniformity if (originalHeight && newIndex !== 0) { heroImage.style.height = originalHeight + "px"; heroImage.style.objectFit = "cover"; } else if (newIndex === 0) { // Reset to original for the first image heroImage.style.height = "auto"; heroImage.style.objectFit = "initial"; } // Fade in new image heroImage.style.opacity = "1"; // Update indicators updateIndicators(newIndex); }, 300); }; newImage.src = carouselImages[newIndex]; } // Function to update carousel indicators function updateIndicators(activeIndex) { for (let i = 0; i < 5; i++) { const indicator = document.getElementById(`carousel-indicator-${i}`); if (indicator) { indicator.style.opacity = i === activeIndex ? "1" : "0.5"; } } } // Arrow button click handler arrowButton.addEventListener("click", (e) => { e.preventDefault(); e.stopPropagation(); currentImageIndex = (currentImageIndex + 1) % carouselImages.length; updateImage(currentImageIndex); }); // Indicator click handlers for (let i = 0; i < 5; i++) { const indicator = document.getElementById(`carousel-indicator-${i}`); if (indicator) { indicator.addEventListener("click", (e) => { e.preventDefault(); e.stopPropagation(); currentImageIndex = i; updateImage(currentImageIndex); }); } } // Capture original height after image loads if (heroImage.complete) { captureOriginalHeight(); } else { heroImage.addEventListener("load", captureOriginalHeight); } // Auto-advance carousel every 5 seconds setInterval(() => { currentImageIndex = (currentImageIndex + 1) % carouselImages.length; updateImage(currentImageIndex); }, 5000); } // Mobile Menu Functionality function initMobileMenu() { const mobileMenuButton = document.getElementById("mobile-menu-button"); const mobileMenuClose = document.getElementById("mobile-menu-close"); const mobileMenu = document.getElementById("mobile-menu"); const mobileMenuOverlay = document.getElementById("mobile-menu-overlay"); if ( !mobileMenuButton || !mobileMenuClose || !mobileMenu || !mobileMenuOverlay ) { console.log("Mobile menu elements not found"); return; } // Function to open mobile menu function openMobileMenu() { mobileMenu.classList.remove("translate-x-full"); mobileMenuOverlay.classList.remove("hidden"); document.body.style.overflow = "hidden"; // Prevent background scrolling } // Function to close mobile menu function closeMobileMenu() { mobileMenu.classList.add("translate-x-full"); mobileMenuOverlay.classList.add("hidden"); document.body.style.overflow = ""; // Restore scrolling } // Event listeners mobileMenuButton.addEventListener("click", openMobileMenu); mobileMenuClose.addEventListener("click", closeMobileMenu); mobileMenuOverlay.addEventListener("click", closeMobileMenu); // Close menu when clicking on navigation links const mobileNavLinks = mobileMenu.querySelectorAll("a"); mobileNavLinks.forEach((link) => { link.addEventListener("click", closeMobileMenu); }); // Close menu on escape key document.addEventListener("keydown", (e) => { if ( e.key === "Escape" && !mobileMenu.classList.contains("translate-x-full") ) { closeMobileMenu(); } }); // Handle window resize - close menu if screen becomes large window.addEventListener("resize", () => { if (window.innerWidth >= 640) { // sm breakpoint closeMobileMenu(); } }); } // Version: 4.8 - Added mobile hamburger menu functionality