@@ -32,7 +32,7 @@ const HEADER_REVEAL_LEAD = 0.04;
3232/** Fraction of runway where the hero text unpins and scrolls away (0–1).
3333 * The video keeps scrubbing underneath. */
3434const UNPIN_THRESHOLD = 0.8 ;
35- const HERO_VIDEO_FPS = 24 ;
35+ const HERO_VIDEO_FPS = 120 ;
3636
3737/** Clamp a value to 0–1. */
3838const clamp01 = ( v : number ) => Math . min ( 1 , Math . max ( 0 , v ) ) ;
@@ -297,9 +297,13 @@ function Home() {
297297 let frameId = 0 ;
298298 let handoffAnimationFrameId = 0 ;
299299 let handoffTimeoutId = 0 ;
300+ let initialRevealAnimationFrameId = 0 ;
301+ let initialRevealTimeoutId = 0 ;
302+ let initialVideoFrameCallbackId = 0 ;
300303 let videoFrameCallbackId = 0 ;
301304 let posterHandoffPending = false ;
302305 let posterIsVisible = true ;
306+ let videoCanReplacePoster = false ;
303307 let disposed = false ;
304308 let lastSeekFrame = - 1 ;
305309
@@ -321,12 +325,50 @@ function Home() {
321325 handoffTimeoutId = 0 ;
322326 }
323327
328+ function cancelInitialVideoReveal ( ) {
329+ if ( initialVideoFrameCallbackId && videoWithFrameCallbacks . cancelVideoFrameCallback ) {
330+ videoWithFrameCallbacks . cancelVideoFrameCallback ( initialVideoFrameCallbackId ) ;
331+ }
332+ initialVideoFrameCallbackId = 0 ;
333+ if ( initialRevealAnimationFrameId ) cancelAnimationFrame ( initialRevealAnimationFrameId ) ;
334+ initialRevealAnimationFrameId = 0 ;
335+ if ( initialRevealTimeoutId ) window . clearTimeout ( initialRevealTimeoutId ) ;
336+ initialRevealTimeoutId = 0 ;
337+ }
338+
324339 function completePosterHandoff ( ) {
325340 if ( disposed ) return ;
326341 cancelPosterHandoff ( ) ;
327342 setPosterVisible ( false ) ;
328343 }
329344
345+ function revealInitialVideoFrame ( ) {
346+ if ( disposed || videoCanReplacePoster ) return ;
347+ cancelInitialVideoReveal ( ) ;
348+ videoCanReplacePoster = true ;
349+ scheduleScrollSync ( ) ;
350+ }
351+
352+ function scheduleInitialVideoReveal ( ) {
353+ if ( videoCanReplacePoster || initialVideoFrameCallbackId || initialRevealAnimationFrameId ) return ;
354+
355+ if ( videoWithFrameCallbacks . requestVideoFrameCallback ) {
356+ initialVideoFrameCallbackId = videoWithFrameCallbacks . requestVideoFrameCallback ( ( ) => {
357+ initialVideoFrameCallbackId = 0 ;
358+ revealInitialVideoFrame ( ) ;
359+ } ) ;
360+ initialRevealTimeoutId = window . setTimeout ( revealInitialVideoFrame , 500 ) ;
361+ return ;
362+ }
363+
364+ initialRevealAnimationFrameId = requestAnimationFrame ( ( ) => {
365+ initialRevealAnimationFrameId = requestAnimationFrame ( ( ) => {
366+ initialRevealAnimationFrameId = 0 ;
367+ revealInitialVideoFrame ( ) ;
368+ } ) ;
369+ } ) ;
370+ }
371+
330372 function schedulePosterHandoff ( ) {
331373 if ( ! posterIsVisible || posterHandoffPending ) return ;
332374 posterHandoffPending = true ;
@@ -389,8 +431,11 @@ function Home() {
389431
390432 if ( targetFrame === 0 ) {
391433 cancelPosterHandoff ( ) ;
434+ if ( ! frameIsCurrent && ! video . seeking ) {
435+ video . currentTime = targetTime ;
436+ }
392437 lastSeekFrame = 0 ;
393- setPosterVisible ( true ) ;
438+ setPosterVisible ( ! ( videoCanReplacePoster && frameIsCurrent && ! video . seeking ) ) ;
394439 } else if ( targetFrame !== lastSeekFrame || ( ! video . seeking && ! frameIsCurrent ) ) {
395440 const needsPosterHandoff = posterIsVisible ;
396441 if ( needsPosterHandoff ) cancelPosterHandoff ( ) ;
@@ -505,17 +550,26 @@ function Home() {
505550 window . addEventListener ( "touchstart" , unlock , { passive : true } ) ;
506551 const handleCanPlayThrough = ( ) => {
507552 unlocked = true ;
553+ scheduleInitialVideoReveal ( ) ;
508554 scheduleScrollSync ( ) ;
509555 } ;
510556 const handleLoadedMetadata = ( ) => {
511557 lastSeekFrame = - 1 ;
558+ videoCanReplacePoster = false ;
512559 cancelPosterHandoff ( ) ;
560+ cancelInitialVideoReveal ( ) ;
513561 setPosterVisible ( true ) ;
514562 scheduleScrollSync ( ) ;
515563 } ;
564+ const handleLoadedData = ( ) => {
565+ scheduleInitialVideoReveal ( ) ;
566+ scheduleScrollSync ( ) ;
567+ } ;
516568 video . addEventListener ( "canplaythrough" , handleCanPlayThrough , { once : true } ) ;
569+ video . addEventListener ( "loadeddata" , handleLoadedData ) ;
517570 video . addEventListener ( "loadedmetadata" , handleLoadedMetadata ) ;
518571 video . addEventListener ( "durationchange" , scheduleScrollSync ) ;
572+ video . addEventListener ( "seeked" , scheduleScrollSync ) ;
519573
520574 function onScroll ( ) {
521575 if ( ! unlocked ) unlock ( ) ;
@@ -529,12 +583,15 @@ function Home() {
529583 return ( ) => {
530584 disposed = true ;
531585 cancelPosterHandoff ( ) ;
586+ cancelInitialVideoReveal ( ) ;
532587 cancelAnimationFrame ( frameId ) ;
533588 window . removeEventListener ( "scroll" , onScroll ) ;
534589 window . removeEventListener ( "touchstart" , unlock ) ;
535590 video . removeEventListener ( "canplaythrough" , handleCanPlayThrough ) ;
591+ video . removeEventListener ( "loadeddata" , handleLoadedData ) ;
536592 video . removeEventListener ( "loadedmetadata" , handleLoadedMetadata ) ;
537593 video . removeEventListener ( "durationchange" , scheduleScrollSync ) ;
594+ video . removeEventListener ( "seeked" , scheduleScrollSync ) ;
538595 } ;
539596 } , [ ] ) ;
540597
0 commit comments