diff --git a/nextjs-app/components/SchoolDetailView.module.css b/nextjs-app/components/SchoolDetailView.module.css index 751f2aa..76cfab1 100644 --- a/nextjs-app/components/SchoolDetailView.module.css +++ b/nextjs-app/components/SchoolDetailView.module.css @@ -196,6 +196,17 @@ color: var(--text-primary, #1a1612); } +.sectionNavLinkActive { + background: var(--accent-coral, #e07256); + color: white; + font-weight: 600; +} + +.sectionNavLinkActive:hover { + background: var(--accent-coral-dark, #c45a3f); + color: white; +} + /* Unified card for all content sections */ .card { background: var(--bg-card, white); diff --git a/nextjs-app/components/SchoolDetailView.tsx b/nextjs-app/components/SchoolDetailView.tsx index 21da6e6..e8bc83b 100644 --- a/nextjs-app/components/SchoolDetailView.tsx +++ b/nextjs-app/components/SchoolDetailView.tsx @@ -73,6 +73,8 @@ export function SchoolDetailView({ const { addSchool, removeSchool, isSelected } = useComparison(); const isInComparison = isSelected(schoolInfo.urn); + const [activeSection, setActiveSection] = useState(''); + const latestResults = yearlyData.length > 0 ? yearlyData[yearlyData.length - 1] : null; // Phase detection @@ -80,6 +82,42 @@ export function SchoolDetailView({ const isSecondary = phase.toLowerCase().includes('secondary') || phase.toLowerCase() === 'all-through'; const isPrimary = !isSecondary; + // Track active section as user scrolls + useEffect(() => { + const ids = navItems.map(n => n.id); + if (!ids.length) return; + + const observers: IntersectionObserver[] = []; + + // We keep a map of how much each section is visible so we can pick the + // most-visible one when multiple overlap the viewport. + const ratioMap: Record = {}; + + const pickActive = () => { + const top = Object.entries(ratioMap).sort((a, b) => b[1] - a[1])[0]; + setActiveSection(top?.[1] > 0 ? top[0] : ''); + }; + + ids.forEach(id => { + const el = document.getElementById(id); + if (!el) return; + ratioMap[id] = 0; + const obs = new IntersectionObserver( + ([entry]) => { + ratioMap[id] = entry.intersectionRatio; + pickActive(); + }, + { threshold: [0, 0.1, 0.25, 0.5, 0.75, 1.0], rootMargin: '-56px 0px 0px 0px' }, + ); + obs.observe(el); + observers.push(obs); + }); + + return () => observers.forEach(o => o.disconnect()); + // navItems identity is stable per render — eslint-disable is intentional + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [navItems.map(n => n.id).join(',')]); + // National averages (fetched dynamically so they stay current) const [nationalAvg, setNationalAvg] = useState(null); useEffect(() => { @@ -314,7 +352,13 @@ export function SchoolDetailView({ {navItems.length > 0 &&
} {navItems.map(({ id, label }) => ( - {label} + + {label} + ))}