App.Nav = function Nav({ user, page, params }) { const [active, setActive] = React.useState(location.hash || '#/'); const [editingNickname, setEditingNickname] = React.useState(false); const [nicknameInput, setNicknameInput] = React.useState(''); const [saving, setSaving] = React.useState(false); const [navHidden, setNavHidden] = React.useState(false); const lastScrollY = React.useRef(0); const navRef = React.useRef(null); React.useEffect(() => { const handler = () => setActive(location.hash || '#/'); window.addEventListener('hashchange', handler); return () => window.removeEventListener('hashchange', handler); }, []); // Auto-hide nav on scroll React.useEffect(() => { const threshold = 56; const onScroll = () => { const currentY = window.scrollY; const isMobile = window.innerWidth <= 768; if (isMobile) { // Mobile: only show nav when scrolled to the top setNavHidden(currentY > threshold); } else { // Desktop: show/hide based on scroll direction if (currentY > threshold && currentY > lastScrollY.current) { setNavHidden(true); } else if (currentY < lastScrollY.current) { setNavHidden(false); } } lastScrollY.current = currentY; }; window.addEventListener('scroll', onScroll, { passive: true }); return () => window.removeEventListener('scroll', onScroll); }, []); // Update --nav-height CSS variable based on visibility React.useEffect(() => { if (navHidden) { document.documentElement.style.setProperty('--nav-height', '0px'); } else if (navRef.current) { document.documentElement.style.setProperty('--nav-height', navRef.current.offsetHeight + 'px'); } }, [navHidden]); // Set initial --nav-height on mount and resize React.useEffect(() => { const updateNavHeight = () => { if (navRef.current && !navHidden) { document.documentElement.style.setProperty('--nav-height', navRef.current.offsetHeight + 'px'); } }; updateNavHeight(); window.addEventListener('resize', updateNavHeight); return () => window.removeEventListener('resize', updateNavHeight); }, []); const baseLinks = [ { hash: '#/', label: 'Home' }, { hash: '#/combos', label: 'Race Combos' }, { hash: '#/create-combo', label: 'Create Race Combo' }, { hash: '#/cars', label: 'Cars' }, { hash: '#/tracks', label: 'Tracks' }, { hash: '#/polls', label: 'Polls' }, ]; const isPollSubPage = page === 'vote-poll' || page === 'poll-results'; const links = isPollSubPage && params && params.code ? baseLinks.concat([ { hash: '#/poll/' + params.code, label: 'Vote', temp: true }, { hash: '#/results/' + params.code, label: 'Results', temp: true }, ]) : baseLinks; const handleProfileClick = () => { if (!user) return; setNicknameInput(user.nickname || ''); setEditingNickname(true); }; const handleNicknameSave = () => { const trimmed = nicknameInput.trim(); if (!trimmed) { setEditingNickname(false); return; } setSaving(true); App.API.post('user', { fingerprint: App.fingerprint, nickname: trimmed, }).then(data => { if (window._setUser) window._setUser(data); setEditingNickname(false); setSaving(false); }).catch(() => { setSaving(false); }); }; const handleNicknameKeyDown = (e) => { if (e.key === 'Enter') handleNicknameSave(); if (e.key === 'Escape') setEditingNickname(false); }; const profileDisplay = user ? (user.nickname ? user.nickname + ' (id: ' + user.id + ')' : 'id: ' + user.id) : ''; return ( ); };