feat: menu and ponters

This commit is contained in:
2026-04-03 16:44:52 +02:00
parent e1c7ea7635
commit 0e7e435edd
8 changed files with 290 additions and 10 deletions

View File

@@ -0,0 +1,87 @@
import { useEffect, useRef, useState } from "react";
import { useLocation, useNavigate } from "react-router-dom";
import menuSvg from "@/assets/menu.min.svg";
const NAV_ITEMS = [
{ label: "Browse", path: "/browse" },
{ label: "Compose", path: "/" },
] as const;
/**
* Smooth scroll-following: tracks the scroll container and lerps
* the menu's vertical offset so it glides into place with eased acceleration.
*/
function useSmoothScroll(scrollSelector: string, ease = 0.08) {
const [offset, setOffset] = useState(0);
const targetRef = useRef(0);
const currentRef = useRef(0);
const rafRef = useRef(0);
useEffect(() => {
const container = document.querySelector(scrollSelector);
if (!container) return;
const onScroll = () => {
targetRef.current = container.scrollTop;
};
container.addEventListener("scroll", onScroll, { passive: true });
const tick = () => {
const diff = targetRef.current - currentRef.current;
if (Math.abs(diff) < 0.5) {
currentRef.current = targetRef.current;
} else {
currentRef.current += diff * ease;
}
setOffset(currentRef.current);
rafRef.current = requestAnimationFrame(tick);
};
rafRef.current = requestAnimationFrame(tick);
return () => {
container.removeEventListener("scroll", onScroll);
cancelAnimationFrame(rafRef.current);
};
}, [scrollSelector, ease]);
return offset;
}
export default function NavMenu() {
const navigate = useNavigate();
const location = useLocation();
const scrollY = useSmoothScroll("[data-scroll-root]", 0.07);
return (
<div
className="nav-menu-root"
style={{ transform: `translateY(${scrollY}px)` }}
>
{/* Frame — menu.min.svg as mask, colored by theme */}
<div
className="nav-menu-frame"
style={{
WebkitMaskImage: `url(${menuSvg})`,
maskImage: `url(${menuSvg})`,
}}
/>
{/* Navigation links */}
<nav className="nav-menu-links">
{NAV_ITEMS.map(({ label, path }) => {
const active = location.pathname === path;
return (
<button
key={path}
onClick={() => navigate(path)}
className="nav-menu-item"
data-active={active || undefined}
>
{label}
</button>
);
})}
</nav>
</div>
);
}