/** * NaveedK Homepage Interactions * Production build: accessible, scoped, responsive, and Elementor-safe. * * Replace the previous inline homepage script with this file. * Load once, preferably in the footer with `defer`. */ (() => { "use strict";const INSTANCE_KEY = "__NGP_HOMEPAGE_INSTANCE__"; const page = document.querySelector(".ngp-page[data-page-component='naveedk-homepage'], .ngp-page#ngp-homepage");if (!page) return;// Prevent duplicate execution when Elementor or an optimisation plugin re-runs scripts. if (window[INSTANCE_KEY]?.page === page) return; window[INSTANCE_KEY]?.destroy?.();const controller = new AbortController(); const { signal } = controller; const cleanups = [];const $ = (selector, root = page) => root.querySelector(selector); const $$ = (selector, root = page) => Array.from(root.querySelectorAll(selector));const on = (target, type, listener, options = {}) => { target?.addEventListener(type, listener, { ...options, signal }); };const finePointerQuery = window.matchMedia("(hover: hover) and (pointer: fine)"); const reducedMotionQuery = window.matchMedia("(prefers-reduced-motion: reduce)"); const desktopScrollQuery = window.matchMedia("(min-width: 901px)"); const desktopCommandQuery = window.matchMedia("(min-width: 781px)");let hasFinePointer = finePointerQuery.matches; let prefersReducedMotion = reducedMotionQuery.matches;page.classList.add("ngp-js"); page.dataset.ngpJsInitialized = "true";const listenToMediaQuery = (query, listener) => { if (typeof query.addEventListener === "function") { query.addEventListener("change", listener, { signal }); return; }// Legacy Safari fallback cannot use AbortSignal. query.addListener(listener); cleanups.push(() => query.removeListener(listener)); };/* -------------------------------------------------------------------------- Custom cursor -------------------------------------------------------------------------- */const cursor = $(".ngp-cursor"); const cursorRing = $(".ngp-cursor-ring");if (cursor && cursorRing) { const hoverSelector = [ "a", "button", "summary", "label", "input", "textarea", "select", "[role='button']", ".ngp-bcard", ".ngp-service-card", ".ngp-hcase-card", ".ngp-trust-card", ".ngp-authority-link" ].join(",");const nativePointerSelector = [ "a", "button", "summary", "label", "input", "textarea", "select", "[role='button']", ".ngp-btn", ".ngp-float", ".ngp-cmd-list button", ".ngp-service-card", ".ngp-insight", ".ngp-authority-link" ].join(",");let pointerX = 0; let pointerY = 0; let ringX = 0; let ringY = 0; let cursorFrame = 0; let pointerInitialised = false; let activeHoverTarget = null;const stopCursorFrame = () => { if (!cursorFrame) return; window.cancelAnimationFrame(cursorFrame); cursorFrame = 0; };const resetCursor = () => { stopCursorFrame(); activeHoverTarget = null; pointerInitialised = false; cursorRing.classList.remove("is-hover"); page.classList.remove("ngp-use-native-pointer"); };const renderCursorRing = () => { if (!hasFinePointer || prefersReducedMotion || document.hidden || !page.isConnected) { stopCursorFrame(); return; }ringX += (pointerX - ringX) * 0.14; ringY += (pointerY - ringY) * 0.14;cursorRing.style.transform = `translate3d(${ringX - 19}px, ${ringY - 19}px, 0)`;const remainingDistance = Math.abs(pointerX - ringX) + Math.abs(pointerY - ringY);if (remainingDistance > 0.2) { cursorFrame = window.requestAnimationFrame(renderCursorRing); } else { cursorFrame = 0; } };const requestCursorFrame = () => { if (!cursorFrame) { cursorFrame = window.requestAnimationFrame(renderCursorRing); } };on(document, "pointermove", (event) => { if (!hasFinePointer || prefersReducedMotion || document.hidden) return;pointerX = event.clientX; pointerY = event.clientY;if (!pointerInitialised) { ringX = pointerX; ringY = pointerY; pointerInitialised = true; }cursor.style.transform = `translate3d(${pointerX - 5}px, ${pointerY - 5}px, 0)`;requestCursorFrame(); }, { passive: true });const applyHoverState = (target) => { activeHoverTarget = target; cursorRing.classList.add("is-hover"); page.classList.toggle( "ngp-use-native-pointer", target.matches(nativePointerSelector) ); };on(page, "pointerover", (event) => { if (!hasFinePointer || prefersReducedMotion) return;const target = event.target.closest?.(hoverSelector); if (!target || !page.contains(target)) return; if (event.relatedTarget && target.contains(event.relatedTarget)) return;applyHoverState(target); });on(page, "pointerout", (event) => { if (!activeHoverTarget) return; if (event.relatedTarget && activeHoverTarget.contains(event.relatedTarget)) return;const nextTarget = event.relatedTarget?.closest?.(hoverSelector);if (nextTarget && page.contains(nextTarget)) { applyHoverState(nextTarget); return; }activeHoverTarget = null; cursorRing.classList.remove("is-hover"); page.classList.remove("ngp-use-native-pointer"); });on(page, "pointerleave", resetCursor); on(window, "blur", resetCursor); on(document, "visibilitychange", () => { if (document.hidden) resetCursor(); });listenToMediaQuery(finePointerQuery, (event) => { hasFinePointer = event.matches; if (!hasFinePointer) resetCursor(); });listenToMediaQuery(reducedMotionQuery, (event) => { prefersReducedMotion = event.matches; if (prefersReducedMotion) resetCursor(); });cleanups.push(resetCursor); }/* -------------------------------------------------------------------------- Rotating hero phrase -------------------------------------------------------------------------- */const word = $("#ngpWord"); const words = ["Get Chosen.", "Earn Trust.", "Get Contacted.", "Grow."];if (word) { const dynamicWrapper = word.closest(".ngp-dynamic"); const heading = word.closest("h1"); const originalText = word.textContent.trim() || words[0];let wordIndex = Math.max(0, words.indexOf(originalText)); let rotationTimer = 0; let transitionTimer = 0; let measurementFrame = 0;// Keep the accessible H1 stable while the visual phrase changes. if (heading && !heading.hasAttribute("aria-label")) { heading.setAttribute("aria-label", heading.textContent.replace(/\s+/g, " ").trim()); } word.setAttribute("aria-hidden", "true");const clearWordTimers = () => { window.clearTimeout(rotationTimer); window.clearTimeout(transitionTimer); rotationTimer = 0; transitionTimer = 0; };const measureLongestPhrase = () => { if (!dynamicWrapper || window.innerWidth < 480) { dynamicWrapper?.style.removeProperty("min-inline-size"); return; }window.cancelAnimationFrame(measurementFrame); measurementFrame = window.requestAnimationFrame(() => { const probe = document.createElement("span"); const computed = window.getComputedStyle(word);probe.setAttribute("aria-hidden", "true"); probe.style.cssText = [ "position:fixed", "left:-9999px", "top:-9999px", "visibility:hidden", "white-space:nowrap", `font:${computed.font}`, `font-family:${computed.fontFamily}`, `font-size:${computed.fontSize}`, `font-weight:${computed.fontWeight}`, `font-style:${computed.fontStyle}`, `letter-spacing:${computed.letterSpacing}`, `text-transform:${computed.textTransform}` ].join(";");document.body.appendChild(probe);let maximumWidth = 0; words.forEach((phrase) => { probe.textContent = phrase; maximumWidth = Math.max(maximumWidth, probe.getBoundingClientRect().width); });probe.remove(); dynamicWrapper.style.display = "inline-block"; dynamicWrapper.style.minInlineSize = `${Math.ceil(maximumWidth)}px`; }); };const scheduleRotation = () => { clearWordTimers();if (prefersReducedMotion || document.hidden || !word.isConnected) { word.textContent = originalText; word.style.removeProperty("opacity"); word.style.removeProperty("transform"); word.style.removeProperty("transition"); return; }rotationTimer = window.setTimeout(rotateWord, 2400); };const rotateWord = () => { if (prefersReducedMotion || document.hidden || !word.isConnected) { scheduleRotation(); return; }wordIndex = (wordIndex + 1) % words.length; word.style.transition = "opacity 0.28s ease, transform 0.28s ease"; word.style.opacity = "0"; word.style.transform = "translate3d(0, -10px, 0)";transitionTimer = window.setTimeout(() => { word.textContent = words[wordIndex]; word.style.transition = "none"; word.style.transform = "translate3d(0, 10px, 0)";window.requestAnimationFrame(() => { word.style.transition = "opacity 0.28s ease, transform 0.28s ease"; word.style.opacity = "1"; word.style.transform = "translate3d(0, 0, 0)"; scheduleRotation(); }); }, 240); };on(document, "visibilitychange", scheduleRotation); on(window, "resize", measureLongestPhrase, { passive: true });listenToMediaQuery(reducedMotionQuery, (event) => { prefersReducedMotion = event.matches; scheduleRotation(); });if (document.fonts?.ready) { document.fonts.ready.then(measureLongestPhrase).catch(() => {}); } else { measureLongestPhrase(); }measureLongestPhrase(); scheduleRotation();cleanups.push(() => { clearWordTimers(); window.cancelAnimationFrame(measurementFrame); }); }/* -------------------------------------------------------------------------- Reveal effects -------------------------------------------------------------------------- */const revealElements = $$(".reveal"); let revealObserver = null;const initialiseReveals = () => { revealObserver?.disconnect(); revealObserver = null;if ( prefersReducedMotion || !("IntersectionObserver" in window) ) { revealElements.forEach((element) => element.classList.add("visible")); return; }revealObserver = new IntersectionObserver((entries, observer) => { entries.forEach((entry) => { if (!entry.isIntersecting) return; entry.target.classList.add("visible"); observer.unobserve(entry.target); }); }, { threshold: 0.12, rootMargin: "0px 0px -40px 0px" });revealElements.forEach((element) => { const rect = element.getBoundingClientRect(); const nearViewport = rect.top < window.innerHeight * 1.1 && rect.bottom > -40;if (nearViewport) { element.classList.add("visible"); } else { element.classList.remove("visible"); revealObserver.observe(element); } }); };listenToMediaQuery(reducedMotionQuery, (event) => { prefersReducedMotion = event.matches; initialiseReveals(); });initialiseReveals(); cleanups.push(() => revealObserver?.disconnect());/* -------------------------------------------------------------------------- Horizontal growth-scenario section -------------------------------------------------------------------------- */const stages = $$("[data-hscroll]"); let horizontalMetrics = []; let horizontalUpdateFrame = 0; let horizontalMeasureFrame = 0; let horizontalResizeObserver = null;const getStickyOffset = () => { let offset = 0;const adminBar = document.getElementById("wpadminbar"); if (adminBar) { const style = window.getComputedStyle(adminBar); const rect = adminBar.getBoundingClientRect();if (style.position === "fixed" && rect.height > 0 && rect.bottom > 0) { offset = Math.max(offset, rect.bottom); } }const headerSelectors = [ ".elementor-location-header", "header.site-header", ".site-header", "header[role='banner']" ].join(",");document.querySelectorAll(headerSelectors).forEach((header) => { if (page.contains(header)) return;const style = window.getComputedStyle(header); if (style.position !== "fixed" && style.position !== "sticky") return;const rect = header.getBoundingClientRect(); const beginsNearCurrentOffset = rect.top <= offset + 3;if (rect.height > 0 && rect.bottom > offset && beginsNearCurrentOffset) { offset = Math.max(offset, rect.bottom); } });return Math.max(0, Math.round(offset)); };const resetHorizontalStage = (metric) => { metric.stage.classList.remove("is-before", "is-fixed", "is-after"); metric.stage.style.setProperty("--h-distance", "0px"); metric.stage.style.setProperty("--pin-total", "auto"); metric.track.style.transform = "translate3d(0, 0, 0)"; metric.sticky?.style.removeProperty("top"); metric.sticky?.style.removeProperty("height"); };const horizontalEnabled = () => desktopScrollQuery.matches && !prefersReducedMotion;const measureHorizontal = () => { window.cancelAnimationFrame(horizontalMeasureFrame);horizontalMeasureFrame = window.requestAnimationFrame(() => { const scrollY = window.scrollY || window.pageYOffset; const stickyOffset = getStickyOffset();horizontalMetrics = stages .map((stage) => { const viewport = stage.querySelector(".ngp-hscroll-viewport"); const track = stage.querySelector(".ngp-hcase-track"); const sticky = stage.querySelector(".ngp-hscroll-sticky");if (!viewport || !track || !sticky) return null;const metric = { stage, viewport, track, sticky };stage.classList.remove("is-before", "is-fixed", "is-after"); track.style.transform = "translate3d(0, 0, 0)"; sticky.style.removeProperty("top"); sticky.style.removeProperty("height");if (!horizontalEnabled()) { resetHorizontalStage(metric); return { ...metric, distance: 0, top: 0, end: 0 }; }const distance = Math.max(0, track.scrollWidth - viewport.clientWidth); const availableHeight = Math.max(window.innerHeight - stickyOffset, 620); const totalHeight = availableHeight + distance;stage.style.setProperty("--h-distance", `${distance}px`); stage.style.setProperty("--pin-total", `${totalHeight}px`);const top = stage.getBoundingClientRect().top + scrollY;return { ...metric, distance, top, end: top + distance }; }) .filter(Boolean);updateHorizontal(); }); };const applyFixedStickyOffset = (sticky, offset) => { sticky.style.setProperty("top", `${offset}px`, "important"); sticky.style.setProperty( "height", `calc(100svh - ${offset}px)`, "important" ); };const clearFixedStickyOffset = (sticky) => { sticky.style.removeProperty("top"); sticky.style.removeProperty("height"); };const updateHorizontal = () => { window.cancelAnimationFrame(horizontalUpdateFrame); horizontalUpdateFrame = 0;if (!horizontalEnabled()) { horizontalMetrics.forEach(resetHorizontalStage); return; }const scrollY = window.scrollY || window.pageYOffset; const stickyOffset = getStickyOffset();horizontalMetrics.forEach((metric) => { const { stage, sticky, track, distance, top, end } = metric;stage.classList.remove("is-before", "is-fixed", "is-after");if (!distance || scrollY < top) { stage.classList.add("is-before"); clearFixedStickyOffset(sticky); track.style.transform = "translate3d(0, 0, 0)"; return; }if (scrollY > end) { stage.classList.add("is-after"); clearFixedStickyOffset(sticky); track.style.transform = `translate3d(${-distance}px, 0, 0)`; return; }stage.classList.add("is-fixed"); applyFixedStickyOffset(sticky, stickyOffset);const progress = Math.min(1, Math.max(0, (scrollY - top) / distance)); track.style.transform = `translate3d(${-distance * progress}px, 0, 0)`; }); };const requestHorizontalUpdate = () => { if (horizontalUpdateFrame) return; horizontalUpdateFrame = window.requestAnimationFrame(updateHorizontal); };on(window, "scroll", requestHorizontalUpdate, { passive: true }); on(window, "resize", measureHorizontal, { passive: true }); on(window, "orientationchange", measureHorizontal, { passive: true }); on(window, "load", measureHorizontal, { once: true, passive: true });listenToMediaQuery(desktopScrollQuery, measureHorizontal); listenToMediaQuery(reducedMotionQuery, (event) => { prefersReducedMotion = event.matches; measureHorizontal(); });if ("ResizeObserver" in window && stages.length) { horizontalResizeObserver = new ResizeObserver(measureHorizontal);stages.forEach((stage) => { horizontalResizeObserver.observe(stage); const viewport = stage.querySelector(".ngp-hscroll-viewport"); const track = stage.querySelector(".ngp-hcase-track"); if (viewport) horizontalResizeObserver.observe(viewport); if (track) horizontalResizeObserver.observe(track); }); }if (document.fonts?.ready) { document.fonts.ready.then(measureHorizontal).catch(() => {}); }measureHorizontal();cleanups.push(() => { window.cancelAnimationFrame(horizontalUpdateFrame); window.cancelAnimationFrame(horizontalMeasureFrame); horizontalResizeObserver?.disconnect(); horizontalMetrics.forEach(resetHorizontalStage); });/* -------------------------------------------------------------------------- Growth command menu -------------------------------------------------------------------------- */const commandDialog = $("#ngpCmd"); const commandOpenButton = $("#ngpCmdOpen"); const commandCloseButton = $("#ngpCmdClose"); const commandInput = $("#ngpCmdInput"); const commandList = $("#ngpCmdList"); const allCommandItems = $$(".ngp-cmd-list button");if ( commandDialog && commandOpenButton && commandCloseButton && commandInput && commandList ) { let commandIsOpen = false; let activeIndex = 0; let visibleCommandItems = [...allCommandItems]; let previouslyFocusedElement = null; let previousBodyOverflow = ""; let previousBodyPaddingRight = "";const commandStatus = document.createElement("p"); commandStatus.className = "ngp-sr"; commandStatus.id = "ngpCmdStatus"; commandStatus.setAttribute("role", "status"); commandStatus.setAttribute("aria-live", "polite"); commandDialog.querySelector(".ngp-cmd-panel")?.appendChild(commandStatus);commandOpenButton.setAttribute("aria-controls", commandDialog.id); commandOpenButton.setAttribute("aria-expanded", "false");commandInput.setAttribute("role", "combobox"); commandInput.setAttribute("aria-autocomplete", "list"); commandInput.setAttribute("aria-controls", commandList.id); commandInput.setAttribute("aria-expanded", "false"); commandInput.setAttribute("aria-describedby", commandStatus.id);commandList.setAttribute("role", "listbox");allCommandItems.forEach((button, itemIndex) => { if (!button.id) button.id = `ngpCmdOption${itemIndex + 1}`; button.setAttribute("role", "option"); button.setAttribute("aria-selected", "false"); });const setActiveCommand = (requestedIndex, shouldScroll = true) => { if (!visibleCommandItems.length) { activeIndex = 0; commandInput.removeAttribute("aria-activedescendant"); return; }const previousItem = visibleCommandItems[activeIndex]; previousItem?.classList.remove("is-active"); previousItem?.setAttribute("aria-selected", "false");activeIndex = (requestedIndex + visibleCommandItems.length) % visibleCommandItems.length;const activeItem = visibleCommandItems[activeIndex]; activeItem.classList.add("is-active"); activeItem.setAttribute("aria-selected", "true"); commandInput.setAttribute("aria-activedescendant", activeItem.id);if (shouldScroll) { activeItem.scrollIntoView({ block: "nearest" }); } };const filterCommandItems = () => { const query = commandInput.value.toLocaleLowerCase().trim(); visibleCommandItems = [];allCommandItems.forEach((button) => { const matches = !query || button.textContent.toLocaleLowerCase().includes(query);button.hidden = !matches; button.classList.remove("is-active"); button.setAttribute("aria-selected", "false");if (matches) visibleCommandItems.push(button); });activeIndex = 0; setActiveCommand(0, false);const resultCount = visibleCommandItems.length; commandStatus.textContent = resultCount ? `${resultCount} option${resultCount === 1 ? "" : "s"} available.` : "No matching growth options found."; };const lockPageScroll = () => { previousBodyOverflow = document.body.style.overflow; previousBodyPaddingRight = document.body.style.paddingRight;const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;document.body.style.overflow = "hidden"; if (scrollbarWidth > 0) { document.body.style.paddingRight = `${scrollbarWidth}px`; } };const unlockPageScroll = () => { document.body.style.overflow = previousBodyOverflow; document.body.style.paddingRight = previousBodyPaddingRight; };const openCommand = () => { if ( commandIsOpen || !desktopCommandQuery.matches || !commandDialog.isConnected ) { return; }previouslyFocusedElement = document.activeElement instanceof HTMLElement ? document.activeElement : commandOpenButton;commandDialog.removeAttribute("inert"); commandDialog.classList.add("is-open"); commandDialog.setAttribute("aria-hidden", "false"); commandOpenButton.setAttribute("aria-expanded", "true"); commandInput.setAttribute("aria-expanded", "true");commandIsOpen = true; filterCommandItems(); lockPageScroll();window.requestAnimationFrame(() => commandInput.focus({ preventScroll: true })); };const closeCommand = ({ restoreFocus = true } = {}) => { if (!commandIsOpen) return;commandDialog.classList.remove("is-open"); commandDialog.setAttribute("aria-hidden", "true"); commandDialog.setAttribute("inert", ""); commandOpenButton.setAttribute("aria-expanded", "false"); commandInput.setAttribute("aria-expanded", "false"); commandInput.removeAttribute("aria-activedescendant");commandIsOpen = false; commandInput.value = ""; filterCommandItems(); unlockPageScroll();if (restoreFocus) { window.requestAnimationFrame(() => { const target = previouslyFocusedElement?.isConnected ? previouslyFocusedElement : commandOpenButton; target?.focus?.({ preventScroll: true }); }); } };const getFocusableElements = () => [ commandInput, commandCloseButton, ...visibleCommandItems.filter((button) => !button.hidden && !button.disabled) ].filter((element) => element && !element.hidden && element.getAttribute("aria-hidden") !== "true" );const isSafeCommandUrl = (value) => { if (!value) return false;try { const url = new URL(value, window.location.href); return ["http:", "https:", "mailto:", "tel:"].includes(url.protocol); } catch { return false; } };const activateCommandItem = (button) => { const targetUrl = button?.dataset.url?.trim(); if (!isSafeCommandUrl(targetUrl)) return;closeCommand({ restoreFocus: false }); window.location.assign(targetUrl); };on(commandOpenButton, "click", openCommand); on(commandCloseButton, "click", () => closeCommand()); on(commandInput, "input", filterCommandItems);on(commandDialog, "click", (event) => { if (event.target === commandDialog) closeCommand(); });on(commandList, "pointerover", (event) => { const button = event.target.closest("button[data-url]"); if (!button || button.hidden) return;const itemIndex = visibleCommandItems.indexOf(button); if (itemIndex >= 0) setActiveCommand(itemIndex, false); });on(commandList, "click", (event) => { const button = event.target.closest("button[data-url]"); if (!button || button.hidden) return; activateCommandItem(button); });on(document, "keydown", (event) => { const commandShortcut = (event.metaKey || event.ctrlKey) && !event.altKey && !event.shiftKey && event.key.toLocaleLowerCase() === "k";if (commandShortcut && desktopCommandQuery.matches) { event.preventDefault(); commandIsOpen ? closeCommand() : openCommand(); return; }if (!commandIsOpen) return;if (event.key === "Escape") { event.preventDefault(); closeCommand(); return; }if (event.key === "ArrowDown") { event.preventDefault(); setActiveCommand(activeIndex + 1); return; }if (event.key === "ArrowUp") { event.preventDefault(); setActiveCommand(activeIndex - 1); return; }if (event.key === "Enter" && document.activeElement === commandInput) { event.preventDefault(); activateCommandItem(visibleCommandItems[activeIndex]); return; }if (event.key === "Tab") { const focusableElements = getFocusableElements(); if (!focusableElements.length) return;const firstElement = focusableElements[0]; const lastElement = focusableElements[focusableElements.length - 1];if (event.shiftKey && document.activeElement === firstElement) { event.preventDefault(); lastElement.focus(); } else if (!event.shiftKey && document.activeElement === lastElement) { event.preventDefault(); firstElement.focus(); } } });listenToMediaQuery(desktopCommandQuery, (event) => { if (!event.matches && commandIsOpen) closeCommand(); });// Display the correct platform shortcut without relying on deprecated APIs. const shortcutLabel = commandOpenButton.querySelector("kbd"); if (shortcutLabel) { const platform = navigator.userAgentData?.platform || navigator.platform || ""; shortcutLabel.textContent = /mac|iphone|ipad|ipod/i.test(platform) ? "⌘K" : "Ctrl K"; }filterCommandItems();cleanups.push(() => { closeCommand({ restoreFocus: false }); commandStatus.remove(); }); }/* -------------------------------------------------------------------------- Lifecycle and cleanup -------------------------------------------------------------------------- */const destroy = () => { cleanups.reverse().forEach((cleanup) => { try { cleanup(); } catch { // Cleanup should never block the remaining teardown steps. } });controller.abort(); page.classList.remove("ngp-js", "ngp-use-native-pointer"); delete page.dataset.ngpJsInitialized;if (window[INSTANCE_KEY]?.page === page) { delete window[INSTANCE_KEY]; } };window[INSTANCE_KEY] = { page, destroy };// Clean up automatically if Elementor replaces the widget in the editor. const removalObserver = new MutationObserver(() => { if (!page.isConnected) { removalObserver.disconnect(); destroy(); } });removalObserver.observe(document.documentElement, { childList: true, subtree: true });cleanups.push(() => removalObserver.disconnect()); })();