Collision Detection in JavaScript for a ball and square

I am attempting to recreate a mobile app called ballz within javascript. This is my first project so I apologize if it hurts your head to read through it.

The ball is shot using the mouse to determine its velocity.
The ball should continually check for collision with a static block.
Depending on if the ball hits a corner or side of the block, the velocity is calculated using the code I provided.

The issue is that the collision response is all over the place. Sometimes the sideCollisionReponse works flawlessly, sometimes the cornerCollisionResponse works flawlessly. However, most of the time, the ball clips through blocks, reflects sporadically, or a combination of the two.

document.addEventListener('DOMContentLoaded', function() {
    const canvas = document.querySelector('canvas');
    const c = canvas.getContext("2d");

    canvas.width = 360;
    canvas.height = 600;
    let isDragging = false;
    let aimX = 0;
    let aimY = 0;

    function drawPoints(points) {
        c.font = '24px Arial';
        c.fillStyle = 'white';
        c.textAlign = 'center';
        c.fillText('Points: ' + points, canvas.width / 2, 30);
    }

    class Sprite {
        constructor(position) {
            this.position = position;
            this.velocity = {
                x: 0,
                y: 0
            };
            this.isShooting = false;
            this.isStuck = false;
            this.points = 0;
            this.radius = 10;
        }

        // May be able to optimize by defining the ball size into a variable for size specific calls
        draw() {
            c.beginPath();
            c.arc(this.position.x, this.position.y, 10, 0, 2 * Math.PI);
            c.fillStyle = "orange";
            c.fill();
        }

        checkCollision() {

            this.wallsCollisionResponse();

            for (let i = 0; i < blocks.length; i++) {
                let block = blocks[i];
                const topLeftDist = Math.sqrt((this.position.x - block.x) ** 2 + (this.position.y - block.y) ** 2);
                const topRightDist = Math.sqrt((this.position.x - block.x - block.width) ** 2 + (this.position.y - block.y) ** 2);
                const bottomLeftDist = Math.sqrt((this.position.x - block.x) ** 2 + (this.position.y - block.y - block.height) ** 2);
                const bottomRightDist = Math.sqrt((this.position.x - block.x - block.width) ** 2 + (this.position.y - block.y - block.height) ** 2);
                const isCornerCollision = (topLeftDist <= this.radius || topRightDist <= this.radius || bottomLeftDist <= this.radius || bottomRightDist <= this.radius)

                if (!isCornerCollision) {
                    this.sidesCollisionResponse(block);
                } else if (isCornerCollision) {
                    this.cornerCollisionResponse(block);
                }
            }
        };

        wallsCollisionResponse() {
            let x = this.position.x
            let y = this.position.y

            // Check collision with walls
            if (x - this.radius <= 0 || x + this.radius >= canvas.width) {
                this.velocity.x = -this.velocity.x; // Reverse horizontal velocity
                this.position.x = Math.max(this.radius, Math.min(canvas.width - this.radius, x)); // Limit position within bounds
            }
            if (y - this.radius <= 40 || y + this.radius >= canvas.height) {
                this.velocity.y = -this.velocity.y; // Reverse vertical velocity
                this.position.y = Math.max(this.radius + 40, Math.min(canvas.height - this.radius, y)); // Limit position within bounds
            }

            // Check if ball hits bottom
            if (y + this.radius >= canvas.height) {
                this.isStuck = true;
                this.velocity = {
                    x: 0,
                    y: 0
                };
            }
        }


        cornerCollisionResponse(block) {
            // get line from ball center to corner
            const v1x = this.position.x - block.x
            const v1y = this.position.y - block.y;

            // normalize the line and rotated 90deg to get tanget
            const len = (v1x ** 2 + v1y ** 2) ** 0.5;
            const tx = -v1y / len;
            const ty = v1x / len;

            const dot = (this.velocity.x * tx + this.velocity.y * ty) * 2;

            this.velocity.x = -this.velocity.x + tx * dot;
            this.velocity.y = -this.velocity.y + ty * dot;

            block.decreaseValue();
        }


        sidesCollisionResponse(block) {
            let x = this.position.x
            let y = this.position.y
            let maxSpeed = 20;
            if (
                block.x + block.width >= x - this.radius &&
                block.x <= x + this.radius &&
                block.y + block.height >= y - this.radius &&
                block.y <= y + this.radius
            ) {
                // Calculate collision normal (assuming block is axis-aligned)
                let normalX = 0;
                let normalY = 0;
                if (x < block.x) {
                    normalX = -1; // Collision from left
                } else if (x > block.x + block.width) {
                    normalX = 1; // Collision from right
                }
                if (y < block.y) {
                    normalY = -1; // Collision from top
                } else if (y > block.y + block.height) {
                    normalY = 1; // Collision from bottom
                }

                console.log('Initial velocity1:', this.velocity);

                // Reflect velocity using dot product with normal vector
                let dotProduct = this.velocity.x * normalX + this.velocity.y * normalY;
                this.velocity.x -= 2 * dotProduct * normalX;
                this.velocity.y -= 2 * dotProduct * normalY;

                console.log('Initial velocity2:', this.velocity);

                // Limit velocity to maxSpeed
                const currentSpeed = Math.sqrt(this.velocity.x * this.velocity.x + this.velocity.y * this.velocity.y);
                if (currentSpeed > maxSpeed) {
                    const ratio = maxSpeed / currentSpeed;
                    this.velocity.x *= ratio;
                    this.velocity.y *= ratio;
                }

                this.limitVelocity();

                // Adjust position to prevent clipping
                const overlapX = Math.abs(x - block.x - block.width);
                const overlapY = Math.abs(y - block.y - block.height);
                const separationX = overlapX * normalX;
                const separationY = overlapY * normalY;
                //this.position.x += separationX;
                //this.position.y += separationY;

                block.decreaseValue();
            }
        }


        limitVelocity() {
            // Limit velocity to maxSpeed
            const maxValue = 20;
            if (this.velocity.x > maxValue) {
                this.velocity.x = maxValue;
            } else if (this.velocity.x < -maxValue) {
                this.velocity.x = -maxValue
            }
            if (this.velocity.y > maxValue) {
                this.velocity.y = maxValue;
            } else if (this.velocity.y < -maxValue) {
                this.velocity.y = -maxValue
            }
        }


        update() {
            if (this.isShooting && !this.isStuck) {
                this.move();
                this.checkCollision();
            }
        }

        move() {
            this.position.x += this.velocity.x;
            this.position.y += this.velocity.y;
            // Simulate gravity
            // this.velocity.y += 0.2;
        }


        shoot(mouseX, mouseY) {
            // Calculate direction vector from ball to mouse
            const dx = mouseX - this.position.x;
            const dy = mouseY - this.position.y;
            const magnitude = Math.sqrt(dx * dx + dy * dy);

            const maxSpeed = 20; // Maximum speed limit

            if (magnitude !== 0) {
                const normalizedDx = dx / magnitude;
                const normalizedDy = dy / magnitude;
                const speed = Math.min(magnitude, maxSpeed); // Limit speed to maximum speed
                this.velocity.x = normalizedDx * speed;
                this.velocity.y = normalizedDy * speed;
            }
            this.isShooting = true;
        }


        reset(position) {
            this.position = position; // Maintains last position on reset
            this.velocity = {
                x: 0,
                y: 0
            };
            this.isShooting = false; // this may be unneccessary
            this.isStuck = false;
        }

        getPoints() {
            this.points++;
            return this.points;
        }
    }
    const player = new Sprite({
        x: 180,
        y: 590
    });


    class Block {
        constructor(x, y, width, height, value) {
            this.x = x;
            this.y = y;
            this.width = width;
            this.height = height;
            this.value = value + 1;
        }

        draw() {
            c.fillStyle = 'green';
            c.fillRect(this.x, this.y, this.width, this.height);
            // Text
            c.fillStyle = 'white';
            c.font = '12px Arial'; // Adjust the font size and style as needed
            c.fillText(this.value.toString(), this.x + this.width / 2, this.y + this.height / 1.75);
        }

        decreaseValue() {
            if (this.value > 0) {
                this.value - 1;
                if (this.value === 0) {
                    // Remove block if value reaches 0
                    blocks.splice(blocks.indexOf(this), 1);
                }
            }
        }

        shiftDown() {
            this.y += this.height + 6
        }
    }
    const blocks = [];

    function createBlocks() {
        const blockWidth = 40;
        const blockHeight = 40;
        const numBlocks = 1;
        //Math.floor(canvas.width / blockWidth);

        for (let i = 0; i < numBlocks; i++) {
            const blockValue = player.points;
            blocks.push(new Block(120, 80, blockWidth, blockHeight, blockValue));
        }
    }

    createBlocks();

    function animate() {
        requestAnimationFrame(animate); // clears canvas before next frame
        c.clearRect(0, 0, canvas.width, canvas.height);
        c.fillStyle = '#4D4D4D'; // Top bar color
        c.fillRect(0, 0, canvas.width, 40);


        if (isDragging && !player.isShooting) {
            const dx = aimX - player.position.x;
            const dy = aimY - player.position.y;
            const magnitude = Math.sqrt(dx * dx + dy * dy);
            const maxSegments = 20; // Maximum number of segments
            const totalTime = 1; // Total time for preview (adjust as needed)

            for (let i = 0; i < maxSegments; i++) {
                const progress = i / maxSegments; // Progress along the trajectory
                const time = progress * totalTime; // Time at this segment
                const previewX = player.position.x + dx * time;
                const previewY = player.position.y + dy * time; // Vertical motion with gravity + 0.5 * 9.81 * time * time
                c.beginPath();
                c.arc(previewX, previewY, player.radius, 0, 2 * Math.PI);
                const alpha = 1 - i * (0.5 / maxSegments); // Decrease transparency for each segment
                c.fillStyle = `rgba(255, 165, 0, ${alpha})`; // Transparent orange
                c.fill();
            }
        }


        player.draw();
        player.update();

        drawPoints(player.points);

        blocks.forEach((block, index) => {
            block.draw();
        });

        if (player.isStuck) {
            player.reset({
                x: player.position.x,
                y: player.position.y
            })
            player.getPoints();
            blocks.forEach((block, index) => {
                block.shiftDown();
            })
            createBlocks();

        }
    }

    animate();

    canvas.addEventListener("mousedown", (event) => {
        const rect = canvas.getBoundingClientRect();
        const mouseX = event.clientX - rect.left;
        const mouseY = event.clientY - rect.top;

        if (!player.isShooting && mouseX >= 0 && mouseX <= canvas.width && mouseY >= 0 && mouseY <= canvas.height - 20) {
            isDragging = true;
            aimX = mouseX;
            aimY = mouseY;
        }
    })

    canvas.addEventListener("mouseup", (event) => {
        if (isDragging) {
            isDragging = false;
            const rect = canvas.getBoundingClientRect();
            const mouseX = event.clientX - rect.left;
            const mouseY = event.clientY - rect.top;
            if (mouseX >= 0 && mouseX <= canvas.width && mouseY >= 0 && mouseY <= canvas.height - 20) {
                player.shoot(aimX, aimY); // Pass aim position to shoot method
            }
        }
    })

    canvas.addEventListener("mousemove", (event) => {
        if (isDragging) {
            const rect = canvas.getBoundingClientRect();
            aimX = event.clientX - rect.left;
            aimY = event.clientY - rect.top;
        }
    })
});