From 9f51edfaa95c14f55a30a6eceafb9099eeed437f Mon Sep 17 00:00:00 2001 From: Thomas Hallock Date: Mon, 3 Nov 2025 09:25:44 -0600 Subject: [PATCH] feat(games): add autoplay and improve carousel layout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Major improvements to the games hero carousel: - Added smooth autoplay (4s delay, stops on interaction/hover) - Made carousel full-width to reduce virtual scrolling artifacts - Added horizontal padding for better buffer on edges - Restructured layout: carousel is now full-width, rest of content remains constrained to max-width - Removed containScroll setting to improve infinite loop behavior The carousel now smoothly rotates through games automatically and has better visual consistency at the loop edges. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- apps/web/src/app/games/page.tsx | 495 ++++++++++++++++---------------- 1 file changed, 251 insertions(+), 244 deletions(-) diff --git a/apps/web/src/app/games/page.tsx b/apps/web/src/app/games/page.tsx index 58607ff4..f3a54f79 100644 --- a/apps/web/src/app/games/page.tsx +++ b/apps/web/src/app/games/page.tsx @@ -1,5 +1,6 @@ 'use client' +import Autoplay from 'embla-carousel-autoplay' import useEmblaCarousel from 'embla-carousel-react' import Link from 'next/link' import { useRouter } from 'next/navigation' @@ -32,12 +33,16 @@ function GamesPageContent() { // Check if user has any stats to show const hasStats = profile.gamesPlayed > 0 - // Embla carousel setup for games hero carousel - const [gamesEmblaRef, gamesEmblaApi] = useEmblaCarousel({ - loop: true, - align: 'center', - containScroll: 'trimSnaps', - }) + // Embla carousel setup for games hero carousel with autoplay + const [gamesEmblaRef, gamesEmblaApi] = useEmblaCarousel( + { + loop: true, + align: 'center', + slidesToScroll: 1, + skipSnaps: false, + }, + [Autoplay({ delay: 4000, stopOnInteraction: true, stopOnMouseEnter: true })] + ) const [gamesSelectedIndex, setGamesSelectedIndex] = useState(0) // Embla carousel setup for player carousel @@ -108,252 +113,254 @@ function GamesPageContent() { })} /> + {/* Games Hero Carousel - Full Width */} +
+

+ 🎮 Available Games +

+ + {/* Carousel */} +
+
+ {availableGames.map((game) => { + const gameIndex = availableGames.indexOf(game) + const isActive = gameIndex === gamesSelectedIndex + + return ( +
+ +
+ {/* Dark gradient overlay for readability */} +
+ + {/* Content */} +
+ {/* Icon and Title */} +
+
+ {game.manifest.icon} +
+
+

+ {game.manifest.displayName} +

+

+ {game.manifest.difficulty} •{' '} + {game.manifest.maxPlayers === 1 + ? 'Solo' + : `1-${game.manifest.maxPlayers} Players`} +

+
+
+ + {/* Description */} +

+ {game.manifest.description} +

+ + {/* Chips */} +
+ {game.manifest.chips.map((chip) => ( + + {chip} + + ))} +
+
+
+ +
+ ) + })} +
+
+ + {/* Navigation Dots */} +
+ {availableGames.map((game, index) => ( + + ))} +
+
+ + {/* Rest of content - constrained width */}
- {/* Games Hero Carousel */} -
-

- 🎮 Available Games -

- - {/* Carousel */} -
-
- {availableGames.map((game) => { - const gameIndex = availableGames.indexOf(game) - const isActive = gameIndex === gamesSelectedIndex - - return ( -
- -
- {/* Dark gradient overlay for readability */} -
- - {/* Content */} -
- {/* Icon and Title */} -
-
- {game.manifest.icon} -
-
-

- {game.manifest.displayName} -

-

- {game.manifest.difficulty} •{' '} - {game.manifest.maxPlayers === 1 - ? 'Solo' - : `1-${game.manifest.maxPlayers} Players`} -

-
-
- - {/* Description */} -

- {game.manifest.description} -

- - {/* Chips */} -
- {game.manifest.chips.map((chip) => ( - - {chip} - - ))} -
-
-
- -
- ) - })} -
-
- - {/* Navigation Dots */} -
- {availableGames.map((game, index) => ( - - ))} -
-
- {/* Enter Arcade Button */}