"nextPiece" and "holdPiece" canvases do not display straight tetrominoes correctly (cannot be fully displayed) and square tetrominoes (slightly aligned to the left)

Below is a Tetris game implemented in JavaScript with a simple but functional GUI using HTML5 and CSS.

### HTML (index.html)
```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Enhanced Tetris</title>
    <link rel="stylesheet" href="styles.css">
</head>
<body>
    <div class="container">
        <canvas id="gameBoard" width="300" height="600"></canvas>
        <div class="info">
            <h1 class="game-title">Tetris</h1>
            <p>Use Arrow Keys to move and rotate the pieces.</p>
            <p>Score: <span id="score">0</span></p>
            <p>Lines Cleared: <span id="lines">0</span></p>
            <p>Level: <span id="level">1</span></p>
            <p>High Score: <span id="highScore">0</span></p>
            <canvas id="nextPiece" width="150" height="150"></canvas>
            <canvas id="holdPiece" width="150" height="150"></canvas>
            <button id="holdButton" style="display: none;">Hold</button>
            <button id="pauseButton" style="display: none;">Pause</button>
            <button id="playButton">Play</button>
        </div>
    </div>

    <!-- Game Over Modal -->
    <div id="gameOverModal" class="modal">
        <div class="modal-content">
            <h2>Game Over</h2>
            <p>Your Score: <span id="finalScore">0</span></p>
            <p>Your Level: <span id="finalLevel">1</span></p>
            <p>Lines Cleared: <span id="finalLines">0</span></p>
            <button id="restartButton">Restart</button>
            <button id="exitButton">Exit</button>
        </div>
    </div>

    <!-- Audio Elements -->
    <audio id="moveSound" src="sounds/move.mp3"></audio>
    <audio id="rotateSound" src="sounds/rotate.mp3"></audio>
    <audio id="clearSound" src="sounds/clear.mp3"></audio>
    <audio id="gameOverSound" src="sounds/gameover.mp3"></audio>
    <audio id="modalAppearSound" src="sounds/modalAppear.mp3"></audio> <!-- Optional -->
    <script src="tetris.js"></script>
</body>
</html>
```

### CSS (styles.css)
```css
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');

* {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
}

body {
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    display: flex;
    justify-content: center;
    align-items: center;
    min-height: 100vh;
    background: linear-gradient(135deg, #1e1e1e, #2c2c2c);
    color: white;
    overflow: hidden;
    animation: backgroundMove 10s linear infinite;
    background-size: 400% 400%;
}

@keyframes backgroundMove {
    0% { background-position: 0% 50%; }
    50% { background-position: 100% 50%; }
    100% { background-position: 0% 50%; }
}

.container {
    display: flex;
    flex-direction: row;
    align-items: flex-start;
    gap: 20px;
}

canvas {
    background-color: #111;
    border: 4px solid #3e3e3e;
    box-shadow: 0 0 20px rgba(0,0,0,0.5);
}

.info {
    display: flex;
    flex-direction: column;
    align-items: center;
}

.game-title {
    font-family: 'Press Start 2P', cursive;
    font-size: 2rem;
    margin-bottom: 10px;
    animation: titleEntrance 1s ease-out;
}

@keyframes titleEntrance {
    from {
        transform: translateY(-50px);
        opacity: 0;
    }
    to {
        transform: translateY(0);
        opacity: 1;
    }
}

.info p {
    font-size: 1.2rem;
    margin: 5px 0;
}

button {
    background: linear-gradient(145deg, #ff4081, #ff79a1);
    color: white;
    border: none;
    padding: 12px 24px;
    font-size: 1rem;
    cursor: pointer;
    margin-top: 10px;
    border-radius: 8px;
    box-shadow:  5px 5px 15px #bebebe,
                -5px -5px 15px #ffffff;
    transition: all 0.3s ease;
}

button:hover {
    transform: translateY(-2px);
    box-shadow:  7px 7px 20px #bebebe,
                -7px -7px 20px #ffffff;
}

button:active {
    transform: translateY(0px);
    box-shadow: inset 2px 2px 5px #bebebe,
                inset -2px -2px 5px #ffffff;
}

#score, #level, #lines, #highScore, #finalScore, #finalLevel, #finalLines {
    font-size: 1.2rem;
    font-weight: bold;
}

.modal {
    display: none; /* Hidden by default */
    position: fixed; /* Stay in place */
    z-index: 10; /* Sit on top */
    left: 0;
    top: 0;
    width: 100%; /* Full width */
    height: 100%; /* Full height */
    overflow: auto; /* Enable scroll if needed */
    background: rgba(0, 0, 0, 0.8); /* Black w/ opacity */
    justify-content: center;
    align-items: center;
    animation: fadeIn 0.5s;
}

@keyframes fadeIn {
    from { opacity: 0; }
    to { opacity: 1; }
}

.modal-content {
    background-color: #2c2c2c;
    padding: 30px 20px;
    border-radius: 10px;
    text-align: center;
    animation: slideDown 0.5s;
    position: relative;
    overflow: hidden;
}

@keyframes slideDown {
    from { transform: translateY(-50px); opacity: 0; }
    to { transform: translateY(0); opacity: 1; }
}

.modal-content h2 {
    font-family: 'Press Start 2P', cursive;
    font-size: 2rem;
    margin-bottom: 15px;
    animation: flash 1s infinite;
}

@keyframes flash {
    0%, 50%, 100% { opacity: 1; }
    25%, 75% { opacity: 0.5; }
}

.modal-content p {
    font-size: 1.2rem;
    margin: 10px 0;
}

.modal-content button {
    width: 120px;
    margin: 15px 10px 0 10px;
    padding: 10px;
}

/* Responsive Adjustments */
@media (max-width: 600px) {
    .container {
        flex-direction: column;
        align-items: center;
    }
    
    canvas#gameBoard {
        width: 250px;
        height: 500px;
    }
    
    canvas#nextPiece, canvas#holdPiece {
        width: 120px; /* Reduced size for smaller screens */
        height: 120px;
    }

    .info p {
        font-size: 1rem;
    }

    .info button {
        padding: 10px 20px;
        font-size: 0.9rem;
    }
}
```

### JavaScript (tetris.js)
```javascript
const canvas = document.getElementById('gameBoard');
const ctx = canvas.getContext('2d');
const playButton = document.getElementById('playButton');
const scoreDisplay = document.getElementById('score');
const levelDisplay = document.getElementById('level');
const nextPieceCanvas = document.getElementById('nextPiece');
const nextCtx = nextPieceCanvas.getContext('2d');
const holdPieceCanvas = document.getElementById('holdPiece');
const holdCtx = holdPieceCanvas.getContext('2d');
const holdButton = document.getElementById('holdButton'); // Ensure this line is present

// Modal Elements
const gameOverModal = document.getElementById('gameOverModal');
const finalScoreDisplay = document.getElementById('finalScore');
const finalLevelDisplay = document.getElementById('finalLevel');
const restartButton = document.getElementById('restartButton');
const exitButton = document.getElementById('exitButton');

const moveSound = document.getElementById('moveSound');
const rotateSound = document.getElementById('rotateSound');
const clearSound = document.getElementById('clearSound');
const gameOverSound = document.getElementById('gameOverSound');
const modalAppearSound = document.getElementById('modalAppearSound'); // Optional

const ROWS = 20;
const COLS = 10;
const BLOCK_SIZE = 30;
const EMPTY_COLOR = '#111';
const COLORS = ['#FF5733', '#33FF57', '#3357FF', '#FF33A8', '#FF8F33', '#FFD433', '#33FFF6'];

let board = [];
let currentPiece;
let nextPiece = null;
let holdPiece = null;
let score = 0;
let level = 1;
let gameInterval;
let gameSpeed = 500; // initial speed in ms
const SPEED_INCREMENT = 50; // speed increase every level
const LINES_PER_LEVEL = 10;
let canHold = true;
let keyCooldown = false;
const KEY_DELAY = 100; // milliseconds

// Tetromino shapes
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
];

// Create and initialize the board
function createBoard() {
    board = Array.from({ length: ROWS }, () => Array(COLS).fill(EMPTY_COLOR));
}

// Draw a single block at (x, y) with the specified color
function drawBlock(x, y, color) {
    const blockX = x * BLOCK_SIZE;
    const blockY = y * BLOCK_SIZE;

    // Create gradient
    const gradient = ctx.createLinearGradient(blockX, blockY, blockX + BLOCK_SIZE, blockY + BLOCK_SIZE);
    gradient.addColorStop(0, lightenColor(color, 0.3));
    gradient.addColorStop(1, color);

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

    // Add shadow
    ctx.shadowColor = 'rgba(0, 0, 0, 0.3)';
    ctx.shadowBlur = 10;
    ctx.shadowOffsetX = 2;
    ctx.shadowOffsetY = 2;

    ctx.strokeStyle = '#333';
    ctx.lineWidth = 2;
    ctx.strokeRect(blockX, blockY, BLOCK_SIZE, BLOCK_SIZE);

    // Reset shadow
    ctx.shadowColor = 'transparent';
    ctx.shadowBlur = 0;
    ctx.shadowOffsetX = 0;
    ctx.shadowOffsetY = 0;
}

// Utility function to lighten a color
function lightenColor(color, percent) {
    const num = parseInt(color.replace('#',''),16),
          amt = Math.round(2.55 * percent * 100),
          R = (num >> 16) + amt,
          G = ((num >> 8) & 0x00FF) + amt,
          B = (num & 0x0000FF) + amt;
    return '#' + (
        0x1000000 + 
        (R<255?R<1?0:R:255)*0x10000 + 
        (G<255?G<1?0:G:255)*0x100 + 
        (B<255?B<1?0:B:255)
    ).toString(16).slice(1);
}

// Clear the entire canvas
function clearCanvas() {
    ctx.fillStyle = EMPTY_COLOR;
    ctx.fillRect(0, 0, canvas.width, canvas.height);
}

// Render the board and the current piece
function render() {
    clearCanvas();
    drawBoard();
    if (currentPiece) {
        currentPiece.draw();
    }
}

// Draw the board blocks
function drawBoard() {
    for (let row = 0; row < ROWS; row++) {
        for (let col = 0; col < COLS; col++) {
            if (board[row][col] !== EMPTY_COLOR) {
                drawBlock(col, row, board[row][col]);
            }
        }
    }
}

// Generate a random Tetromino piece
function randomPiece() {
    if (!nextPiece) {
        nextPiece = new Piece(SHAPES[Math.floor(Math.random() * SHAPES.length)], COLORS[Math.floor(Math.random() * COLORS.length)]);
    }
    const piece = nextPiece;
    nextPiece = new Piece(SHAPES[Math.floor(Math.random() * SHAPES.length)], COLORS[Math.floor(Math.random() * COLORS.length)]);
    drawNextPiece();
    return piece;
}

// Piece class representing a Tetromino
class Piece {
    constructor(shape, color) {
        this.shape = shape;
        this.color = color;
        this.x = Math.floor(COLS / 2) - Math.floor(this.shape[0].length / 2);
        this.y = 0;
    }

    // Draw the piece on the canvas
    draw() {
        this.shape.forEach((row, dy) => {
            row.forEach((value, dx) => {
                if (value) {
                    drawBlock(this.x + dx, this.y + dy, this.color);
                }
            });
        });
    }

    // Move the piece by (dx, dy)
    move(dx, dy) {
        if (!this.collides(this.x + dx, this.y + dy, this.shape)) {
            this.x += dx;
            this.y += dy;
            return true;
        }
        return false;
    }

    // Rotate the piece clockwise
    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;
            rotateSound.play();
        } else {
            // Attempt to shift piece to the left or right if rotation causes collision
            if (!this.collides(this.x - 1, this.y, rotatedShape)) {
                this.x -= 1;
                this.shape = rotatedShape;
                rotateSound.play();
            } else if (!this.collides(this.x + 1, this.y, rotatedShape)) {
                this.x += 1;
                this.shape = rotatedShape;
                rotateSound.play();
            }
        }
    }

    // Check if the piece collides with the board or boundaries
    collides(x, y, shape) {
        return shape.some((row, dy) => {
            return row.some((value, dx) => {
                let newX = x + dx;
                let newY = y + dy;
                // Allow pieces to spawn above the board
                if (newY < 0) return false;
                return (
                    value &&
                    (newX < 0 || newX >= COLS || newY >= ROWS || board[newY][newX] !== EMPTY_COLOR)
                );
            });
        });
    }

    // Lock the piece into the board
    lock() {
        this.shape.forEach((row, dy) => {
            row.forEach((value, dx) => {
                if (value) {
                    const newY = this.y + dy;
                    const newX = this.x + dx;
                    if (newY >= 0 && newY < ROWS && newX >= 0 && newX < COLS) {
                        board[newY][newX] = this.color;
                    }
                }
            });
        });
    }
}

// Clear completed lines with animation and update the score
function clearLinesWithAnimation(callback) {
    let linesCleared = [];
    for (let row = ROWS - 1; row >= 0; row--) {
        if (board[row].every(cell => cell !== EMPTY_COLOR)) {
            linesCleared.push(row);
        }
    }

    if (linesCleared.length === 0) {
        callback(0);
        return;
    }

    // Animate each cleared line sequentially
    let animationPromise = Promise.resolve();

    linesCleared.forEach(row => {
        animationPromise = animationPromise.then(() => animateLineClear(row));
    });

    animationPromise.then(() => {
        // Remove cleared lines
        linesCleared.forEach(row => {
            board.splice(row, 1);
            board.unshift(Array(COLS).fill(EMPTY_COLOR));
        });

        // Update the score after animation
        if (linesCleared.length > 0) {
            clearSound.play();
            score += linesCleared.length * 10;
            scoreDisplay.textContent = score;
            callback(linesCleared.length);
        }
    });
}

// Function to animate the line clearing with a fading effect
function animateLineClear(row) {
    return new Promise((resolve) => {
        const totalFrames = 20;
        let currentFrame = 0;
        const originalColors = board[row].slice(); // Store original colors

        function animate() {
            if (currentFrame < totalFrames) {
                const fadeFactor = currentFrame / totalFrames;
                board[row].forEach((_, col) => {
                    board[row][col] = fadeColor(originalColors[col], fadeFactor);
                });

                render();
                currentFrame++;
                requestAnimationFrame(animate);
            } else {
                // Line has been cleared, proceed
                resolve();
            }
        }

        animate();
    });
}

// Utility function to fade a color based on the fade factor (0 to 1)
function fadeColor(color, factor) {
    // Convert HEX to RGB
    let r, g, b;
    if (color.startsWith('#')) {
        const hex = color.substring(1);
        const bigint = parseInt(hex, 16);
        r = (bigint >> 16) & 255;
        g = (bigint >> 8) & 255;
        b = bigint & 255;
    } else if (color.startsWith('rgb')) {
        const rgbValues = color.match(/\d+/g);
        r = parseInt(rgbValues[0]);
        g = parseInt(rgbValues[1]);
        b = parseInt(rgbValues[2]);
    }

    // Calculate faded color towards EMPTY_COLOR (#111)
    const targetR = 17; // HEX for #111
    const targetG = 17;
    const targetB = 17;

    const newR = Math.floor(r + (targetR - r) * factor);
    const newG = Math.floor(g + (targetG - g) * factor);
    const newB = Math.floor(b + (targetB - b) * factor);

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

// The main game loop that updates the game state
function gameLoop() {
    if (!currentPiece.move(0, 1)) {
        currentPiece.lock();
        canHold = true; // Reset hold ability
        clearInterval(gameInterval);
        
        // Initiate animation and then proceed
        clearLinesWithAnimation((cleared) => {
            if (cleared > 0) {
                if (Math.floor(score / (LINES_PER_LEVEL * 10)) + 1 > level) {
                    levelUp();
                }
            }
            if (board[0].some(cell => cell !== EMPTY_COLOR)) {
                gameOver();
                return;
            }
            currentPiece = randomPiece();
            // Resume the game loop
            gameInterval = setInterval(gameLoop, gameSpeed);
        });

    }
    render();
}

// Handle game over state
function gameOver() {
    clearInterval(gameInterval);
    gameOverSound.play();
    modalAppearSound.play(); // Optional
    
    // Update final score and level in the modal
    finalScoreDisplay.textContent = score;
    finalLevelDisplay.textContent = level;
    
    // Display the Game Over modal
    gameOverModal.style.display = 'flex';
    
    // Hide the Hold button
    holdButton.style.display = 'none';
}

// Handle user input for controlling the Tetromino
document.addEventListener('keydown', (e) => {
    // Prevent input if modal is active
    if (gameOverModal.style.display === 'flex') return;
    
    if (!currentPiece || keyCooldown) return;
    let moved = false;
    switch (e.key) {
        case 'ArrowLeft':
            moved = currentPiece.move(-1, 0);
            if (moved) moveSound.play();
            break;
        case 'ArrowRight':
            moved = currentPiece.move(1, 0);
            if (moved) moveSound.play();
            break;
        case 'ArrowDown':
            moved = currentPiece.move(0, 1);
            if (moved) moveSound.play();
            break;
        case 'ArrowUp':
            currentPiece.rotate();
            // Rotation sound is handled inside the rotate method
            moved = true;
            break;
    }
    if (moved) {
        render();
        keyCooldown = true;
        setTimeout(() => { keyCooldown = false; }, KEY_DELAY);
    }
});

// Start or restart the game
function startGame() {
    createBoard();
    currentPiece = randomPiece();
    score = 0;
    level = 1;
    scoreDisplay.textContent = score;
    levelDisplay.textContent = level;
    holdPiece = null;
    drawHoldPiece();
    playButton.style.display = 'none';
    clearInterval(gameInterval);
    gameSpeed = 500; // Reset game speed
    gameInterval = setInterval(gameLoop, gameSpeed);
    canHold = true;
    keyCooldown = false;
    
    // Reset and hide the Game Over modal if it's currently displayed
    gameOverModal.style.display = 'none';
    
    // Show the Hold button
    holdButton.style.display = 'inline-block';
    
    render();
}

// Play button event listener
playButton.addEventListener('click', startGame);

// Restart button in the modal
restartButton.addEventListener('click', () => {
    startGame();
});

// Exit button in the modal
exitButton.addEventListener('click', () => {
    // Define what "exit" should do. For this example, we'll reload the page.
    window.location.reload();
});

// Hold feature implementation
holdButton.addEventListener('click', () => {
    if (!canHold || !currentPiece) return;
    if (holdPiece) {
        [holdPiece, currentPiece] = [currentPiece, holdPiece];
        currentPiece.x = Math.floor(COLS / 2) - Math.floor(currentPiece.shape[0].length / 2);
        currentPiece.y = 0;
    } else {
        holdPiece = currentPiece;
        currentPiece = randomPiece();
    }
    drawHoldPiece();
    drawNextPiece();
    canHold = false;
    render();
});

// Draw the next piece in the preview canvas
function drawNextPiece() {
    nextCtx.fillStyle = EMPTY_COLOR;
    nextCtx.fillRect(0, 0, nextPieceCanvas.width, nextPieceCanvas.height);
    if (nextPiece) {
        nextPiece.shape.forEach((row, dy) => {
            row.forEach((value, dx) => {
                if (value) {
                    nextCtx.fillStyle = nextPiece.color;
                    nextCtx.fillRect(dx * 30, dy * 30, 30, 30);
                    nextCtx.strokeStyle = '#333';
                    nextCtx.strokeRect(dx * 30, dy * 30, 30, 30);
                }
            });
        });
    }
}

// Draw the hold piece
function drawHoldPiece() {
    holdCtx.fillStyle = EMPTY_COLOR;
    holdCtx.fillRect(0, 0, holdPieceCanvas.width, holdPieceCanvas.height);
    if (holdPiece) {
        holdPiece.shape.forEach((row, dy) => {
            row.forEach((value, dx) => {
                if (value) {
                    holdCtx.fillStyle = holdPiece.color;
                    holdCtx.fillRect(dx * 30, dy * 30, 30, 30);
                    holdCtx.strokeStyle = '#333';
                    holdCtx.strokeRect(dx * 30, dy * 30, 30, 30);
                }
            });
        });
    }
}

// Level up function
function levelUp() {
    clearInterval(gameInterval);
    level++;
    levelDisplay.textContent = level;
    gameSpeed = Math.max(100, gameSpeed - SPEED_INCREMENT);
    gameInterval = setInterval(gameLoop, gameSpeed);
}

// Initialize hold and next piece displays
function initializeDisplays() {
    drawNextPiece();
    drawHoldPiece();
}

// Initial setup
initializeDisplays();
```