Can you help me rewrite the "game over" logic? Don't analysis the old one, just rewrite it from sketch

// Constants and Configuration
const CONFIG = {
    canvasWidth: 300,
    canvasHeight: 600,
    previewCanvasSize: 150,
    holdCanvasSize: 150,
    rows: 20,
    cols: 10,
    blockSize: 30,
    previewBlockSize: 20,
    initialSpeed: 500,
    speedIncrement: 50,
    linesPerLevel: 10,
    emptyColor: '#111',
    colors: ['#FF5733', '#33FF57', '#3357FF', '#FF33A8', '#FF8F33', '#FFD433', '#33FFF6'],
    keyDelay: 100,
    scorePoints: [0, 40, 100, 300, 1200] // [0, single, double, triple, tetris]
};

// DOM Elements
const DOM = {
    gameBoard: document.getElementById('gameBoard'),
    nextPieceCanvas: document.getElementById('nextPiece'),
    holdPieceCanvas: document.getElementById('holdPiece'),
    playButton: document.getElementById('playButton'),
    holdButton: document.getElementById('holdButton'),
    pauseButton: document.getElementById('pauseButton'),
    score: document.getElementById('score'),
    level: document.getElementById('level'),
    lines: document.getElementById('lines'),
    highScore: document.getElementById('highScore'),
    gameOverModal: document.getElementById('gameOverModal'),
    finalScore: document.getElementById('finalScore'),
    finalLevel: document.getElementById('finalLevel'),
    finalLines: document.getElementById('finalLines'),
    restartButton: document.getElementById('restartButton'),
    exitButton: document.getElementById('exitButton'),
    modalAppearSound: document.getElementById('modalAppearSound')
};

// Audio Elements (holdSound removed)
const AudioElements = {
    move: document.getElementById('moveSound'),
    rotate: document.getElementById('rotateSound'),
    clear: document.getElementById('clearSound'),
    gameOver: document.getElementById('gameOverSound')
};

// Canvas Contexts
const ctx = DOM.gameBoard.getContext('2d');
const nextCtx = DOM.nextPieceCanvas.getContext('2d');
const holdCtx = DOM.holdPieceCanvas.getContext('2d');

// Game Variables
let board = [];
let currentPiece = null;
let nextPiece = null;
let holdPiece = null;
let score = 0;
let level = 1;
let lines = 0;
let highScore = 0;
let gameInterval = null;
let gameSpeed = CONFIG.initialSpeed;
let canHold = true;
let isPaused = false;
let keyCooldown = false;

// Tetromino Definitions
const SHAPES = [
    [[1, 1, 1, 1]], // I
    [[1, 1], [1, 1]], // O
    [[0, 1, 1], [1, 1, 0]], // S
    [[1, 1, 0], [0, 1, 1]], // Z
    [[1, 1, 1], [0, 1, 0]], // T
    [[1, 1, 1], [1, 0, 0]], // L
    [[1, 1, 1], [0, 0, 1]]  // J
];

// Utility Functions

/**
 * Lightens a hex color by a given percentage.
 * @param {string} color - The original hex color.
 * @param {number} percent - The percentage to lighten (0 to 1).
 * @returns {string} - The lightened hex color.
 */
function lightenColor(color, percent) {
    const num = parseInt(color.replace('#', ''), 16);
    const amt = Math.round(2.55 * percent * 100);
    let R = (num >> 16) + amt;
    let G = ((num >> 8) & 0x00FF) + amt;
    let B = (num & 0x0000FF) + amt;
    R = R < 255 ? (R < 1 ? 0 : R) : 255;
    G = G < 255 ? (G < 1 ? 0 : G) : 255;
    B = B < 255 ? (B < 1 ? 0 : B) : 255;
    return '#' + (0x1000000 + R * 0x10000 + G * 0x100 + B).toString(16).slice(1);
}

/**
 * Fades a color towards a target color based on a factor.
 * @param {string} color - The original color in HEX or RGB format.
 * @param {number} factor - The fade factor between 0 and 1.
 * @returns {string} - The faded color in RGB format.
 */
function fadeColor(color, factor) {
    let r, g, b;
    if (color.startsWith('#')) {
        const hex = color.substring(1);
        const bigint = parseInt(hex, 16);
        r = (bigint >> 16) & 255;
        g = ((bigint >> 8) & 0x00FF);
        b = bigint & 0x0000FF;
    } else if (color.startsWith('rgb')) {
        const rgbValues = color.match(/\d+/g);
        [r, g, b] = rgbValues.map(Number);
    }

    const target = 17; // HEX for #111
    const newR = Math.floor(r + (target - r) * factor);
    const newG = Math.floor(g + (target - g) * factor);
    const newB = Math.floor(b + (target - b) * factor);

    return `rgb(${newR}, ${newG}, ${newB})`;
}

// Board Management

/**
 * Initializes the game board as a 2D array filled with empty colors.
 */
function createBoard() {
    board = Array.from({ length: CONFIG.rows }, () => Array(CONFIG.cols).fill(CONFIG.emptyColor));
}

/**
 * Draws a single block on the main game board.
 * @param {number} x - The x-coordinate (column).
 * @param {number} y - The y-coordinate (row).
 * @param {string} color - The color of the block.
 */
function drawBlock(x, y, color) {
    const blockX = x * CONFIG.blockSize;
    const blockY = y * CONFIG.blockSize;

    // Create gradient for a more appealing look
    const gradient = ctx.createLinearGradient(blockX, blockY, blockX + CONFIG.blockSize, blockY + CONFIG.blockSize);
    gradient.addColorStop(0, lightenColor(color, 0.3));
    gradient.addColorStop(1, color);

    ctx.fillStyle = gradient;
    ctx.fillRect(blockX, blockY, CONFIG.blockSize, CONFIG.blockSize);

    // Add border and shadow
    ctx.strokeStyle = '#333';
    ctx.lineWidth = 2;
    ctx.strokeRect(blockX, blockY, CONFIG.blockSize, CONFIG.blockSize);
}

/**
 * Clears the main game canvas.
 */
function clearCanvas() {
    ctx.fillStyle = CONFIG.emptyColor;
    ctx.fillRect(0, 0, CONFIG.canvasWidth, CONFIG.canvasHeight);
}

/**
 * Renders the entire game state.
 */
function render() {
    clearCanvas();
    drawBoard();
    if (currentPiece) {
        currentPiece.draw();
    }
    drawNextPiece();
    drawHoldPiece();
}

/**
 * Draws all the locked blocks on the main game board.
 */
function drawBoard() {
    for (let row = 0; row < CONFIG.rows; row++) {
        for (let col = 0; col < CONFIG.cols; col++) {
            if (board[row][col] !== CONFIG.emptyColor) {
                drawBlock(col, row, board[row][col]);
            }
        }
    }
}

// Tetromino Class

class Tetromino {
    /**
     * Creates a Tetromino piece.
     * @param {number[][]} shape - 2D array representing the shape.
     * @param {string} color - The color of the Tetromino.
     */
    constructor(shape, color) {
        this.shape = shape;
        this.color = color;
        // Ensure the piece spawns within the visible area
        this.x = Math.floor(CONFIG.cols / 2) - Math.floor(this.shape[0].length / 2);
        this.y = 0; // Starting y position
    }

    /**
     * Draws the Tetromino on the main game canvas.
     */
    draw() {
        this.shape.forEach((row, dy) => {
            row.forEach((cell, dx) => {
                if (cell) {
                    drawBlock(this.x + dx, this.y + dy, this.color);
                }
            });
        });
    }

    /**
     * Moves the Tetromino by (dx, dy) if possible.
     * @param {number} dx - Change in x (columns).
     * @param {number} dy - Change in y (rows).
     * @returns {boolean} - True if moved, else false.
     */
    move(dx, dy) {
        if (!this.collides(this.x + dx, this.y + dy, this.shape)) {
            this.x += dx;
            this.y += dy;
            return true;
        }
        return false;
    }

    /**
     * Rotates the Tetromino clockwise with wall kicks.
     */
    rotate() {
        const rotatedShape = this.shape[0].map((_, i) => this.shape.map(row => row[i]).reverse());
        if (!this.collides(this.x, this.y, rotatedShape)) {
            this.shape = rotatedShape;
            AudioElements.rotate.play();
        } else {
            // Attempt wall kicks
            if (!this.collides(this.x - 1, this.y, rotatedShape)) {
                this.x -= 1;
                this.shape = rotatedShape;
                AudioElements.rotate.play();
            } else if (!this.collides(this.x + 1, this.y, rotatedShape)) {
                this.x += 1;
                this.shape = rotatedShape;
                AudioElements.rotate.play();
            }
        }
    }

    /**
     * Checks for collision with the board or boundaries.
     * @param {number} x - Proposed x-coordinate.
     * @param {number} y - Proposed y-coordinate.
     * @param {number[][]} shape - Proposed shape after rotation.
     * @returns {boolean} - True if collision occurs, else false.
     */
    collides(x, y, shape) {
        return shape.some((row, dy) => {
            return row.some((cell, dx) => {
                const newX = x + dx;
                const newY = y + dy;
                // Allow spawning above the board, but ensure pieces inside the board do not collide
                if (newY < 0) return false;
                return (
                    cell &&
                    (newX < 0 ||
                        newX >= CONFIG.cols ||
                        newY >= CONFIG.rows ||
                        board[newY][newX] !== CONFIG.emptyColor)
                );
            });
        });
    }

    /**
     * Locks the Tetromino into the board.
     */
    lock() {
        this.shape.forEach((row, dy) => {
            row.forEach((cell, dx) => {
                if (cell) {
                    const newX = this.x + dx;
                    const newY = this.y + dy;
                    if (newY >= 0 && newY < CONFIG.rows && newX >= 0 && newX < CONFIG.cols) {
                        board[newY][newX] = this.color;
                    } else if (newY < 0) {
                        // This situation should be handled during spawn now
                        // But if it occurs, consider it a Game Over
                        // This is an extra safeguard
                        endGame();
                    }
                }
            });
        });
    }
}

// Game Mechanics

/**
 * Generates a random Tetromino piece.
 * @returns {Tetromino} - The new Tetromino piece.
 */
function generateRandomPiece() {
    const shape = SHAPES[Math.floor(Math.random() * SHAPES.length)];
    const color = CONFIG.colors[Math.floor(Math.random() * CONFIG.colors.length)];
    return new Tetromino(shape, color);
}

/**
 * Handles clearing of completed lines with animation.
 * @returns {Promise<number>} - Number of lines cleared.
 */
function clearLinesWithAnimation() {
    return new Promise((resolve) => {
        const linesCleared = [];

        for (let row = 0; row < CONFIG.rows; row++) {
            if (board[row].every(cell => cell !== CONFIG.emptyColor)) {
                linesCleared.push(row);
            }
        }

        if (linesCleared.length === 0) return resolve(0);

        // Animate clearing
        animateLineClears(linesCleared).then(() => {
            // Remove cleared lines
            linesCleared.forEach(row => {
                board.splice(row, 1);
                board.unshift(Array(CONFIG.cols).fill(CONFIG.emptyColor));
            });

            // Update score and lines
            AudioElements.clear.play();
            score += CONFIG.scorePoints[linesCleared.length] || 0;
            lines += linesCleared.length;
            DOM.score.textContent = score;
            DOM.lines.textContent = lines;

            // Check for level up
            if (Math.floor(lines / CONFIG.linesPerLevel) + 1 > level) {
                levelUp();
            }

            resolve(linesCleared.length);
        });
    });
}

/**
 * Animates the clearing of multiple lines.
 * @param {number[]} rows - Array of row indices to clear.
 * @returns {Promise} - Resolves after animation completes.
 */
function animateLineClears(rows) {
    return new Promise((resolve) => {
        const totalFrames = 20;
        let currentFrame = 0;
        const originalColors = rows.map(row => [...board[row]]);

        function animate() {
            if (currentFrame < totalFrames) {
                const fadeFactor = currentFrame / totalFrames;
                rows.forEach(row => {
                    board[row] = board[row].map(color => fadeColor(color, fadeFactor));
                });
                render();
                currentFrame++;
                requestAnimationFrame(animate);
            } else {
                resolve();
            }
        }

        animate();
    });
}

/**
 * Handles the main game loop.
 */
function gameLoop() {
    if (!currentPiece.move(0, 1)) {
        currentPiece.lock();
        canHold = true;
        clearInterval(gameInterval);

        clearLinesWithAnimation().then((cleared) => {
            // After locking and clearing, check if the new piece can be spawned
            currentPiece = nextPiece || generateRandomPiece();
            nextPiece = generateRandomPiece();

            // NEW: Check if the new piece collides upon spawning
            if (currentPiece.collides(currentPiece.x, currentPiece.y, currentPiece.shape)) {
                // If collision occurs, Game Over
                endGame();
                return;
            }

            render();
            gameInterval = setInterval(gameLoop, gameSpeed);
        });
    }
    render();
}

/**
 * Ends the game and displays the Game Over modal.
 */
function endGame() {
    clearInterval(gameInterval);
    AudioElements.gameOver.play();
    if (DOM.modalAppearSound) DOM.modalAppearSound.play();

    // Update High Score
    if (score > highScore) {
        highScore = score;
        localStorage.setItem('tetrisHighScore', highScore);
        DOM.highScore.textContent = highScore;
    }

    // Populate Game Over Modal
    DOM.finalScore.textContent = score;
    DOM.finalLevel.textContent = level;
    DOM.finalLines.textContent = lines;

    // Display Modal
    DOM.gameOverModal.style.display = 'flex';

    // Reset Visibility of Buttons
    DOM.holdButton.style.display = 'none';
    DOM.pauseButton.style.display = 'none';
    DOM.playButton.style.display = 'inline-block';
    DOM.playButton.textContent = 'Play Again';
}

/**
 * Increases the game level and adjusts the game speed.
 */
function levelUp() {
    level++;
    DOM.level.textContent = level;
    gameSpeed = Math.max(100, gameSpeed - CONFIG.speedIncrement);
    clearInterval(gameInterval);
    gameInterval = setInterval(gameLoop, gameSpeed);
}

/**
 * Updates the High Score display.
 */
function updateHighScore() {
    highScore = localStorage.getItem('tetrisHighScore') || 0;
    DOM.highScore.textContent = highScore;
}

/**
 * Draws a Tetromino piece on a given canvas centered.
 * @param {CanvasRenderingContext2D} context - The canvas context.
 * @param {Tetromino|null} piece - The Tetromino to draw.
 * @param {number} canvasWidth - Width of the canvas.
 * @param {number} canvasHeight - Height of the canvas.
 */
function drawPreviewPiece(context, piece, canvasWidth, canvasHeight) {
    context.fillStyle = CONFIG.emptyColor;
    context.fillRect(0, 0, canvasWidth, canvasHeight);

    if (!piece) return;

    const shape = piece.shape;
    const color = piece.color;

    const rows = shape.length;
    const cols = shape[0].length;

    const pieceWidth = cols * CONFIG.previewBlockSize;
    const pieceHeight = rows * CONFIG.previewBlockSize;

    const startX = Math.floor((canvasWidth - pieceWidth) / 2);
    const startY = Math.floor((canvasHeight - pieceHeight) / 2);

    shape.forEach((row, dy) => {
        row.forEach((cell, dx) => {
            if (cell) {
                const x = startX + dx * CONFIG.previewBlockSize;
                const y = startY + dy * CONFIG.previewBlockSize;

                // Draw block with border
                context.fillStyle = color;
                context.fillRect(x, y, CONFIG.previewBlockSize, CONFIG.previewBlockSize);
                context.strokeStyle = '#333';
                context.strokeRect(x, y, CONFIG.previewBlockSize, CONFIG.previewBlockSize);
            }
        });
    });
}

/**
 * Renders the Next Piece preview.
 */
function drawNextPiece() {
    drawPreviewPiece(nextCtx, nextPiece, CONFIG.previewCanvasSize, CONFIG.previewCanvasSize);
}

/**
 * Renders the Hold Piece preview.
 */
function drawHoldPiece() {
    drawPreviewPiece(holdCtx, holdPiece, CONFIG.holdCanvasSize, CONFIG.holdCanvasSize);
}

// Event Handlers

/**
 * Handles keydown events for piece control.
 * @param {KeyboardEvent} e - The keyboard event.
 */
function handleKeyDown(e) {
    if (DOM.gameOverModal.style.display === 'flex') return;
    if (!currentPiece || keyCooldown) return;

    let moved = false;

    switch (e.key) {
        case 'ArrowLeft':
            moved = currentPiece.move(-1, 0);
            if (moved) AudioElements.move.play();
            break;
        case 'ArrowRight':
            moved = currentPiece.move(1, 0);
            if (moved) AudioElements.move.play();
            break;
        case 'ArrowDown':
            moved = currentPiece.move(0, 1);
            if (moved) AudioElements.move.play();
            break;
        case 'ArrowUp':
            currentPiece.rotate();
            moved = true;
            break;
        case ' ':
            // Hard Drop
            while (currentPiece.move(0, 1)) {}
            AudioElements.move.play();
            moved = true;
            break;
    }

    if (moved) {
        render();
        keyCooldown = true;
        setTimeout(() => { keyCooldown = false; }, CONFIG.keyDelay);
    }
}

/**
 * Starts or restarts the game.
 */
function startGame() {
    createBoard();
    currentPiece = generateRandomPiece();
    nextPiece = generateRandomPiece();
    score = 0;
    level = 1;
    lines = 0;
    gameSpeed = CONFIG.initialSpeed;
    canHold = true;
    isPaused = false;
    keyCooldown = false;
    holdPiece = null; // Reset hold piece

    // Update Displays
    DOM.score.textContent = score;
    DOM.level.textContent = level;
    DOM.lines.textContent = lines;
    DOM.highScore.textContent = highScore;
    drawNextPiece();
    drawHoldPiece();

    // Hide/Show Buttons
    DOM.playButton.style.display = 'none';
    DOM.holdButton.style.display = 'inline-block';
    DOM.pauseButton.style.display = 'inline-block';
    DOM.pauseButton.textContent = 'Pause';

    // Hide Game Over Modal if visible
    DOM.gameOverModal.style.display = 'none';
    DOM.playButton.textContent = 'Play'; // Reset Play button text

    // Start Game Loop
    clearInterval(gameInterval);
    gameInterval = setInterval(gameLoop, gameSpeed);

    // NEW: Check for immediate Game Over upon spawning the first piece
    if (currentPiece.collides(currentPiece.x, currentPiece.y, currentPiece.shape)) {
        // If collision occurs, Game Over immediately
        endGame();
    }

    render();
}

/**
 * Resets the game state without starting a new game.
 * This function is used by the "Restart" button.
 */
function resetGame() {
    createBoard();
    currentPiece = generateRandomPiece();
    nextPiece = generateRandomPiece();
    score = 0;
    level = 1;
    lines = 0;
    gameSpeed = CONFIG.initialSpeed;
    canHold = true;
    isPaused = false;
    keyCooldown = false;
    holdPiece = null; // Reset hold piece

    // Update Displays
    DOM.score.textContent = score;
    DOM.level.textContent = level;
    DOM.lines.textContent = lines;
    DOM.highScore.textContent = highScore;
    drawNextPiece();
    drawHoldPiece();

    // Hide/Show Buttons
    DOM.playButton.style.display = 'none';
    DOM.holdButton.style.display = 'inline-block';
    DOM.pauseButton.style.display = 'inline-block';
    DOM.pauseButton.textContent = 'Pause';

    // Hide Game Over Modal
    DOM.gameOverModal.style.display = 'none';

    // Start Game Loop
    clearInterval(gameInterval);
    gameInterval = setInterval(gameLoop, gameSpeed);

    // NEW: Check for immediate Game Over upon resetting
    if (currentPiece.collides(currentPiece.x, currentPiece.y, currentPiece.shape)) {
        // If collision occurs, Game Over immediately
        endGame();
    }

    render();
}

/**
 * Toggles the pause state of the game.
 */
function togglePause() {
    if (isPaused) {
        // Resume Game
        gameInterval = setInterval(gameLoop, gameSpeed);
        DOM.pauseButton.textContent = 'Pause';
        isPaused = false;
    } else {
        // Pause Game
        clearInterval(gameInterval);
        DOM.pauseButton.textContent = 'Resume';
        isPaused = true;
    }
}

/**
 * Handles the hold functionality.
 */
function holdCurrentPiece() {
    if (!canHold || !currentPiece) return;

    // Removed AudioElements.hold.play(); as holdSound is not defined

    if (holdPiece) {
        [holdPiece, currentPiece] = [currentPiece, holdPiece];
        currentPiece.x = Math.floor(CONFIG.cols / 2) - Math.floor(currentPiece.shape[0].length / 2);
        currentPiece.y = 0;

        // NEW: After swapping, check if the new current piece collides
        if (currentPiece.collides(currentPiece.x, currentPiece.y, currentPiece.shape)) {
            endGame();
            return;
        }
    } else {
        holdPiece = currentPiece;
        currentPiece = nextPiece || generateRandomPiece();
        nextPiece = generateRandomPiece();

        // NEW: Check if the new current piece can be placed
        if (currentPiece.collides(currentPiece.x, currentPiece.y, currentPiece.shape)) {
            endGame();
            return;
        }
    }

    drawHoldPiece();
    drawNextPiece();
    canHold = false;
    render();
}

// Button Event Listeners
DOM.playButton.addEventListener('click', startGame);
DOM.restartButton.addEventListener('click', resetGame); // Attach resetGame to Restart button
DOM.exitButton.addEventListener('click', () => {
    window.location.reload();
});
DOM.pauseButton.addEventListener('click', togglePause);
DOM.holdButton.addEventListener('click', holdCurrentPiece);

// Keyboard Event Listener
document.addEventListener('keydown', handleKeyDown);

// Initialization Functions

/**
 * Initializes high score and displays on load.
 */
function initializeHighScore() {
    highScore = localStorage.getItem('tetrisHighScore') || 0;
    DOM.highScore.textContent = highScore;
}

/**
 * Initializes the preview and hold displays.
 */
function initializeDisplays() {
    drawNextPiece();
    drawHoldPiece();
}

// On Window Load
window.onload = () => {
    initializeHighScore();
    initializeDisplays();
};