59 lines
1.6 KiB
TypeScript
59 lines
1.6 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect } from "react";
|
|
import dynamic from "next/dynamic";
|
|
import { usePathname, useRouter } from "next/navigation";
|
|
import Navbar from "./Navbar";
|
|
import ParticleCanvas from "./ParticleCanvas";
|
|
import Footer from "./Footer";
|
|
import GlobalSearch from "./GlobalSearch";
|
|
|
|
const KeyboardShortcuts = dynamic(() => import("./KeyboardShortcuts"), { ssr: false });
|
|
|
|
export default function SiteShell({ children, showFooter = false }: { children: React.ReactNode; showFooter?: boolean }) {
|
|
const pathname = usePathname();
|
|
const router = useRouter();
|
|
|
|
useEffect(() => {
|
|
const observer = new IntersectionObserver(
|
|
(entries) =>
|
|
entries.forEach((e) => {
|
|
if (e.isIntersecting) {
|
|
e.target.classList.add("visible");
|
|
observer.unobserve(e.target);
|
|
}
|
|
}),
|
|
{ threshold: 0.08, rootMargin: "0px 0px -40px 0px" }
|
|
);
|
|
const observeAll = () => {
|
|
document.querySelectorAll(".reveal:not(.visible)").forEach((el) => observer.observe(el));
|
|
};
|
|
observeAll();
|
|
const mo = new MutationObserver(() => observeAll());
|
|
mo.observe(document.body, { childList: true, subtree: true });
|
|
return () => {
|
|
observer.disconnect();
|
|
mo.disconnect();
|
|
};
|
|
}, []);
|
|
|
|
return (
|
|
<>
|
|
<ParticleCanvas />
|
|
<Navbar />
|
|
<GlobalSearch />
|
|
<KeyboardShortcuts
|
|
onOpenMatcher={() => {
|
|
if (pathname === "/") {
|
|
window.dispatchEvent(new Event("open-matcher"));
|
|
} else {
|
|
router.push("/#matcher");
|
|
}
|
|
}}
|
|
/>
|
|
{children}
|
|
{showFooter && <Footer />}
|
|
</>
|
|
);
|
|
}
|