Spaces:
Running
Running
| 'use client' | |
| import Image from 'next/image' | |
| import { useEffect, useState, useCallback } from 'react' | |
| const NAV_SECTIONS = ['criteria', 'leaderboard', 'about', 'insights'] as const | |
| const NAV_LABELS: Record<string, string> = { | |
| criteria: 'Overview', | |
| leaderboard: 'Results', | |
| about: 'Methodology', | |
| insights: 'Insights', | |
| } | |
| export default function Nav({ modelCount, lastUpdated }: { modelCount: number; lastUpdated: string }) { | |
| const [scrolled, setScrolled] = useState(false) | |
| const [showTitle, setShowTitle] = useState(false) | |
| const [activeSection, setActiveSection] = useState<string | null>(null) | |
| const [menuOpen, setMenuOpen] = useState(false) | |
| useEffect(() => { | |
| const update = () => { | |
| setScrolled(window.scrollY > 20) | |
| const heroTitle = document.getElementById('hero-title') | |
| if (heroTitle) { | |
| const rect = heroTitle.getBoundingClientRect() | |
| setShowTitle(rect.bottom < 0) | |
| } | |
| const scrollMid = window.scrollY + window.innerHeight * 0.35 | |
| let current: string | null = null | |
| for (const id of NAV_SECTIONS) { | |
| const el = document.getElementById(id) | |
| if (el && el.offsetTop <= scrollMid) current = id | |
| } | |
| setActiveSection(current) | |
| } | |
| update() | |
| window.addEventListener('scroll', update, { passive: true }) | |
| return () => window.removeEventListener('scroll', update) | |
| }, []) | |
| const closeMenu = useCallback(() => setMenuOpen(false), []) | |
| return ( | |
| <> | |
| <nav | |
| className="nav-bar" | |
| style={{ boxShadow: scrolled ? '0 1px 8px rgba(0,0,0,0.08)' : 'none' }} | |
| > | |
| <div className="nav-logo"> | |
| <Image | |
| src="/govtech-logo.jpg" | |
| alt="GovTech Singapore" | |
| height={26} | |
| width={104} | |
| style={{ objectFit: 'contain', objectPosition: 'left center', width: 'auto', height: 26 }} | |
| priority | |
| /> | |
| <span | |
| className="nav-logo-sep" | |
| style={{ | |
| opacity: showTitle ? 1 : 0, | |
| transition: 'opacity 0.35s cubic-bezier(0.16, 1, 0.3, 1)', | |
| }} | |
| /> | |
| <span | |
| className="nav-logo-bench" | |
| style={{ | |
| opacity: showTitle ? 1 : 0, | |
| transform: showTitle ? 'translateY(0)' : 'translateY(4px)', | |
| transition: 'opacity 0.35s cubic-bezier(0.16, 1, 0.3, 1), transform 0.35s cubic-bezier(0.16, 1, 0.3, 1)', | |
| }} | |
| > | |
| Responsible AI Bench | |
| </span> | |
| </div> | |
| <ul className="nav-links"> | |
| {NAV_SECTIONS.map(id => ( | |
| <li key={id}> | |
| <a href={`#${id}`} className={activeSection === id ? 'nav-active' : ''}> | |
| {NAV_LABELS[id]} | |
| </a> | |
| </li> | |
| ))} | |
| </ul> | |
| <button | |
| className="nav-hamburger" | |
| onClick={() => setMenuOpen(v => !v)} | |
| aria-label="Toggle navigation menu" | |
| aria-expanded={menuOpen} | |
| > | |
| <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"> | |
| {menuOpen ? ( | |
| <> | |
| <line x1="6" y1="6" x2="18" y2="18" /> | |
| <line x1="6" y1="18" x2="18" y2="6" /> | |
| </> | |
| ) : ( | |
| <> | |
| <line x1="4" y1="7" x2="20" y2="7" /> | |
| <line x1="4" y1="12" x2="20" y2="12" /> | |
| <line x1="4" y1="17" x2="20" y2="17" /> | |
| </> | |
| )} | |
| </svg> | |
| </button> | |
| </nav> | |
| <div className={`nav-mobile-menu ${menuOpen ? 'open' : ''}`}> | |
| {NAV_SECTIONS.map(id => ( | |
| <a | |
| key={id} | |
| href={`#${id}`} | |
| className={activeSection === id ? 'nav-active' : ''} | |
| onClick={closeMenu} | |
| > | |
| {NAV_LABELS[id]} | |
| </a> | |
| ))} | |
| </div> | |
| </> | |
| ) | |
| } | |