// Update year in footer and handle smooth scrolling document.addEventListener("DOMContentLoaded", function () { // 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) { // Calculate offset for header height (112px = 28 * 4) 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 (e) { // Don't prevent default - let the browser handle the navigation // The smooth scrolling will work on the target page }); }); // Highlight current page link const currentPage = window.location.pathname.split("/").pop() || "index.html"; const currentPageLink = document.querySelector( `.nav-link[href="${currentPage}"]` ); if (currentPageLink) { currentPageLink.classList.add("active"); } // Active link highlighting on scroll const sections = document.querySelectorAll("section[id]"); const navItems = document.querySelectorAll('.nav-link[href^="#"]'); function updateActiveLink() { const scrollPosition = window.scrollY + 150; // Offset for better detection sections.forEach((section) => { const sectionTop = section.offsetTop; const sectionHeight = section.offsetHeight; const sectionId = section.getAttribute("id"); if ( scrollPosition >= sectionTop && scrollPosition < sectionTop + sectionHeight ) { // Remove active class from all nav links navItems.forEach((item) => item.classList.remove("active")); // Add active class to corresponding nav link const activeLink = document.querySelector( `.nav-link[href="#${sectionId}"]` ); if (activeLink) { activeLink.classList.add("active"); } } }); } // Listen for scroll events window.addEventListener("scroll", updateActiveLink); // Initial call to set active link on page load updateActiveLink(); // 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 tagButtons = document.querySelectorAll(".tag-filter"); let activeTag = ""; // empty = no tag filter let currentPage = 1; const pageSize = 4; // number of posts per page function normalize(text) { return (text || "") .toLowerCase() .replace(/[^a-z0-9]+/g, " ") .trim(); } function getFilteredArticles() { const articles = Array.from( document.querySelectorAll("#main-blog-content article") ); // Sort by date descending if data-date is present (YYYY-MM-DD) articles.sort((a, b) => { const ad = a.getAttribute("data-date") || ""; const bd = b.getAttribute("data-date") || ""; // Fallback: keep original order if dates missing 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); // Show only items for current page 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"; }); // Update pagination visibility and active state 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() { // Reset to first page when filters change currentPage = 1; renderPage(); } blogSearchInput.addEventListener("input", filterPosts); // Tag filtering tagButtons.forEach((btn) => { btn.addEventListener("click", () => { const clickedTag = normalize(btn.getAttribute("data-tag")); // Toggle logic: clicking active tag clears it activeTag = activeTag === clickedTag ? "" : clickedTag; // Visual active state tagButtons.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(); }); }); // Pagination handlers pageButtons.forEach((btn) => btn.addEventListener("click", () => { currentPage = Number(btn.getAttribute("data-page")) || 1; renderPage(); }) ); if (nextButton) { nextButton.addEventListener("click", () => { currentPage += 1; renderPage(); }); } // Ensure correct initial state on load 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; // Ensure non-submitting behavior subscribeButton.type = "button"; // Create or reuse feedback element right after the row 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@]+$/; // Reset error styles 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", }); } }); } });