(() => { "use strict"; const header = document.getElementById("siteHeader"); const navLinks = Array.from(document.querySelectorAll('.site-header .nav-link[href^="#"]')); const sections = navLinks .map((link) => document.querySelector(link.getAttribute("href"))) .filter(Boolean); const updateHeader = () => { if (!header) return; header.classList.toggle("is-sticky", window.scrollY > 90); }; const updateActiveNavigation = () => { if (!navLinks.length || !sections.length) return; const marker = window.scrollY + Math.min(260, window.innerHeight * 0.32); let activeId = sections[0].id; sections.forEach((section) => { if (section.offsetTop <= marker) activeId = section.id; }); navLinks.forEach((link) => { const isActive = link.getAttribute("href") === `#${activeId}`; link.classList.toggle("active", isActive); if (isActive) link.setAttribute("aria-current", "page"); else link.removeAttribute("aria-current"); }); }; let ticking = false; const onScroll = () => { if (ticking) return; ticking = true; requestAnimationFrame(() => { updateHeader(); updateActiveNavigation(); ticking = false; }); }; window.addEventListener("scroll", onScroll, { passive: true }); window.addEventListener("resize", updateActiveNavigation, { passive: true }); updateHeader(); updateActiveNavigation(); // Smooth in-page navigation with sticky-header compensation. document.querySelectorAll('a[href^="#"]').forEach((link) => { link.addEventListener("click", (event) => { const selector = link.getAttribute("href"); if (!selector || selector === "#") return; const destination = document.querySelector(selector); if (!destination) return; event.preventDefault(); destination.scrollIntoView({ behavior: window.matchMedia("(prefers-reduced-motion: reduce)").matches ? "auto" : "smooth", block: "start" }); }); }); // Static demo registration form: validates locally and confirms submission. const form = document.getElementById("registrationForm"); const message = document.getElementById("formMessage"); form?.addEventListener("submit", (event) => { event.preventDefault(); if (!form.checkValidity()) { form.classList.add("was-validated"); if (message) message.textContent = "Please complete all required fields."; return; } form.classList.remove("was-validated"); if (message) { message.textContent = "Thank you. Your registration interest has been recorded in this demo."; } const submitButton = form.querySelector('button[type="submit"]'); if (submitButton) { const originalText = submitButton.textContent; submitButton.disabled = true; submitButton.textContent = "Submitted"; window.setTimeout(() => { submitButton.disabled = false; submitButton.textContent = originalText; form.reset(); if (message) message.textContent = ""; }, 3500); } }); // Escape closes an open offcanvas/modal through Bootstrap's native handlers. document.addEventListener("keydown", (event) => { if (event.key !== "Escape") return; document.activeElement?.blur?.(); }); })(); /* Back to Top Button */ (() => { "use strict"; const backToTopButton = document.getElementById("backToTop"); if (!backToTopButton) return; const reducedMotionQuery = window.matchMedia( "(prefers-reduced-motion: reduce)" ); const updateBackToTopButton = () => { const shouldShow = window.scrollY > 500; backToTopButton.classList.toggle("is-visible", shouldShow); backToTopButton.setAttribute("aria-hidden", String(!shouldShow)); backToTopButton.tabIndex = shouldShow ? 0 : -1; }; let scrollTicking = false; window.addEventListener( "scroll", () => { if (scrollTicking) return; scrollTicking = true; window.requestAnimationFrame(() => { updateBackToTopButton(); scrollTicking = false; }); }, { passive: true } ); backToTopButton.addEventListener("click", () => { window.scrollTo({ top: 0, left: 0, behavior: reducedMotionQuery.matches ? "auto" : "smooth" }); }); updateBackToTopButton(); })(); /* FAQ Accordion Module */ (() => { "use strict"; const initFaqAccordion = () => { const triggers = document.querySelectorAll(".faq-trigger"); if (!triggers.length) return; triggers.forEach((trigger) => { trigger.addEventListener("click", () => { const expanded = trigger.getAttribute("aria-expanded") === "true"; const targetId = trigger.getAttribute("aria-controls"); const targetPanel = document.getElementById(targetId); if (!targetPanel) return; const nextState = !expanded; trigger.setAttribute("aria-expanded", String(nextState)); const faqItem = trigger.closest(".faq-item"); if (faqItem) { faqItem.classList.toggle("is-open", nextState); } if (expanded) { // Smoothly collapse targetPanel.style.maxHeight = targetPanel.scrollHeight + "px"; requestAnimationFrame(() => { targetPanel.style.maxHeight = "0px"; }); window.setTimeout(() => { if (trigger.getAttribute("aria-expanded") === "false") { targetPanel.hidden = true; } }, 350); } else { // Smoothly expand targetPanel.hidden = false; targetPanel.style.maxHeight = "0px"; const contentHeight = targetPanel.scrollHeight; requestAnimationFrame(() => { targetPanel.style.maxHeight = contentHeight + "px"; }); window.setTimeout(() => { if (trigger.getAttribute("aria-expanded") === "true") { targetPanel.style.maxHeight = "none"; } }, 350); } }); }); }; if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", initFaqAccordion); } else { initFaqAccordion(); } })(); /* Mobile Navigation & Offcanvas Controller */ (() => { "use strict"; const initMobileMenu = () => { const togglers = document.querySelectorAll(".navbar-toggler"); const mobileMenu = document.getElementById("mobileMenu"); if (!mobileMenu) return; const getOffcanvasInstance = () => { if (window.bootstrap && window.bootstrap.Offcanvas) { return window.bootstrap.Offcanvas.getInstance(mobileMenu) || new window.bootstrap.Offcanvas(mobileMenu); } return null; }; const closeMenu = () => { const bsOffcanvas = getOffcanvasInstance(); if (bsOffcanvas) { bsOffcanvas.hide(); } else { mobileMenu.classList.remove("show"); document.querySelectorAll(".offcanvas-backdrop").forEach((el) => el.remove()); document.body.classList.remove("modal-open", "offcanvas-open"); document.body.style.overflow = ""; } togglers.forEach((t) => { t.setAttribute("aria-expanded", "false"); t.classList.remove("is-open"); }); }; // Sync hamburger icon state on Bootstrap offcanvas show/hide events mobileMenu.addEventListener("show.bs.offcanvas", () => { togglers.forEach((t) => { t.setAttribute("aria-expanded", "true"); t.classList.add("is-open"); }); }); mobileMenu.addEventListener("hide.bs.offcanvas", () => { togglers.forEach((t) => { t.setAttribute("aria-expanded", "false"); t.classList.remove("is-open"); }); }); mobileMenu.addEventListener("hidden.bs.offcanvas", () => { togglers.forEach((t) => { t.setAttribute("aria-expanded", "false"); t.classList.remove("is-open"); }); document.querySelectorAll(".offcanvas-backdrop").forEach((b) => b.remove()); document.body.style.overflow = ""; }); // Close menu when clicking any nav link inside the mobile offcanvas menu const offcanvasLinks = mobileMenu.querySelectorAll("a"); offcanvasLinks.forEach((link) => { link.addEventListener("click", (e) => { const href = link.getAttribute("href"); // Hide offcanvas menu closeMenu(); if (href && href.startsWith("#") && href.length > 1) { const target = document.querySelector(href); if (target) { e.preventDefault(); setTimeout(() => { target.scrollIntoView({ behavior: window.matchMedia("(prefers-reduced-motion: reduce)").matches ? "auto" : "smooth", block: "start" }); }, 200); } } }); }); }; if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", initMobileMenu); } else { initMobileMenu(); } })();