@@ -19,102 +19,61 @@ interface AppProps {
1919 onReady ?: ( ) => void ;
2020}
2121
22- function App ( { onReady } : AppProps ) {
23- const [ isScrolledPastHero , setIsScrolledPastHero ] = useState ( false ) ;
24- const heroSectionRef = useRef < HTMLElement | null > ( null ) ;
25- const location = useLocation ( ) ;
22+ const doubleRAF = ( cb : ( ) => void ) => requestAnimationFrame ( ( ) => requestAnimationFrame ( cb ) ) ;
2623
27- useEffect ( ( ) => {
28- const handleScroll = ( ) => {
29- if ( heroSectionRef . current ) {
30- const heroBottom = heroSectionRef . current . offsetTop + heroSectionRef . current . offsetHeight ;
31- const scrollY = window . scrollY ;
32- setIsScrolledPastHero ( scrollY > heroBottom ) ;
33- }
34- } ;
35-
36- window . addEventListener ( 'scroll' , handleScroll ) ;
37- handleScroll ( ) ; // Check initial state
24+ function scrollToWithOffset ( element : Element ) {
25+ const top = element . getBoundingClientRect ( ) . top + globalThis . pageYOffset - 100 ;
26+ globalThis . scrollTo ( { top, behavior : 'smooth' } ) ;
27+ }
3828
39- return ( ) => {
40- window . removeEventListener ( 'scroll' , handleScroll ) ;
29+ function useHeroScroll ( heroRef : React . RefObject < HTMLElement | null > ) {
30+ const [ isPast , setIsPast ] = useState ( false ) ;
31+ useEffect ( ( ) => {
32+ const check = ( ) => {
33+ if ( ! heroRef . current ) return ;
34+ setIsPast ( globalThis . scrollY > heroRef . current . offsetTop + heroRef . current . offsetHeight ) ;
4135 } ;
42- } , [ ] ) ;
36+ globalThis . addEventListener ( 'scroll' , check ) ;
37+ check ( ) ;
38+ return ( ) => globalThis . removeEventListener ( 'scroll' , check ) ;
39+ } , [ heroRef ] ) ;
40+ return isPast ;
41+ }
4342
44- // Handle hash scrolling from navigation state and URL hash
43+ function useHashScroll ( location : ReturnType < typeof useLocation > ) {
4544 useEffect ( ( ) => {
46- // Only handle on main page
4745 if ( location . pathname !== '/' ) return ;
48-
49- // Check both navigation state and URL hash
5046 const state = location . state as { scrollToHash ?: string } | null ;
51- const hashFromState = state ?. scrollToHash ;
52- const hashFromUrl = location . hash ;
53-
54- // Also check window.location.hash as fallback (for direct navigation)
55- const hashFromWindow = window . location . hash ;
56-
57- const hash = hashFromState || hashFromUrl || hashFromWindow ;
47+ const hash = state ?. scrollToHash || location . hash || globalThis . location . hash ;
5848 if ( ! hash ) return ;
59-
60- // Remove # if present and ensure it starts with #
6149 const cleanHash = hash . startsWith ( '#' ) ? hash : `#${ hash } ` ;
62-
63- // Helper function to scroll to element with offset
64- const scrollToElement = ( element : Element ) => {
65- const elementTop = element . getBoundingClientRect ( ) . top + window . pageYOffset ;
66- const offset = 100 ; // Offset for header
67- window . scrollTo ( {
68- top : elementTop - offset ,
69- behavior : 'smooth' ,
70- } ) ;
71- } ;
72-
73- // Helper function to execute after double RAF (ensures DOM is painted)
74- const executeAfterDoubleRAF = ( callback : ( ) => void ) => {
75- requestAnimationFrame ( ( ) => {
76- requestAnimationFrame ( callback ) ;
77- } ) ;
78- } ;
79-
80- // Robust scrolling: retry until element exists or timeout
81- const maxAttempts = 50 ; // Increased attempts for slower renders
8250 let attempts = 0 ;
83-
8451 const tryScroll = ( ) => {
8552 attempts ++ ;
86- const element = document . querySelector ( cleanHash ) ;
87-
88- if ( element ) {
89- // Element found, scroll to it with slight offset for header
90- executeAfterDoubleRAF ( ( ) => scrollToElement ( element ) ) ;
53+ const el = document . querySelector ( cleanHash ) ;
54+ if ( el ) {
55+ doubleRAF ( ( ) => scrollToWithOffset ( el ) ) ;
9156 return ;
9257 }
93-
94- // Element not found yet, retry after a short delay
95- if ( attempts < maxAttempts ) {
96- setTimeout ( tryScroll , appConfig . animations . scrollRetryDelayCrossPage ) ;
97- }
58+ if ( attempts < 50 ) setTimeout ( tryScroll , appConfig . animations . scrollRetryDelayCrossPage ) ;
9859 } ;
99-
100- // Start trying after a delay to allow DOM to render (longer delay for cross-page navigation)
10160 setTimeout ( tryScroll , appConfig . animations . scrollInitialDelayCrossPage ) ;
10261 } , [ location . state , location . hash , location . pathname ] ) ;
62+ }
10363
104- // Signal that App is ready (after initial render)
64+ function useAppReady ( onReady ?: ( ) => void ) {
10565 useEffect ( ( ) => {
10666 if ( ! onReady ) return ;
107-
108- // Helper function to execute after double RAF (ensures DOM is painted)
109- const executeAfterDoubleRAF = ( callback : ( ) => void ) => {
110- requestAnimationFrame ( ( ) => {
111- requestAnimationFrame ( callback ) ;
112- } ) ;
113- } ;
114-
115- // Use requestAnimationFrame to ensure DOM is painted
116- executeAfterDoubleRAF ( onReady ) ;
67+ doubleRAF ( onReady ) ;
11768 } , [ onReady ] ) ;
69+ }
70+
71+ function App ( { onReady } : AppProps ) {
72+ const heroSectionRef = useRef < HTMLElement | null > ( null ) ;
73+ const location = useLocation ( ) ;
74+ const isScrolledPastHero = useHeroScroll ( heroSectionRef ) ;
75+ useHashScroll ( location ) ;
76+ useAppReady ( onReady ) ;
11877
11978 return (
12079 < div
0 commit comments