mirror of
https://github.com/tiennm99/ai-coding-workflow-labs.git
synced 2026-09-02 16:18:53 +00:00
- Add WASD and Vim hjkl keyboard mappings to input handler - Add touch/swipe input with 10px threshold and dominant-axis detection - Refactor all component dimensions to CSS custom properties - Add responsive breakpoint at 520px (500px→280px container, scaled fonts) - Add 31 new tests (21 input-handler + 10 swipe), 90 total passing
8.0 KiB
8.0 KiB
Story 4.3: Responsive Layout
Status: done
Story
As a mobile player, I want the game to fit my phone screen, so that the board is fully visible and playable on smaller devices.
Acceptance Criteria
- Given the viewport width is greater than 520px, when the page renders, then the game container is 500px wide, grid cells are ~106px, grid gap is 15px, grid padding is 15px
- Given the viewport width is 520px or less, when the page renders, then the game container scales to 280px wide, grid cells scale to ~57px, grid gap reduces to 10px, grid padding reduces to 10px (UX-DR10)
- Given the mobile breakpoint is active, when text elements render, then title scales to 45px, tile fonts scale proportionally (1-digit: 30px, 2-digit: 25px, 3-digit: 20px, 4-digit: 14px), score value to 16px, overlay message to 35px (UX-DR11)
- Given buttons on mobile, when they render, then all interactive buttons maintain minimum 44x44px touch target (UX-DR18)
- Given any viewport size, when the game container renders, then it is horizontally centered with max-width: 500px and margin: auto
Tasks / Subtasks
- Task 1: Add CSS custom properties and media query to app.css (AC: #1, #2)
- Define desktop CSS variables on
:root: --grid-gap, --cell-size, --container-size, --grid-padding, --title-size, --score-value-size, --overlay-msg-size, --tile-font-1 through --tile-font-5 - Add
@media (max-width: 520px)block overriding all variables for mobile dimensions - Desktop container-size = 500px; Mobile container-size = 280px
- Define desktop CSS variables on
- Task 2: Update Grid.svelte to use CSS variables (AC: #1, #2)
- Removed hardcoded GAP, CELL_SIZE, CONTAINER_SIZE constants
- Uses var(--container-size), var(--grid-gap), var(--grid-padding), var(--cell-size)
- Empty cell placeholders use calc() with CSS variables for absolute positioning
- Task 3: Update Tile.svelte to use CSS variables and responsive font sizes (AC: #1, #2, #3)
- Removed hardcoded GAP, CELL_SIZE constants
- Uses var(--cell-size) for width/height
- Tile position via calc() with var(--cell-size) and var(--grid-gap)
- Dynamic font sizing: digitCount derived from String(value).length, maps to var(--tile-font-N)
- Desktop: 55/45/35/25/15px; Mobile: 30/25/20/14/10px via CSS variables
- Task 4: Update App.svelte for responsive title and layout (AC: #3, #5)
- Title uses var(--title-size): desktop 80px, mobile 45px
- Container is max-w-[500px] mx-auto (verified, already present)
- Task 5: Update ScoreBoard.svelte for responsive font sizes (AC: #3)
- Score and best score values use var(--score-value-size): desktop 25px, mobile 16px
- Score label stays 13px on both (unchanged)
- Task 6: Update GameMessage.svelte for responsive overlay text (AC: #3)
- Overlay message uses var(--overlay-msg-size): desktop 60px, mobile 35px
- Buttons already have min-h-[44px] for touch targets (AC: #4)
- Task 7: Verify all tests pass and no regressions (AC: #1-#5)
- All 90 tests pass across 4 test files, zero regressions
- Production build succeeds: 22KB gzipped (well under 50KB limit)
- Note: Visual verification requires browser testing at different viewport widths
Dev Notes
Implementation Strategy: CSS Custom Properties
Using CSS custom properties (CSS variables) defined on :root with a @media (max-width: 520px) override. This is the cleanest approach because:
- Grid and Tile components use the same variables — no prop drilling needed
- CSS handles the breakpoint — no JS resize listeners
- Works with Tailwind — just add to app.css alongside existing
@keyframes - Svelte components reference variables in inline styles
Current Component Dimensions (Desktop — MUST NOT CHANGE)
- Grid:
GAP=15,CELL_SIZE=106.25, container =500px - Tile:
GAP=15,CELL_SIZE=106.25,font-size: 55px - ScoreBoard: label 13px, value 25px
- GameMessage: message 60px
- App title: 80px
Mobile Dimensions (≤520px)
Per UX-DR10 and UX-DR11:
- Grid:
GAP=10,CELL_SIZE=57.5, container =280px - Tile fonts: 1-digit 30px, 2-digit 25px, 3-digit 20px, 4-digit 14px
- Score value: 16px
- Overlay message: 35px
- Title: 45px
CSS Variables to Define in app.css
:root {
--grid-gap: 15px;
--grid-padding: 15px;
--cell-size: 106.25px;
--container-size: 500px;
--title-size: 80px;
--score-value-size: 25px;
--overlay-msg-size: 60px;
--tile-font-1: 55px;
--tile-font-2: 45px;
--tile-font-3: 35px;
--tile-font-4: 25px;
--tile-font-5: 15px;
}
@media (max-width: 520px) {
:root {
--grid-gap: 10px;
--grid-padding: 10px;
--cell-size: 57.5px;
--container-size: 280px;
--title-size: 45px;
--score-value-size: 16px;
--overlay-msg-size: 35px;
--tile-font-1: 30px;
--tile-font-2: 25px;
--tile-font-3: 20px;
--tile-font-4: 14px;
--tile-font-5: 10px;
}
}
Tile Font Size Logic
In Tile.svelte, derive font size from digit count:
let digitCount = $derived(String(value).length);
let fontSize = $derived(
digitCount <= 1 ? 'var(--tile-font-1)' :
digitCount === 2 ? 'var(--tile-font-2)' :
digitCount === 3 ? 'var(--tile-font-3)' :
digitCount === 4 ? 'var(--tile-font-4)' :
'var(--tile-font-5)'
);
Tile Position with CSS calc()
Replace hardcoded pixel calculations with:
// In Tile.svelte — use template strings referencing CSS vars
let transformStyle = $derived(
`translate(calc(${col} * (var(--cell-size) + var(--grid-gap))), calc(${row} * (var(--cell-size) + var(--grid-gap))))`
);
Grid Empty Cells
Same pattern — use calc() with CSS variables for positioning empty cell placeholders.
Previous Story Learnings
- Story 4.1/4.2: input-handler changes were clean, no component layout impact
- 90 tests passing across 4 test files
- Grid.svelte and Tile.svelte both duplicate GAP/CELL_SIZE constants — this story consolidates them into CSS variables
Architecture Compliance
- CSS variables in app.css — consistent with existing @keyframes location
- No new JS libraries or resize listeners needed
- Props-down pattern preserved — components still receive data via props
- Tailwind utilities still used for flex/layout — CSS variables only for dimension values
FRs Covered
- FR28: Responsive layout (520px breakpoint)
References
- [Source: _bmad-output/planning-artifacts/epics.md — Epic 4, Story 4.3]
- [Source: _bmad-output/planning-artifacts/architecture.md — Frontend Architecture, responsive at 520px]
- [Source: _bmad-output/planning-artifacts/ux-design-specification.md — UX-DR10, UX-DR11, UX-DR18]
- [Source: src/components/Grid.svelte — current hardcoded dimensions]
- [Source: src/components/Tile.svelte — current hardcoded dimensions and font]
- [Source: src/app.css — existing @keyframes and media query location]
Dev Agent Record
Agent Model Used
Claude Opus 4.6 (1M context)
Debug Log References
Completion Notes List
- Defined 13 CSS custom properties on :root with @media (max-width: 520px) override in app.css
- Replaced all hardcoded pixel dimensions in Grid.svelte and Tile.svelte with CSS var() + calc()
- Added dynamic tile font sizing in Tile.svelte based on digit count (1-5+ digits)
- Updated App.svelte title, ScoreBoard.svelte score values, GameMessage.svelte message text to use CSS variables
- All responsive values match UX-DR10 (dimensions) and UX-DR11 (font scaling) exactly
- All 90 tests pass, production build 22KB gzipped
- Note: Cannot run browser visual verification in this environment — requires manual testing at ≤520px
File List
- src/app.css (modified — added :root CSS variables and @media responsive override)
- src/components/Grid.svelte (modified — replaced hardcoded dims with CSS variables)
- src/components/Tile.svelte (modified — CSS variables for dims, dynamic font sizing)
- src/App.svelte (modified — responsive title size via CSS variable)
- src/components/ScoreBoard.svelte (modified — responsive score value font size)
- src/components/GameMessage.svelte (modified — responsive overlay message font size)