personal website
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