@@ -3280,6 +3280,95 @@ function drawConfetti() {
32803280 }
32813281}
32823282
3283+ // Helper functions for 2.5D visual effects
3284+ function drawRect3D ( x : number , y : number , width : number , height : number , color : string , depth : number = 8 ) {
3285+ // Draw main face
3286+ ctx . fillStyle = color ;
3287+ ctx . fillRect ( x , y , width , height ) ;
3288+
3289+ // Draw right side (darker)
3290+ const sideColor = darkenColor ( color , 0.3 ) ;
3291+ ctx . fillStyle = sideColor ;
3292+ ctx . beginPath ( ) ;
3293+ ctx . moveTo ( x + width , y ) ;
3294+ ctx . lineTo ( x + width + depth , y - depth ) ;
3295+ ctx . lineTo ( x + width + depth , y + height - depth ) ;
3296+ ctx . lineTo ( x + width , y + height ) ;
3297+ ctx . closePath ( ) ;
3298+ ctx . fill ( ) ;
3299+
3300+ // Draw top side (lighter)
3301+ const topColor = lightenColor ( color , 0.2 ) ;
3302+ ctx . fillStyle = topColor ;
3303+ ctx . beginPath ( ) ;
3304+ ctx . moveTo ( x , y ) ;
3305+ ctx . lineTo ( x + depth , y - depth ) ;
3306+ ctx . lineTo ( x + width + depth , y - depth ) ;
3307+ ctx . lineTo ( x + width , y ) ;
3308+ ctx . closePath ( ) ;
3309+ ctx . fill ( ) ;
3310+ }
3311+
3312+ function drawShadow ( x : number , y : number , width : number , height : number , offsetX : number = 3 , offsetY : number = 3 ) {
3313+ ctx . save ( ) ;
3314+ ctx . fillStyle = 'rgba(0, 0, 0, 0.2)' ;
3315+ ctx . fillRect ( x + offsetX , y + offsetY , width , height ) ;
3316+ ctx . restore ( ) ;
3317+ }
3318+
3319+ function darkenColor ( color : string , factor : number ) : string {
3320+ if ( color . startsWith ( '#' ) ) {
3321+ // Handle hex colors
3322+ const hex = color . slice ( 1 ) ;
3323+ const r = parseInt ( hex . substr ( 0 , 2 ) , 16 ) ;
3324+ const g = parseInt ( hex . substr ( 2 , 2 ) , 16 ) ;
3325+ const b = parseInt ( hex . substr ( 4 , 2 ) , 16 ) ;
3326+ return `rgb(${ Math . floor ( r * ( 1 - factor ) ) } , ${ Math . floor ( g * ( 1 - factor ) ) } , ${ Math . floor ( b * ( 1 - factor ) ) } )` ;
3327+ }
3328+ // Return as-is for named colors or rgb colors - we'll handle the main cases
3329+ return color ;
3330+ }
3331+
3332+ function lightenColor ( color : string , factor : number ) : string {
3333+ if ( color . startsWith ( '#' ) ) {
3334+ // Handle hex colors
3335+ const hex = color . slice ( 1 ) ;
3336+ const r = parseInt ( hex . substr ( 0 , 2 ) , 16 ) ;
3337+ const g = parseInt ( hex . substr ( 2 , 2 ) , 16 ) ;
3338+ const b = parseInt ( hex . substr ( 4 , 2 ) , 16 ) ;
3339+ return `rgb(${ Math . floor ( r + ( 255 - r ) * factor ) } , ${ Math . floor ( g + ( 255 - g ) * factor ) } , ${ Math . floor ( b + ( 255 - b ) * factor ) } )` ;
3340+ }
3341+ // Return as-is for named colors or rgb colors
3342+ return color ;
3343+ }
3344+
3345+ function drawCoin3D ( x : number , y : number , radius : number ) {
3346+ // Draw shadow first
3347+ ctx . save ( ) ;
3348+ ctx . fillStyle = 'rgba(0, 0, 0, 0.2)' ;
3349+ ctx . beginPath ( ) ;
3350+ ctx . ellipse ( x + 2 , y + 3 , radius * 0.8 , radius * 0.3 , 0 , 0 , 2 * Math . PI ) ;
3351+ ctx . fill ( ) ;
3352+ ctx . restore ( ) ;
3353+
3354+ // Draw coin with gradient
3355+ const gradient = ctx . createRadialGradient ( x - radius * 0.3 , y - radius * 0.3 , 0 , x , y , radius ) ;
3356+ gradient . addColorStop ( 0 , '#4df' ) ;
3357+ gradient . addColorStop ( 0.7 , '#0cf' ) ;
3358+ gradient . addColorStop ( 1 , '#0af' ) ;
3359+
3360+ ctx . fillStyle = gradient ;
3361+ ctx . beginPath ( ) ;
3362+ ctx . arc ( x , y , radius , 0 , 2 * Math . PI ) ;
3363+ ctx . fill ( ) ;
3364+
3365+ // Add highlight
3366+ ctx . fillStyle = 'rgba(255, 255, 255, 0.6)' ;
3367+ ctx . beginPath ( ) ;
3368+ ctx . arc ( x - radius * 0.3 , y - radius * 0.3 , radius * 0.3 , 0 , 2 * Math . PI ) ;
3369+ ctx . fill ( ) ;
3370+ }
3371+
32833372function draw ( ) {
32843373 // Draw background
32853374 if ( imageBg && imageBgLoaded && imageBgObj ) {
@@ -3346,8 +3435,7 @@ function draw() {
33463435 ctx . scale ( scale , scale ) ;
33473436 }
33483437 ctx . translate ( - cameraX , - cameraY ) ;
3349- // Draw platforms
3350- ctx . fillStyle = '#654321' ;
3438+ // Draw platforms with 3D effect
33513439 let lowestPlatformIndex = - 1 ;
33523440 if ( levelType === 'vertical' && platforms . length > 0 ) {
33533441 let maxY = - Infinity ;
@@ -3361,6 +3449,15 @@ function draw() {
33613449 for ( let i = 0 ; i < platforms . length ; i ++ ) {
33623450 const plat = platforms [ i ] ;
33633451 if ( 'isSlope' in plat && plat . isSlope ) {
3452+ // Draw shadow for slope platforms
3453+ drawShadow ( plat . x , plat . y , plat . width , plat . height ) ;
3454+
3455+ // Draw slope platform with gradient
3456+ const gradient = ctx . createLinearGradient ( plat . x , plat . y , plat . x , plat . y + plat . height ) ;
3457+ gradient . addColorStop ( 0 , '#8b6f47' ) ;
3458+ gradient . addColorStop ( 1 , '#654321' ) ;
3459+ ctx . fillStyle = gradient ;
3460+
33643461 ctx . beginPath ( ) ;
33653462 ctx . moveTo ( plat . x , plat . y ) ;
33663463 ctx . lineTo ( plat . x + plat . width , plat . endY ) ;
@@ -3369,7 +3466,11 @@ function draw() {
33693466 ctx . closePath ( ) ;
33703467 ctx . fill ( ) ;
33713468 } else {
3372- ctx . fillRect ( plat . x , plat . y , plat . width , plat . height ) ;
3469+ // Draw shadow first
3470+ drawShadow ( plat . x , plat . y , plat . width , plat . height ) ;
3471+
3472+ // Draw 3D platform
3473+ drawRect3D ( plat . x , plat . y , plat . width , plat . height , '#654321' , 6 ) ;
33733474 }
33743475 // Draw up-arrow on the visually lowest platform in vertical mode
33753476 if ( levelType === 'vertical' && i === lowestPlatformIndex ) {
@@ -3379,29 +3480,34 @@ function draw() {
33793480 ctx . textAlign = 'center' ;
33803481 ctx . textBaseline = 'middle' ;
33813482 ctx . globalAlpha = 0.85 ;
3483+ ctx . shadowColor = 'rgba(0, 0, 0, 0.5)' ;
3484+ ctx . shadowOffsetX = 2 ;
3485+ ctx . shadowOffsetY = 2 ;
3486+ ctx . shadowBlur = 4 ;
33823487 ctx . fillText ( '↑' , plat . x + plat . width / 2 , plat . y + plat . height / 2 ) ;
33833488 ctx . globalAlpha = 1 ;
3489+ ctx . shadowColor = 'transparent' ;
3490+ ctx . shadowOffsetX = 0 ;
3491+ ctx . shadowOffsetY = 0 ;
3492+ ctx . shadowBlur = 0 ;
33843493 ctx . restore ( ) ;
33853494 }
33863495 }
3387- // Draw moving platforms
3388- ctx . fillStyle = '#888' ;
3496+ // Draw moving platforms with 3D effect
33893497 for ( const plat of movingPlatforms ) {
3390- ctx . fillRect ( plat . x , plat . y , plat . width , plat . height ) ;
3498+ drawShadow ( plat . x , plat . y , plat . width , plat . height ) ;
3499+ drawRect3D ( plat . x , plat . y , plat . width , plat . height , '#888' , 4 ) ;
33913500 }
3392- // Draw boxes
3393- ctx . fillStyle = '#b5651d' ;
3501+ // Draw boxes with 3D effect
33943502 for ( const box of boxes ) {
3395- ctx . fillRect ( box . x , box . y , box . width , box . height ) ;
3503+ drawShadow ( box . x , box . y , box . width , box . height ) ;
3504+ drawRect3D ( box . x , box . y , box . width , box . height , '#b5651d' , 5 ) ;
33963505 }
3397- // Draw collectibles
3506+ // Draw collectibles with 3D effects
33983507 for ( const c of collectibles ) {
33993508 if ( ! c . collected ) {
34003509 if ( c . type === 'coin' ) {
3401- ctx . fillStyle = '#0cf' ;
3402- ctx . beginPath ( ) ;
3403- ctx . arc ( c . x + c . width / 2 , c . y + c . height / 2 , 10 , 0 , 2 * Math . PI ) ;
3404- ctx . fill ( ) ;
3510+ drawCoin3D ( c . x + c . width / 2 , c . y + c . height / 2 , 10 ) ;
34053511 } else if ( c . type === 'heart' ) {
34063512 // Draw a heart shape
34073513 ctx . save ( ) ;
@@ -3453,45 +3559,113 @@ function draw() {
34533559 }
34543560 }
34553561 }
3456- // Draw spikes
3457- ctx . fillStyle = '#e33' ;
3562+ // Draw spikes with 3D effect
34583563 for ( const spike of spikes ) {
3564+ // Draw shadow
3565+ ctx . save ( ) ;
3566+ ctx . fillStyle = 'rgba(0, 0, 0, 0.2)' ;
3567+ ctx . beginPath ( ) ;
3568+ ctx . moveTo ( spike . x + 2 , spike . y + spike . height + 2 ) ;
3569+ ctx . lineTo ( spike . x + spike . width / 2 + 2 , spike . y + 2 ) ;
3570+ ctx . lineTo ( spike . x + spike . width + 2 , spike . y + spike . height + 2 ) ;
3571+ ctx . closePath ( ) ;
3572+ ctx . fill ( ) ;
3573+ ctx . restore ( ) ;
3574+
3575+ // Draw spike with gradient
3576+ const gradient = ctx . createLinearGradient ( spike . x , spike . y , spike . x + spike . width , spike . y + spike . height ) ;
3577+ gradient . addColorStop ( 0 , '#f55' ) ;
3578+ gradient . addColorStop ( 0.5 , '#e33' ) ;
3579+ gradient . addColorStop ( 1 , '#c22' ) ;
3580+ ctx . fillStyle = gradient ;
3581+
34593582 ctx . beginPath ( ) ;
34603583 ctx . moveTo ( spike . x , spike . y + spike . height ) ;
34613584 ctx . lineTo ( spike . x + spike . width / 2 , spike . y ) ;
34623585 ctx . lineTo ( spike . x + spike . width , spike . y + spike . height ) ;
34633586 ctx . closePath ( ) ;
34643587 ctx . fill ( ) ;
3588+
3589+ // Add highlight
3590+ ctx . strokeStyle = '#f77' ;
3591+ ctx . lineWidth = 1 ;
3592+ ctx . beginPath ( ) ;
3593+ ctx . moveTo ( spike . x + spike . width / 2 , spike . y ) ;
3594+ ctx . lineTo ( spike . x + spike . width / 4 , spike . y + spike . height / 2 ) ;
3595+ ctx . stroke ( ) ;
34653596 }
3466- // Draw spawn tubes
3467- ctx . fillStyle = '#0a8000' ; // Dark green for tubes
3597+ // Draw spawn tubes with 3D effect
34683598 for ( const tube of tubes ) {
3469- // Draw tube body
3470- ctx . fillRect ( tube . x , tube . y , tube . width , tube . height ) ;
3599+ // Draw shadow
3600+ drawShadow ( tube . x , tube . y , tube . width , tube . height ) ;
3601+
3602+ // Draw tube body with 3D effect
3603+ drawRect3D ( tube . x , tube . y , tube . width , tube . height , '#0a8000' , 3 ) ;
34713604
34723605 // Draw tube opening (darker green) - larger opening for bigger tubes at the TOP where it meets the platform
3473- ctx . fillStyle = '#064000' ;
3474- const openingY = Math . max ( tube . y , GROUND_Y - 15 ) ; // Position opening at platform level
3606+ const gradient = ctx . createRadialGradient (
3607+ tube . x + tube . width / 2 ,
3608+ Math . max ( tube . y , GROUND_Y - 15 ) + 7 ,
3609+ 0 ,
3610+ tube . x + tube . width / 2 ,
3611+ Math . max ( tube . y , GROUND_Y - 15 ) + 7 ,
3612+ tube . width / 2
3613+ ) ;
3614+ gradient . addColorStop ( 0 , '#064000' ) ;
3615+ gradient . addColorStop ( 1 , '#032000' ) ;
3616+ ctx . fillStyle = gradient ;
3617+ const openingY = Math . max ( tube . y , GROUND_Y - 15 ) ;
34753618 ctx . fillRect ( tube . x + 4 , openingY , tube . width - 8 , 15 ) ;
34763619
34773620 // Draw pipe details (light green lines) - adjusted for longer tubes
34783621 ctx . fillStyle = '#0c8000' ;
34793622 ctx . fillRect ( tube . x + 8 , tube . y + 8 , 3 , tube . height - 16 ) ;
34803623 ctx . fillRect ( tube . x + tube . width - 11 , tube . y + 8 , 3 , tube . height - 16 ) ;
34813624
3482- // Add more horizontal bands for longer tubes
3625+ // Add more horizontal bands for longer tubes with gradient
3626+ const bandGradient = ctx . createLinearGradient ( tube . x , 0 , tube . x + tube . width , 0 ) ;
3627+ bandGradient . addColorStop ( 0 , '#0c8000' ) ;
3628+ bandGradient . addColorStop ( 0.5 , '#0e9000' ) ;
3629+ bandGradient . addColorStop ( 1 , '#0c8000' ) ;
3630+ ctx . fillStyle = bandGradient ;
34833631 ctx . fillRect ( tube . x + 4 , tube . y + tube . height / 4 , tube . width - 8 , 2 ) ;
34843632 ctx . fillRect ( tube . x + 4 , tube . y + tube . height / 2 , tube . width - 8 , 2 ) ;
34853633 ctx . fillRect ( tube . x + 4 , tube . y + ( 3 * tube . height ) / 4 , tube . width - 8 , 2 ) ;
3486-
3487- ctx . fillStyle = '#0a8000' ; // Reset to main tube color
34883634 }
3489- // Draw enemies
3635+ // Draw enemies with 3D effects
34903636 for ( const enemy of enemies ) {
34913637 if ( enemy . alive && ropeAnimation . targetEnemy !== enemy ) {
3638+ // Draw shadow
3639+ ctx . save ( ) ;
3640+ ctx . fillStyle = 'rgba(0, 0, 0, 0.2)' ;
3641+ ctx . beginPath ( ) ;
3642+ ctx . ellipse (
3643+ enemy . x + enemy . width / 2 + 2 ,
3644+ enemy . y + enemy . height + 2 ,
3645+ enemy . width * 0.4 ,
3646+ enemy . height * 0.2 ,
3647+ 0 ,
3648+ 0 ,
3649+ 2 * Math . PI
3650+ ) ;
3651+ ctx . fill ( ) ;
3652+ ctx . restore ( ) ;
3653+
34923654 if ( enemy . type === 'circle' ) {
3493- // Draw circle enemies as circles
3494- ctx . fillStyle = '#f06' ; // Pink color for circle enemies
3655+ // Draw circle enemies with 3D gradient
3656+ const gradient = ctx . createRadialGradient (
3657+ enemy . x + enemy . width / 2 - enemy . width * 0.2 ,
3658+ enemy . y + enemy . height / 2 - enemy . height * 0.2 ,
3659+ 0 ,
3660+ enemy . x + enemy . width / 2 ,
3661+ enemy . y + enemy . height / 2 ,
3662+ enemy . width / 2
3663+ ) ;
3664+ gradient . addColorStop ( 0 , '#f8a' ) ;
3665+ gradient . addColorStop ( 0.7 , '#f06' ) ;
3666+ gradient . addColorStop ( 1 , '#d04' ) ;
3667+
3668+ ctx . fillStyle = gradient ;
34953669 ctx . beginPath ( ) ;
34963670 ctx . arc (
34973671 enemy . x + enemy . width / 2 ,
@@ -3501,15 +3675,28 @@ function draw() {
35013675 2 * Math . PI
35023676 ) ;
35033677 ctx . fill ( ) ;
3678+
3679+ // Add highlight
3680+ ctx . fillStyle = 'rgba(255, 255, 255, 0.4)' ;
3681+ ctx . beginPath ( ) ;
3682+ ctx . arc (
3683+ enemy . x + enemy . width / 2 - enemy . width * 0.25 ,
3684+ enemy . y + enemy . height / 2 - enemy . height * 0.25 ,
3685+ enemy . width * 0.15 ,
3686+ 0 ,
3687+ 2 * Math . PI
3688+ ) ;
3689+ ctx . fill ( ) ;
3690+
35043691 // Add simple eyes to make it look more enemy-like
35053692 ctx . fillStyle = '#000' ;
35063693 const eyeSize = 3 ;
35073694 ctx . fillRect ( enemy . x + 8 , enemy . y + 8 , eyeSize , eyeSize ) ;
35083695 ctx . fillRect ( enemy . x + enemy . width - 11 , enemy . y + 8 , eyeSize , eyeSize ) ;
35093696 } else {
3510- // Draw square enemies as rectangles (original behavior)
3511- ctx . fillStyle = '#f90' ; // Orange color for square enemies
3512- ctx . fillRect ( enemy . x , enemy . y , enemy . width , enemy . height ) ;
3697+ // Draw square enemies with 3D effect
3698+ drawRect3D ( enemy . x , enemy . y , enemy . width , enemy . height , '#f90' , 3 ) ;
3699+
35133700 // Add simple eyes to make it look more enemy-like
35143701 ctx . fillStyle = '#000' ;
35153702 const eyeSize = 4 ;
@@ -3532,14 +3719,27 @@ function draw() {
35323719
35333720 // Draw player character (original square or custom emoji)
35343721 ctx . save ( ) ;
3722+
3723+ // Draw player shadow first
3724+ ctx . save ( ) ;
3725+ ctx . fillStyle = 'rgba(0, 0, 0, 0.3)' ;
3726+ ctx . fillRect (
3727+ player . x - cameraX + 3 ,
3728+ player . y - cameraY + player . height - 2 ,
3729+ player . width ,
3730+ 8
3731+ ) ;
3732+ ctx . restore ( ) ;
3733+
35353734 if ( playerCharacter === 'SQUARE' ) {
3536- // Draw original yellow rectangle
3537- ctx . fillStyle = '#ff0' ;
3538- ctx . fillRect (
3735+ // Draw original yellow rectangle with 3D effect
3736+ drawRect3D (
35393737 player . x - cameraX ,
35403738 player . y - cameraY ,
35413739 player . width ,
3542- player . height
3740+ player . height ,
3741+ '#ff0' ,
3742+ 4
35433743 ) ;
35443744 } else {
35453745 // Draw custom emoji character
0 commit comments