643643 text-transform : uppercase;
644644 }
645645
646+ .replay-button {
647+ position : absolute;
648+ left : 2.2% ;
649+ bottom : 2.2% ;
650+ z-index : 95 ;
651+ display : inline-flex;
652+ align-items : center;
653+ justify-content : center;
654+ gap : 8px ;
655+ min-height : 36px ;
656+ padding : 0 14px 0 12px ;
657+ border : 1px solid rgba (250 , 249 , 245 , 0.14 );
658+ border-radius : 999px ;
659+ background : rgba (37 , 35 , 32 , 0.9 );
660+ color : var (--cream );
661+ box-shadow : 0 18px 48px rgba (0 , 0 , 0 , 0.28 ), inset 0 1px 0 rgba (250 , 249 , 245 , 0.06 );
662+ font-family : var (--sans );
663+ font-size : clamp (10px , 0.72vw , 14px );
664+ font-weight : 600 ;
665+ line-height : 1 ;
666+ cursor : pointer;
667+ opacity : 0 ;
668+ pointer-events : none;
669+ transform : translateY (8px );
670+ transition : opacity 180ms ease, transform 180ms ease, border-color 160ms ease, background 160ms ease;
671+ backdrop-filter : blur (14px );
672+ }
673+
674+ .replay-button svg {
675+ width : 16px ;
676+ height : 16px ;
677+ flex : 0 0 auto;
678+ color : var (--green );
679+ }
680+
681+ .replay-button .is-visible {
682+ opacity : 1 ;
683+ pointer-events : auto;
684+ transform : translateY (0 );
685+ }
686+
687+ .replay-button : not (: disabled ): hover ,
688+ .replay-button : not (: disabled ): focus-visible {
689+ border-color : rgba (204 , 120 , 92 , 0.55 );
690+ background : rgba (47 , 44 , 40 , 0.96 );
691+ outline : none;
692+ }
693+
694+ .replay-button : not (: disabled ): hover svg ,
695+ .replay-button : not (: disabled ): focus-visible svg {
696+ color : var (--gold );
697+ }
698+
646699 .stage {
647700 background :
648701 radial-gradient (circle at 18% 16% , rgba (204 , 120 , 92 , 0.16 ), transparent 28% ),
@@ -1004,12 +1057,22 @@ <h1>DeepPaperNote</h1>
10041057 < div class ="final-rule "> </ div >
10051058 </ section >
10061059
1060+ < button class ="replay-button " id ="replayButton " type ="button " aria-label ="Replay demo " disabled >
1061+ < svg viewBox ="0 0 16 16 " aria-hidden ="true " focusable ="false ">
1062+ < path
1063+ d ="M8.8 2.2a5.8 5.8 0 1 1-5.55 7.45.78.78 0 0 1 1.5-.43 4.25 4.25 0 1 0 .98-4.21h1.8a.75.75 0 0 1 0 1.5H3.75A.75.75 0 0 1 3 5.76V2a.75.75 0 0 1 1.5 0v1.74A5.77 5.77 0 0 1 8.8 2.2Z "
1064+ fill ="currentColor "
1065+ />
1066+ </ svg >
1067+ < span > Replay</ span >
1068+ </ button >
10071069 < div class ="watermark "> Created by Huashu-Design</ div >
10081070 </ main >
10091071
10101072 < script >
10111073 const DURATION = 24 ;
1012- const startTime = performance . now ( ) ;
1074+ let playbackStartTime = performance . now ( ) ;
1075+ let animationFrameId = 0 ;
10131076 const reducedMotion = window . matchMedia ( "(prefers-reduced-motion: reduce)" ) . matches ;
10141077 const $ = ( id ) => document . getElementById ( id ) ;
10151078
@@ -1027,6 +1090,7 @@ <h1>DeepPaperNote</h1>
10271090 phraseTwo : $ ( "phraseTwo" ) ,
10281091 phraseThree : $ ( "phraseThree" ) ,
10291092 final : $ ( "final" ) ,
1093+ replayButton : $ ( "replayButton" ) ,
10301094 nodes : [ $ ( "node0" ) , $ ( "node1" ) , $ ( "node2" ) , $ ( "node3" ) , $ ( "node4" ) ] ,
10311095 labels : [ $ ( "label0" ) , $ ( "label1" ) , $ ( "label2" ) , $ ( "label3" ) , $ ( "label4" ) ]
10321096 } ;
@@ -1106,32 +1170,60 @@ <h1>DeepPaperNote</h1>
11061170 }
11071171 }
11081172
1173+ function setReplayVisible ( isVisible ) {
1174+ els . replayButton . classList . toggle ( "is-visible" , isVisible ) ;
1175+ els . replayButton . disabled = ! isVisible ;
1176+ }
1177+
1178+ function startPlayback ( ) {
1179+ window . __manualSeek = false ;
1180+ playbackStartTime = performance . now ( ) ;
1181+ setReplayVisible ( false ) ;
1182+ cancelAnimationFrame ( animationFrameId ) ;
1183+
1184+ if ( reducedMotion ) {
1185+ render ( DURATION ) ;
1186+ setReplayVisible ( true ) ;
1187+ return ;
1188+ }
1189+
1190+ render ( 0 ) ;
1191+ animationFrameId = requestAnimationFrame ( tick ) ;
1192+ }
1193+
11091194 function tick ( now ) {
11101195 if ( window . __manualSeek ) return ;
1111- const t = reducedMotion ? 18 : ( now - startTime ) / 1000 ;
1196+ const t = ( now - playbackStartTime ) / 1000 ;
11121197 render ( t ) ;
11131198 if ( t < DURATION ) {
1114- requestAnimationFrame ( tick ) ;
1199+ animationFrameId = requestAnimationFrame ( tick ) ;
11151200 } else {
11161201 render ( DURATION ) ;
1202+ animationFrameId = 0 ;
1203+ setReplayVisible ( true ) ;
11171204 }
11181205 }
11191206
11201207 window . __seek = ( seconds ) => {
11211208 window . __manualSeek = true ;
1209+ cancelAnimationFrame ( animationFrameId ) ;
1210+ animationFrameId = 0 ;
1211+ setReplayVisible ( Number ( seconds ) >= DURATION ) ;
11221212 render ( Number ( seconds ) || 0 ) ;
11231213 } ;
11241214
1215+ els . replayButton . addEventListener ( "click" , startPlayback ) ;
1216+
11251217 if ( document . fonts && document . fonts . ready ) {
11261218 document . fonts . ready . then ( ( ) => requestAnimationFrame ( ( ) => {
11271219 render ( 0 ) ;
11281220 window . __ready = true ;
1129- requestAnimationFrame ( tick ) ;
1221+ startPlayback ( ) ;
11301222 } ) ) ;
11311223 } else {
11321224 render ( 0 ) ;
11331225 window . __ready = true ;
1134- requestAnimationFrame ( tick ) ;
1226+ startPlayback ( ) ;
11351227 }
11361228 </ script >
11371229</ body >
0 commit comments