@@ -11,3 +11,87 @@ function focusElement (id) {
1111 const element = document . getElementById ( id ) ;
1212 element . focus ( ) ;
1313}
14+
15+ // Confetti celebration effect
16+ function triggerConfetti ( ) {
17+ const duration = 3000 ;
18+ const colors = [ '#E94560' , '#0F3460' , '#16213E' , '#FFD700' , '#00D4FF' , '#FF6B6B' , '#4ECDC4' ] ;
19+
20+ // Create canvas
21+ const canvas = document . createElement ( 'canvas' ) ;
22+ canvas . id = 'confetti-canvas' ;
23+ canvas . style . cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:9999;' ;
24+ document . body . appendChild ( canvas ) ;
25+
26+ const ctx = canvas . getContext ( '2d' ) ;
27+ canvas . width = window . innerWidth ;
28+ canvas . height = window . innerHeight ;
29+
30+ const particles = [ ] ;
31+ const particleCount = 150 ;
32+
33+ // Create particles
34+ for ( let i = 0 ; i < particleCount ; i ++ ) {
35+ particles . push ( {
36+ x : Math . random ( ) * canvas . width ,
37+ y : Math . random ( ) * canvas . height - canvas . height ,
38+ size : Math . random ( ) * 10 + 5 ,
39+ color : colors [ Math . floor ( Math . random ( ) * colors . length ) ] ,
40+ speedY : Math . random ( ) * 3 + 2 ,
41+ speedX : Math . random ( ) * 4 - 2 ,
42+ rotation : Math . random ( ) * 360 ,
43+ rotationSpeed : Math . random ( ) * 10 - 5 ,
44+ shape : Math . random ( ) > 0.5 ? 'rect' : 'circle' ,
45+ opacity : 1
46+ } ) ;
47+ }
48+
49+ const startTime = Date . now ( ) ;
50+
51+ function animate ( ) {
52+ const elapsed = Date . now ( ) - startTime ;
53+
54+ if ( elapsed > duration ) {
55+ canvas . remove ( ) ;
56+ return ;
57+ }
58+
59+ ctx . clearRect ( 0 , 0 , canvas . width , canvas . height ) ;
60+
61+ // Fade out in the last 500ms
62+ const fadeStart = duration - 500 ;
63+ const globalOpacity = elapsed > fadeStart ? 1 - ( elapsed - fadeStart ) / 500 : 1 ;
64+
65+ particles . forEach ( p => {
66+ ctx . save ( ) ;
67+ ctx . globalAlpha = globalOpacity * p . opacity ;
68+ ctx . translate ( p . x , p . y ) ;
69+ ctx . rotate ( p . rotation * Math . PI / 180 ) ;
70+ ctx . fillStyle = p . color ;
71+
72+ if ( p . shape === 'rect' ) {
73+ ctx . fillRect ( - p . size / 2 , - p . size / 2 , p . size , p . size * 0.6 ) ;
74+ } else {
75+ ctx . beginPath ( ) ;
76+ ctx . arc ( 0 , 0 , p . size / 2 , 0 , Math . PI * 2 ) ;
77+ ctx . fill ( ) ;
78+ }
79+
80+ ctx . restore ( ) ;
81+
82+ // Update position
83+ p . y += p . speedY ;
84+ p . x += p . speedX ;
85+ p . rotation += p . rotationSpeed ;
86+ p . speedY += 0.1 ; // gravity
87+
88+ // Wrap around horizontally
89+ if ( p . x < 0 ) p . x = canvas . width ;
90+ if ( p . x > canvas . width ) p . x = 0 ;
91+ } ) ;
92+
93+ requestAnimationFrame ( animate ) ;
94+ }
95+
96+ animate ( ) ;
97+ }
0 commit comments