// 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 search functionality (only runs on blog page) const blogSearchInput = document.getElementById("blog-search-input"); if (blogSearchInput) { const pagination = document.getElementById("blog-pagination"); const pageButtons = document.querySelectorAll( "#blog-pagination .blog-page-btn" ); const nextButton = document.getElementById("blog-next-btn"); const blogTagButtons = document.querySelectorAll(".tag-filter"); let activeTag = ""; let currentPage = 1; const pageSize = 4; function normalize(text) { return (text || "") .toLowerCase() .replace(/[^a-z0-9]+/g, " ") .trim(); } function getFilteredArticles() { const articles = Array.from( document.querySelectorAll("#main-blog-content article") ); articles.sort((a, b) => { const ad = a.getAttribute("data-date") || ""; const bd = b.getAttribute("data-date") || ""; if (!ad && !bd) return 0; return bd.localeCompare(ad); }); const query = normalize(blogSearchInput.value.trim()); return articles.filter((article) => { const title = article.querySelector("h2")?.textContent || ""; const excerpt = article.querySelector("p")?.textContent || ""; const haystack = normalize(`${title} ${excerpt}`); const tagList = (article.getAttribute("data-tags") || "") .split(",") .map((t) => normalize(t)); const matchesQuery = query === "" || haystack.includes(query); const matchesTag = activeTag === "" || tagList.includes(activeTag); return matchesQuery && matchesTag; }); } function renderPage() { const filtered = getFilteredArticles(); const total = filtered.length; const totalPages = Math.max(1, Math.ceil(total / pageSize)); currentPage = Math.min(Math.max(1, currentPage), totalPages); const start = (currentPage - 1) * pageSize; const end = start + pageSize; const toShow = new Set(filtered.slice(start, end)); document.querySelectorAll("#main-blog-content article").forEach((a) => { a.style.display = toShow.has(a) ? "" : "none"; }); 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 filterPosts() { currentPage = 1; renderPage(); } blogSearchInput.addEventListener("input", filterPosts); blogTagButtons.forEach((btn) => { btn.addEventListener("click", () => { const clickedTag = normalize(btn.getAttribute("data-tag")); activeTag = activeTag === clickedTag ? "" : clickedTag; blogTagButtons.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"); } filterPosts(); }); }); pageButtons.forEach((btn) => btn.addEventListener("click", () => { currentPage = Number(btn.getAttribute("data-page")) || 1; renderPage(); }) ); if (nextButton) { nextButton.addEventListener("click", () => { currentPage += 1; renderPage(); }); } renderPage(); } // 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"; }); }); // 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); }); }); } // Story arrow button functionality const storyArrowButton = document.getElementById("story-arrow-button"); if (storyArrowButton) { storyArrowButton.addEventListener("click", function () { // You can customize this action - for now, let's scroll to the products section const productsSection = document.getElementById("products"); if (productsSection) { const headerHeight = 112; const targetPosition = productsSection.offsetTop - headerHeight; window.scrollTo({ top: targetPosition, behavior: "smooth", }); } }); } // 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 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; // 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"); } else { sizeButtons[index].classList.remove("bg-uc-gold", "text-white"); sizeButtons[index].classList.add("bg-floral-white", "text-black"); } } }); } // 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; // Set selected color if (color.selected) { colorButtons[index].classList.add("border-2", "border-black"); } else { colorButtons[index].classList.remove("border-2", "border-black"); } } }); } // 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 relatedProducts = data.products .filter((p) => p.category === product.category && p.id !== product.id) .slice(0, 4); console.log("Related products found:", relatedProducts); relatedGrid.innerHTML = relatedProducts .map( (p) => `
${
              p.alt || p.name
            }

${ p.name }

` ) .join(""); } } catch (error) { console.error("Error loading product data:", error); } })(); } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", initSite); } else { initSite(); }