update image and about section
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
* Menu open/close + sidebar state is read from HeaderUIProvider so BodyOverlay (sibling at body level) can react.
|
||||
*/
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { MouseEvent, useEffect, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { usePathname } from "next/navigation";
|
||||
@@ -44,6 +44,34 @@ export default function Header() {
|
||||
const dmHeaderActive = (key: string) =>
|
||||
(CURRENT_PAGE_ALIASES[key] ?? []).includes(currentPage) ? " active" : "";
|
||||
|
||||
const scrollToNavTarget = (targetId: string) => {
|
||||
const target = document.getElementById(targetId);
|
||||
if (target) {
|
||||
target.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
return;
|
||||
}
|
||||
|
||||
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||
};
|
||||
|
||||
const handleNavClick = (
|
||||
event: MouseEvent<HTMLAnchorElement>,
|
||||
targetPath: string,
|
||||
targetId: string,
|
||||
shouldCloseMenu = false,
|
||||
) => {
|
||||
if (shouldCloseMenu) closeAll();
|
||||
|
||||
if (pathname !== targetPath) return;
|
||||
|
||||
event.preventDefault();
|
||||
const targetUrl = `${targetPath}#${targetId}`;
|
||||
if (`${window.location.pathname}${window.location.hash}` !== targetUrl) {
|
||||
window.history.pushState(null, "", targetUrl);
|
||||
}
|
||||
requestAnimationFrame(() => scrollToNavTarget(targetId));
|
||||
};
|
||||
|
||||
// Mirror of header.php <script> block (lines 628-660):
|
||||
// - on doc.ready: $('.header-hide-until-scroll').addClass('header-visible-scrolled')
|
||||
// - on scroll: toggleClass('dm-header-scrolled', scrollTop > 50)
|
||||
@@ -406,22 +434,22 @@ export default function Header() {
|
||||
<nav>
|
||||
<ul id="menu-main-menu" className="main-menu">
|
||||
<li id="menu-item-10508" className={`menu-item menu-item-type-custom menu-item-object-custom menu-item-10508${dmHeaderActive("home")}`}>
|
||||
<Link href="/">Home</Link>
|
||||
<Link href="/#home" onClick={(event) => handleNavClick(event, "/", "home")}>Home</Link>
|
||||
</li>
|
||||
<li id="menu-item-10509" className={`menu-item menu-item-type-custom menu-item-object-custom menu-item-10509${dmHeaderActive("how-it-works")}`}>
|
||||
<Link href="/how-it-works">How It Works</Link>
|
||||
<Link href="/how-it-works#how-it-works" onClick={(event) => handleNavClick(event, "/how-it-works", "how-it-works")}>How It Works</Link>
|
||||
</li>
|
||||
<li id="menu-item-10510" className={`menu-item menu-item-type-custom menu-item-object-custom menu-item-10510${dmHeaderActive("miletruth")}`}>
|
||||
<Link href="/miletruth">MileTruth™ AI</Link>
|
||||
<Link href="/miletruth#miletruth" onClick={(event) => handleNavClick(event, "/miletruth", "miletruth")}>MileTruth™ AI</Link>
|
||||
</li>
|
||||
<li id="menu-item-10511" className={`menu-item menu-item-type-custom menu-item-10511${dmHeaderActive("solutions")}`}>
|
||||
<Link href="/solutions">Solutions</Link>
|
||||
<Link href="/solutions#solutions" onClick={(event) => handleNavClick(event, "/solutions", "solutions")}>Solutions</Link>
|
||||
</li>
|
||||
<li id="menu-item-10512" className={`menu-item menu-item-type-custom menu-item-object-custom menu-item-10512${dmHeaderActive("about")}`}>
|
||||
<Link href="/about-us">About</Link>
|
||||
<Link href="/about-us#about" onClick={(event) => handleNavClick(event, "/about-us", "about")}>About</Link>
|
||||
</li>
|
||||
<li id="menu-item-10535" className={`menu-item menu-item-type-post_type menu-item-object-page menu-item-10535${dmHeaderActive("blogs")}`}>
|
||||
<Link href="/blog">Blogs</Link>
|
||||
<Link href="/blog#blogs" onClick={(event) => handleNavClick(event, "/blog", "blogs")}>Blogs</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
@@ -454,22 +482,22 @@ export default function Header() {
|
||||
<nav>
|
||||
<ul id="menu-main-menu-1" className="main-menu">
|
||||
<li className={`menu-item menu-item-type-custom menu-item-object-custom menu-item-10508${dmHeaderActive("home")}`}>
|
||||
<Link href="/" onClick={closeAll}>Home</Link>
|
||||
<Link href="/#home" onClick={(event) => handleNavClick(event, "/", "home", true)}>Home</Link>
|
||||
</li>
|
||||
<li className={`menu-item menu-item-type-custom menu-item-object-custom menu-item-10509${dmHeaderActive("how-it-works")}`}>
|
||||
<Link href="/how-it-works" onClick={closeAll}>How It Works</Link>
|
||||
<Link href="/how-it-works#how-it-works" onClick={(event) => handleNavClick(event, "/how-it-works", "how-it-works", true)}>How It Works</Link>
|
||||
</li>
|
||||
<li className={`menu-item menu-item-type-custom menu-item-object-custom menu-item-10510${dmHeaderActive("miletruth")}`}>
|
||||
<Link href="/miletruth" onClick={closeAll}>MileTruth™ AI</Link>
|
||||
<Link href="/miletruth#miletruth" onClick={(event) => handleNavClick(event, "/miletruth", "miletruth", true)}>MileTruth™ AI</Link>
|
||||
</li>
|
||||
<li className={`menu-item menu-item-type-custom menu-item-10511${dmHeaderActive("solutions")}`}>
|
||||
<Link href="/solutions" onClick={closeAll}>Solutions</Link>
|
||||
<Link href="/solutions#solutions" onClick={(event) => handleNavClick(event, "/solutions", "solutions", true)}>Solutions</Link>
|
||||
</li>
|
||||
<li className={`menu-item menu-item-type-custom menu-item-object-custom menu-item-has-children menu-item-10512${dmHeaderActive("about")}`}>
|
||||
<Link href="/about-us" onClick={closeAll}>About</Link>
|
||||
<Link href="/about-us#about" onClick={(event) => handleNavClick(event, "/about-us", "about", true)}>About</Link>
|
||||
</li>
|
||||
<li className={`menu-item menu-item-type-post_type menu-item-object-page menu-item-10535${dmHeaderActive("blogs")}`}>
|
||||
<Link href="/blog" onClick={closeAll}>Blogs</Link>
|
||||
<Link href="/blog#blogs" onClick={(event) => handleNavClick(event, "/blog", "blogs", true)}>Blogs</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
@@ -1,56 +1,161 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useEffect, useRef, useState, type MutableRefObject } from "react";
|
||||
import Image from "next/image";
|
||||
import { usePathname } from "next/navigation";
|
||||
|
||||
/**
|
||||
* LoadingScreen
|
||||
* ---------------------------------------------------------------------------
|
||||
* Native reimplementation of the legacy WordPress page-loader: a black
|
||||
* full-screen overlay with a centered, pulsing Doormile logo that fades out.
|
||||
* Route-transition loader only: a black full-screen overlay with a centered,
|
||||
* pulsing Doormile logo. It intentionally does not run on initial page render,
|
||||
* image loading, lazy component loading, API requests, or scroll.
|
||||
*
|
||||
* Shows only on initial application boot (until the window finishes loading,
|
||||
* min ~450ms to avoid a flash, capped at 2.5s so it never blocks). It must not
|
||||
* reappear during client-side route transitions: Next keeps the current page
|
||||
* visible while the next route payload is prepared, and a global overlay here
|
||||
* would create an artificial black flash between otherwise-ready pages.
|
||||
* The App Router does not expose the old `next/router` routeChangeStart /
|
||||
* routeChangeComplete event API. This component provides the same behavior by
|
||||
* detecting internal route-link starts and completing when `usePathname()`
|
||||
* reports the committed route.
|
||||
*/
|
||||
type Phase = "visible" | "hiding" | "gone";
|
||||
type Phase = "hidden" | "visible" | "hiding";
|
||||
|
||||
const MIN_SHOW_MS = 450;
|
||||
const MAX_SHOW_MS = 2500;
|
||||
const MIN_VISIBLE_MS = 420;
|
||||
const MAX_VISIBLE_MS = 800;
|
||||
|
||||
function getRoutePath(url: URL) {
|
||||
return `${url.pathname}${url.search}`;
|
||||
}
|
||||
|
||||
export default function LoadingScreen() {
|
||||
const [phase, setPhase] = useState<Phase>("visible");
|
||||
const bootComplete = useRef(false);
|
||||
const pathname = usePathname();
|
||||
const [phase, setPhase] = useState<Phase>("hidden");
|
||||
const phaseRef = useRef<Phase>("hidden");
|
||||
const visibleSince = useRef(0);
|
||||
const pendingPath = useRef<string | null>(null);
|
||||
const currentRoutePath = useRef<string | null>(null);
|
||||
const hideTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const safetyTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
const setLoaderPhase = (nextPhase: Phase) => {
|
||||
phaseRef.current = nextPhase;
|
||||
setPhase(nextPhase);
|
||||
};
|
||||
|
||||
const clearTimer = (timer: MutableRefObject<ReturnType<typeof setTimeout> | null>) => {
|
||||
if (!timer.current) return;
|
||||
clearTimeout(timer.current);
|
||||
timer.current = null;
|
||||
};
|
||||
|
||||
const completeTransition = () => {
|
||||
pendingPath.current = null;
|
||||
clearTimer(safetyTimer);
|
||||
|
||||
if (phaseRef.current === "hidden" || phaseRef.current === "hiding") return;
|
||||
|
||||
const elapsed = performance.now() - visibleSince.current;
|
||||
const wait = Math.max(0, MIN_VISIBLE_MS - elapsed);
|
||||
clearTimer(hideTimer);
|
||||
hideTimer.current = setTimeout(() => {
|
||||
setLoaderPhase("hiding");
|
||||
hideTimer.current = setTimeout(() => setLoaderPhase("hidden"), 360);
|
||||
}, wait);
|
||||
};
|
||||
|
||||
// Initial load: hide once the page is ready.
|
||||
useEffect(() => {
|
||||
const start = performance.now();
|
||||
let began = false;
|
||||
let fadeTimer: ReturnType<typeof setTimeout>;
|
||||
currentRoutePath.current = `${pathname}${window.location.search}`;
|
||||
completeTransition();
|
||||
// `phase` intentionally stays out of this dependency list. The route commit
|
||||
// is the completion signal; phase changes should not repeatedly restart hide.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [pathname]);
|
||||
|
||||
const begin = () => {
|
||||
if (began || bootComplete.current) return;
|
||||
began = true;
|
||||
const wait = Math.max(0, MIN_SHOW_MS - (performance.now() - start));
|
||||
fadeTimer = setTimeout(() => setPhase("hiding"), wait);
|
||||
useEffect(() => {
|
||||
const startTransition = (targetPath: string, force = false) => {
|
||||
if (!force && targetPath === getRoutePath(new URL(window.location.href))) return;
|
||||
if (pendingPath.current === targetPath && phaseRef.current === "visible") return;
|
||||
|
||||
pendingPath.current = targetPath;
|
||||
clearTimer(hideTimer);
|
||||
clearTimer(safetyTimer);
|
||||
|
||||
visibleSince.current = performance.now();
|
||||
setLoaderPhase("visible");
|
||||
|
||||
safetyTimer.current = setTimeout(() => {
|
||||
completeTransition();
|
||||
}, MAX_VISIBLE_MS);
|
||||
};
|
||||
|
||||
const cap = setTimeout(begin, MAX_SHOW_MS);
|
||||
const onReady = () => begin();
|
||||
const getInternalRouteTarget = (anchor: HTMLAnchorElement) => {
|
||||
const rawHref = anchor.getAttribute("href");
|
||||
if (!rawHref || rawHref.startsWith("#")) return null;
|
||||
if (anchor.target && anchor.target !== "_self") return null;
|
||||
if (anchor.hasAttribute("download")) return null;
|
||||
if (/^(mailto:|tel:|sms:|javascript:)/i.test(rawHref)) return null;
|
||||
|
||||
if (document.readyState === "complete") begin();
|
||||
else window.addEventListener("load", onReady, { once: true });
|
||||
const url = new URL(rawHref, window.location.href);
|
||||
if (url.origin !== window.location.origin) return null;
|
||||
|
||||
const current = new URL(window.location.href);
|
||||
const sameRoute = url.pathname === current.pathname && url.search === current.search;
|
||||
if (sameRoute) return null;
|
||||
|
||||
return getRoutePath(url);
|
||||
};
|
||||
|
||||
const handleDocumentClick = (event: MouseEvent) => {
|
||||
if (event.defaultPrevented) return;
|
||||
if (event.button !== 0) return;
|
||||
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
|
||||
|
||||
const anchor = (event.target as Element | null)?.closest("a[href]");
|
||||
if (!anchor || !(anchor instanceof HTMLAnchorElement)) return;
|
||||
|
||||
const targetPath = getInternalRouteTarget(anchor);
|
||||
if (targetPath) startTransition(targetPath);
|
||||
};
|
||||
|
||||
const originalPushState = window.history.pushState;
|
||||
const originalReplaceState = window.history.replaceState;
|
||||
|
||||
window.history.pushState = function patchedPushState(...args) {
|
||||
const urlArg = args[2];
|
||||
if (typeof urlArg === "string" || urlArg instanceof URL) {
|
||||
const url = new URL(urlArg, window.location.href);
|
||||
if (url.origin === window.location.origin) startTransition(getRoutePath(url));
|
||||
}
|
||||
return originalPushState.apply(this, args);
|
||||
};
|
||||
|
||||
window.history.replaceState = function patchedReplaceState(...args) {
|
||||
const urlArg = args[2];
|
||||
if (typeof urlArg === "string" || urlArg instanceof URL) {
|
||||
const url = new URL(urlArg, window.location.href);
|
||||
if (url.origin === window.location.origin) startTransition(getRoutePath(url));
|
||||
}
|
||||
return originalReplaceState.apply(this, args);
|
||||
};
|
||||
|
||||
const handlePopState = () => {
|
||||
const targetPath = getRoutePath(new URL(window.location.href));
|
||||
if (targetPath !== currentRoutePath.current) startTransition(targetPath, true);
|
||||
};
|
||||
|
||||
document.addEventListener("click", handleDocumentClick, true);
|
||||
window.addEventListener("popstate", handlePopState);
|
||||
|
||||
return () => {
|
||||
clearTimeout(cap);
|
||||
clearTimeout(fadeTimer);
|
||||
window.removeEventListener("load", onReady);
|
||||
document.removeEventListener("click", handleDocumentClick, true);
|
||||
window.removeEventListener("popstate", handlePopState);
|
||||
window.history.pushState = originalPushState;
|
||||
window.history.replaceState = originalReplaceState;
|
||||
clearTimer(hideTimer);
|
||||
clearTimer(safetyTimer);
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
if (phase === "gone") return null;
|
||||
if (phase === "hidden") return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -60,8 +165,8 @@ export default function LoadingScreen() {
|
||||
aria-label="Loading"
|
||||
onTransitionEnd={(e) => {
|
||||
if (e.propertyName === "opacity" && phase === "hiding") {
|
||||
bootComplete.current = true;
|
||||
setPhase("gone");
|
||||
setPhase("hidden");
|
||||
phaseRef.current = "hidden";
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user