Fix nav indicator animation when scrolled#12
Conversation
Replace layoutId-based indicator with a single persistent motion.span that animates left/width using offsetLeft/offsetWidth (parent-relative, scroll-independent). The old approach used Framer Motion layoutId which converts viewport coords to document coords via scrollY — causing a phantom vertical animation for sticky elements when scroll position changed between measurements. Also scroll to top on nav click/keyboard for consistent UX. Closes #9 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Resolve conflict: keep indicator fix, accept ThemeToggle extraction. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Bugbot Autofix is ON, but it could not run because the branch was deleted or merged before Autofix could start.
This is the final PR Bugbot will review for you during this billing cycle
Your free Bugbot reviews will reset on March 11
Details
You are on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle.
To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.
| if (activeLink) { | ||
| setIndicator({ left: activeLink.offsetLeft, width: activeLink.offsetWidth, opacity: 1 }) | ||
| } | ||
| }, [activeIndex]) |
There was a problem hiding this comment.
Indicator persists when no nav item is active
Low Severity
When activeIndex is -1 (e.g., on a 404 page or a route not in navItems), the useLayoutEffect returns early without updating the indicator state. If the indicator was previously visible (opacity: 1), it remains highlighted on the last active tab. The old layoutId approach correctly removed the indicator by not rendering the motion.span at all. The early return needs to also set opacity: 0 to hide the indicator.
| if (activeLink) { | ||
| setIndicator({ left: activeLink.offsetLeft, width: activeLink.offsetWidth, opacity: 1 }) | ||
| } | ||
| }, [activeIndex]) |
There was a problem hiding this comment.
Indicator position stale after font swap or resize
Low Severity
The useLayoutEffect only re-measures when activeIndex changes. Since the site uses Google Fonts with display: "swap", the initial measurement may use fallback font metrics. When the custom font (Montserrat) loads and text reflows, offsetLeft/offsetWidth values change but the effect doesn't re-run, leaving the indicator misaligned. The previous layoutId approach handled layout changes automatically.


Summary
layoutId-based nav indicator with a single persistentmotion.spanthat animatesleft/widthusingoffsetLeft/offsetWidth(parent-relative, scroll-independent)layoutIdapproach converted viewport coords to document coords viascrollY, causing a phantom vertical animation for the sticky nav when scroll position changed between measurementsCloses #9
Test plan
🤖 Generated with Claude Code
Note
Low Risk
UI-only changes to navigation animation and click/keyboard handlers; low risk aside from minor layout/animation regressions on resize or tab measurement.
Overview
Fixes the desktop top-nav active indicator animation when the page is scrolled by replacing the per-link
layoutIdindicator with a single persistentmotion.spanthat animatesleft/widthbased on measuredoffsetLeft/offsetWidthvia a container ref +useLayoutEffect.Adds
window.scrollTo(0, 0)on desktop nav clicks, mobile menu nav clicks, and numeric keyboard shortcuts to ensure navigation always starts at the top of the new page.Written by Cursor Bugbot for commit 3b60838. This will update automatically on new commits. Configure here.