Tetris Dev Journal — October 26, 2025
A two-day enhancement sprint covering responsive layout overhaul, mobile touch controls, and complete audio integration.
October 26, 2025 | 12 min read
Introduction
Over the past two days, I undertook an intensive development sprint focused on transforming our HTML5 Tetris game from a functional desktop experience into a polished, responsive application that works seamlessly across all devices. The work centered on three major pillars: a complete layout reimagination using modern CSS Grid techniques, full-featured mobile touch controls with thoughtful UX considerations, and comprehensive audio integration including both background music and synthesized sound effects.
The sprint was motivated by user feedback indicating that while the core gameplay was solid, the experience degraded significantly on mobile devices and tablets. Players reported awkward viewport fitting issues, no way to play on touch devices without a keyboard, and a lack of audio feedback that made the game feel flat. These weren't just nice-to-have features—they were fundamental gaps that prevented the game from reaching its full potential as a modern web application.
Phase 1: Layout Reimagination
Initial Challenges
The original Tetris layout faced several critical issues. The playfield needed to preserve perfectly square cells across an enormous range of viewport sizes and pixel densities while coexisting harmoniously with informational side panels. The prior implementation produced awkward whitespace on wide screens, clipped content on narrow viewports, and sometimes distorted the aspect ratio of tetromino cells, which broke the fundamental visual language of the game.
Three-Column Grid System
I redesigned the layout around CSS Grid with three distinct columns: a left hero column containing game description and statistics, a center board column housing the playfield canvas, and a right support column with next-piece preview, hold-piece display, and control hints.
.play-grid {
display: grid;
grid-template-columns:
minmax(320px, var(--left-w-desktop))
minmax(400px, 1fr)
minmax(260px, var(--sidebar-w-desktop));
gap: var(--gap-desktop);
padding-inline: clamp(12px, 2vw, 24px);
}
Viewport Calculations
The playfield sizing strategy prioritizes height over width. I set the game container to maintain the correct aspect ratio using the CSS aspect-ratio property, combined with dynamic viewport height units (dvh) to avoid jarring layout shifts on mobile browsers.
.game-container {
aspect-ratio: 1 / 2;
height: var(--board-h-desktop);
max-height: calc(100dvh - 120px);
width: auto;
}
The use of dvh instead of traditional vh units is crucial here. On mobile browsers, vh includes the space occupied by browser chrome like address bars and toolbars, which can shift during scrolling. The dvh unit respects only the actual visible viewport, preventing jarring layout shifts.
Phase 2: Mobile Touch Controls
Design Intent
Once the layout was stable and responsive, I turned my attention to input. Keyboard controls are second nature for desktop Tetris players, but mobile users were completely locked out. The goal was to create touch controls that felt natural, responsive, and didn't obscure the playfield during critical gameplay moments.
CSS Media Query Strategy
Rather than relying on user-agent sniffing—an error-prone and maintenance-heavy approach—I used feature detection with hover and pointer media queries:
@media (hover: hover) and (pointer: fine) {
.mobile-controls {
display: none;
}
}
@media (pointer: coarse) and (hover: none) {
.mobile-controls {
position: fixed;
left: 50%;
transform: translateX(-50%);
bottom: max(12px, env(safe-area-inset-bottom));
z-index: 50;
}
}
This approach elegantly handles edge cases like tablets with attached keyboards or desktop browsers in responsive mode. The controls appear only when the device truly has a coarse pointer and lacks hover capability.
Bottom-Floating Control Panel
The touch control panel lives at the bottom of the screen, fixed-positioned and horizontally centered. The translucent background with backdrop blur provides visual separation from the game board while maintaining awareness of block positions near the bottom. The use of env(safe-area-inset-bottom) ensures the panel clears iPhone home indicators and Android gesture bars.
Phase 3: Audio Integration
Background Music Pipeline
With layout and input stable, I focused on audio—the final piece needed to create an engaging, complete experience. I sourced two royalty-free Tetris-style theme tracks, each approximately 3.4MB, and integrated them into a playback system that respects browser autoplay policies and user preferences.
Music playback follows strict lifecycle rules tied to game state:
- Start: Music begins on the first user gesture to comply with autoplay policies
- Pause: When the game is paused or the browser tab loses visibility, music pauses
- Stop: On game over, music stops and resets to the beginning
- Track switching: Two tracks allow for variety; future updates will enable user selection
Web Audio API for Sound Effects
Rather than loading separate audio files for every sound effect—which would increase load time and require careful asset management—I leveraged the Web Audio API to synthesize sounds procedurally. This approach gives tight latency, small memory footprint, and the authentic 8-bit retro aesthetic that suits Tetris perfectly.
Each sound effect is built from an oscillator with a carefully shaped envelope. The audio context initializes lazily on first interaction to comply with browser policies.
Line Clear Sound Design
Each line-clear event triggers a distinct musical pattern based on the number of lines cleared:
- Single line: A bright two-note ascending chirp (C to E, 523Hz and 659Hz)
- Double lines: A three-note rising arpeggio (B to D to F#)
- Triple lines: A four-note fast arpeggio (A to C# to E to A)
- Tetris (four lines): A celebratory burst using sawtooth waveforms for brightness, followed by a descending flourish
These patterns were tuned by ear across multiple devices to balance pleasantness with clarity and avoid ear fatigue during extended play sessions.
Bug Fixes and Polish
Pause Button Duplicate Handler
Early in testing, I discovered that clicking the Pause button would sometimes toggle the state twice, effectively canceling the pause. Root cause analysis revealed that the event handler was being attached twice through different initialization paths. The fix centralized event listener attachment and used an AbortController pattern to ensure clean teardown.
Canvas Sizing Refinements
On high-DPI displays (Retina screens, high-res Android devices), the canvas appeared blurry because the CSS pixel dimensions didn't match the drawing buffer size. I implemented proper HiDPI scaling by setting the canvas width and height to the client dimensions times devicePixelRatio, then scaling the drawing context accordingly while keeping CSS size in CSS pixels.
Visual Feedback Enhancements
Small polish touches included adding :active and :focus states to all interactive elements for better accessibility, implementing a ghost piece that shows where the current tetromino will land, subtle score animation when lines are cleared, and improved contrast ratios to meet WCAG AA standards.
Technical Statistics
The two-day sprint produced substantial changes across the codebase:
- Commits: 21 focused commits with clear, descriptive messages
- Lines changed: +962 insertions, -424 deletions
- Net growth: +538 lines, representing a 32% increase in total codebase size
File-level breakdown:
tetris.css: Major overhaul with +778 lines covering grid layout, responsive rules, and visual polishtetris.js: +163 lines adding touch controls, audio management, and enhanced game state handlingindex.html: Restructured HTML with +337 lines to support new layout and control panelstetris.overrides.css: New file with +108 lines isolating responsive layout overridesaudio/: Two new music tracks totaling 6.8MB
Challenges and Solutions
Viewport Fitting at 1312px
The most persistent challenge was achieving perfect layout fit around the 1312px viewport width—a common resolution for 13-inch laptops. The grid gap and column widths caused the total layout width to exceed 1312px, forcing horizontal scroll or clipping the sidebar.
Solution: I reduced column width targets and grid gaps through iterative testing: left column from 420px to 350px, right sidebar from 300px to 290px, grid gap from 24px to 20px. These pixel-perfect adjustments ensured all three columns fit comfortably at 1312px while maintaining visual balance and readability.
Autoplay Policy Compliance
Modern browsers block autoplaying audio to protect users from intrusive ads and unexpected noise. The initial implementation attempted to start music immediately on page load, which was consistently blocked.
Solution: Deferred all audio initialization until the first user gesture. The audio context creation is lazy, and music playback only begins when the user clicks Start Game or interacts with the canvas.
Touch Device Detection
Initial implementations used user-agent parsing to detect mobile devices, which broke on tablets with keyboard support and missed desktop touch displays.
Solution: Feature queries with @media (hover: none) and (pointer: coarse) provide accurate, semantic detection. Runtime matchMedia checks allow dynamic adaptation when users attach keyboards or rotate devices.
Lessons Learned
Key Takeaways
CSS Grid with modern units is transformative. Using clamp(), dvh, and aspect-ratio together produces robust responsive layouts with minimal magic numbers. The declarative approach is easier to reason about than JavaScript-driven sizing.
Safe-area environment variables are essential. Ignoring env(safe-area-inset-bottom) on iOS creates a miserable experience with controls hidden behind home indicators. Adding a single max() call fixes it universally.
Synthesized audio is underrated. Web Audio API synthesizers add zero download weight, provide sub-10ms latency, and give full control over timbre and timing. For retro games, they're perfect.
Separation of concerns pays dividends. Creating tetris.overrides.css for game-specific responsive rules made code reviews faster and experimentation safer. Clear file boundaries reduce cognitive load.
Next Steps
- Settings panel: Add a dedicated UI for audio volumes, toggle options for haptics and visual effects, and a left-handed control layout toggle
- Music crossfade: Implement smooth transitions between tracks using gain node automation
- Container queries: Once browser support is universal, migrate from viewport-based media queries to container queries
- Control persistence: Save the user's preferred control scheme to localStorage and restore on load
- Testing infrastructure: Add automated visual regression tests for layout breakpoints and unit tests for audio routing logic
Conclusion
This two-day sprint transformed the Tetris game from a keyboard-only desktop application into a genuinely cross-platform experience. The combination of thoughtful responsive layout, ergonomic touch controls, and comprehensive audio feedback elevates the game from a technical demo to a polished product users can enjoy anywhere.
The 21 commits and 962 lines of new code represent not just features added, but problems solved: viewport fitting, input parity, audio lifecycle management, and device capability detection. Each challenge required researching modern web standards, experimenting with multiple approaches, and validating solutions across devices.
Most importantly, the work is architected for extension. The separated concerns, clear state management, and modern web platform APIs provide a solid foundation for future enhancements. Whether adding multiplayer, implementing power-ups, or creating visual themes, the infrastructure is ready.