112 lines
3.2 KiB
TypeScript
112 lines
3.2 KiB
TypeScript
import { useState, useEffect, useRef, useCallback } from 'react'
|
|
import Lenis from 'lenis'
|
|
import { gsap } from 'gsap'
|
|
import { ScrollTrigger } from 'gsap/ScrollTrigger'
|
|
|
|
import LoadingScreen from './components/LoadingScreen'
|
|
import Navigation from './components/Navigation'
|
|
import HeroSection from './components/HeroSection'
|
|
import AboutSection from './components/AboutSection'
|
|
import InterestsSection from './components/InterestsSection'
|
|
import ProjectsSection from './components/ProjectsSection'
|
|
import ExperienceSection from './components/ExperienceSection'
|
|
import TopicsSection from './components/TopicsSection'
|
|
import ContactSection from './components/ContactSection'
|
|
import Footer from './components/Footer'
|
|
import FloatingParticles from './components/FloatingParticles'
|
|
import ScrollProgress from './components/ScrollProgress'
|
|
import NoiseOverlay from './components/NoiseOverlay'
|
|
import { useReducedMotion } from './hooks/useReducedMotion'
|
|
|
|
gsap.registerPlugin(ScrollTrigger)
|
|
|
|
export default function App() {
|
|
const [loaded, setLoaded] = useState(false)
|
|
const [showContent, setShowContent] = useState(false)
|
|
const lenisRef = useRef<Lenis | null>(null)
|
|
const mainRef = useRef<HTMLElement>(null)
|
|
const capiAudioRef = useRef<HTMLAudioElement | null>(null)
|
|
const reducedMotion = useReducedMotion()
|
|
|
|
useEffect(() => {
|
|
capiAudioRef.current = new Audio('./media/capi.mp3')
|
|
capiAudioRef.current.preload = 'auto'
|
|
capiAudioRef.current.loop = true
|
|
capiAudioRef.current.volume = 0.25
|
|
}, [])
|
|
|
|
useEffect(() => {
|
|
if (!loaded) return
|
|
|
|
const lenis = new Lenis({
|
|
duration: 1.2,
|
|
easing: (t: number) => Math.min(1, 1.001 - Math.pow(2, -10 * t)),
|
|
smoothWheel: true,
|
|
})
|
|
lenisRef.current = lenis
|
|
|
|
lenis.on('scroll', ScrollTrigger.update)
|
|
|
|
const rafCallback = (time: number) => {
|
|
lenis.raf(time * 1000)
|
|
}
|
|
gsap.ticker.add(rafCallback)
|
|
gsap.ticker.lagSmoothing(0)
|
|
|
|
return () => {
|
|
gsap.ticker.remove(rafCallback)
|
|
lenis.destroy()
|
|
}
|
|
}, [loaded])
|
|
|
|
const handleLoadComplete = useCallback(() => {
|
|
setLoaded(true)
|
|
// Small delay before showing content for transition
|
|
setTimeout(() => {
|
|
setShowContent(true)
|
|
}, 100)
|
|
}, [])
|
|
|
|
useEffect(() => {
|
|
if (showContent) {
|
|
// Start ambient capi.mp3 loop
|
|
capiAudioRef.current?.play().catch(() => {})
|
|
}
|
|
}, [showContent])
|
|
|
|
useEffect(() => {
|
|
if (showContent && mainRef.current && !reducedMotion) {
|
|
gsap.fromTo(
|
|
mainRef.current,
|
|
{ opacity: 0, y: 40 },
|
|
{ opacity: 1, y: 0, duration: 1.2, ease: 'expo.out' }
|
|
)
|
|
}
|
|
}, [showContent, reducedMotion])
|
|
|
|
return (
|
|
<>
|
|
<LoadingScreen onLoadComplete={handleLoadComplete} />
|
|
|
|
{showContent && (
|
|
<>
|
|
<ScrollProgress />
|
|
<NoiseOverlay />
|
|
<FloatingParticles />
|
|
<Navigation />
|
|
<main ref={mainRef} className="relative" style={{ opacity: reducedMotion ? 1 : 0 }}>
|
|
<HeroSection />
|
|
<AboutSection />
|
|
<InterestsSection />
|
|
<ProjectsSection />
|
|
<ExperienceSection />
|
|
<TopicsSection />
|
|
<ContactSection />
|
|
<Footer />
|
|
</main>
|
|
</>
|
|
)}
|
|
</>
|
|
)
|
|
}
|