Spaces:
Sleeping
Sleeping
| import React, { useState, useEffect } from 'react'; | |
| const NAV_LINKS = [ | |
| { id: 'features', label: 'Features' }, | |
| { id: 'benchmarks', label: 'Benchmarks' }, | |
| { id: 'demo', label: 'Demo' }, | |
| { id: 'failure-analysis', label: 'Failure Analysis' }, | |
| ]; | |
| const GITHUB_URL = 'https://github.com/Gaurav711cgu'; | |
| function BrainDBIcon({ size = 24 }) { | |
| return ( | |
| <svg width={size} height={size} viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg"> | |
| {/* Brain outline */} | |
| <path d="M16 4C12 4 9 6.5 8 9.5C6 10 4.5 12 4.5 14.5C4.5 16.5 5.5 18 6.5 19C6 20 6 21.5 7 23C8 24.5 10 25 11.5 25C12.5 26.5 14 27.5 16 27.5C18 27.5 19.5 26.5 20.5 25C22 25 24 24.5 25 23C26 21.5 26 20 25.5 19C26.5 18 27.5 16.5 27.5 14.5C27.5 12 26 10 24 9.5C23 6.5 20 4 16 4Z" stroke="var(--purple-l)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" /> | |
| {/* Brain fold lines */} | |
| <path d="M16 4V14M16 14C14 14 12 12 10 13M16 14C18 14 20 12 22 13" stroke="var(--purple-l)" strokeWidth="1" strokeLinecap="round" opacity="0.6" /> | |
| {/* Database cylinder */} | |
| <ellipse cx="23" cy="23" rx="5" ry="2" stroke="var(--teal)" strokeWidth="1.5" fill="rgba(20,184,166,0.15)" /> | |
| <path d="M18 23V27C18 28.1 20.2 29 23 29C25.8 29 28 28.1 28 27V23" stroke="var(--teal)" strokeWidth="1.5" /> | |
| <path d="M18 25C18 26.1 20.2 27 23 27C25.8 27 28 26.1 28 25" stroke="var(--teal)" strokeWidth="1" opacity="0.5" /> | |
| </svg> | |
| ); | |
| } | |
| export default function Nav({ activeSection, onNavigate }) { | |
| const [scrolled, setScrolled] = useState(false); | |
| const [mobileOpen, setMobileOpen] = useState(false); | |
| useEffect(() => { | |
| const handleScroll = () => setScrolled(window.scrollY > 60); | |
| window.addEventListener('scroll', handleScroll, { passive: true }); | |
| return () => window.removeEventListener('scroll', handleScroll); | |
| }, []); | |
| return ( | |
| <nav className={`nv-nav ${scrolled ? 'scrolled' : ''}`} data-testid="sticky-nav" role="navigation" aria-label="Main navigation"> | |
| <div className="nv-container" style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', height: 64 }}> | |
| {/* Logo */} | |
| <div style={{ display: 'flex', alignItems: 'center', gap: 10, cursor: 'pointer' }} onClick={() => onNavigate('hero')} data-testid="brand-logo" role="button" tabIndex={0} aria-label="NeuralVault — scroll to top" onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') onNavigate('hero'); }}> | |
| <BrainDBIcon size={28} /> | |
| <span style={{ fontFamily: 'var(--font-heading)', fontWeight: 700, fontSize: 18, color: 'var(--text)' }}>NeuralVault</span> | |
| </div> | |
| {/* Center nav links - desktop */} | |
| <div style={{ display: 'flex', gap: 8, alignItems: 'center' }} className="desktop-nav-links"> | |
| {NAV_LINKS.map((link) => ( | |
| <button | |
| key={link.id} | |
| onClick={() => onNavigate(link.id)} | |
| data-testid={`nav-link-${link.id}`} | |
| style={{ | |
| background: 'none', | |
| border: 'none', | |
| padding: '6px 14px', | |
| fontSize: 14, | |
| fontFamily: 'var(--font-body)', | |
| color: activeSection === link.id ? 'var(--purple-l)' : 'var(--text2)', | |
| cursor: 'pointer', | |
| borderBottom: activeSection === link.id ? '2px solid var(--purple)' : '2px solid transparent', | |
| transition: 'color 0.2s ease', | |
| }} | |
| > | |
| {link.label} | |
| </button> | |
| ))} | |
| </div> | |
| {/* Right buttons */} | |
| <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}> | |
| <a href={GITHUB_URL} target="_blank" rel="noopener noreferrer" className="nv-btn-ghost" data-testid="github-link" style={{ textDecoration: 'none', fontSize: 13 }}> | |
| GitHub | |
| </a> | |
| <button className="nv-btn-primary" onClick={() => onNavigate('demo')} data-testid="hero-primary-cta" style={{ fontSize: 13, padding: '8px 16px' }}> | |
| Launch Demo | |
| </button> | |
| {/* Mobile hamburger */} | |
| <button | |
| className="mobile-menu-btn" | |
| onClick={() => setMobileOpen(!mobileOpen)} | |
| data-testid="nav-mobile-menu-button" | |
| aria-label={mobileOpen ? 'Close navigation menu' : 'Open navigation menu'} | |
| aria-expanded={mobileOpen} | |
| style={{ display: 'none', background: 'none', border: '1px solid var(--border)', borderRadius: 6, padding: '6px 8px', color: 'var(--text)', cursor: 'pointer' }} | |
| > | |
| <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"> | |
| <path d="M3 12h18M3 6h18M3 18h18" /> | |
| </svg> | |
| </button> | |
| </div> | |
| </div> | |
| {/* Mobile dropdown */} | |
| {mobileOpen && ( | |
| <div style={{ background: 'var(--surface)', borderTop: '1px solid var(--border)', padding: 16 }} className="mobile-nav-dropdown"> | |
| {NAV_LINKS.map((link) => ( | |
| <button | |
| key={link.id} | |
| onClick={() => { onNavigate(link.id); setMobileOpen(false); }} | |
| style={{ display: 'block', width: '100%', textAlign: 'left', background: 'none', border: 'none', padding: '10px 0', fontSize: 14, fontFamily: 'var(--font-body)', color: activeSection === link.id ? 'var(--purple-l)' : 'var(--text2)', cursor: 'pointer' }} | |
| > | |
| {link.label} | |
| </button> | |
| ))} | |
| </div> | |
| )} | |
| <style>{` | |
| @media (max-width: 768px) { | |
| .desktop-nav-links { display: none ; } | |
| .mobile-menu-btn { display: block ; } | |
| } | |
| `}</style> | |
| </nav> | |
| ); | |
| } | |