polished webspages and completed implementation.

This commit is contained in:
George Birikorang 2025-08-19 05:16:00 -04:00
parent 23791a2768
commit 9a3a106fca
7 changed files with 1194 additions and 172 deletions

View file

@ -1,8 +1,366 @@
// Update year in footer
// 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",
});
}
});
}
});