Spaces:
Sleeping
Sleeping
| import React, { useState, useEffect } from 'react'; | |
| import { useNavigate, useLocation } from 'react-router-dom'; | |
| import { Menu, X } from 'lucide-react'; | |
| const NAV_LINKS = [ | |
| { id: 'circuits', label: 'Circuits' }, | |
| { id: 'patching', label: 'Patching' }, | |
| { id: 'monosemanticity', label: 'Monosemanticity' }, | |
| { id: 'alignment', label: 'Alignment' }, | |
| { id: 'attention', label: 'Attention' }, | |
| { id: 'failures', label: 'Findings' }, | |
| { id: 'blog', label: 'Blog' }, | |
| ]; | |
| export const Nav = () => { | |
| const [scrolled, setScrolled] = useState(false); | |
| const [active, setActive] = useState(''); | |
| const [mobileOpen, setMobileOpen] = useState(false); | |
| const navigate = useNavigate(); | |
| const location = useLocation(); | |
| const isHomePage = location.pathname === '/'; | |
| const [modelStatus, setModelStatus] = useState('active'); | |
| useEffect(() => { | |
| sessionStorage.setItem('cs_model_status', 'active'); | |
| window.dispatchEvent(new CustomEvent('cs_model_status_change', { detail: 'active' })); | |
| }, []); | |
| useEffect(() => { | |
| const handleScroll = () => { | |
| setScrolled(window.scrollY > 50); | |
| const sections = NAV_LINKS.map(l => document.getElementById(l.id)); | |
| for (let i = sections.length - 1; i >= 0; i--) { | |
| if (sections[i] && sections[i].getBoundingClientRect().top < 150) { | |
| setActive(NAV_LINKS[i].id); | |
| break; | |
| } | |
| } | |
| }; | |
| window.addEventListener('scroll', handleScroll, { passive: true }); | |
| return () => window.removeEventListener('scroll', handleScroll); | |
| }, []); | |
| const scrollTo = (id) => { | |
| if (!isHomePage) { | |
| navigate('/'); | |
| setTimeout(() => { | |
| document.getElementById(id)?.scrollIntoView({ behavior: 'smooth' }); | |
| }, 100); | |
| } else { | |
| document.getElementById(id)?.scrollIntoView({ behavior: 'smooth' }); | |
| } | |
| setMobileOpen(false); | |
| }; | |
| return ( | |
| <nav | |
| data-testid="top-nav" | |
| className={`fixed top-0 left-0 right-0 z-50 transition-all duration-300 ${ | |
| scrolled | |
| ? 'backdrop-blur-xl border-b' | |
| : 'border-b border-transparent' | |
| }`} | |
| style={{ | |
| background: scrolled ? 'rgba(6,8,16,0.92)' : 'transparent', | |
| borderColor: scrolled ? '#1E2B45' : 'transparent', | |
| }} | |
| > | |
| <div className="section-container flex items-center justify-between h-16"> | |
| {/* Logo */} | |
| <div className="flex items-center gap-2 cursor-pointer" onClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })}> | |
| <svg width="28" height="28" viewBox="0 0 28 28" fill="none"> | |
| <line x1="14" y1="2" x2="14" y2="26" stroke="url(#tealGrad)" strokeWidth="2" /> | |
| <line x1="6" y1="8" x2="14" y2="8" stroke="#00D9C0" strokeWidth="1.5" /> | |
| <line x1="6" y1="14" x2="14" y2="14" stroke="#4A9EFF" strokeWidth="1.5" /> | |
| <line x1="6" y1="20" x2="14" y2="20" stroke="#9B59F5" strokeWidth="1.5" /> | |
| <circle cx="6" cy="8" r="2" fill="#00D9C0" /> | |
| <circle cx="6" cy="14" r="2" fill="#4A9EFF" /> | |
| <circle cx="6" cy="20" r="2" fill="#9B59F5" /> | |
| <circle cx="14" cy="8" r="1.5" fill="#00D9C0" opacity="0.6" /> | |
| <circle cx="14" cy="14" r="1.5" fill="#4A9EFF" opacity="0.6" /> | |
| <circle cx="14" cy="20" r="1.5" fill="#9B59F5" opacity="0.6" /> | |
| <line x1="14" y1="8" x2="22" y2="6" stroke="#00D9C0" strokeWidth="1" opacity="0.5" /> | |
| <line x1="14" y1="14" x2="22" y2="12" stroke="#4A9EFF" strokeWidth="1" opacity="0.5" /> | |
| <line x1="14" y1="20" x2="22" y2="22" stroke="#9B59F5" strokeWidth="1" opacity="0.5" /> | |
| <defs> | |
| <linearGradient id="tealGrad" x1="14" y1="2" x2="14" y2="26"> | |
| <stop stopColor="#00D9C0" /> | |
| <stop offset="1" stopColor="#4A9EFF" /> | |
| </linearGradient> | |
| </defs> | |
| </svg> | |
| <span style={{ fontFamily: "'Space Grotesk', sans-serif", fontWeight: 700, fontSize: 18, color: '#E8EEF8' }}> | |
| CircuitScope | |
| </span> | |
| <div className="flex items-center gap-1.5 ml-2 px-2 py-0.5 rounded-full border text-[10px] font-mono tracking-wider font-semibold transition-all duration-300" | |
| style={{ | |
| background: | |
| modelStatus === 'active' ? 'rgba(0,217,192,0.08)' : | |
| modelStatus === 'loading' ? 'rgba(74,158,255,0.08)' : | |
| 'rgba(245,158,11,0.08)', | |
| borderColor: | |
| modelStatus === 'active' ? 'rgba(0,217,192,0.25)' : | |
| modelStatus === 'loading' ? 'rgba(74,158,255,0.25)' : | |
| 'rgba(245,158,11,0.25)', | |
| color: | |
| modelStatus === 'active' ? '#00D9C0' : | |
| modelStatus === 'loading' ? '#4A9EFF' : | |
| '#F59E0B', | |
| }} | |
| > | |
| <span className="relative flex h-1.5 w-1.5"> | |
| <span className={`animate-ping absolute inline-flex h-full w-full rounded-full opacity-75 ${ | |
| modelStatus === 'active' ? 'bg-[#00D9C0]' : | |
| modelStatus === 'loading' ? 'bg-[#4A9EFF]' : | |
| 'bg-[#F59E0B]' | |
| }`}></span> | |
| <span className={`relative inline-flex rounded-full h-1.5 w-1.5 ${ | |
| modelStatus === 'active' ? 'bg-[#00D9C0]' : | |
| modelStatus === 'loading' ? 'bg-[#4A9EFF]' : | |
| 'bg-[#F59E0B]' | |
| }`}></span> | |
| </span> | |
| {modelStatus === 'active' ? 'GPT-2 ACTIVE' : | |
| modelStatus === 'loading' ? 'LOADING GPT-2...' : | |
| 'GPT-2 ACTIVE'} | |
| </div> | |
| </div> | |
| {/* Desktop Links */} | |
| <div className="hidden md:flex items-center gap-6"> | |
| {NAV_LINKS.map(link => ( | |
| <button | |
| key={link.id} | |
| onClick={() => scrollTo(link.id)} | |
| className="text-sm transition-colors duration-200" | |
| style={{ | |
| color: active === link.id ? '#00D9C0' : '#8A9BC4', | |
| fontFamily: "'Inter', sans-serif", | |
| fontWeight: 500, | |
| background: 'none', | |
| border: 'none', | |
| cursor: 'pointer', | |
| }} | |
| onMouseEnter={e => { if (active !== link.id) e.target.style.color = '#E8EEF8'; }} | |
| onMouseLeave={e => { if (active !== link.id) e.target.style.color = '#8A9BC4'; }} | |
| > | |
| {link.label} | |
| </button> | |
| ))} | |
| </div> | |
| {/* Right Buttons */} | |
| <div className="hidden md:flex items-center gap-3"> | |
| <button onClick={() => navigate('/blog')} className="btn-ghost" style={{ padding: '8px 16px', fontSize: 13 }}>Read the Blog</button> | |
| <button onClick={() => scrollTo('circuits')} className="btn-primary" style={{ padding: '8px 16px', fontSize: 13 }}>Explore Live</button> | |
| </div> | |
| {/* Mobile Menu */} | |
| <button | |
| data-testid="nav-mobile-menu-button" | |
| className="md:hidden p-2" | |
| onClick={() => setMobileOpen(!mobileOpen)} | |
| style={{ background: 'none', border: 'none', color: '#E8EEF8', cursor: 'pointer' }} | |
| > | |
| {mobileOpen ? <X size={24} /> : <Menu size={24} />} | |
| </button> | |
| </div> | |
| {/* Mobile Drawer */} | |
| {mobileOpen && ( | |
| <div className="md:hidden" style={{ background: '#0C0F1A', borderTop: '1px solid #1E2B45', padding: '16px' }}> | |
| {NAV_LINKS.map(link => ( | |
| <button | |
| key={link.id} | |
| onClick={() => scrollTo(link.id)} | |
| className="block w-full text-left py-3 text-sm" | |
| style={{ | |
| color: active === link.id ? '#00D9C0' : '#8A9BC4', | |
| background: 'none', | |
| border: 'none', | |
| cursor: 'pointer', | |
| borderBottom: '1px solid #1E2B45', | |
| }} | |
| > | |
| {link.label} | |
| </button> | |
| ))} | |
| </div> | |
| )} | |
| </nav> | |
| ); | |
| }; | |