5 epics, 20 stories covering all 31 functional requirements, 9 NFRs, and 20 UX design requirements.
29 KiB
stepsCompleted, inputDocuments
| stepsCompleted | inputDocuments | |||||||
|---|---|---|---|---|---|---|---|---|
|
|
try-bmad - Epic Breakdown
Overview
This document provides the complete epic and story breakdown for try-bmad (2048 Clone), decomposing the requirements from the PRD, UX Design, and Architecture into implementable stories.
Requirements Inventory
Functional Requirements
FR1: Render a 4x4 grid with empty cell placeholders using Svelte components FR2: Spawn 2 random tiles on game initialization FR3: Spawn 1 random tile after each valid move with 90/10 probability split (value 2 vs value 4) FR4: Slide all movable tiles in the input direction (up, down, left, right) FR5: Merge identical adjacent tiles in the movement path using leading-edge merge order FR6: Enforce once-per-move merge rule (a tile can only merge once per move) FR7: Track and display current score, incrementing by the value of each merged tile FR8: Track and display best score, updating when current score exceeds it FR9: Persist best score across sessions using localStorage FR10: Detect game over condition (no valid moves remaining on a full board) FR11: Detect win condition (2048 tile created) and display win overlay (trigger once per game) FR12: Provide "Keep playing" mode after winning, allowing continued play past 2048 FR13: Provide New Game button to restart the game (no confirmation dialog) FR14: Accept keyboard input: arrow keys for directional movement FR15: Accept keyboard input: WASD keys for directional movement FR16: Accept keyboard input: Vim hjkl keys for directional movement FR17: Accept touch/swipe input for mobile with 10px minimum threshold and dominant-axis detection FR18: Display tile colors matching original 2048 12-tier color system based on tile value FR19: Animate tile sliding with 100ms CSS transition (ease-in-out) FR20: Animate new tile spawn with 200ms pop animation (scale 0 to 1.0) FR21: Animate tile merge with 200ms bounce animation (scale 1.0 to 1.2 to 1.0) FR22: Animate score addition with "+N" float animation (600ms, rises and fades) FR23: Persist full game state to localStorage after every valid move (survive page refresh) FR24: Restore game state from localStorage on page load FR25: Display game over overlay with "Try again" button FR26: Display win overlay with "Keep going" and "New Game" buttons FR27: Fade in win/game-over overlays with 800ms opacity transition FR28: Render responsive layout: 500px container on desktop, 280px on mobile at 520px breakpoint FR29: Display tile glow effect (box-shadow) on tiles with value 128 or higher FR30: Apply dynamic font sizing on tiles based on digit count (55/45/35/25/15px) FR31: Deploy as static site to GitHub Pages via GitHub Actions workflow
NonFunctional Requirements
NFR1: Initial page load under 1 second (small Svelte + Tailwind bundle) NFR2: All CSS animations at 60fps framerate NFR3: Input-to-visual-response latency under 16ms NFR4: Production bundle size under 50KB gzipped NFR5: Support modern evergreen browsers: Chrome, Firefox, Safari, Edge (latest versions) NFR6: WCAG AA accessibility compliance for color contrast and keyboard navigation NFR7: Respect prefers-reduced-motion media query (disable animations when active) NFR8: No backend, no server communication — fully client-side application NFR9: Game logic implemented as pure JavaScript functions, testable independently from UI
Additional Requirements
- Starter template:
npm create vite@latestwith Svelte template + Tailwind CSS v4 via@tailwindcss/viteplugin + Vitest for unit testing - Project structure:
src/lib/for pure JS game logic (zero Svelte imports),src/components/for Svelte UI components - Game logic module returns immutable new state objects — never mutates input
- Props-down component architecture: App.svelte owns all state, children are presentational
- No Svelte stores — props and callback props only
- localStorage schema:
gameStatekey (grid, score, won, keepPlaying) +bestScorekey - CSS transitions for all animations — no JS setTimeout for animation sequencing
- Use
transitionendevents for animation coordination isAnimatingflag prevents input processing during slide transition with input queuing- GitHub Actions workflow for automated build and deploy to gh-pages branch
- Vite
baseconfig set to repo name for correct GitHub Pages asset paths - Clear Sans font file (woff2) in
public/fonts/with fallback chain - Constants module (
constants.js) for GRID_SIZE, WIN_VALUE, SPAWN_PROBABILITY, DIRECTIONS, TILE_COLORS
UX Design Requirements
UX-DR1: Implement 12-tier tile color system with exact hex values (2:#eee4da, 4:#ede0c8, 8:#f2b179, 16:#f59563, 32:#f67c5f, 64:#f65e3b, 128:#edcf72, 256:#edcc61, 512:#edc850, 1024:#edc53f, 2048:#edc22e, 4096+:#3c3a32) UX-DR2: Implement text color switching — dark text (#776e65) for tiles 2 and 4, white text (#f9f6f2) for tiles 8 and above UX-DR3: Apply page background #faf8ef, grid background #bbada0, empty cell color #cdc1b4 UX-DR4: Implement Clear Sans font family with fallback chain (Helvetica Neue, Arial, sans-serif) UX-DR5: Implement dynamic tile font sizing by digit count — 1 digit: 55px, 2 digits: 45px, 3 digits: 35px, 4 digits: 25px, 5+ digits: 15px (bold) UX-DR6: Implement glow effect on 128+ tiles: box-shadow 0 0 30px 10px rgba(243, 215, 116, 0.4) UX-DR7: Implement score box styling — background #bbada0, label #eee4da 13px uppercase, value #f9f6f2 25px bold UX-DR8: Implement button styling — background #8f7a66, text #f9f6f2, 18px bold, hover lighten 10%, active darken 5% UX-DR9: Implement game title at 80px bold in #776e65 UX-DR10: Implement responsive breakpoint at 520px — scale container from 500px to 280px, grid gap from 15px to 10px, tile fonts proportionally reduced UX-DR11: Implement mobile font scaling — title 45px, tile 1-digit 30px, 2-digit 25px, 3-digit 20px, 4-digit 14px, score 16px, overlay 35px UX-DR12: Implement ARIA roles — role="application" on game container, role="grid" on board, role="gridcell" on tiles, role="alertdialog" on overlays UX-DR13: Implement aria-live="polite" on score display for screen reader announcements UX-DR14: Implement focus management — auto-focus action button on overlay appear, trap focus within overlay, restore focus on dismiss UX-DR15: Implement keyboard navigation — Tab to New Game button, Enter to activate buttons, visible focus indicator UX-DR16: Implement overlay patterns — game over: rgba(238, 228, 218, 0.73) background, win: rgba(237, 194, 46, 0.5) background UX-DR17: Implement non-blocking animation system — input queuing during transitions, moves execute after current animation completes UX-DR18: Implement touch targets minimum 44x44px for New Game button and overlay action buttons UX-DR19: Implement zero-chrome layout — grid as dominant element, header holds only title + scores + new game button UX-DR20: Implement semantic HTML — use main, header, button elements (no divs-as-buttons)
FR Coverage Map
| FR | Epic | Description |
|---|---|---|
| FR1 | Epic 1 | 4x4 grid rendering |
| FR2 | Epic 1 | Spawn 2 tiles at start |
| FR3 | Epic 1 | Spawn 1 tile per move (90/10) |
| FR4 | Epic 1 | Slide tiles in 4 directions |
| FR5 | Epic 1 | Leading-edge merge order |
| FR6 | Epic 1 | Once-per-move merge rule |
| FR7 | Epic 1 | Current score tracking |
| FR8 | Epic 2 | Best score display |
| FR9 | Epic 2 | Best score localStorage persistence |
| FR10 | Epic 1 | Game over detection |
| FR11 | Epic 1 | Win detection + overlay |
| FR12 | Epic 2 | Keep playing mode |
| FR13 | Epic 1 | New Game button |
| FR14 | Epic 1 | Arrow key input |
| FR15 | Epic 4 | WASD key input |
| FR16 | Epic 4 | Vim hjkl key input |
| FR17 | Epic 4 | Touch/swipe input |
| FR18 | Epic 1 | 12-tier tile colors |
| FR19 | Epic 3 | Slide animation (100ms) |
| FR20 | Epic 3 | Spawn pop animation (200ms) |
| FR21 | Epic 3 | Merge bounce animation (200ms) |
| FR22 | Epic 3 | Score float animation (600ms) |
| FR23 | Epic 2 | Game state persistence |
| FR24 | Epic 2 | Game state restore on load |
| FR25 | Epic 1 | Game over overlay |
| FR26 | Epic 1 | Win overlay |
| FR27 | Epic 3 | Overlay fade animation (800ms) |
| FR28 | Epic 4 | Responsive layout (520px breakpoint) |
| FR29 | Epic 5 | Tile glow on 128+ |
| FR30 | Epic 5 | Dynamic font sizing |
| FR31 | Epic 5 | GitHub Pages deployment |
Epic List
Epic 1: Play a Complete Game (Desktop)
Player can play 2048 from start to finish — slide tiles, merge numbers, track score, win or lose, and restart. Includes project scaffold as first story. FRs covered: FR1, FR2, FR3, FR4, FR5, FR6, FR7, FR10, FR11, FR13, FR14, FR18, FR25, FR26
Epic 2: Save Progress & Keep Playing
Player's game survives page refresh, best score persists across sessions, and they can continue playing past 2048. FRs covered: FR8, FR9, FR12, FR23, FR24
Epic 3: Smooth Animations & Game Feel
Every interaction feels satisfying with smooth slide, merge, spawn, score, and overlay animations matching original 2048 timings. FRs covered: FR19, FR20, FR21, FR22, FR27
Epic 4: Mobile & Multi-Input Support
Game is fully playable on mobile with touch/swipe and responsive layout, plus WASD and Vim key support on desktop. FRs covered: FR15, FR16, FR17, FR28
Epic 5: Visual Polish & Deployment
Final visual refinements (glow effects, dynamic font sizing) and public deployment to GitHub Pages. FRs covered: FR29, FR30, FR31
Epic 1: Play a Complete Game (Desktop)
Player can play 2048 from start to finish — slide tiles, merge numbers, track score, win or lose, and restart. Includes project scaffold as first story.
Story 1.1: Project Scaffold & Dev Environment
As a developer, I want a properly configured Svelte + Tailwind + Vite project with the correct folder structure, So that all subsequent stories have a solid foundation to build on.
Acceptance Criteria:
Given no project exists
When the scaffold is created using npm create vite@latest with the Svelte template
Then the project builds and runs with npm run dev
And Tailwind CSS v4 is installed via @tailwindcss/vite plugin and @import "tailwindcss" works in app.css
And Vitest is installed as a dev dependency and npx vitest runs without error
And the folder structure matches Architecture: src/lib/, src/components/, public/fonts/
And src/lib/constants.js exists with GRID_SIZE (4), WIN_VALUE (2048), SPAWN_PROBABILITY (0.9), DIRECTIONS, and TILE_COLORS (12-tier hex values)
And Clear Sans font is placed in public/fonts/ with @font-face declaration in app.css
And page background is set to #faf8ef
Story 1.2: Game Logic Module
As a developer, I want a pure JavaScript game logic module with complete unit tests, So that all game mechanics are correct and independently testable before connecting to the UI.
Acceptance Criteria:
Given the constants module exists
When initGame() is called
Then it returns a game state with a 4x4 grid (2D array of zeros) with exactly 2 random tiles placed (90% chance value 2, 10% chance value 4)
And score is 0, won is false, keepPlaying is false
Given a valid game state
When move(state, direction) is called with a direction (up/down/left/right)
Then it returns a new state object (never mutates input) with tiles slid to the leading edge
And identical adjacent tiles in the movement path merge using leading-edge order
And each tile merges at most once per move (once-per-move rule enforced)
And score delta equals the sum of all merged tile values
And one new random tile (90/10 split) is added to a random empty cell
Given a game state where no tiles can move in the requested direction
When move(state, direction) is called
Then the original state is returned unchanged (no new tile spawned)
Given a game state
When isGameOver(state) is called
Then it returns true only when the board is full AND no adjacent tiles share the same value
Given a game state When a merge creates a tile with value 2048 Then the won flag is set to true in the returned state
And all functions have passing Vitest unit tests covering edge cases (full board, corner merges, chain prevention)
Story 1.3: Game Board & Tile Rendering
As a player, I want to see a 4x4 game grid with colored numbered tiles, So that I can visually understand the game state.
Acceptance Criteria:
Given the app loads
When the Grid component renders
Then a 4x4 grid with #bbada0 background and 16 empty cell placeholders (#cdc1b4) is displayed
And the grid has 15px gap and 15px padding with 6px border radius
And the grid container is 500px wide and centered on the page
Given a game state with tiles
When the Tile component renders for each tile
Then each tile displays its numeric value centered in the cell
And tile background color matches the 12-tier color system (UX-DR1)
And text color is #776e65 for values 2 and 4, #f9f6f2 for values 8+ (UX-DR2)
And font family is Clear Sans with fallback chain (UX-DR4)
And tiles have role="gridcell" and aria-label with tile value (UX-DR12)
And the grid has role="grid" and aria-label="Game board" (UX-DR12)
Story 1.4: Game Header, Score Display & New Game Button
As a player, I want to see the game title, my current score, and a New Game button, So that I can track my progress and restart anytime.
Acceptance Criteria:
Given the app loads
When the header renders
Then the game title "2048" is displayed at 80px bold in #776e65 (UX-DR9)
And a subtitle row shows "Join the numbers and get to the 2048 tile!" with the New Game button right-aligned
And the layout follows zero-chrome design — header holds only title + scores + new game (UX-DR19)
And semantic HTML is used: <header>, <main>, <button> elements (UX-DR20)
Given a ScoreBoard component
When it renders with score and bestScore props
Then two side-by-side boxes display "SCORE" and "BEST" labels (13px uppercase, #eee4da) with numeric values (25px bold, #f9f6f2) on #bbada0 background (UX-DR7)
And score display has aria-live="polite" for screen reader updates (UX-DR13)
Given the New Game button
When it renders
Then it has #8f7a66 background, #f9f6f2 text, 18px bold styling (UX-DR8)
And it has a visible focus indicator and is reachable via Tab key (UX-DR15)
And the button has minimum 44x44px touch target (UX-DR18)
Given the player clicks New Game When the click event fires Then the game resets to a fresh state with 2 random tiles and score 0 (no confirmation dialog) (FR13)
Story 1.5: Keyboard Input & Interactive Gameplay
As a player, I want to press arrow keys to slide tiles and play the game interactively, So that I can experience the core 2048 gameplay loop.
Acceptance Criteria:
Given the game is in playing state
When the player presses an arrow key (Up/Down/Left/Right)
Then the game logic move() is called with the corresponding direction
And the UI updates to reflect the new game state (tiles at new positions, new tile spawned)
And the current score updates to reflect any merges
Given the player presses an arrow key in a direction where no tiles can move When the move is processed Then no state change occurs, no new tile is spawned
Given the game container
When it renders
Then it has role="application" for game-specific keyboard behavior (UX-DR12)
And keyboard events are captured on the window
Given the input handler module (input-handler.js)
When a keyboard event fires
Then it translates the raw event to a direction string and passes it to the game logic
And non-game keys are ignored
Story 1.6: Win & Game Over Overlays
As a player, I want to see a clear message when I win or lose, with options to continue or restart, So that I know the game outcome and can take my next action.
Acceptance Criteria:
Given the game detects no valid moves remaining (game over)
When the game over state is triggered
Then a GameMessage overlay appears over the grid with "Game over!" text
And the overlay background is rgba(238, 228, 218, 0.73) (UX-DR16)
And a "Try again" button is displayed (UX-DR8 styling)
And the overlay has role="alertdialog" and aria-modal="true" (UX-DR12)
And focus auto-moves to the "Try again" button (UX-DR14)
Given a merge creates the 2048 tile for the first time in this game
When the win state is triggered
Then a GameMessage overlay appears with "You win!" text
And the overlay background is rgba(237, 194, 46, 0.5) (UX-DR16)
And "Keep going" and "New Game" buttons are displayed
And focus auto-moves to the primary action button (UX-DR14)
Given the player clicks "Try again" or "New Game" on an overlay When the click fires Then the game resets to a fresh state (FR13)
Given the player clicks "Keep going" on the win overlay When the click fires Then the overlay dismisses and gameplay continues (win overlay will not trigger again this game)
Epic 2: Save Progress & Keep Playing
Player's game survives page refresh, best score persists across sessions, and they can continue playing past 2048.
Story 2.1: Best Score Tracking
As a player, I want to see my best score alongside my current score, So that I have a motivational anchor and can track my all-time progress.
Acceptance Criteria:
Given the game is running When the current score exceeds the best score after a merge Then the best score display updates immediately to match the current score
Given the player starts a new game When the game resets Then the best score remains unchanged (it persists across games within the session)
Story 2.2: Game State Persistence
As a player, I want my in-progress game to survive a page refresh, So that I never lose my progress unexpectedly.
Acceptance Criteria:
Given the player makes a valid move
When the game state updates
Then the full game state is saved to localStorage under the gameState key as JSON: { grid, score, won, keepPlaying }
And the best score is saved separately under the bestScore key as a single number
Given the player opens the app and a saved gameState exists in localStorage
When the app initializes
Then the game restores the saved grid, score, won, and keepPlaying values
And the best score is restored from the bestScore key
And the player sees their previous board state with no loading indicator (silent restore)
Given the player opens the app and no saved game exists (or localStorage data is corrupted) When the app initializes Then a fresh game starts with 2 random tiles and score 0 (silent recovery, no error shown)
Given the storage.js module
When JSON.parse fails on corrupted localStorage data
Then it catches the error silently and returns null (triggering fresh game start)
Given the player clicks New Game
When the game resets
Then the gameState key in localStorage is cleared
And the bestScore key is preserved
Story 2.3: Keep Playing Mode
As a player, I want to continue playing after reaching 2048, So that I can push for higher tiles and a bigger score.
Acceptance Criteria:
Given the player has won and clicked "Keep going"
When gameplay resumes
Then the keepPlaying flag is set to true in game state
And subsequent merges creating 2048+ tiles do not trigger the win overlay again
Given the player is in keep-playing mode and closes the browser
When they reopen the app
Then the game restores with keepPlaying: true and no win overlay appears
Given the player is in keep-playing mode When no valid moves remain Then the game over overlay appears normally
Epic 3: Smooth Animations & Game Feel
Every interaction feels satisfying with smooth slide, merge, spawn, score, and overlay animations matching original 2048 timings.
Story 3.1: Tile Slide Animation
As a player, I want tiles to slide smoothly to their new positions, So that the game feels responsive and I can visually track tile movement.
Acceptance Criteria:
Given the player makes a valid move
When tiles change position
Then each tile animates from its old position to its new position using CSS transform: translate with a 100ms ease-in-out transition
And all movable tiles animate simultaneously
And the animation runs at 60fps (GPU-accelerated CSS transition)
Given an animation is in progress
When the player presses another arrow key
Then the input is queued and executes after the current animation completes (UX-DR17)
And the isAnimating flag prevents immediate processing
Given the user has prefers-reduced-motion enabled
When a move occurs
Then tiles appear instantly at their new positions with no transition
Story 3.2: Tile Spawn Pop Animation
As a player, I want new tiles to pop into existence, So that I can easily spot where the new tile appeared.
Acceptance Criteria:
Given a new tile is spawned after a move
When the tile appears on the grid
Then it plays a pop animation scaling from 0 to 1.0 over 200ms using CSS @keyframes
And the pop animation begins after the slide animation completes (sequential: slide then spawn)
Given the user has prefers-reduced-motion enabled
When a new tile spawns
Then the tile appears instantly at full size with no animation
Story 3.3: Tile Merge Bounce Animation
As a player, I want merged tiles to bounce briefly, So that I get satisfying visual feedback when tiles combine.
Acceptance Criteria:
Given two tiles merge during a move
When the merge occurs
Then the resulting tile plays a bounce animation scaling from 1.0 to 1.2 back to 1.0 over 200ms using CSS @keyframes
And the bounce animation can run concurrently with the score float animation
Given the user has prefers-reduced-motion enabled
When tiles merge
Then the merged tile appears at normal scale with no animation
Story 3.4: Score Float Animation
As a player, I want to see "+N" float up from the score when I earn points, So that I feel the immediate reward of each merge.
Acceptance Criteria:
Given a merge occurs and the score increases
When the score updates
Then a "+N" text element appears near the score box (where N is the score delta)
And it animates upward and fades out over 600ms using CSS @keyframes (translate-y + opacity)
And the animation uses ease-out timing
Given multiple merges occur in a single move When the score updates Then a single "+N" float shows the total score delta for that move
Given the user has prefers-reduced-motion enabled
When the score updates
Then no float animation appears (score value still updates)
Story 3.5: Win & Game Over Overlay Fade Animation
As a player, I want win and game over overlays to fade in smoothly, So that the transition feels polished rather than abrupt.
Acceptance Criteria:
Given the game reaches a win or game over state
When the overlay appears
Then it fades in with an 800ms opacity transition from 0 to 1
And the overlay uses pointer-events: none during fade-in, switching to auto when complete
Given the user has prefers-reduced-motion enabled
When a game-ending state triggers
Then the overlay appears instantly with no fade transition
Epic 4: Mobile & Multi-Input Support
Game is fully playable on mobile with touch/swipe and responsive layout, plus WASD and Vim key support on desktop.
Story 4.1: WASD & Vim Keyboard Support
As a desktop player, I want to use WASD or Vim hjkl keys to control the game, So that I can play with my preferred keyboard layout.
Acceptance Criteria:
Given the game is in playing state When the player presses W/A/S/D keys Then they map to Up/Left/Down/Right directions respectively and trigger a move
Given the game is in playing state When the player presses h/j/k/l keys Then they map to Left/Down/Up/Right directions respectively and trigger a move
Given the input handler When any mapped key is pressed Then it produces identical behavior to the corresponding arrow key (same game logic path)
Story 4.2: Touch & Swipe Input
As a mobile player, I want to swipe on the game board to slide tiles, So that I can play the game naturally on a touchscreen.
Acceptance Criteria:
Given the player touches the game grid and swipes When the swipe distance exceeds 10px minimum threshold Then the dominant axis (larger delta between horizontal and vertical) determines the direction And the corresponding move is executed
Given the player touches and lifts with less than 10px movement When the touch event completes Then no move is triggered (prevents accidental swipes)
Given a diagonal swipe When the touch event completes Then the axis with the larger delta wins (e.g., deltaX=30, deltaY=15 → horizontal → Left or Right based on sign)
Given an animation is in progress on mobile When the player swipes again Then the input is queued and executes after the current animation completes (same queuing as keyboard)
Story 4.3: Responsive Layout
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
Epic 5: Visual Polish & Deployment
Final visual refinements (glow effects, dynamic font sizing) and public deployment to GitHub Pages.
Story 5.1: Tile Glow Effects
As a player, I want high-value tiles (128+) to glow, So that I get visual feedback of my progress and milestone tiles feel special.
Acceptance Criteria:
Given a tile has a value of 128 or higher
When it renders on the grid
Then it displays a glow effect: box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0.4) (UX-DR6)
Given a tile has a value below 128 When it renders on the grid Then no glow effect is applied
Given a tile merges from 64+64 to 128 When the merge completes Then the glow effect appears on the resulting tile
Story 5.2: Dynamic Tile Font Sizing
As a player, I want tile numbers to be clearly readable at any value, So that I can always see tile values regardless of digit count.
Acceptance Criteria:
Given a tile with a 1-digit value (2, 4, 8) When it renders Then the font size is 55px bold (desktop) / 30px bold (mobile) (UX-DR5, UX-DR11)
Given a tile with a 2-digit value (16, 32, 64) When it renders Then the font size is 45px bold (desktop) / 25px bold (mobile)
Given a tile with a 3-digit value (128, 256, 512) When it renders Then the font size is 35px bold (desktop) / 20px bold (mobile)
Given a tile with a 4-digit value (1024, 2048, 4096, 8192) When it renders Then the font size is 25px bold (desktop) / 14px bold (mobile)
Given a tile with a 5+ digit value (16384, 32768, 65536) When it renders Then the font size is 15px bold (desktop) / proportionally scaled (mobile)
Story 5.3: GitHub Pages Deployment
As a developer, I want the game automatically deployed to GitHub Pages on push to main, So that the game is publicly accessible without manual deployment steps.
Acceptance Criteria:
Given the Vite config
When the production build runs
Then the base option is set to the repository name for correct asset paths on GitHub Pages
And npm run build produces a dist/ directory with the complete static site
Given a .github/workflows/deploy.yml workflow file
When code is pushed to the main branch
Then GitHub Actions runs npm ci, npm run build, and deploys the dist/ directory to the gh-pages branch
Given the deployment completes When a user visits the GitHub Pages URL Then the game loads and is fully playable And all assets (fonts, CSS, JS) load correctly via relative paths And the production bundle is under 50KB gzipped (NFR4)