@@ -21,7 +21,8 @@ interface Step {
2121 from : number ;
2222 to : number ;
2323 progress : number ;
24- narration : string ;
24+ narrationTitle : string ;
25+ narrationDescription : string ;
2526 explode ?: boolean ;
2627 skipRemaining ?: boolean ;
2728 untaken ?: boolean ;
@@ -36,21 +37,35 @@ interface ExplosionParticle {
3637 startTime : number ;
3738}
3839
39- const ArrayEvaluationDemo : React . FC = ( ) => {
40+ interface Narration {
41+ title : string ;
42+ description : string ;
43+ }
44+
45+ type ArrayEvaluationDemoProps = {
46+ demoType ?: EvaluationType | undefined ;
47+ } ;
48+
49+ const ArrayEvaluationDemo : React . FC = ( {
50+ demoType,
51+ } : ArrayEvaluationDemoProps ) => {
4052 const [ progress , setProgress ] = useState ( 0 ) ;
4153 const [ playing , setPlaying ] = useState ( false ) ;
42- const [ type , setType ] = useState < EvaluationType > ( 'eager' ) ;
54+ const [ type , setType ] = useState < EvaluationType > ( demoType || 'eager' ) ;
4355 const [ explosions , setExplosions ] = useState < ExplosionParticle [ ] > ( [ ] ) ;
4456 const [ positions , setPositions ] = useState < Position [ ] > ( [ ] ) ;
4557 const processedStepsRef = useRef < Set < string > > ( new Set ( ) ) ;
4658 const animationRef = useRef < number | null > ( null ) ;
4759
4860 const EXPLOSION_DURATION = 500 ;
49- const ANIMATION_STEP = 0.2 ;
61+ // const ANIMATION_STEP = 0.2;
62+ const ANIMATION_STEP = 0.15 ; // Reduced from 0.2 to slow down animation
63+ const FRAME_DURATION = 16 ; // roughly 60fps
64+
5065 const MAX_PROGRESS = 150 ;
5166 const isEven = ( n : number ) : boolean => n % 2 === 0 ;
5267
53- const columns = [ 'Array ' , 'map' , 'select' , 'take(3) ' ] ;
68+ const columns = [ '[ ] ' , 'map' , 'select' , 'take' ] ;
5469 const dots : Dot [ ] = Array ( 7 )
5570 . fill ( null )
5671 . map ( ( _ , i ) => ( { pos : i , column : 0 } ) ) ;
@@ -64,7 +79,8 @@ const ArrayEvaluationDemo: React.FC = () => {
6479 from : 0 ,
6580 to : 1 ,
6681 progress : i * 8 + 8 ,
67- narration : `Map to blue: Item ${ i } ` ,
82+ narrationTitle : 'Map to blue' ,
83+ narrationDescription : `Item ${ i } ` ,
6884 } ) ) ,
6985 ...Array ( 7 )
7086 . fill ( null )
@@ -74,108 +90,150 @@ const ArrayEvaluationDemo: React.FC = () => {
7490 to : 2 ,
7591 progress : 64 + i * 8 ,
7692 explode : ! isEven ( i ) ,
77- narration : `Select even? Item ${ i } : ${ isEven ( i ) ? 'yes' : 'no' } ` ,
93+ narrationTitle : `Select even?` ,
94+ narrationDescription : `Item ${ i } : ${ isEven ( i ) ? 'yes' : 'no' } ` ,
7895 } ) ) ,
7996 ...[ 0 , 2 , 4 ] . map ( ( i , idx ) => ( {
8097 pos : i ,
8198 from : 2 ,
8299 to : 3 ,
83100 progress : 128 + idx * 8 ,
84- narration : `Take ${ idx + 1 } : Item ${ i } ` ,
101+ narrationTitle : `Take 3` ,
102+ narrationDescription : `Item ${ i } ` ,
85103 } ) ) ,
86104 {
87105 pos : 6 ,
88106 from : 2 ,
89107 to : 2 ,
90108 progress : 144 ,
91109 untaken : true ,
92- narration : 'Done - took 3 items' ,
110+ narrationTitle : 'Done!' ,
111+ narrationDescription : '3 items taken' ,
93112 } ,
94113 ] ,
95114 lazy : [
96- { pos : 0 , from : 0 , to : 1 , progress : 8 , narration : 'Map to blue: Item 0' } ,
115+ {
116+ pos : 0 ,
117+ from : 0 ,
118+ to : 1 ,
119+ progress : 8 ,
120+ narrationTitle : 'Map to blue' ,
121+ narrationDescription : 'Item 0' ,
122+ } ,
97123 {
98124 pos : 0 ,
99125 from : 1 ,
100126 to : 2 ,
101127 progress : 16 ,
102- narration : 'Select even? Item 0: yes' ,
128+ narrationTitle : 'Select even?' ,
129+ narrationDescription : 'Item 0: yes' ,
130+ } ,
131+ {
132+ pos : 0 ,
133+ from : 2 ,
134+ to : 3 ,
135+ progress : 24 ,
136+ narrationTitle : 'Take 1' ,
137+ narrationDescription : 'Item 0' ,
103138 } ,
104- { pos : 0 , from : 2 , to : 3 , progress : 24 , narration : 'Take 1: Item 0' } ,
105139 {
106140 pos : 1 ,
107141 from : 0 ,
108142 to : 1 ,
109143 progress : 32 ,
110- narration : 'Map to blue: Item 1' ,
144+ narrationTitle : 'Map to blue' ,
145+ narrationDescription : 'Item 1' ,
111146 } ,
112147 {
113148 pos : 1 ,
114149 from : 1 ,
115150 to : 2 ,
116151 progress : 40 ,
117152 explode : true ,
118- narration : 'Select even? Item 1: no' ,
153+ narrationTitle : 'Select even?' ,
154+ narrationDescription : 'Item 1: no' ,
119155 } ,
120156 {
121157 pos : 2 ,
122158 from : 0 ,
123159 to : 1 ,
124160 progress : 56 ,
125- narration : 'Map to blue: Item 2' ,
161+ narrationTitle : 'Map to blue' ,
162+ narrationDescription : 'Item 2' ,
126163 } ,
127164 {
128165 pos : 2 ,
129166 from : 1 ,
130167 to : 2 ,
131168 progress : 64 ,
132- narration : 'Select even? Item 2: yes' ,
169+ narrationTitle : 'Select even?' ,
170+ narrationDescription : 'Item 2: yes' ,
171+ } ,
172+ {
173+ pos : 2 ,
174+ from : 2 ,
175+ to : 3 ,
176+ progress : 72 ,
177+ narrationTitle : 'Take 3' ,
178+ narrationDescription : 'Item 2' ,
133179 } ,
134- { pos : 2 , from : 2 , to : 3 , progress : 72 , narration : 'Take 2: Item 2' } ,
135180 {
136181 pos : 3 ,
137182 from : 0 ,
138183 to : 1 ,
139184 progress : 84 ,
140- narration : 'Map to blue: Item 3' ,
185+ narrationTitle : 'Map to blue' ,
186+ narrationDescription : 'Item 3' ,
141187 } ,
142188 {
143189 pos : 3 ,
144190 from : 1 ,
145191 to : 2 ,
146192 progress : 92 ,
147193 explode : true ,
148- narration : 'Select even? Item 3: no' ,
194+ narrationTitle : 'Select even?' ,
195+ narrationDescription : 'Item 3: no' ,
149196 } ,
150197 {
151198 pos : 4 ,
152199 from : 0 ,
153200 to : 1 ,
154201 progress : 108 ,
155- narration : 'Map to blue: Item 4' ,
202+ narrationTitle : 'Map to blue' ,
203+ narrationDescription : 'Item 4' ,
156204 } ,
157205 {
158206 pos : 4 ,
159207 from : 1 ,
160208 to : 2 ,
161209 progress : 116 ,
162- narration : 'Select even? Item 4: yes' ,
210+ narrationTitle : 'Select even?' ,
211+ narrationDescription : 'Item 4: yes' ,
212+ } ,
213+ {
214+ pos : 4 ,
215+ from : 2 ,
216+ to : 3 ,
217+ progress : 124 ,
218+ narrationTitle : 'Take 3' ,
219+ narrationDescription : 'Item 4' ,
163220 } ,
164- { pos : 4 , from : 2 , to : 3 , progress : 124 , narration : 'Take 3: Item 4' } ,
165221 {
166222 pos : 4 ,
167223 from : 3 ,
168224 to : 3 ,
169225 progress : 132 ,
170- narration : 'Done - took 3 items' ,
226+ narrationTitle : 'Done!' ,
227+ narrationDescription : '3 items taken' ,
171228 } ,
172229 {
173230 pos : 5 ,
174231 from : 0 ,
175232 to : 0 ,
176233 progress : 140 ,
177234 skipRemaining : true ,
178- narration : 'Remaining items skipped' ,
235+ narrationTitle : 'Done!' ,
236+ narrationDescription : 'Remaining items skipped' ,
179237 } ,
180238 ] ,
181239 } ;
@@ -208,14 +266,37 @@ const ArrayEvaluationDemo: React.FC = () => {
208266 }
209267 } , [ ] ) ;
210268
211- const getNarration = useCallback ( ( ) => {
269+ const getNarration = useCallback ( ( ) : Narration => {
212270 const currentSteps = stepsRef . current [ type ] ;
271+
272+ // Find any currently animating step
273+ const animatingStep = currentSteps . find ( ( step ) => {
274+ const stepStart = step . progress - 8 ;
275+ const stepEnd = step . progress ;
276+ return progress >= stepStart && progress <= stepEnd ;
277+ } ) ;
278+
279+ if ( animatingStep ) {
280+ return {
281+ title : animatingStep . narrationTitle ,
282+ description : animatingStep . narrationDescription ,
283+ } ;
284+ }
285+
286+ // If no step is currently animating, find the last completed step
213287 for ( let i = currentSteps . length - 1 ; i >= 0 ; i -- ) {
214- if ( progress >= currentSteps [ i ] . progress ) {
215- return currentSteps [ i ] . narration ;
288+ if ( progress > currentSteps [ i ] . progress ) {
289+ return {
290+ title : currentSteps [ i ] . narrationTitle ,
291+ description : currentSteps [ i ] . narrationDescription ,
292+ } ;
216293 }
217294 }
218- return 'Ready to start' ;
295+
296+ return {
297+ title : 'Ready to start' ,
298+ description : 'Press play to start the animation' ,
299+ } ;
219300 } , [ progress , type ] ) ;
220301
221302 const getLatestStepForItem = useCallback (
@@ -282,7 +363,7 @@ const ArrayEvaluationDemo: React.FC = () => {
282363 }
283364 } else if ( latestStep ) {
284365 column = latestStep . to ;
285- exploded = latestStep . explode ;
366+ exploded = latestStep . explode || false ;
286367 }
287368
288369 return {
@@ -291,7 +372,7 @@ const ArrayEvaluationDemo: React.FC = () => {
291372 transformed : column >= 1 ,
292373 exploded,
293374 skipped : skipRemainingItems || ( latestStep ?. skipRemaining && i !== 4 ) ,
294- } ;
375+ } as Position ;
295376 } ) ;
296377
297378 setPositions ( newPositions ) ;
@@ -337,22 +418,36 @@ const ArrayEvaluationDemo: React.FC = () => {
337418 useEffect ( ( ) => {
338419 cleanup ( ) ;
339420 if ( playing ) {
340- const animate = ( ) => {
341- setProgress ( ( prev ) => {
342- const next = Math . min ( prev + ANIMATION_STEP , MAX_PROGRESS ) ;
343- if ( next >= MAX_PROGRESS ) {
344- setPlaying ( false ) ;
421+ let lastTime = performance . now ( ) ;
422+
423+ const animate = ( currentTime : number ) => {
424+ const deltaTime = currentTime - lastTime ;
425+
426+ if ( deltaTime >= FRAME_DURATION ) {
427+ // Only update if enough time has passed
428+ setProgress ( ( prev ) => {
429+ const next = Math . min ( prev + ANIMATION_STEP , MAX_PROGRESS ) ;
430+ if ( next >= MAX_PROGRESS ) {
431+ setPlaying ( false ) ;
432+ return next ;
433+ }
345434 return next ;
346- }
435+ } ) ;
436+ lastTime = currentTime ;
437+ }
438+
439+ if ( playing ) {
347440 animationRef . current = requestAnimationFrame ( animate ) ;
348- return next ;
349- } ) ;
441+ }
350442 } ;
443+
351444 animationRef . current = requestAnimationFrame ( animate ) ;
352445 }
353446 return cleanup ;
354447 } , [ playing , cleanup ] ) ;
355448
449+ const narration = getNarration ( ) ;
450+
356451 return (
357452 < Card className = 'w-full max-w-2xl' >
358453 < CardHeader >
@@ -366,10 +461,12 @@ const ArrayEvaluationDemo: React.FC = () => {
366461 </ CardTitle >
367462 </ CardHeader >
368463 < CardContent >
369- < div className = 'h-12 mb-4 flex items-center justify-center text-lg font-medium' >
370- { getNarration ( ) }
464+ < div className = 'h-12 mb-4 text-lg font-medium text-center' >
465+ { narration . title }
466+ < div className = 'text-sm text-gray-500 font-normal' >
467+ { narration . description }
468+ </ div >
371469 </ div >
372-
373470 < svg viewBox = '0 0 400 400' className = 'w-full h-80 mb-4' >
374471 { columns . map ( ( col , i ) => (
375472 < text
@@ -440,23 +537,25 @@ const ArrayEvaluationDemo: React.FC = () => {
440537 </ svg >
441538
442539 < div className = 'space-y-4' >
443- < div className = 'flex items-center justify-between mb-4' >
444- < div className = 'flex items-center gap-2' >
445- < span > Eager</ span >
446- < Switch
447- checked = { type === 'lazy' }
448- onCheckedChange = { ( checked : boolean ) => {
449- cleanup ( ) ;
450- setPlaying ( false ) ;
451- setProgress ( 0 ) ;
452- setType ( checked ? 'lazy' : 'eager' ) ;
453- setExplosions ( [ ] ) ;
454- processedStepsRef . current . clear ( ) ;
455- } }
456- />
457- < span > Lazy</ span >
540+ { demoType || (
541+ < div className = 'flex items-center justify-between mb-4' >
542+ < div className = 'flex items-center gap-2' >
543+ < span > Eager</ span >
544+ < Switch
545+ checked = { type === 'lazy' }
546+ onCheckedChange = { ( checked : boolean ) => {
547+ cleanup ( ) ;
548+ setPlaying ( false ) ;
549+ setProgress ( 0 ) ;
550+ setType ( checked ? 'lazy' : 'eager' ) ;
551+ setExplosions ( [ ] ) ;
552+ processedStepsRef . current . clear ( ) ;
553+ } }
554+ />
555+ < span > Lazy</ span >
556+ </ div >
458557 </ div >
459- </ div >
558+ ) }
460559
461560 < Slider
462561 value = { [ progress ] }
0 commit comments