Queue-North-Website/main.js

333 lines
8.5 KiB
JavaScript

document.addEventListener("DOMContentLoaded", function () {
const pages = Array.from(document.querySelectorAll(".page"));
const navLinks = Array.from(document.querySelectorAll("[data-route]"));
const dropdowns = Array.from(document.querySelectorAll(".nav-dropdown"));
const siteNav = document.querySelector(".site-nav");
const mobileNavToggle = document.querySelector(".mobile-nav-toggle");
function closeMobileNav() {
if (!siteNav || !mobileNavToggle) return;
siteNav.classList.remove("open");
mobileNavToggle.setAttribute("aria-expanded", "false");
}
function openMobileNav() {
if (!siteNav || !mobileNavToggle) return;
siteNav.classList.add("open");
mobileNavToggle.setAttribute("aria-expanded", "true");
}
function toggleMobileNav() {
if (!siteNav || !mobileNavToggle) return;
const willOpen = !siteNav.classList.contains("open");
if (willOpen) {
openMobileNav();
} else {
closeMobileNav();
closeAllDropdowns();
}
}
function closeAllDropdowns(exceptDropdown = null) {
if (mobileNavToggle) {
mobileNavToggle.addEventListener("click", (e) => {
e.preventDefault();
toggleMobileNav();
});
}
dropdowns.forEach((dropdown) => {
if (dropdown === exceptDropdown) return;
dropdown.classList.remove("open");
const toggle = dropdown.querySelector(".nav-dropdown-toggle");
if (toggle) {
toggle.setAttribute("aria-expanded", "false");
}
});
}
function syncDropdownState() {
dropdowns.forEach((dropdown) => {
const toggle = dropdown.querySelector(".nav-dropdown-toggle");
if (!toggle) return;
const expanded = dropdown.classList.contains("open") ? "true" : "false";
toggle.setAttribute("aria-expanded", expanded);
});
}
dropdowns.forEach((dropdown) => {
const toggle = dropdown.querySelector(".nav-dropdown-toggle");
const dropdownMenuLinks = Array.from(dropdown.querySelectorAll(".nav-dropdown-menu [data-route]"));
if (!toggle) return;
toggle.addEventListener("click", (e) => {
e.preventDefault();
const parentRoute = toggle.dataset.route || "home";
if (window.innerWidth <= 900) {
closeAllDropdowns();
window.location.hash = parentRoute === "eightx8" ? "8x8" : parentRoute;
setActive(parentRoute);
return;
}
const willOpen = !dropdown.classList.contains("open");
closeAllDropdowns(dropdown);
dropdown.classList.toggle("open", willOpen);
toggle.setAttribute("aria-expanded", willOpen ? "true" : "false");
if (willOpen) {
window.location.hash = parentRoute === "eightx8" ? "8x8" : parentRoute;
setActive(parentRoute);
}
});
dropdownMenuLinks.forEach((link) => {
link.addEventListener("click", () => {
closeAllDropdowns();
window.setTimeout(() => {
if (document.activeElement) {
document.activeElement.blur();
}
}, 0);
});
});
});
document.addEventListener("click", (e) => {
const clickedInsideDropdown = dropdowns.some((dropdown) => dropdown.contains(e.target));
const clickedInsideNav = siteNav ? siteNav.contains(e.target) : false;
const clickedMobileToggle = mobileNavToggle ? mobileNavToggle.contains(e.target) : false;
if (!clickedInsideDropdown) {
closeAllDropdowns();
}
if (window.innerWidth <= 900 && !clickedInsideNav && !clickedMobileToggle) {
closeMobileNav();
}
});
document.addEventListener("keydown", (e) => {
if (e.key === "Escape") {
closeAllDropdowns();
closeMobileNav();
}
});
const serviceRoutes = new Set([
"services",
"unified-communications",
"contact-center",
"managed-support",
"consulting-training",
"infrastructure-cabling",
"wireless-access",
"local-networking"
]);
const industryRoutes = new Set([
"industries",
"healthcare",
"retail",
"manufacturing",
"education-finance"
]);
function setActive(route) {
const requested = route || "home";
const hasRoute = pages.some((p) => p.dataset.page === requested);
const target = hasRoute ? requested : "home";
pages.forEach((p) => {
const isActive = p.dataset.page === target;
p.classList.toggle("active", isActive);
p.setAttribute("aria-hidden", isActive ? "false" : "true");
});
navLinks.forEach((a) => {
const routeName = a.dataset.route;
const isServiceParent = routeName === "services" && serviceRoutes.has(target);
const isIndustryParent = routeName === "industries" && industryRoutes.has(target);
const isActive = routeName === target || isServiceParent || isIndustryParent;
a.classList.toggle("active", isActive);
if (isActive) {
a.setAttribute("aria-current", "page");
} else {
a.removeAttribute("aria-current");
}
});
syncDropdownState();
window.scrollTo({ top: 0, behavior: "instant" });
if (target === "home") {
startHeroMotion();
} else {
stopHeroMotion();
}
}
function routeFromHash() {
const hash = (window.location.hash || "#home").replace("#", "").trim();
if (hash === "8x8") return "eightx8";
return hash || "home";
}
navLinks.forEach((a) => {
a.addEventListener("click", (e) => {
const route = a.dataset.route;
if (!route) return;
e.preventDefault();
window.location.hash = route === "eightx8" ? "8x8" : route;
setActive(route);
if (window.innerWidth <= 900) {
closeMobileNav();
closeAllDropdowns();
}
});
});
const cta = document.getElementById("cta-consultation");
if (cta) {
cta.addEventListener("click", () => {
window.location.hash = "contact";
setActive("contact");
closeMobileNav();
closeAllDropdowns();
});
}
window.addEventListener("hashchange", () => {
setActive(routeFromHash());
});
window.addEventListener("resize", () => {
if (window.innerWidth <= 900) {
closeAllDropdowns();
}
});
const yearSpan = document.getElementById("year");
if (yearSpan) yearSpan.textContent = new Date().getFullYear();
const rotatorEl = document.getElementById("hero-rotator-text");
const lockupEl = document.getElementById("hero-lockup");
const phrases = [
"Technical Precision",
"Operational Clarity",
"Human-Centered Support"
];
const phraseFadeIn = 1100;
const phraseHold = 1900;
const phraseFadeOut = 1100;
const betweenGap = 500;
const lockupFadeIn = 1200;
const lockupHold = 6500;
const loopGap = 800;
let heroTimer = null;
let heroRunId = 0;
function showEl(el) {
if (!el) return;
el.classList.add("is-visible");
el.classList.remove("is-hidden");
}
function hideEl(el) {
if (!el) return;
el.classList.remove("is-visible");
el.classList.add("is-hidden");
}
function setText(el, text) {
if (!el) return;
el.textContent = text;
}
function sleep(ms) {
return new Promise((resolve) => {
heroTimer = window.setTimeout(resolve, ms);
});
}
function clearHeroTimer() {
if (heroTimer) {
window.clearTimeout(heroTimer);
heroTimer = null;
}
}
async function heroLoop(runId) {
if (!rotatorEl || !lockupEl) return;
hideEl(lockupEl);
hideEl(rotatorEl);
while (runId === heroRunId) {
for (let i = 0; i < phrases.length; i++) {
if (runId !== heroRunId) return;
setText(rotatorEl, phrases[i]);
rotatorEl.style.setProperty("--hero-fade-ms", `${phraseFadeIn}ms`);
rotatorEl.style.setProperty("--hero-drift-ms", `${phraseFadeIn + phraseHold + phraseFadeOut}ms`);
showEl(rotatorEl);
await sleep(phraseFadeIn);
await sleep(phraseHold);
hideEl(rotatorEl);
await sleep(phraseFadeOut);
await sleep(betweenGap);
}
if (runId !== heroRunId) return;
lockupEl.style.setProperty("--hero-lockup-fade-ms", `${lockupFadeIn}ms`);
showEl(lockupEl);
await sleep(lockupFadeIn);
await sleep(lockupHold);
hideEl(lockupEl);
await sleep(loopGap);
}
}
function startHeroMotion() {
if (!rotatorEl || !lockupEl) return;
heroRunId += 1;
clearHeroTimer();
hideEl(lockupEl);
hideEl(rotatorEl);
heroLoop(heroRunId);
}
function stopHeroMotion() {
heroRunId += 1;
clearHeroTimer();
hideEl(lockupEl);
hideEl(rotatorEl);
}
window.addEventListener("resize", () => {
if (window.innerWidth > 900) {
closeMobileNav();
}
});
setActive(routeFromHash());
});