Add Simon Says memory sequence game#120
Conversation
- Implement complete Simon Says game with two-page structure - Landing page with game setup (difficulty, button count) - Gameplay page with full game logic and audio - Support for 4, 7, and 10 button configurations - Normal and Strict game modes - Keyboard controls (number keys, WASD, arrow keys) - Audio feedback with Web Audio API - Local storage for high score persistence - Responsive design with mobile support - SSR-safe implementation with proper hydration guards - TypeScript interfaces for type safety - Error boundaries and client-side API protection Resolves #105
|
@Sainava is attempting to deploy a commit to the ajaynegi45's projects Team on Vercel. A member of the Team first needs to authorize it. |
Summary by CodeRabbit
WalkthroughAdds a new Simon Says game: a Games page card, a landing/setup page (difficulty and button-count), a client-only play page with sequence logic, audio and keyboard support, score persistence, and two CSS modules for setup and play styling and layout. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant Games as Game List
participant Setup as SimonSays Setup
participant Router
participant Play as SimonSays Play
participant Engine as Game Logic
participant Audio as AudioContext
participant Store as localStorage
User->>Games: Click "Simon Says"
Games->>Router: Navigate /game/simon-says
Router->>Setup: Render landing/setup
User->>Setup: Choose difficulty & button count
User->>Setup: Click "Start Playing"
Setup->>Router: Navigate to /game/simon-says/play?settings
Router->>Play: Render play page
Play->>Store: Load best score
Play->>Engine: Init sequence/state
User->>Play: Click "Start"
Play->>Engine: Generate & show sequence
Engine->>Audio: Play tones (visual highlights)
Engine-->>User: Sequence displayed
loop Player turn
User->>Play: Input (click/keyboard)
Play->>Audio: Play tone
Play->>Engine: Validate input
alt Correct & round complete
Engine->>Store: Update best score if needed
Engine-->>Play: Extend sequence
else Mistake
alt Normal
Engine-->>Play: Retry same round
else Strict
Engine-->>Play: Game over
end
end
end
User->>Play: Toggle sound / Back
Play->>Router: Navigate back to setup
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: ASSERTIVE Plan: Pro 📒 Files selected for processing (1)
🧰 Additional context used🪛 Biome (2.1.2)src/app/game/simon-says/play/page.tsx[error] 411-411: Provide an explicit type prop for the button element. The default type of a button is submit, which causes the submission of a form when placed inside a (lint/a11y/useButtonType) [error] 458-473: Provide an explicit type prop for the button element. The default type of a button is submit, which causes the submission of a form when placed inside a (lint/a11y/useButtonType) [error] 480-485: Provide an explicit type prop for the button element. The default type of a button is submit, which causes the submission of a form when placed inside a (lint/a11y/useButtonType) 🔇 Additional comments (12)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
👋 Hello @Sainava! Thanks for contributing to Old-School-Game – a curated collection of classic, brain-training games that boost memory, focus, creativity, and long-term brain health 🎯🧠. In today's world full of short-form distractions, this project helps rebuild deep focus and mental endurance through thoughtful gameplay. ✅ Important Things To Do:
✅ Your pull request has been submitted successfully, and a maintainer will review it soon. |
There was a problem hiding this comment.
Actionable comments posted: 3
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
⛔ Files ignored due to path filters (1)
public/SimonSays.pngis excluded by!**/*.png
📒 Files selected for processing (5)
src/app/game/page.tsx(2 hunks)src/app/game/simon-says/page.module.css(1 hunks)src/app/game/simon-says/page.tsx(1 hunks)src/app/game/simon-says/play/page.module.css(1 hunks)src/app/game/simon-says/play/page.tsx(1 hunks)
🧰 Additional context used
🪛 Biome (2.1.2)
src/app/game/simon-says/page.tsx
[error] 34-37: Provide an explicit type prop for the button element.
The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset
(lint/a11y/useButtonType)
[error] 41-44: Provide an explicit type prop for the button element.
The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset
(lint/a11y/useButtonType)
[error] 54-57: Provide an explicit type prop for the button element.
The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset
(lint/a11y/useButtonType)
[error] 61-64: Provide an explicit type prop for the button element.
The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset
(lint/a11y/useButtonType)
[error] 68-71: Provide an explicit type prop for the button element.
The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset
(lint/a11y/useButtonType)
[error] 78-78: Provide an explicit type prop for the button element.
The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset
(lint/a11y/useButtonType)
src/app/game/simon-says/play/page.tsx
[error] 391-391: Provide an explicit type prop for the button element.
The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset
(lint/a11y/useButtonType)
[error] 438-453: Provide an explicit type prop for the button element.
The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset
(lint/a11y/useButtonType)
[error] 460-465: Provide an explicit type prop for the button element.
The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset
(lint/a11y/useButtonType)
🔇 Additional comments (15)
src/app/game/page.tsx (1)
9-9: Nice addition!The Simon Says card follows the same pattern as the other game cards. The import, link, and styling look good and match the new game directory structure. Great consistency!
Also applies to: 50-63
src/app/game/simon-says/play/page.module.css (1)
1-349: Well-structured CSS module!The play page styles are nicely organized with clear sections for layout, stats, controls, game board, and responsive behavior. The responsive breakpoints and animations look good. Great job keeping the styles modular and readable!
src/app/game/simon-says/play/page.tsx (13)
7-56: Great type definitions!Your interfaces and types are clear and well-structured. The
BUTTON_CONFIGSsetup nicely handles the different difficulty levels (4, 7, 10 buttons) with distinct colors and tones. Using musical note frequencies for tones is a nice touch!
58-88: Smart SSR safety pattern!The
mountedstate and the mount effect at lines 85-87 ensure that any client-only code (likelocalStorageorwindowaccess) waits until after hydration. This prevents SSR mismatches. Well done!
90-107: Solid URL parameter handling!The validation checks for
difficultyandbuttonsare thorough, and themountedguard ensures this runs safely on the client side. The try-catch is a nice touch for robustness. Great work!
110-139: Excellent Web Audio API usage!Your audio context initialization handles SSR safety and Safari's
webkitAudioContextfallback nicely. The tone generation with oscillator and gain node is spot-on, and the exponential ramp creates a smooth fade-out. The try-catch ensures graceful degradation if audio fails. Really well implemented!
142-169: Perfect localStorage integration!The best score persistence is well-designed with unique keys per difficulty and button count. The SSR guards (
mounted,typeof window) and try-catch blocks ensure it's safe and robust. Nice job!
172-190: Clean game initialization!The
generateNextStepfunction correctly generates random button indices, andstartNewGamesets up the initial game state perfectly. CallinginitAudioContexthere ensures audio is ready when the sequence starts. Looks great!
193-225: Good sequence display logic!The
showSequencefunction handles the timing and visual feedback well. The async/await pattern with promises makes the sequence flow clear. One small note: if the component unmounts mid-sequence, only the last timeout is cleaned up (line 346-349). The earlier timeouts will still fire, but since they only callsetActiveButton, it's harmless. For now, this is fine, but if you wanted to be extra careful, you could store all timeout IDs in an array and clear them all on unmount.
228-287: Core game logic looks solid!The
handleButtonClickfunction correctly handles both success and failure cases. The distinction between strict mode (game over on mistake) and normal mode (retry current round) is implemented perfectly. The score calculation is correct—score only increases when a round is completed successfully. Great job on the game mechanics!
290-334: Keyboard controls are well-implemented!The number key bindings (1-9, 0) work perfectly for all button counts, and the arrow/WASD controls for 4-button mode are a nice UX touch. The guard checks ensure inputs are only processed during the player's turn. Great attention to detail!
337-350: Effects are correctly wired!The effect that triggers
showSequencewhen the game status changes to 'showing' is spot-on. The cleanup effect ensures the last timeout is cleared on unmount, preventing memory leaks. Nice!
352-371: User-friendly status messages!The
getStatusMessagefunction provides clear feedback to the player at each stage of the game. The navigation back to the setup page is straightforward. Looks good!
374-385: Smart loading state!The loading guard before
mountedis true prevents any client-only rendering issues during SSR. This ensures a smooth hydration experience. Well done!
483-501: Perfect Suspense setup!Wrapping
SimonSaysGameinSuspenseis the right approach for handlinguseSearchParamsin Next.js client components. The fallback UI is clean and provides good feedback while loading. Great!
| /* Simon Says Game Styles */ | ||
| .container { | ||
| max-width: 1000px; | ||
| margin: 0 auto; | ||
| padding: 20px; | ||
| text-align: center; | ||
| background: white; | ||
| min-height: 100vh; | ||
| color: #333; | ||
| } | ||
|
|
||
| .header { | ||
| margin-bottom: 30px; | ||
| } | ||
|
|
||
| .title { | ||
| font-size: 2.8rem; | ||
| font-weight: bold; | ||
| margin-bottom: 10px; | ||
| background: linear-gradient(135deg, #9333EA, #7C3AED); | ||
| -webkit-background-clip: text; | ||
| -webkit-text-fill-color: transparent; | ||
| background-clip: text; | ||
| text-shadow: none; | ||
| } | ||
|
|
||
| .subtitle { | ||
| font-size: 1.3rem; | ||
| color: #666; | ||
| margin-bottom: 20px; | ||
| } | ||
|
|
||
| /* Game Setup */ | ||
| .gameSetup { | ||
| max-width: 700px; | ||
| margin: 0 auto 3rem auto; | ||
| padding: 2rem; | ||
| background: #f8f9fa; | ||
| border-radius: 16px; | ||
| border: 2px solid #e2e8f0; | ||
| } | ||
|
|
||
| .setupTitle { | ||
| font-size: 1.8rem; | ||
| margin-bottom: 2rem; | ||
| color: #333; | ||
| text-align: center; | ||
| } | ||
|
|
||
| .setupSection { | ||
| margin-bottom: 2rem; | ||
| text-align: center; | ||
| } | ||
|
|
||
| .setupSection h3 { | ||
| font-size: 1.2rem; | ||
| margin-bottom: 1rem; | ||
| color: #333; | ||
| } | ||
|
|
||
| .buttonGroup { | ||
| display: flex; | ||
| justify-content: center; | ||
| gap: 15px; | ||
| flex-wrap: wrap; | ||
| } | ||
|
|
||
| .setupButton { | ||
| padding: 15px 25px; | ||
| border: 2px solid #e2e8f0; | ||
| border-radius: 12px; | ||
| background: white; | ||
| color: #333; | ||
| cursor: pointer; | ||
| font-weight: 500; | ||
| transition: all 0.3s ease; | ||
| font-size: 1rem; | ||
| display: flex; | ||
| flex-direction: column; | ||
| align-items: center; | ||
| gap: 5px; | ||
| min-width: 120px; | ||
| } | ||
|
|
||
| .setupButton:hover { | ||
| border-color: #9333EA; | ||
| transform: translateY(-2px); | ||
| } | ||
|
|
||
| .setupButton.active { | ||
| background: linear-gradient(135deg, #9333EA, #7C3AED); | ||
| color: white; | ||
| border-color: #9333EA; | ||
| } | ||
|
|
||
| .modeDescription { | ||
| font-size: 0.8rem; | ||
| opacity: 0.8; | ||
| } | ||
|
|
||
| .playButton { | ||
| background: linear-gradient(135deg, #22C55E, #16A34A); | ||
| color: white; | ||
| border: none; | ||
| padding: 18px 40px; | ||
| font-size: 1.3rem; | ||
| font-weight: bold; | ||
| border-radius: 12px; | ||
| cursor: pointer; | ||
| transition: all 0.3s ease; | ||
| box-shadow: 0 4px 15px rgba(34, 197, 94, 0.3); | ||
| margin-top: 1.5rem; | ||
| } | ||
|
|
||
| .playButton:hover { | ||
| background: linear-gradient(135deg, #16A34A, #15803D); | ||
| transform: translateY(-2px); | ||
| box-shadow: 0 6px 20px rgba(34, 197, 94, 0.4); | ||
| } | ||
|
|
||
| .soundControl { | ||
| text-align: center; | ||
| } | ||
|
|
||
| .soundToggle { | ||
| display: flex; | ||
| align-items: center; | ||
| justify-content: center; | ||
| gap: 8px; | ||
| cursor: pointer; | ||
| font-size: 1rem; | ||
| } | ||
|
|
||
| .soundToggle input[type="checkbox"] { | ||
| transform: scale(1.2); | ||
| cursor: pointer; | ||
| } | ||
|
|
||
| /* Game Stats */ | ||
| .gameStats { | ||
| display: flex; | ||
| justify-content: center; | ||
| gap: 30px; | ||
| margin-bottom: 20px; | ||
| flex-wrap: wrap; | ||
| } | ||
|
|
||
| .stat { | ||
| background: #f8f9fa; | ||
| padding: 15px 25px; | ||
| border-radius: 12px; | ||
| border: 2px solid #e2e8f0; | ||
| color: #2d3748; | ||
| min-width: 100px; | ||
| box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); | ||
| } | ||
|
|
||
| .statLabel { | ||
| display: block; | ||
| font-size: 0.9rem; | ||
| color: #666; | ||
| margin-bottom: 5px; | ||
| } | ||
|
|
||
| .statValue { | ||
| display: block; | ||
| font-size: 1.5rem; | ||
| font-weight: bold; | ||
| color: #9333EA; | ||
| } | ||
|
|
||
| /* Status Message */ | ||
| .statusMessage { | ||
| margin-bottom: 30px; | ||
| padding: 15px; | ||
| background: linear-gradient(135deg, #f3f4f6, #e5e7eb); | ||
| border-radius: 12px; | ||
| border-left: 4px solid #9333EA; | ||
| } | ||
|
|
||
| .statusMessage p { | ||
| font-size: 1.1rem; | ||
| font-weight: 500; | ||
| margin: 0; | ||
| color: #374151; | ||
| } | ||
|
|
||
| /* Game Board */ | ||
| .gameBoard { | ||
| display: grid; | ||
| gap: 20px; | ||
| margin: 30px auto; | ||
| max-width: 600px; | ||
| perspective: 1000px; | ||
| padding: 20px; | ||
| } | ||
|
|
||
| .grid4 { | ||
| grid-template-columns: repeat(2, 1fr); | ||
| max-width: 400px; | ||
| } | ||
|
|
||
| .grid7 { | ||
| grid-template-columns: repeat(3, 1fr); | ||
| max-width: 500px; | ||
| } | ||
|
|
||
| .grid10 { | ||
| grid-template-columns: repeat(4, 1fr); | ||
| max-width: 600px; | ||
| } | ||
|
|
||
| .gameButton { | ||
| aspect-ratio: 1; | ||
| border: none; | ||
| border-radius: 20px; | ||
| cursor: pointer; | ||
| font-size: 1rem; | ||
| font-weight: bold; | ||
| color: white; | ||
| text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); | ||
| transition: all 0.3s ease; | ||
| position: relative; | ||
| overflow: hidden; | ||
| min-height: 100px; | ||
| display: flex; | ||
| flex-direction: column; | ||
| align-items: center; | ||
| justify-content: center; | ||
| gap: 5px; | ||
| transform-style: preserve-3d; | ||
| } | ||
|
|
||
| .gameButton:hover:not(:disabled) { | ||
| transform: translateY(-5px) scale(1.05); | ||
| box-shadow: 0 10px 25px rgba(0, 0, 0, 0.3) !important; | ||
| } | ||
|
|
||
| .gameButton:active:not(:disabled) { | ||
| transform: translateY(-2px) scale(0.98); | ||
| } | ||
|
|
||
| .gameButton:disabled { | ||
| cursor: not-allowed; | ||
| opacity: 0.8; | ||
| } | ||
|
|
||
| .gameButton.active { | ||
| transform: translateY(-5px) scale(1.1); | ||
| filter: brightness(1.3); | ||
| animation: pulse 0.6s ease; | ||
| } | ||
|
|
||
| @keyframes pulse { | ||
| 0%, 100% { transform: translateY(-5px) scale(1.1); } | ||
| 50% { transform: translateY(-8px) scale(1.15); } | ||
| } | ||
|
|
||
| .buttonLabel { | ||
| font-size: 1rem; | ||
| font-weight: bold; | ||
| } | ||
|
|
||
|
|
||
|
|
||
| /* Action Buttons */ | ||
| .actionButtons { | ||
| margin: 30px 0; | ||
| } | ||
|
|
||
| .startButton { | ||
| background: linear-gradient(135deg, #22C55E, #16A34A); | ||
| color: white; | ||
| border: none; | ||
| padding: 15px 40px; | ||
| font-size: 1.2rem; | ||
| font-weight: bold; | ||
| border-radius: 12px; | ||
| cursor: pointer; | ||
| transition: all 0.3s ease; | ||
| box-shadow: 0 4px 15px rgba(34, 197, 94, 0.3); | ||
| } | ||
|
|
||
| .startButton:hover:not(:disabled) { | ||
| background: linear-gradient(135deg, #16A34A, #15803D); | ||
| transform: translateY(-2px); | ||
| box-shadow: 0 6px 20px rgba(34, 197, 94, 0.4); | ||
| } | ||
|
|
||
| .startButton:disabled { | ||
| opacity: 0.6; | ||
| cursor: not-allowed; | ||
| transform: none; | ||
| } | ||
|
|
||
| /* Keyboard Info */ | ||
| .keyboardInfo { | ||
| margin: 20px 0; | ||
| padding: 15px; | ||
| background: #f8f9fa; | ||
| border-radius: 10px; | ||
| border: 1px solid #e2e8f0; | ||
| } | ||
|
|
||
| .keyboardInfo h4 { | ||
| margin: 0 0 8px 0; | ||
| color: #333; | ||
| font-size: 1rem; | ||
| } | ||
|
|
||
| .keyboardInfo p { | ||
| margin: 0; | ||
| color: #666; | ||
| font-size: 0.9rem; | ||
| } | ||
|
|
||
| /* Rules and Benefits */ | ||
| .rulesContainer { | ||
| max-width: 800px; | ||
| margin: 3rem auto; | ||
| padding: 0 1rem; | ||
| font-family: system-ui, -apple-system, sans-serif; | ||
| text-align: left; | ||
| } | ||
|
|
||
| .sectionTitle { | ||
| font-size: 1.6rem; | ||
| margin: 2rem 0 1.2rem 0; | ||
| color: #111; | ||
| font-weight: 600; | ||
| letter-spacing: -0.01em; | ||
| text-align: center; | ||
| } | ||
|
|
||
| .rulesGrid { | ||
| display: grid; | ||
| grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); | ||
| gap: 1.5rem; | ||
| margin-bottom: 2rem; | ||
| } | ||
|
|
||
| .ruleCard { | ||
| background: white; | ||
| padding: 1.5rem; | ||
| border-radius: 12px; | ||
| border: 2px solid #e2e8f0; | ||
| text-align: center; | ||
| transition: all 0.3s ease; | ||
| } | ||
|
|
||
| .ruleCard:hover { | ||
| border-color: #9333EA; | ||
| transform: translateY(-2px); | ||
| } | ||
|
|
||
| .ruleNumber { | ||
| width: 40px; | ||
| height: 40px; | ||
| background: linear-gradient(135deg, #9333EA, #7C3AED); | ||
| color: white; | ||
| border-radius: 50%; | ||
| display: flex; | ||
| align-items: center; | ||
| justify-content: center; | ||
| font-weight: bold; | ||
| font-size: 1.2rem; | ||
| margin: 0 auto 1rem auto; | ||
| } | ||
|
|
||
| .ruleCard h4 { | ||
| margin: 0 0 0.5rem 0; | ||
| color: #333; | ||
| font-size: 1.1rem; | ||
| } | ||
|
|
||
| .ruleCard p { | ||
| margin: 0; | ||
| color: #666; | ||
| line-height: 1.5; | ||
| } | ||
|
|
||
|
|
||
|
|
||
| .algorithmText { | ||
| line-height: 1.7; | ||
| color: #333; | ||
| background: #f8f9fa; | ||
| padding: 1.5rem; | ||
| border-radius: 10px; | ||
| border-left: 4px solid #9333EA; | ||
| } | ||
|
|
||
| .rulesGrid { | ||
| display: grid; | ||
| grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); | ||
| gap: 1.5rem; | ||
| margin-bottom: 2rem; | ||
| } | ||
|
|
||
| .ruleCard { | ||
| background: white; | ||
| padding: 1.5rem; | ||
| border-radius: 12px; | ||
| border: 2px solid #e2e8f0; | ||
| text-align: center; | ||
| transition: all 0.3s ease; | ||
| } | ||
|
|
||
| .ruleCard:hover { | ||
| border-color: #9333EA; | ||
| transform: translateY(-2px); | ||
| } | ||
|
|
||
| .ruleNumber { | ||
| width: 40px; | ||
| height: 40px; | ||
| background: linear-gradient(135deg, #9333EA, #7C3AED); | ||
| color: white; | ||
| border-radius: 50%; | ||
| display: flex; | ||
| align-items: center; | ||
| justify-content: center; | ||
| font-weight: bold; | ||
| font-size: 1.2rem; | ||
| margin: 0 auto 1rem auto; | ||
| } | ||
|
|
||
| .ruleCard h4 { | ||
| margin: 0 0 0.5rem 0; | ||
| color: #333; | ||
| font-size: 1.1rem; | ||
| } | ||
|
|
||
| .ruleCard p { | ||
| margin: 0; | ||
| color: #666; | ||
| line-height: 1.5; | ||
| } | ||
|
|
||
| .controlsInfo { | ||
| margin: 2rem 0; | ||
| background: white; | ||
| padding: 1.5rem; | ||
| border-radius: 12px; | ||
| border: 2px solid #e2e8f0; | ||
| } | ||
|
|
||
| .controlsInfo h3 { | ||
| margin: 0 0 1rem 0; | ||
| color: #333; | ||
| text-align: center; | ||
| } | ||
|
|
||
| .controlsList { | ||
| display: flex; | ||
| flex-direction: column; | ||
| gap: 0.8rem; | ||
| } | ||
|
|
||
| .controlItem { | ||
| display: flex; | ||
| justify-content: space-between; | ||
| align-items: center; | ||
| padding: 0.8rem; | ||
| background: #f8f9fa; | ||
| border-radius: 8px; | ||
| } | ||
|
|
||
| .controlKey { | ||
| font-weight: bold; | ||
| color: #9333EA; | ||
| background: #f3f0ff; | ||
| padding: 4px 8px; | ||
| border-radius: 4px; | ||
| font-size: 0.9rem; | ||
| } | ||
|
|
||
| .rulesGrid { | ||
| display: grid; | ||
| grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); | ||
| gap: 1rem; | ||
| margin: 1rem 0; | ||
| } | ||
|
|
||
| .ruleCard { | ||
| background: linear-gradient(145deg, #f8fafc, #e2e8f0); | ||
| border: 1px solid #cbd5e1; | ||
| border-radius: 12px; | ||
| padding: 1rem; | ||
| transition: all 0.3s ease; | ||
| display: flex; | ||
| align-items: flex-start; | ||
| gap: 0.75rem; | ||
| } | ||
|
|
||
| .ruleCard:hover { | ||
| transform: translateY(-2px); | ||
| box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1); | ||
| border-color: #9333EA; | ||
| } | ||
|
|
||
| .ruleNumber { | ||
| background: linear-gradient(135deg, #9333EA, #7c3aed); | ||
| color: white; | ||
| width: 24px; | ||
| height: 24px; | ||
| border-radius: 50%; | ||
| display: flex; | ||
| align-items: center; | ||
| justify-content: center; | ||
| font-weight: bold; | ||
| font-size: 0.875rem; | ||
| flex-shrink: 0; | ||
| margin-top: 2px; | ||
| } | ||
|
|
||
| .controlsInfo { | ||
| display: flex; | ||
| flex-wrap: wrap; | ||
| gap: 1rem; | ||
| margin: 1rem 0; | ||
| padding: 1rem; | ||
| background: linear-gradient(145deg, #f1f5f9, #e2e8f0); | ||
| border-radius: 12px; | ||
| border: 1px solid #cbd5e1; | ||
| } | ||
|
|
||
| .controlKey { | ||
| background: linear-gradient(145deg, #ffffff, #f1f5f9); | ||
| border: 1px solid #cbd5e1; | ||
| border-radius: 6px; | ||
| padding: 0.5rem 0.75rem; | ||
| font-family: 'Courier New', monospace; | ||
| font-weight: bold; | ||
| color: #475569; | ||
| font-size: 0.875rem; | ||
| box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); | ||
| transition: all 0.2s ease; | ||
| } | ||
|
|
||
| .controlKey:hover { | ||
| background: linear-gradient(145deg, #f1f5f9, #e2e8f0); | ||
| transform: translateY(-1px); | ||
| } | ||
|
|
||
| /* Responsive Design */ | ||
| @media (max-width: 768px) { | ||
| .container { | ||
| padding: 15px; | ||
| } | ||
|
|
||
| .title { | ||
| font-size: 2.2rem; | ||
| } | ||
|
|
||
| .subtitle { | ||
| font-size: 1.1rem; | ||
| } | ||
|
|
||
| .gameControls { | ||
| gap: 15px; | ||
| } | ||
|
|
||
| .buttonGroup { | ||
| gap: 8px; | ||
| } | ||
|
|
||
| .gameSetup { | ||
| padding: 1.5rem; | ||
| } | ||
|
|
||
| .setupTitle { | ||
| font-size: 1.5rem; | ||
| } | ||
|
|
||
| .setupButton { | ||
| padding: 12px 20px; | ||
| font-size: 0.9rem; | ||
| min-width: 100px; | ||
| } | ||
|
|
||
| .playButton { | ||
| padding: 15px 30px; | ||
| font-size: 1.1rem; | ||
| } | ||
|
|
||
| .rulesGrid { | ||
| grid-template-columns: 1fr; | ||
| gap: 1rem; | ||
| } | ||
|
|
||
| .ruleCard { | ||
| padding: 1.2rem; | ||
| } | ||
|
|
||
| .controlsInfo, | ||
| .tipsSection { | ||
| padding: 1.2rem; | ||
| } | ||
|
|
||
| .gameStats { | ||
| gap: 15px; | ||
| } | ||
|
|
||
| .stat { | ||
| padding: 12px 20px; | ||
| min-width: 80px; | ||
| } | ||
|
|
||
| .gameBoard { | ||
| gap: 15px; | ||
| padding: 15px; | ||
| max-width: 350px; | ||
| } | ||
|
|
||
| .grid4 { | ||
| max-width: 280px; | ||
| } | ||
|
|
||
| .grid7 { | ||
| max-width: 320px; | ||
| } | ||
|
|
||
| .grid10 { | ||
| max-width: 350px; | ||
| } | ||
|
|
||
| .gameButton { | ||
| min-height: 80px; | ||
| font-size: 0.9rem; | ||
| } | ||
|
|
||
| .buttonLabel { | ||
| font-size: 0.9rem; | ||
| } | ||
|
|
||
|
|
||
|
|
||
| .startButton { | ||
| padding: 12px 30px; | ||
| font-size: 1.1rem; | ||
| } | ||
|
|
||
| .sectionTitle { | ||
| font-size: 1.4rem; | ||
| } | ||
|
|
||
|
|
||
| } | ||
|
|
||
| @media (max-width: 480px) { | ||
| .gameBoard { | ||
| gap: 10px; | ||
| max-width: 300px; | ||
| } | ||
|
|
||
| .grid4 { | ||
| max-width: 240px; | ||
| } | ||
|
|
||
| .grid7 { | ||
| max-width: 280px; | ||
| } | ||
|
|
||
| .grid10 { | ||
| max-width: 300px; | ||
| } | ||
|
|
||
| .gameButton { | ||
| min-height: 70px; | ||
| border-radius: 15px; | ||
| } | ||
|
|
||
| .buttonLabel { | ||
| font-size: 0.8rem; | ||
| } | ||
|
|
||
|
|
||
|
|
||
| .rulesContainer { | ||
| padding: 0 0.5rem; | ||
| } | ||
|
|
||
| .sectionTitle { | ||
| font-size: 1.25rem; | ||
| } | ||
|
|
||
| .rulesGrid { | ||
| grid-template-columns: 1fr; | ||
| gap: 0.75rem; | ||
| } | ||
|
|
||
| .ruleCard { | ||
| padding: 0.75rem; | ||
| } | ||
|
|
||
| .ruleNumber { | ||
| width: 20px; | ||
| height: 20px; | ||
| font-size: 0.75rem; | ||
| margin-right: 0.5rem; | ||
| } | ||
|
|
||
| .controlsInfo { | ||
| gap: 0.75rem; | ||
| } | ||
|
|
||
| .controlKey { | ||
| padding: 0.25rem 0.5rem; | ||
| font-size: 0.75rem; | ||
| } | ||
|
|
||
| } No newline at end of file |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Consider reducing CSS duplication
I noticed quite a bit of CSS duplication, both within this file and between this file and play/page.module.css. For example:
Within this file:
.rulesGrid,.ruleCard,.ruleNumber,.controlsInfo, and.controlKeyare defined multiple times (lines 335-380, 393-438, 478-545). This creates three nearly identical rule card sections!
Between this file and play/page.module.css:
- Common layout styles like
.container,.header,.title,.gameStats,.stat,.gameBoard,.grid4/.grid7/.grid10,.gameButton,.startButton, and.keyboardInfoappear in both files with very similar (or identical) rules.
This duplication makes the code harder to maintain—if you need to change a color or spacing, you'd have to update it in multiple places.
Here are a few ways to fix this:
- Extract shared styles to a common CSS module (e.g.,
simon-says-shared.module.css) and import it in both landing and play pages. - Use CSS variables for repeated values (colors, spacing, etc.) to ensure consistency.
- Remove the duplicate rule blocks within this file—you only need one definition of
.rulesGrid,.ruleCard, etc.
Cleaning this up will reduce your bundle size, improve maintainability, and follow the DRY (Don't Repeat Yourself) principle. Would you like me to help you identify which styles to extract or merge?
🤖 Prompt for AI Agents
In src/app/game/simon-says/page.module.css (lines 1-713) there are multiple
duplicated blocks for .rulesGrid, .ruleCard, .ruleNumber, .controlsInfo and
.controlKey within this file and many shared layout rules duplicated across
play/page.module.css; remove the intra-file duplicates by keeping a single
canonical definition for each repeated selector (merge the variants into one
consistent rule set), extract truly shared layout/visual styles (e.g.,
.container, .header, .title, .gameStats, .stat, .gameBoard,
.grid4/.grid7/.grid10, .gameButton, .startButton, .keyboardInfo) into a new
shared CSS module (e.g., simon-says-shared.module.css) and import it from both
pages, and introduce CSS variables for repeated values (colors, spacing, radii)
so the merged rules reference variables rather than hard-coded values.
| <button | ||
| className={`${styles.setupButton} ${difficulty === 'normal' ? styles.active : ''}`} | ||
| onClick={() => setDifficulty('normal')} | ||
| > | ||
| Normal | ||
| <span className={styles.modeDescription}>Retry on mistake</span> | ||
| </button> | ||
| <button | ||
| className={`${styles.setupButton} ${difficulty === 'strict' ? styles.active : ''}`} | ||
| onClick={() => setDifficulty('strict')} | ||
| > | ||
| Strict | ||
| <span className={styles.modeDescription}>Game over on mistake</span> | ||
| </button> | ||
| </div> | ||
| </div> | ||
|
|
||
| <div className={styles.setupSection}> | ||
| <h3>Difficulty Level</h3> | ||
| <div className={styles.buttonGroup}> | ||
| <button | ||
| className={`${styles.setupButton} ${buttonCount === 4 ? styles.active : ''}`} | ||
| onClick={() => setButtonCount(4)} | ||
| > | ||
| Easy | ||
| <span className={styles.modeDescription}>4 Buttons</span> | ||
| </button> | ||
| <button | ||
| className={`${styles.setupButton} ${buttonCount === 7 ? styles.active : ''}`} | ||
| onClick={() => setButtonCount(7)} | ||
| > | ||
| Medium | ||
| <span className={styles.modeDescription}>7 Buttons</span> | ||
| </button> | ||
| <button | ||
| className={`${styles.setupButton} ${buttonCount === 10 ? styles.active : ''}`} | ||
| onClick={() => setButtonCount(10)} | ||
| > | ||
| Hard | ||
| <span className={styles.modeDescription}>10 Buttons</span> | ||
| </button> | ||
| </div> | ||
| </div> | ||
|
|
||
| <button className={styles.playButton} onClick={startGame}> | ||
| Start Playing | ||
| </button> |
There was a problem hiding this comment.
Add type="button" to all buttons
Right now, all your buttons are missing the type="button" prop. By default, buttons have type="submit", which can cause unexpected behavior if they're ever inside a form. Even though there's no form here, it's a good practice to always specify type="button" for non-submit buttons.
Here's how to fix it—just add type="button" to each button:
<button
+ type="button"
className={`${styles.setupButton} ${difficulty === 'normal' ? styles.active : ''}`}
onClick={() => setDifficulty('normal')}
>Apply this same change to all the other buttons in this file (lines 41, 54, 61, 68, and 78). This will make your code more robust and follow best practices!
As per static analysis hints.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <button | |
| className={`${styles.setupButton} ${difficulty === 'normal' ? styles.active : ''}`} | |
| onClick={() => setDifficulty('normal')} | |
| > | |
| Normal | |
| <span className={styles.modeDescription}>Retry on mistake</span> | |
| </button> | |
| <button | |
| className={`${styles.setupButton} ${difficulty === 'strict' ? styles.active : ''}`} | |
| onClick={() => setDifficulty('strict')} | |
| > | |
| Strict | |
| <span className={styles.modeDescription}>Game over on mistake</span> | |
| </button> | |
| </div> | |
| </div> | |
| <div className={styles.setupSection}> | |
| <h3>Difficulty Level</h3> | |
| <div className={styles.buttonGroup}> | |
| <button | |
| className={`${styles.setupButton} ${buttonCount === 4 ? styles.active : ''}`} | |
| onClick={() => setButtonCount(4)} | |
| > | |
| Easy | |
| <span className={styles.modeDescription}>4 Buttons</span> | |
| </button> | |
| <button | |
| className={`${styles.setupButton} ${buttonCount === 7 ? styles.active : ''}`} | |
| onClick={() => setButtonCount(7)} | |
| > | |
| Medium | |
| <span className={styles.modeDescription}>7 Buttons</span> | |
| </button> | |
| <button | |
| className={`${styles.setupButton} ${buttonCount === 10 ? styles.active : ''}`} | |
| onClick={() => setButtonCount(10)} | |
| > | |
| Hard | |
| <span className={styles.modeDescription}>10 Buttons</span> | |
| </button> | |
| </div> | |
| </div> | |
| <button className={styles.playButton} onClick={startGame}> | |
| Start Playing | |
| </button> | |
| <button | |
| type="button" | |
| className={`${styles.setupButton} ${difficulty === 'normal' ? styles.active : ''}`} | |
| onClick={() => setDifficulty('normal')} | |
| > | |
| Normal | |
| <span className={styles.modeDescription}>Retry on mistake</span> | |
| </button> |
🧰 Tools
🪛 Biome (2.1.2)
[error] 34-37: Provide an explicit type prop for the button element.
The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset
(lint/a11y/useButtonType)
[error] 41-44: Provide an explicit type prop for the button element.
The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset
(lint/a11y/useButtonType)
[error] 54-57: Provide an explicit type prop for the button element.
The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset
(lint/a11y/useButtonType)
[error] 61-64: Provide an explicit type prop for the button element.
The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset
(lint/a11y/useButtonType)
[error] 68-71: Provide an explicit type prop for the button element.
The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset
(lint/a11y/useButtonType)
[error] 78-78: Provide an explicit type prop for the button element.
The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset
(lint/a11y/useButtonType)
🤖 Prompt for AI Agents
In src/app/game/simon-says/page.tsx around lines 34 to 80, all button elements
are missing an explicit type attribute which defaults to "submit" and can cause
unintended form submissions; add type="button" to each non-submit button on the
listed lines (buttons at ~41, 54, 61, 68, and 78) and any other buttons in this
file to ensure they behave as plain clickable controls.
| <button className={styles.backButton} onClick={goBack}> | ||
| ← Back to Setup | ||
| </button> |
There was a problem hiding this comment.
Add type="button" to all buttons
Just like in the landing page, all your buttons here are missing the type="button" prop. This can cause unexpected behavior if they're ever inside a form. It's a simple fix—just add type="button" to each button:
For the back button (line 391):
<button
+ type="button"
className={styles.backButton}
onClick={goBack}
>For the game buttons (lines 438-455):
<button
+ type="button"
key={button.id}
className={`${styles.gameButton} ${For the start button (lines 460-467):
<button
+ type="button"
className={styles.startButton}
onClick={startNewGame}Adding type="button" to all three makes your code more robust and follows best practices!
As per static analysis hints.
Also applies to: 438-455, 460-467
🧰 Tools
🪛 Biome (2.1.2)
[error] 391-391: Provide an explicit type prop for the button element.
The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset
(lint/a11y/useButtonType)
🤖 Prompt for AI Agents
In src/app/game/simon-says/play/page.tsx around lines 391, 438-455, and 460-467,
all button elements are missing an explicit type which can cause them to act as
form submit buttons; update each <button> (the back button at ~391, every game
button in the block ~438-455, and the start button in ~460-467) to include
type="button" in their JSX attributes so they do not trigger form submission and
follow best practices.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
- Improve centering of rules grid on mobile screens - Remove duplicate CSS rules in media queries - Enhance responsive layout for better card alignment - Ensure proper width and spacing on small screens
- Add text-align: center to rulesContainer on mobile screens - Keep individual rule card text left-aligned within centered cards - This fixes the alignment issue where cards appeared left-aligned on small screens
- Replace grid with flex column for deterministic alignment - Introduce .ruleText wrapper for consistent spacing - Remove duplicate .rulesGrid definitions & conflicts - Add single-source responsive overrides (<=480px) - Ensure left text alignment inside centered container
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (3)
src/app/game/simon-says/page.tsx (1)
34-80: Please give every setup button an explicit typeRight now each of these buttons is missing
type="button". Browsers default totype="submit", so if this page ever sits inside a form, a simple click could submit the form by surprise. Let’s prevent that now.Here’s a diff showing the first few updates—repeat the same change for the remaining buttons, including “Start Playing”:
<button + type="button" className={`${styles.setupButton} ${difficulty === 'normal' ? styles.active : ''}`} onClick={() => setDifficulty('normal')} > @@ <button + type="button" className={`${styles.setupButton} ${difficulty === 'strict' ? styles.active : ''}`} onClick={() => setDifficulty('strict')} > @@ <button + type="button" className={styles.playButton} onClick={startGame} >Adding the type keeps each control behaving like a normal clickable button everywhere. 😊
As per static analysis hints.src/app/game/simon-says/play/page.tsx (1)
397-472: Addtype="button"to every game controlJust like on the landing page, these
<button>elements default totype="submit". If we ever wrap this play view in a form (or copy the component elsewhere), a simple click could trigger an unexpected submit.Let’s add an explicit type to each one:
- <button className={styles.backButton} onClick={goBack}> + <button type="button" className={styles.backButton} onClick={goBack}> @@ - <button + <button + type="button" key={button.id} className={`${styles.gameButton} ${ activeButton === button.id ? styles.active : '' }`} @@ - <button + <button + type="button" className={styles.startButton} onClick={startNewGame} disabled={gameState.gameStatus === 'showing'} >This tiny addition keeps every control predictable and prevents accidental form submissions down the road. Nicely done on the rest of the interactivity! 🌈
As per static analysis hints.src/app/game/simon-says/page.module.css (1)
335-840: Let’s merge the repeated rule/control stylesI spotted the same selectors repeated three times (
.rulesGrid,.ruleCard,.ruleNumber,.controlsInfo,.controlKey, etc.). Each repetition tweaks tiny details, so the browser ends up applying whichever block comes last. That makes the final look hard to predict and painful to maintain—changing one color means hunting through the whole file.A gentle path forward:
- Keep a single definition for each selector in this module.
- If you need variations, split them into modifier classes (e.g.,
.ruleCard,.ruleCardHighlight).- Move anything shared with the play page into a small shared CSS module or define CSS variables so both files use the same tokens.
This tidy-up will shrink the file, silence the duplicate-property lint warning, and make future edits way easier. I’m happy to help outline the shared pieces if you’d like! 😊
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (4)
src/app/game/simon-says/page.module.css(1 hunks)src/app/game/simon-says/page.tsx(1 hunks)src/app/game/simon-says/play/page.module.css(1 hunks)src/app/game/simon-says/play/page.tsx(1 hunks)
🧰 Additional context used
🪛 Biome (2.1.2)
src/app/game/simon-says/page.module.css
[error] 616-616: Duplicate properties can lead to unexpected behavior and may override previous declarations unintentionally.
text-align is already defined here.
Remove or rename the duplicate property to ensure consistent styling.
(lint/suspicious/noDuplicateProperties)
src/app/game/simon-says/play/page.tsx
[error] 65-65: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 66-66: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 68-68: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 78-78: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 79-79: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 80-80: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 81-81: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 82-82: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 83-83: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 85-85: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 86-86: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 91-91: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 96-96: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 116-116: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 124-124: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 148-148: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 164-164: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 178-178: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 184-184: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 199-199: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 234-234: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 296-296: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 343-343: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 350-350: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 397-397: Provide an explicit type prop for the button element.
The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset
(lint/a11y/useButtonType)
[error] 444-459: Provide an explicit type prop for the button element.
The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset
(lint/a11y/useButtonType)
[error] 466-471: Provide an explicit type prop for the button element.
The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset
(lint/a11y/useButtonType)
src/app/game/simon-says/page.tsx
[error] 34-37: Provide an explicit type prop for the button element.
The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset
(lint/a11y/useButtonType)
[error] 41-44: Provide an explicit type prop for the button element.
The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset
(lint/a11y/useButtonType)
[error] 54-57: Provide an explicit type prop for the button element.
The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset
(lint/a11y/useButtonType)
[error] 61-64: Provide an explicit type prop for the button element.
The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset
(lint/a11y/useButtonType)
[error] 68-71: Provide an explicit type prop for the button element.
The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset
(lint/a11y/useButtonType)
[error] 78-78: Provide an explicit type prop for the button element.
The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset
(lint/a11y/useButtonType)
…ound - Introduce timeoutsRef array + clearScheduledTimeouts helper - Clear scheduled timeouts on new game, new sequence, unmount - Track both highlight and reset timeouts plus player turn transition - Save best score immediately after each successful round - Prevent potential state updates after unmount
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
src/app/game/simon-says/play/page.tsx (1)
412-486: Let’s addtype="button"to these controls.Quick reminder from the earlier review: the back button, every game pad button, and the start/new-game button still need
type="button"so they never act like form submits by accident. It’s just a tiny prop on each<button>and keeps behavior super predictable.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (1)
src/app/game/simon-says/play/page.tsx(1 hunks)
🧰 Additional context used
🪛 Biome (2.1.2)
src/app/game/simon-says/play/page.tsx
[error] 65-65: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 66-66: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 68-68: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 78-78: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 79-79: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 80-80: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 81-81: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 82-82: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 83-83: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 85-85: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 87-87: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 89-89: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 99-99: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 104-104: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 124-124: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 132-132: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 156-156: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 172-172: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 186-186: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 192-192: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 208-208: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 249-249: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 313-313: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 360-360: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 367-367: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 412-412: Provide an explicit type prop for the button element.
The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset
(lint/a11y/useButtonType)
[error] 459-474: Provide an explicit type prop for the button element.
The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset
(lint/a11y/useButtonType)
[error] 481-486: Provide an explicit type prop for the button element.
The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset
(lint/a11y/useButtonType)
| const SimonSaysGame = () => { | ||
| // Early return for SSR | ||
| if (typeof window === 'undefined') { | ||
| return null; | ||
| } | ||
|
|
||
| const searchParams = useSearchParams(); | ||
| const router = useRouter(); | ||
|
|
||
| const [gameState, setGameState] = useState<GameState>({ | ||
| sequence: [], | ||
| playerSequence: [], | ||
| currentRound: 0, | ||
| isShowingSequence: false, | ||
| isPlayerTurn: false, | ||
| gameStatus: 'idle', | ||
| score: 0 | ||
| }); | ||
|
|
||
| const [difficulty, setDifficulty] = useState<Difficulty>('normal'); | ||
| const [buttonCount, setButtonCount] = useState<ButtonCount>(4); | ||
| const [bestScore, setBestScore] = useState<number | null>(null); | ||
| const [activeButton, setActiveButton] = useState<number | null>(null); | ||
| const [soundEnabled, setSoundEnabled] = useState(true); | ||
| const [mounted, setMounted] = useState(false); | ||
|
|
||
| const audioContextRef = useRef<AudioContext | null>(null); | ||
| // Track ALL scheduled timeouts so we can clear them on unmount / restart | ||
| const timeoutsRef = useRef<Array<ReturnType<typeof setTimeout>>>([]); | ||
|
|
||
| const clearScheduledTimeouts = useCallback(() => { | ||
| if (timeoutsRef.current.length) { | ||
| timeoutsRef.current.forEach(id => clearTimeout(id)); | ||
| timeoutsRef.current = []; | ||
| } | ||
| }, []); | ||
|
|
||
| const buttons = BUTTON_CONFIGS[buttonCount]; | ||
|
|
||
| // Handle client-side mounting | ||
| useEffect(() => { | ||
| setMounted(true); | ||
| }, []); | ||
|
|
||
| // Get URL parameters on component mount | ||
| useEffect(() => { | ||
| if (!mounted) return; | ||
|
|
||
| try { |
There was a problem hiding this comment.
Let’s move the window guard so hooks always run in the same order.
Right now we return early whenever window is missing, which means none of the hooks that follow will run in that render. React expects hooks to run every time in exactly the same order; skipping them can lead to mismatched state and the lint rule is catching that. A gentle fix is to remove the early return and instead gate your UI with the existing mounted flag (or add an isClient state set inside useEffect). That way each render still calls every hook, and you can still show null or a loading shell until the client is ready. Happy to walk through the refactor if you’d like!
🧰 Tools
🪛 Biome (2.1.2)
[error] 65-65: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 66-66: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 68-68: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 78-78: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 79-79: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 80-80: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 81-81: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 82-82: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 83-83: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 85-85: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 87-87: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 89-89: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 99-99: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 104-104: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
🤖 Prompt for AI Agents
In src/app/game/simon-says/play/page.tsx around lines 59 to 107, remove the
early return that checks typeof window === 'undefined' so all hooks always run
in the same order; instead rely on the existing mounted state (set to true in
the mounting useEffect) or add an isClient state set inside a useEffect, and
guard rendering/UI that depend on window or browser-only APIs with that
mounted/isClient flag (and ensure any direct window/audioContext usage is
wrapped behind the flag) so hooks are never conditionally skipped while still
rendering null or a loading placeholder until the client is ready.
- Remove early SSR return so hooks always execute in consistent order - Rely on mounted flag for gating UI instead of skipping hooks - Track 300ms activeButton release timeout in timeoutsRef for full cleanup




Resolves #105
This will be an official contribution for GSSOC.