feat: update navigation scroll-spy grouping, fix active links under Lenis scroll, and update footer categories

This commit is contained in:
R-Bharathraj
2026-07-22 18:05:21 +05:30
parent aca42a3213
commit 6b87078274
2 changed files with 56 additions and 21 deletions

View File

@@ -20,16 +20,16 @@ const NAV = [
], ],
}, },
{ {
title: 'Platform', title: 'Spend',
color: '#FF2E88', // pink color: '#FF2E88', // pink
links: [ links: [
{ label: 'Why Now', href: '#why-now' }, { label: 'The Difference', href: '#difference' },
{ label: 'How It Works', href: '#how-it-works' }, { label: 'How It Works', href: '#how-it-works' },
{ label: 'The Loop', href: '#the-loop' }, { label: 'The Loop', href: '#the-loop' },
], ],
}, },
{ {
title: 'Company', title: 'Live',
color: '#8B3DFF', // purple color: '#8B3DFF', // purple
links: [ links: [
{ label: 'The Vision', href: '#the-vision' }, { label: 'The Vision', href: '#the-vision' },

View File

@@ -11,18 +11,39 @@ const CREAM = '#FFFDF5';
/* Scroll distance (px) before the bar solidifies into the sticky sticker. */ /* Scroll distance (px) before the bar solidifies into the sticky sticker. */
const STICK_AFTER = 500; const STICK_AFTER = 500;
const NAV_LINKS = [ /* The page's 15 sections split into three ordered acts. Each nav tab jumps to
{ label: 'Earn', href: '#how-it-works', color: '#C8FF37' }, // lime the start of its act, and the scroll-spy highlights whichever act you're in
{ label: 'Spend', href: '#difference', color: '#FF2E88' }, // pink (any section in `sections` lights that tab). Keep `sections` in page order. */
{ label: 'Live', href: '#the-vision', color: '#8B3DFF' }, // purple const NAV_GROUPS = [
{ label: 'Why Now', href: '#why-now', color: GOLD }, {
label: 'Earn',
color: '#C8FF37', // lime
target: '#the-why',
sections: ['intro', 'the-why', 'difference', 'the-problem', 'the-shift'],
},
{
label: 'Spend',
color: '#FF2E88', // pink
target: '#lyts',
sections: ['lyts', 'one-day', 'feelings', 'the-loop', 'why-now'],
},
{
label: 'Live',
color: '#8B3DFF', // purple
target: '#how-it-works',
sections: ['how-it-works', 'old-vs-new', 'the-vision', 'our-belief', 'the-promise'],
},
]; ];
/** Map a section id → the index of the act (nav group) that owns it. */
const groupForSection = (id: string) =>
NAV_GROUPS.findIndex((g) => g.sections.includes(id));
export default function Header() { export default function Header() {
const [mobileOpen, setMobileOpen] = useState(false); const [mobileOpen, setMobileOpen] = useState(false);
const [hidden, setHidden] = useState(false); const [hidden, setHidden] = useState(false);
const [stuck, setStuck] = useState(false); const [stuck, setStuck] = useState(false);
const [activeHash, setActiveHash] = useState(''); const [activeGroup, setActiveGroup] = useState(-1);
const mobileMenuRef = useRef<HTMLDivElement>(null); const mobileMenuRef = useRef<HTMLDivElement>(null);
const lastScrollYRef = useRef(0); const lastScrollYRef = useRef(0);
@@ -45,9 +66,9 @@ export default function Header() {
return () => window.removeEventListener('scroll', onScroll); return () => window.removeEventListener('scroll', onScroll);
}, []); }, []);
// Lightweight scroll-spy so the current section's nav link lights up. // Scroll-spy across every section → highlight whichever act (group) it's in.
useEffect(() => { useEffect(() => {
const ids = NAV_LINKS.map((l) => l.href.replace('#', '')); const ids = NAV_GROUPS.flatMap((g) => g.sections);
const targets = ids const targets = ids
.map((id) => document.getElementById(id)) .map((id) => document.getElementById(id))
.filter((el): el is HTMLElement => !!el); .filter((el): el is HTMLElement => !!el);
@@ -58,7 +79,7 @@ export default function Header() {
const visible = entries const visible = entries
.filter((e) => e.isIntersecting) .filter((e) => e.isIntersecting)
.sort((a, b) => b.intersectionRatio - a.intersectionRatio)[0]; .sort((a, b) => b.intersectionRatio - a.intersectionRatio)[0];
if (visible) setActiveHash(`#${visible.target.id}`); if (visible) setActiveGroup(groupForSection(visible.target.id));
}, },
{ rootMargin: '-45% 0px -45% 0px', threshold: [0, 0.25, 0.5] }, { rootMargin: '-45% 0px -45% 0px', threshold: [0, 0.25, 0.5] },
); );
@@ -86,7 +107,20 @@ export default function Header() {
return () => { document.body.style.overflow = ''; }; return () => { document.body.style.overflow = ''; };
}, [mobileOpen]); }, [mobileOpen]);
const isActive = (href: string) => href === activeHash; const isActive = (i: number) => i === activeGroup;
/* Lenis smooth-scroll owns the page, so a native hash jump gets overridden
for far-away targets (that's why "Live" appeared dead). Drive the scroll
through Lenis directly, offsetting for the fixed header. */
const handleNavClick = (e: React.MouseEvent, target: string) => {
const el = document.querySelector(target) as HTMLElement | null;
if (!el) return; // let the browser handle it if the section isn't mounted
e.preventDefault();
setMobileOpen(false);
const lenis = (window as unknown as { lenis?: { scrollTo: (t: HTMLElement, o?: object) => void } }).lenis;
if (lenis?.scrollTo) lenis.scrollTo(el, { offset: -90, duration: 1.2 });
else el.scrollIntoView({ behavior: 'smooth' });
};
return ( return (
<div suppressHydrationWarning> <div suppressHydrationWarning>
@@ -140,11 +174,11 @@ export default function Header() {
<nav> <nav>
<ul style={{ listStyle: 'none', margin: 0, padding: 0, display: 'flex', flexDirection: 'column', gap: '12px' }}> <ul style={{ listStyle: 'none', margin: 0, padding: 0, display: 'flex', flexDirection: 'column', gap: '12px' }}>
{NAV_LINKS.map(({ label, href, color }) => ( {NAV_GROUPS.map(({ label, target, color }) => (
<li key={href}> <li key={label}>
<Link <Link
href={href} href={target}
onClick={() => setMobileOpen(false)} onClick={(e) => handleNavClick(e, target)}
className="font-sora" className="font-sora"
style={{ style={{
display: 'block', display: 'block',
@@ -282,13 +316,14 @@ export default function Header() {
alignItems: 'center', alignItems: 'center',
gap: 10, gap: 10,
}}> }}>
{NAV_LINKS.map(({ label, href, color }, i) => { {NAV_GROUPS.map(({ label, target, color }, i) => {
const active = isActive(href); const active = isActive(i);
const tilt = i % 2 === 0 ? -2.5 : 2.5; const tilt = i % 2 === 0 ? -2.5 : 2.5;
return ( return (
<li key={href}> <li key={label}>
<Link <Link
href={href} href={target}
onClick={(e) => handleNavClick(e, target)}
className={`header-nav-tab${active ? ' is-active' : ''}`} className={`header-nav-tab${active ? ' is-active' : ''}`}
style={{ style={{
display: 'inline-flex', display: 'inline-flex',