From 041d0cc08adbf8deb13b744d6d4346b15cd08b62 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 10 May 2026 07:07:43 +0000 Subject: [PATCH] Fix Tetris mobile: controls layout, grid sizing, no double-tap zoom MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Left thumb: ← and ↓ stacked; right thumb: 2×2 grid (↑ rotate, →, ⤓ drop, ⇆ hold) - touch-action: manipulation on buttons stops browser double-tap zoom - Auto-repeat on left/right/down held (200ms → 60ms interval) - JS-driven fitCanvas: measures play-area + side panels, sets board size explicitly - draw() uses cellH = h/ROWS separate from cellW = w/COLS — pieces fill full board height https://claude.ai/code/session_01V6s7b7ReDstVRgFcuZbkx7 --- tetris.html | 84 ++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 60 insertions(+), 24 deletions(-) diff --git a/tetris.html b/tetris.html index 20c34ab..621de8f 100644 --- a/tetris.html +++ b/tetris.html @@ -78,16 +78,20 @@ } button:active { transform: translateY(1px); } .controls { - width: 100%; max-width: 600px; display: grid; - grid-template-columns: repeat(6, 1fr); gap: 6px; flex: 0 0 auto; + width: 100%; max-width: 600px; + display: flex; justify-content: space-between; align-items: flex-end; + flex: 0 0 auto; padding: 0 8px; } + .ctrl-l { display: flex; flex-direction: column; gap: 6px; } + .ctrl-r { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; } .controls button { appearance: none; border: none; background: var(--panel); - color: var(--text); padding: 10px 0; - font-size: 14px; font-weight: 600; - border-radius: 8px; cursor: pointer; - box-shadow: none; display: flex; align-items: center; justify-content: center; + color: var(--text); font-size: 22px; font-weight: 700; + border-radius: 12px; cursor: pointer; box-shadow: none; + display: flex; align-items: center; justify-content: center; flex-direction: column; gap: 2px; + width: 76px; height: 52px; + touch-action: manipulation; } .controls .label { font-size: 9px; color: var(--muted); font-weight: 500; text-transform: uppercase; } .controls button:active { background: var(--grid); } @@ -149,12 +153,16 @@

Tetris

- - - - - - +
+ + +
+
+ + + + +
Tetris bestEl.textContent = best; // -------- Sizing -------- + const BOARD_AR = COLS / ROWS; // 10/20 = 0.5 function fitCanvas() { - const rect = board.getBoundingClientRect(); + const pa = playArea.getBoundingClientRect(); + if (pa.width === 0 || pa.height === 0) return; + const sides = [...playArea.querySelectorAll('.side')]; + const sideTotal = sides.reduce((s, el) => s + el.getBoundingClientRect().width, 0) || 184; + const availW = Math.max(60, pa.width - sideTotal - 16); + const availH = pa.height; + const bH = Math.min(availH, availW / BOARD_AR, 640); + const bW = Math.round(bH * BOARD_AR); + board.style.width = bW + 'px'; + board.style.height = bH + 'px'; const dpr = window.devicePixelRatio || 1; - canvas.width = Math.floor(rect.width * dpr); - canvas.height = Math.floor(rect.height * dpr); + canvas.width = Math.floor(bW * dpr); + canvas.height = Math.floor(bH * dpr); ctx.setTransform(dpr, 0, 0, dpr, 0, 0); draw(); } @@ -354,7 +372,9 @@

Tetris

// Spawn particles on cleared rows const dpr = window.devicePixelRatio || 1; const w = canvas.width / dpr; + const h = canvas.height / dpr; const cellW = w / COLS; + const cellH = h / ROWS; for (const y of clearedRows) { for (let x = 0; x < COLS; x++) { const color = grid[y][x]; @@ -362,7 +382,7 @@

Tetris

const a = Math.random() * Math.PI * 2; const s = 1 + Math.random() * 2; particles.push({ - x: (x + 0.5) * cellW, y: (y + 0.5) * cellW, + x: (x + 0.5) * cellW, y: (y + 0.5) * cellH, vx: Math.cos(a) * s, vy: Math.sin(a) * s - 1, life: 30 + Math.random() * 20, color, @@ -535,12 +555,12 @@

Tetris

// -------- Draw -------- function getCSS(name) { return getComputedStyle(document.documentElement).getPropertyValue(name).trim(); } - function drawCell(x, y, color, cellW, alpha = 1) { + function drawCell(x, y, color, cellW, cellH, alpha = 1) { ctx.globalAlpha = alpha; ctx.fillStyle = color; - ctx.fillRect(x * cellW + 1, y * cellW + 1, cellW - 2, cellW - 2); + ctx.fillRect(x * cellW + 1, y * cellH + 1, cellW - 2, cellH - 2); ctx.fillStyle = 'rgba(255,255,255,0.25)'; - ctx.fillRect(x * cellW + 1, y * cellW + 1, cellW - 2, 3); + ctx.fillRect(x * cellW + 1, y * cellH + 1, cellW - 2, 3); ctx.globalAlpha = 1; } @@ -549,6 +569,7 @@

Tetris

const w = canvas.width / dpr; const h = canvas.height / dpr; const cellW = w / COLS; + const cellH = h / ROWS; ctx.fillStyle = getCSS('--panel'); ctx.fillRect(0, 0, w, h); @@ -560,13 +581,13 @@

Tetris

ctx.beginPath(); ctx.moveTo(i * cellW, 0); ctx.lineTo(i * cellW, h); ctx.stroke(); } for (let j = 1; j < ROWS; j++) { - ctx.beginPath(); ctx.moveTo(0, j * cellW); ctx.lineTo(w, j * cellW); ctx.stroke(); + ctx.beginPath(); ctx.moveTo(0, j * cellH); ctx.lineTo(w, j * cellH); ctx.stroke(); } // Locked blocks for (let y = 0; y < ROWS; y++) { for (let x = 0; x < COLS; x++) { - if (grid[y][x]) drawCell(x, y, grid[y][x], cellW); + if (grid[y][x]) drawCell(x, y, grid[y][x], cellW, cellH); } } @@ -574,14 +595,14 @@

Tetris

if (alive && current) { const gy = ghostY(current); for (const [cx, cy] of current.cells) { - drawCell(current.x + cx, gy + cy, current.color, cellW, 0.18); + drawCell(current.x + cx, gy + cy, current.color, cellW, cellH, 0.18); } } // Current piece if (alive && current) { for (const [cx, cy] of current.cells) { - if (current.y + cy >= 0) drawCell(current.x + cx, current.y + cy, current.color, cellW); + if (current.y + cy >= 0) drawCell(current.x + cx, current.y + cy, current.color, cellW, cellH); } } @@ -622,10 +643,21 @@

Tetris

} // -------- Controls -------- - controls.addEventListener('click', (e) => { + let repeatTimer = null; + function startRepeat(act) { + clearTimeout(repeatTimer); + repeatTimer = setTimeout(function rep() { + if (act === 'left') move(-1); + else if (act === 'right') move(1); + else if (act === 'soft') softDrop(); + repeatTimer = setTimeout(rep, 60); + }, 200); + } + controls.addEventListener('pointerdown', (e) => { ensureAudio(); const btn = e.target.closest('button'); if (!btn) return; + e.preventDefault(); const act = btn.dataset.act; if (act === 'left') move(-1); else if (act === 'right') move(1); @@ -633,7 +665,11 @@

Tetris

else if (act === 'soft') softDrop(); else if (act === 'hard') hardDrop(); else if (act === 'hold') holdPiece(); + if (act === 'left' || act === 'right' || act === 'soft') startRepeat(act); }); + controls.addEventListener('pointerup', () => clearTimeout(repeatTimer)); + controls.addEventListener('pointercancel', () => clearTimeout(repeatTimer)); + controls.addEventListener('pointerleave', () => clearTimeout(repeatTimer)); window.addEventListener('keydown', (e) => { const k = e.key;