-
Notifications
You must be signed in to change notification settings - Fork 0
Fix responsiveness on smaller/mobile screens #8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
ff7353c
Update .gitignore to not exclude Playwright specs
mbijon ce75060
Make game responsive for mobile/portrait screen support -codex
mbijon 2080f81
Minor responsive tweaks to base, sm, md breakpoints -codex
mbijon 4c70c95
Remove typo from hmn in the IDE
mbijon File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,6 +10,7 @@ yarn.lock | |
| # Compiled JavaScript files | ||
| *.js | ||
| *.js.map | ||
| !e2e/*.spec.js | ||
| dist/ | ||
| build/ | ||
| out/ | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| # Responsive Gameplay Grid & Controls Plan | ||
|
|
||
| ## Goals | ||
| - Ensure the gameplay grid scales down gracefully on portrait mobile screens without affecting the current desktop/landscape experience. | ||
| - Tweak the Move direction controls so they remain accessible on small screens while keeping desktop sizing intact. | ||
| - Improve the Bailout button layout and prominence on portrait mobile, matching the Start Game button's visual weight and floating it to the right of the Move controls. | ||
| - On very small viewports (e.g., iPhone SE/iPhone X and smaller), let the gameplay grid container scroll vertically while keeping the Move controls fixed beneath it. | ||
|
|
||
| ## Constraints & Considerations | ||
| - Preserve the existing grid size and layout on desktop and landscape orientations. | ||
| - Keep changes scoped to responsive styles and layout containers to limit gameplay logic churn. | ||
| - Maintain accessibility: buttons must retain minimum touch targets and focus outlines. | ||
| - Verify no regressions in existing tests; add responsive-focused unit/UI checks where practical. | ||
|
|
||
| ## Work Breakdown | ||
| 1. **Audit Current Layout Behaviors** | ||
| - Capture current grid/container dimensions across breakpoints (mobile portrait, mobile landscape, tablet, desktop). | ||
| - Document Tailwind classes or custom CSS that lock grid sizing, gap, or font scales. | ||
| - Inspect Move and Bailout button styles and layout wrapper structure. | ||
| - Findings (Apr 2025): | ||
| - Gameplay grid lives in `src/App.tsx` inside a `div` with Tailwind `grid gap-0 rounded-lg border-2 border-gray-600 bg-gray-800 p-2`; column sizing is controlled via inline `gridTemplateColumns` using a calculated `cellSize` (max 30px) derived from `maxGridWidth = 720` and level size, so large levels (≥18) exceed 540px and overflow small portrait widths. | ||
| - Each cell uses inline `width`/`height` equal to `cellSize` with `minWidth/minHeight` of 4px, so scaling is locked to fixed pixels outside Tailwind breakpoints. | ||
| - Container is wrapped by a flex column centering everything with `min-h-screen`; no constraints prevent the grid from expanding beyond viewport height/width. | ||
| - Move pad sits in a `div` using `grid w-48 grid-cols-3 gap-2` and buttons sized via `px-4 py-3 text-xl`, yielding ~48rem (~192px) width that stays centered; no responsive overrides shrink padding/font below `sm`. | ||
| - Bailout button lives in adjacent flex column with `px-4 py-2 text-sm`; on narrow screens it wraps beneath the Move pad because parent stack `flex-col` aligns center with `gap-6` only switching to `sm:flex-row`. | ||
|
|
||
| 2. **Responsive Grid Sizing Strategy** | ||
| - Introduce CSS/Tailwind utilities that cap grid width/height using viewport-fitting rules (`max-w`, `max-h`, `vw`, `vh`). | ||
| - Adjust the grid container to honor aspect ratio while shrinking on portrait <=640px (mobile-first) without altering base desktop classes. | ||
| - Add safe minimum size thresholds so touch targets remain usable; consider scaling cell font/icons accordingly. | ||
| - Gate a `max-h` + `overflow-y-auto` treatment behind a very-small breakpoint so the grid itself scrolls instead of collapsing into the Move controls. | ||
| - Implementation approach: | ||
| - Introduce a `useViewportSize` hook and clamp `cellSize` via `useMemo`, using viewport width/height bounds with a 30px desktop ceiling and a 2px floor on compact screens so even 100×100 boards stay in view. | ||
| - Cap the grid wrapper with computed `maxWidth`/`maxHeight` values and toggle `overflow-y-auto` for widths ≤380px, containing scroll to the grid while keeping Move controls static. | ||
| - Retain the desktop experience by falling back to `fit-content` sizing whenever the viewport is ≥640px so existing styling remains unchanged. | ||
|
|
||
| 3. **Move Buttons Refinement** | ||
| - Apply responsive utility classes (e.g., `sm:`/`md:` prefixes) to reduce button padding/font-size by ~20-25% on screens <640px. | ||
| - Re-align the button group to the left on small screens via flexbox utilities while preserving current alignment for larger breakpoints. | ||
| - Verify spacing between buttons remains consistent; adjust margin/padding stack to avoid overflow. | ||
|
|
||
| 4. **Bailout Button Enhancements** | ||
| - Match typography and padding to Start Game styles by extracting shared Tailwind classes or creating a reusable style helper. | ||
| - On portrait mobile, float/position the button to the right of the Move controls using responsive flex or grid layout. | ||
| - Ensure wrapping does not occur; test with worst-case localization/label length. | ||
|
|
||
| 5. **Testing & Validation** | ||
| - Update or extend component tests to cover responsive class toggles if testable (snapshot or DOM attribute checks under simulated viewport width). | ||
| - Run `npm run test:run` and `npm run test:e2e` to confirm no regressions. | ||
| - Manually verify layouts in browser dev tools for key breakpoints (iPhone SE, iPhone 14 Pro Max, iPad, desktop wide). | ||
|
|
||
| 6. **Documentation & Follow-up** | ||
| - Note responsive design rationale in code comments or a README snippet if needed. | ||
| - Flag any follow-on tasks (e.g., responsive typography audit) discovered during implementation. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,157 @@ | ||
| import { test, expect } from '@playwright/test'; | ||
|
|
||
| test.describe('Accessibility Tests', () => { | ||
| test.beforeEach(async ({ page }) => { | ||
| await page.goto('/'); | ||
| }); | ||
|
|
||
| test('should have proper heading structure', async ({ page }) => { | ||
| // Check main heading | ||
| const mainHeading = page.getByRole('heading', { level: 1 }); | ||
| await expect(mainHeading).toBeVisible(); | ||
| await expect(mainHeading).toHaveText('🔥 HEATSEEKER 🔥'); | ||
|
|
||
| // Check subheading | ||
| const subHeading = page.getByRole('heading', { level: 2 }); | ||
| await expect(subHeading).toBeVisible(); | ||
| await expect(subHeading).toHaveText('Game Rules:'); | ||
| }); | ||
|
|
||
| test('should have accessible buttons', async ({ page }) => { | ||
| // Start button should be accessible | ||
| const startButton = page.getByRole('button', { name: 'Start Game' }); | ||
| await expect(startButton).toBeVisible(); | ||
| await expect(startButton).toBeEnabled(); | ||
|
|
||
| // Start the game to test mobile controls | ||
| await startButton.click(); | ||
|
|
||
| // Mobile control buttons should be accessible | ||
| const upButton = page.getByRole('button', { name: '↑' }); | ||
| const downButton = page.getByRole('button', { name: '↓' }); | ||
| const leftButton = page.getByRole('button', { name: '←' }); | ||
| const rightButton = page.getByRole('button', { name: '→' }); | ||
|
|
||
| await expect(upButton).toBeVisible(); | ||
| await expect(upButton).toBeEnabled(); | ||
| await expect(downButton).toBeVisible(); | ||
| await expect(downButton).toBeEnabled(); | ||
| await expect(leftButton).toBeVisible(); | ||
| await expect(leftButton).toBeEnabled(); | ||
| await expect(rightButton).toBeVisible(); | ||
| await expect(rightButton).toBeEnabled(); | ||
| }); | ||
|
|
||
| test('should support keyboard navigation', async ({ page }) => { | ||
| // Tab to start button | ||
| await page.keyboard.press('Tab'); | ||
|
|
||
| // Start button should be focused | ||
| const startButton = page.getByRole('button', { name: 'Start Game' }); | ||
| await expect(startButton).toBeFocused(); | ||
|
|
||
| // Press Enter to activate | ||
| await page.keyboard.press('Enter'); | ||
|
|
||
| // Game should start | ||
| await expect(page.getByText('Level: 1 of 10')).toBeVisible(); | ||
| }); | ||
|
|
||
| test('should support keyboard game controls', async ({ page }) => { | ||
| await page.getByRole('button', { name: 'Start Game' }).click(); | ||
|
|
||
| // Arrow keys should work for game control | ||
| await page.keyboard.press('ArrowRight'); | ||
| await expect(page.getByText('Level Moves: 1')).toBeVisible(); | ||
|
|
||
| await page.keyboard.press('ArrowUp'); | ||
| await expect(page.getByText('Level Moves: 2')).toBeVisible(); | ||
|
|
||
| await page.keyboard.press('ArrowLeft'); | ||
| await expect(page.getByText('Level Moves: 3')).toBeVisible(); | ||
|
|
||
| await page.keyboard.press('ArrowDown'); | ||
| await expect(page.getByText('Level Moves: 4')).toBeVisible(); | ||
| }); | ||
|
|
||
| test('should have sufficient color contrast', async ({ page }) => { | ||
| // The game uses color to convey important information | ||
| // We should ensure text has good contrast against backgrounds | ||
|
|
||
| // Check main title contrast | ||
| const title = page.getByText('🔥 HEATSEEKER 🔥'); | ||
| await expect(title).toBeVisible(); | ||
|
|
||
| // Check button contrast | ||
| const startButton = page.getByRole('button', { name: 'Start Game' }); | ||
| await expect(startButton).toBeVisible(); | ||
|
|
||
| // Start game and check game UI contrast | ||
| await startButton.click(); | ||
|
|
||
| const levelText = page.getByText('Level: 1 of 10'); | ||
| await expect(levelText).toBeVisible(); | ||
|
|
||
| const movesText = page.getByText('Level Moves: 0'); | ||
| await expect(movesText).toBeVisible(); | ||
| }); | ||
|
|
||
| test('should work with reduced motion preferences', async ({ page }) => { | ||
| // Simulate reduced motion preference | ||
| await page.emulateMedia({ reducedMotion: 'reduce' }); | ||
|
|
||
| await page.goto('/'); | ||
|
|
||
| // Game should still be functional | ||
| await page.getByRole('button', { name: 'Start Game' }).click(); | ||
| await expect(page.getByText('Level: 1 of 10')).toBeVisible(); | ||
|
|
||
| // Controls should still work | ||
| await page.getByRole('button', { name: '→' }).click(); | ||
| await expect(page.getByText('Level Moves: 1')).toBeVisible(); | ||
| }); | ||
|
|
||
| test('should handle high contrast mode', async ({ page }) => { | ||
| // Simulate high contrast mode | ||
| await page.emulateMedia({ colorScheme: 'dark', forcedColors: 'active' }); | ||
|
|
||
| await page.goto('/'); | ||
|
|
||
| // Essential elements should still be visible | ||
| await expect(page.getByText('🔥 HEATSEEKER 🔥')).toBeVisible(); | ||
| await expect(page.getByRole('button', { name: 'Start Game' })).toBeVisible(); | ||
|
|
||
| // Game should be playable | ||
| await page.getByRole('button', { name: 'Start Game' }).click(); | ||
| await expect(page.getByText('Level: 1 of 10')).toBeVisible(); | ||
| }); | ||
|
|
||
| test('should be usable with screen reader simulation', async ({ page }) => { | ||
| // This test simulates some screen reader behaviors | ||
|
|
||
| // Check that important content has text that would be read aloud | ||
| await expect(page.getByText('🔥 HEATSEEKER 🔥')).toBeVisible(); | ||
|
|
||
| const startButton = page.getByRole('button', { name: 'Start Game' }); | ||
| await expect(startButton).toBeVisible(); | ||
|
|
||
| await startButton.click(); | ||
|
|
||
| // Game status should be clearly communicated | ||
| await expect(page.getByText('Level: 1 of 10')).toBeVisible(); | ||
| await expect(page.getByText('Level Moves: 0')).toBeVisible(); | ||
| }); | ||
|
|
||
| test('should maintain focus management', async ({ page }) => { | ||
| await page.getByRole('button', { name: 'Start Game' }).click(); | ||
|
|
||
| // Tab through mobile controls | ||
| await page.keyboard.press('Tab'); | ||
| await page.keyboard.press('Tab'); | ||
| await page.keyboard.press('Tab'); | ||
|
|
||
| // Should be able to focus on mobile control buttons | ||
| const focusedElement = page.locator(':focus'); | ||
| await expect(focusedElement).toBeVisible(); | ||
| }); | ||
| }); | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[P1] Accessibility test looks for a heading that the UI never renders
The accessibility suite asserts that an
<h2>contains the textGame Rules:. The start screen’s subheading is currentlyDon't step in lava!, so the locator will never resolve and the test fails immediately. Either update the test to match the actual heading or render the expected text in the UI.Useful? React with 👍 / 👎.