at main 3.1 kB view raw
1import "./index.css" 2import { useEffect, useRef, useState } from "react" 3import { SectionNav } from "./components/SectionNav" 4import { Header } from "./components/sections/Header" 5import { Work } from "./components/sections/Work" 6import { Connect } from "./components/sections/Connect" 7import { GuestbookPage } from "./components/sections/GuestbookPage" 8import { sections } from "./data/portfolio" 9 10export function App() { 11 const [activeSection, setActiveSection] = useState("") 12 const [currentPath, setCurrentPath] = useState(window.location.pathname) 13 const sectionsRef = useRef<(HTMLElement | null)[]>([]) 14 15 // Handle SPA navigation 16 useEffect(() => { 17 const handlePopState = () => setCurrentPath(window.location.pathname) 18 window.addEventListener('popstate', handlePopState) 19 return () => window.removeEventListener('popstate', handlePopState) 20 }, []) 21 22 useEffect(() => { 23 if (currentPath === '/guestbook') return // Skip observer on guestbook page 24 25 const observer = new IntersectionObserver( 26 (entries) => { 27 entries.forEach((entry) => { 28 if (entry.isIntersecting) { 29 entry.target.classList.add("animate-fade-in-up") 30 setActiveSection(entry.target.id) 31 } 32 }) 33 }, 34 { threshold: 0.1, rootMargin: "0px 0px -5% 0px" }, 35 ) 36 37 sectionsRef.current.forEach((section) => { 38 if (section) observer.observe(section) 39 }) 40 41 return () => observer.disconnect() 42 }, [currentPath]) 43 44 // Guestbook page 45 if (currentPath === '/guestbook') { 46 return ( 47 <div className="min-h-screen dark:bg-background text-foreground relative"> 48 <div className="fixed top-6 left-6 z-50"> 49 <button 50 onClick={() => { 51 window.history.pushState({}, '', '/') 52 setCurrentPath('/') 53 }} 54 className="px-4 py-2 rounded-full text-sm font-medium bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-200 shadow-md hover:shadow-lg border border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-700 transition-all flex items-center gap-2" 55 > 56 <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" strokeWidth={2}> 57 <path strokeLinecap="round" strokeLinejoin="round" d="M10 19l-7-7m0 0l7-7m-7 7h18" /> 58 </svg> 59 Back 60 </button> 61 </div> 62 <GuestbookPage /> 63 </div> 64 ) 65 } 66 67 return ( 68 <div className="min-h-screen dark:bg-background text-foreground relative"> 69 <SectionNav sections={sections} activeSection={activeSection} /> 70 71 <main> 72 <div className="max-w-4xl mx-auto px-6 sm:px-8 lg:px-16"> 73 <Header 74 sectionRef={(el) => (sectionsRef.current[0] = el)} 75 onGuestbookClick={() => { 76 window.history.pushState({}, '', '/guestbook') 77 setCurrentPath('/guestbook') 78 }} 79 /> 80 </div> 81 <Work sectionRef={(el) => (sectionsRef.current[1] = el)} /> 82 <Connect sectionRef={(el) => (sectionsRef.current[2] = el)} /> 83 </main> 84 </div> 85 ) 86} 87 88export default App