@@ -3,6 +3,11 @@ window.startHeroAnimation = () => {
33 if ( ! canvas ) return ;
44 const ctx = canvas . getContext ( '2d' ) ;
55
6+ // --- NEW: PREVENT DUPLICATE ANIMATIONS ON RESET ---
7+ if ( window . heroAnimationFrame ) {
8+ cancelAnimationFrame ( window . heroAnimationFrame ) ;
9+ }
10+
611 const name = "DANIEL HAN" ;
712 const initialCountdown = ( Math . random ( ) * 1.5 + 3.5 ) . toFixed ( 2 ) ;
813 let countdown = parseFloat ( initialCountdown ) ;
@@ -16,6 +21,12 @@ window.startHeroAnimation = () => {
1621 let crawlStartTime = 0 ;
1722 let isWaving = false ;
1823
24+ // --- NEW STATES FOR THE BUTTON TRIGGER ---
25+ window . isCountingDown = false ;
26+ window . triggerDetonation = ( ) => {
27+ window . isCountingDown = true ;
28+ } ;
29+
1930 let glitchState = 0 ;
2031 let bodyOffsetX = 0 ;
2132 let bodyOffsetY = 0 ;
@@ -183,13 +194,8 @@ window.startHeroAnimation = () => {
183194 this . y += this . vy ;
184195 this . rotation += this . spin ;
185196
186- if ( this . x < 50 ) {
187- this . x = 50 ;
188- this . vx = Math . abs ( this . vx ) * 0.7 ;
189- } else if ( this . x > canvas . width - 50 ) {
190- this . x = canvas . width - 50 ;
191- this . vx = - Math . abs ( this . vx ) * 0.7 ;
192- }
197+ if ( this . x < 50 ) { this . x = 50 ; this . vx *= - 0.7 ; }
198+ else if ( this . x > canvas . width - 50 ) { this . x = canvas . width - 50 ; this . vx *= - 0.7 ; }
193199
194200 const floorY = canvas . height / 2 + 150 ;
195201 if ( this . y > floorY ) {
@@ -219,14 +225,12 @@ window.startHeroAnimation = () => {
219225 cTargetX += ( Math . random ( ) - 0.5 ) * 8 ;
220226 cTargetRot += ( Math . random ( ) - 0.5 ) * 0.15 ;
221227 }
222- // Right arm swings while walking instead of the left
223228 if ( this . id === 'r_arm_1' || this . id === 'r_arm_2' ) {
224229 cTargetX += Math . sin ( walkCycle + Math . PI ) * 20 ;
225230 cTargetRot += Math . sin ( walkCycle + Math . PI ) * 0.3 ;
226231 }
227232 }
228233
229- // PHASE 4: LEFT ARM REACHES DOWN
230234 if ( glitchState === 4 && ( this . id === 'l_arm_1' || this . id === 'l_arm_2' ) ) {
231235 const headParticle = particles . find ( p => p . id === 'head' ) ;
232236 if ( this . id === 'l_arm_2' ) {
@@ -240,15 +244,13 @@ window.startHeroAnimation = () => {
240244 }
241245 }
242246
243- // PHASE 5: LEFT HAND HOLDS HEAD
244247 if ( this . id === 'head' && glitchState === 5 ) {
245248 const lHand = particles . find ( p => p . id === 'l_arm_2' ) ;
246249 this . x += ( lHand . x - this . x ) * 0.4 ;
247250 this . y += ( lHand . y - 15 - this . y ) * 0.4 ;
248251 this . rotation += ( 0 - this . rotation ) * 0.1 ;
249252 }
250253
251- // PHASE 5: LEFT ARM MOVES HEAD BACK TO NECK
252254 if ( glitchState === 5 && ( this . id === 'l_arm_1' || this . id === 'l_arm_2' ) ) {
253255 const neckX = canvas . width / 2 + bodyOffsetX ;
254256 const neckY = canvas . height / 2 - 110 + bodyOffsetY ;
@@ -278,7 +280,6 @@ window.startHeroAnimation = () => {
278280 }
279281
280282 } else {
281- // PHASE 0: RIGHT ARM WAVING ('L')
282283 if ( this . id === 'r_arm_2' ) {
283284 const waveAngle = - 0.85 + Math . sin ( Date . now ( ) * 0.003 ) * 0.6 ;
284285 const pivotX = canvas . width / 2 + 45 ;
@@ -365,17 +366,16 @@ window.startHeroAnimation = () => {
365366 const cx = canvas . width / 2 ;
366367 const cy = canvas . height / 2 + 30 ;
367368
368- // PERFECTED NEW TARGET MAPPING
369369 const targets = [
370- { x : cx , y : cy - 110 , rot : ( Math . random ( ) - 0.5 ) * 0.3 , id : 'head' } , // D
371- { x : cx - 85 , y : cy - 5 , rot : 0.8 , id : 'l_arm_2' } , // A (Left Hand)
372- { x : cx - 45 , y : cy - 40 , rot : 0.8 , id : 'l_arm_1' } , // N (Left Arm)
373- { x : cx , y : cy - 40 , rot : 0 , id : 'torso_1' } , // I (Spine)
374- { x : cx + 45 , y : cy - 60 , rot : - 0.4 , id : 'r_arm_1' } , // E (Right Arm, Raised)
375- { x : cx + 80 , y : cy - 100 , rot : - 0.85 , id : 'r_arm_2' } , // L (Right Hand, Waving Hook)
376- { x : cx - 35 , y : cy + 80 , rot : 0 , id : 'l_leg' } , // H (Left Leg)
377- { x : cx , y : cy + 10 , rot : 0 , id : 'torso_2' } , // A (Pelvis)
378- { x : cx + 35 , y : cy + 80 , rot : 0 , id : 'r_leg' } // N (Right Leg)
370+ { x : cx , y : cy - 110 , rot : ( Math . random ( ) - 0.5 ) * 0.3 , id : 'head' } ,
371+ { x : cx - 85 , y : cy - 5 , rot : 0.8 , id : 'l_arm_2' } ,
372+ { x : cx - 45 , y : cy - 40 , rot : 0.8 , id : 'l_arm_1' } ,
373+ { x : cx , y : cy - 40 , rot : 0 , id : 'torso_1' } ,
374+ { x : cx + 45 , y : cy - 60 , rot : - 0.4 , id : 'r_arm_1' } ,
375+ { x : cx + 80 , y : cy - 100 , rot : - 0.85 , id : 'r_arm_2' } ,
376+ { x : cx - 35 , y : cy + 80 , rot : 0 , id : 'l_leg' } ,
377+ { x : cx , y : cy + 10 , rot : 0 , id : 'torso_2' } ,
378+ { x : cx + 35 , y : cy + 80 , rot : 0 , id : 'r_leg' }
379379 ] ;
380380
381381 particles . forEach ( ( p , index ) => {
@@ -389,6 +389,20 @@ window.startHeroAnimation = () => {
389389 } ) ;
390390 }
391391
392+ function drawIdleName ( ) {
393+ ctx . save ( ) ;
394+ ctx . translate ( canvas . width / 2 , canvas . height / 2 ) ;
395+ ctx . font = 'bold 80px "JetBrains Mono"' ;
396+ ctx . fillStyle = "#00ff41" ;
397+ ctx . shadowColor = "#00ff41" ;
398+ // Very slow, calm breathing effect before the button is clicked
399+ ctx . shadowBlur = 10 + Math . sin ( Date . now ( ) / 400 ) * 5 ;
400+ ctx . textAlign = 'center' ;
401+ ctx . textBaseline = 'middle' ;
402+ ctx . fillText ( name , 0 , 0 ) ;
403+ ctx . restore ( ) ;
404+ }
405+
392406 function drawPulsingName ( ) {
393407 const progress = 1 - ( countdown / initialCountdown ) ;
394408 const intensity = Math . pow ( progress , 4 ) ;
@@ -404,24 +418,36 @@ window.startHeroAnimation = () => {
404418 ctx . textAlign = 'center' ;
405419 ctx . textBaseline = 'middle' ;
406420 ctx . fillText ( name , 0 , 0 ) ;
421+
422+ if ( countdown < 0.6 && Math . random ( ) > 0.4 ) {
423+ ctx . fillStyle = "rgba(255, 0, 60, 0.7)" ;
424+ ctx . fillText ( name , ( Math . random ( ) - 0.5 ) * 20 , ( Math . random ( ) - 0.5 ) * 20 ) ;
425+ ctx . fillStyle = "rgba(0, 212, 255, 0.7)" ;
426+ ctx . fillText ( name , ( Math . random ( ) - 0.5 ) * 20 , ( Math . random ( ) - 0.5 ) * 20 ) ;
427+ }
407428 ctx . restore ( ) ;
408429 }
409430
410431 function animate ( ) {
411- const cycleTime = Date . now ( ) % 16000 ;
432+ // --- MASTER TIMELINE UPDATED TO 17 SECONDS ---
433+ const cycleTime = Date . now ( ) % 17000 ;
412434 let newPhase = 0 ;
413435
414- if ( cycleTime > 5000 && cycleTime < 5400 ) newPhase = 1 ;
415- else if ( cycleTime >= 5400 && cycleTime < 6800 ) newPhase = 2 ;
416- else if ( cycleTime >= 6800 && cycleTime < 10000 ) newPhase = 3 ;
417- else if ( cycleTime >= 10000 && cycleTime < 11200 ) newPhase = 4 ;
418- else if ( cycleTime >= 11200 && cycleTime < 14500 ) newPhase = 5 ;
419- else if ( cycleTime >= 14500 && cycleTime < 16000 ) newPhase = 6 ;
436+ // 1-SECOND GREEN PAUSE INSERTED IN THE MIDDLE OF THE SHAKE
437+ if ( cycleTime > 5000 && cycleTime < 5200 ) newPhase = 1 ; // 1st half of violent red shake
438+ else if ( cycleTime >= 5200 && cycleTime < 5900 ) newPhase = 0 ; // 1-second calm green pause
439+ else if ( cycleTime >= 5900 && cycleTime < 6400 ) newPhase = 1 ; // 2nd half of violent red shake
440+
441+ // The rest of the Frankenstein walk cycle (Shifted +1000ms)
442+ else if ( cycleTime >= 6400 && cycleTime < 7800 ) newPhase = 2 ;
443+ else if ( cycleTime >= 7800 && cycleTime < 11000 ) newPhase = 3 ;
444+ else if ( cycleTime >= 11000 && cycleTime < 12200 ) newPhase = 4 ;
445+ else if ( cycleTime >= 12200 && cycleTime < 15500 ) newPhase = 5 ;
446+ else if ( cycleTime >= 15500 && cycleTime < 17000 ) newPhase = 6 ;
420447
421448 if ( newPhase === 3 ) {
422449 const headParticle = particles . find ( p => p . id === 'head' ) ;
423450 if ( headParticle ) {
424- // Because head bounces left, body walks to the right of the head
425451 let targetOffsetX = headParticle . x - ( canvas . width / 2 ) + 35 ;
426452 let dist = targetOffsetX - bodyOffsetX ;
427453 if ( Math . abs ( dist ) > 30 ) {
@@ -458,7 +484,6 @@ window.startHeroAnimation = () => {
458484 const head = particles . find ( p => p . id === 'head' ) ;
459485 if ( head ) {
460486 head . vy = - 12 - Math . random ( ) * 4 ;
461- // Force bounce LEFT so the left arm can grab it
462487 head . vx = - Math . abs ( Math . random ( ) * 5 + 4 ) ;
463488 head . spin = ( Math . random ( ) - 0.5 ) * 0.8 ;
464489
@@ -470,9 +495,34 @@ window.startHeroAnimation = () => {
470495 glitchState = newPhase ;
471496
472497 const allLettersStopped = exploded && particles . length > 0 && particles . every ( p => p . stopped ) ;
498+
499+ // --- TWITCHING HEAD WAKE-UP SEQUENCE ---
473500 if ( allLettersStopped && ! isCrawling && ! isWaving ) {
474501 if ( timeSinceStop === 0 ) timeSinceStop = Date . now ( ) ;
475- if ( Date . now ( ) - timeSinceStop > 2500 ) {
502+
503+ let elapsedIdle = Date . now ( ) - timeSinceStop ;
504+
505+ // Between 1.5s and 1.7s, the "D" (head) violently twitches
506+ if ( elapsedIdle > 1500 && elapsedIdle < 1700 ) {
507+
508+ // INCREASED FREQUENCY: Now twitches on 90% of frames instead of 50%
509+ if ( Math . random ( ) > 0.1 ) {
510+ // INCREASED MAGNITUDE: Jumps up to 14 pixels instead of 6
511+ particles [ 0 ] . x += ( Math . random ( ) - 0.5 ) * 14 ;
512+ particles [ 0 ] . y += ( Math . random ( ) - 0.5 ) * 14 ;
513+
514+ // Thrashes its rotation twice as hard
515+ particles [ 0 ] . rotation += ( Math . random ( ) - 0.5 ) * 0.8 ;
516+
517+ // Bleeds more often while thrashing
518+ if ( Math . random ( ) > 0.6 ) {
519+ splatters . push ( new BloodSplatter ( particles [ 0 ] . x , particles [ 0 ] . y , ( Math . random ( ) - 0.5 ) * 6 , ( Math . random ( ) - 0.5 ) * 6 , true ) ) ;
520+ }
521+ }
522+ }
523+
524+ // After 2.7 seconds, start crawling
525+ if ( elapsedIdle > 2700 ) {
476526 isCrawling = true ;
477527 crawlStartTime = Date . now ( ) ;
478528 assignStickFigureTargets ( ) ;
@@ -486,7 +536,12 @@ window.startHeroAnimation = () => {
486536
487537 ctx . clearRect ( 0 , 0 , canvas . width , canvas . height ) ;
488538
489- if ( ! exploded ) {
539+ // --- NEW RENDER LOGIC FOR THE IDLE/COUNTDOWN STATE ---
540+ if ( ! isCountingDown ) {
541+ // STATE 1: Waiting for the user to click the button
542+ drawIdleName ( ) ;
543+ } else if ( ! exploded ) {
544+ // STATE 2: Button clicked! Start the countdown
490545 countdown = ( parseFloat ( countdown ) - 0.016 ) . toFixed ( 2 ) ;
491546 if ( countdown <= 0 ) {
492547 countdown = 0 ;
@@ -495,11 +550,8 @@ window.startHeroAnimation = () => {
495550 document . body . style . animation = "glitch 0.3s 2" ;
496551 }
497552 drawPulsingName ( ) ;
498- ctx . fillStyle = "#ff003c" ;
499- ctx . font = 'bold 24px "JetBrains Mono"' ;
500- ctx . textAlign = "center" ;
501- ctx . fillText ( `DETONATION IN: ${ countdown } s` , canvas . width / 2 , canvas . height / 2 + 120 ) ;
502553 } else {
554+ // STATE 3: Exploded, run the Frankenstein logic
503555 splatters = splatters . filter ( s => s . alpha > 0 ) ;
504556 splatters . forEach ( s => {
505557 s . update ( allLettersStopped ) ;
@@ -511,7 +563,7 @@ window.startHeroAnimation = () => {
511563 p . draw ( ) ;
512564 } ) ;
513565 }
514- animationFrame = requestAnimationFrame ( animate ) ;
566+ window . heroAnimationFrame = requestAnimationFrame ( animate ) ;
515567 }
516568
517569 resize ( ) ;
0 commit comments