diff --git a/87-ClaudeCode/03-claude-tetris/CLAUDE.md b/87-ClaudeCode/03-claude-tetris/CLAUDE.md index dc6a082..604dd0a 100644 --- a/87-ClaudeCode/03-claude-tetris/CLAUDE.md +++ b/87-ClaudeCode/03-claude-tetris/CLAUDE.md @@ -20,4 +20,5 @@ All logic lives in a single script, [game.js](game.js), loaded by [index.html](i - **Piece lifecycle**: `lockPiece()` → `merge()` → `clearLines()` (updates score/level/`dropInterval`) → `spawn()` (promotes `next`; calls `endGame()` if the spawn position collides). - **Input** is one `keydown` handler using `e.code`. Rotation (`tryRotate`) is clockwise only, with simple horizontal wall kicks `[0, -1, 1, -2, 2]`. - **Scoring**: line clears use `LINE_SCORES × level`; soft drop gives +1 per cell and hard drop +2 per cell. Level is `floor(lines / 10) + 1`, and `dropInterval = max(100, 1000 - (level-1) * 90)` ms. +- **Theming**: dark is the default. Colors live in CSS custom properties in [style.css](style.css) (`:root` = dark, `:root[data-theme="light"]` = light). The `#theme-toggle` button (ids `theme-toggle`, `theme-toggle-icon`, `theme-toggle-text`) calls `applyTheme()` in `game.js`, which sets `data-theme` on ``, swaps `palette` between `COLORS` and `LIGHT_COLORS`, caches the `--grid` value into `gridColor` (canvas can't read CSS), and repaints via `draw()`/`drawNext()` (the loop is stopped while paused/game over). The choice is persisted in `localStorage` under `tetris-theme`. The button is blurred after click so Space doesn't re-trigger it. Any new color should be a CSS variable, not a hard-coded value. - **Rendering**: `draw()` repaints grid, board, ghost piece (via `ghostY()`, alpha 0.2) and current piece. `drawNext()` renders into the separate preview canvas. diff --git a/87-ClaudeCode/03-claude-tetris/README.md b/87-ClaudeCode/03-claude-tetris/README.md index ae4c040..aaef837 100644 --- a/87-ClaudeCode/03-claude-tetris/README.md +++ b/87-ClaudeCode/03-claude-tetris/README.md @@ -42,6 +42,7 @@ A playable version of classic Tetris with all the mechanics you would expect: - Classic Tetris **scoring system** (100 / 300 / 500 / 800 multiplied by level). - **Levels** that increase every 10 lines and speed up the fall. - **Pause** and **Game Over** with a restart option. +- **Light / dark theme toggle** (top-right button). Dark is the default; the choice is remembered in `localStorage`. --- diff --git a/87-ClaudeCode/03-claude-tetris/game.js b/87-ClaudeCode/03-claude-tetris/game.js index d08fa06..4366acb 100644 --- a/87-ClaudeCode/03-claude-tetris/game.js +++ b/87-ClaudeCode/03-claude-tetris/game.js @@ -15,6 +15,18 @@ const COLORS = [ '#ffb74d', // L - orange ]; +// Slightly deeper tones so pieces and the ghost stay visible on a white board. +const LIGHT_COLORS = [ + null, + '#00acc1', // I + '#f9a825', // O + '#8e24aa', // T + '#43a047', // S + '#e53935', // Z + '#1e88e5', // J + '#fb8c00', // L +]; + const PIECES = [ null, [[0,0,0,0],[1,1,1,1],[0,0,0,0],[0,0,0,0]], // I @@ -39,6 +51,13 @@ const overlay = document.getElementById('overlay'); const overlayTitle = document.getElementById('overlay-title'); const overlayScore = document.getElementById('overlay-score'); const restartBtn = document.getElementById('restart-btn'); +const themeToggle = document.getElementById('theme-toggle'); +const themeToggleIcon = document.getElementById('theme-toggle-icon'); +const themeToggleText = document.getElementById('theme-toggle-text'); + +const THEME_KEY = 'tetris-theme'; +let gridColor = '#22222e'; +let palette = COLORS; let board, current, next, score, lines, level, paused, gameOver, lastTime, dropAccum, dropInterval, animId; @@ -158,7 +177,7 @@ function updateHUD() { function drawBlock(context, x, y, colorIndex, size, alpha) { if (!colorIndex) return; - const color = COLORS[colorIndex]; + const color = palette[colorIndex]; context.globalAlpha = alpha ?? 1; context.fillStyle = color; context.fillRect(x * size + 1, y * size + 1, size - 2, size - 2); @@ -169,7 +188,7 @@ function drawBlock(context, x, y, colorIndex, size, alpha) { } function drawGrid() { - ctx.strokeStyle = '#22222e'; + ctx.strokeStyle = gridColor; ctx.lineWidth = 0.5; for (let c = 1; c < COLS; c++) { ctx.beginPath(); @@ -301,4 +320,34 @@ document.addEventListener('keydown', e => { restartBtn.addEventListener('click', init); +function loadTheme() { + try { + return localStorage.getItem(THEME_KEY) === 'light' ? 'light' : 'dark'; + } catch { + return 'dark'; + } +} + +function applyTheme(theme) { + const isLight = theme === 'light'; + document.documentElement.dataset.theme = theme; + palette = isLight ? LIGHT_COLORS : COLORS; + themeToggle.setAttribute('aria-pressed', String(isLight)); + themeToggleIcon.textContent = isLight ? '☾' : '☀'; + themeToggleText.textContent = isLight ? 'Dark' : 'Light'; + // The canvas can't read CSS variables, so cache the grid color here. + gridColor = getComputedStyle(document.documentElement).getPropertyValue('--grid').trim() || gridColor; + // The loop is stopped while paused/game over, so repaint explicitly. + if (board) { draw(); drawNext(); } +} + +themeToggle.addEventListener('click', () => { + const theme = document.documentElement.dataset.theme === 'light' ? 'dark' : 'light'; + try { localStorage.setItem(THEME_KEY, theme); } catch { /* storage unavailable */ } + applyTheme(theme); + themeToggle.blur(); // keep Space/Enter from re-triggering the toggle +}); + +applyTheme(loadTheme()); + init(); diff --git a/87-ClaudeCode/03-claude-tetris/index.html b/87-ClaudeCode/03-claude-tetris/index.html index 3e59aca..7a81f7c 100644 --- a/87-ClaudeCode/03-claude-tetris/index.html +++ b/87-ClaudeCode/03-claude-tetris/index.html @@ -7,6 +7,11 @@
+ +