···11<script lang="ts">
22- import { isTyping } from '$lib/helper';
32 import type { ContentComponentProps } from '../../types';
43 import { onMount, onDestroy } from 'svelte';
5465 let { item }: ContentComponentProps = $props();
7687 let canvas: HTMLCanvasElement;
88+ let container: HTMLDivElement;
99 let ctx: CanvasRenderingContext2D | null = null;
1010 let animationId: number;
1111···181181 function startGame() {
182182 resetGame();
183183 gameState = 'playing';
184184+ // Focus container so keyboard events work for this game
185185+ container?.focus();
184186 }
185187186188 function jump() {
···207209 }
208210 }
209211212212+ // Handle keyboard input (only responds when this game container is focused)
210213 function handleKeyDown(e: KeyboardEvent) {
211211- if(isTyping()) return;
212212-213214 if (e.code === 'Space' || e.code === 'ArrowUp' || e.code === 'KeyW') {
214215 e.preventDefault();
215216 jump();
···530531 });
531532</script>
532533533533-<svelte:window onkeydown={handleKeyDown} onkeyup={handleKeyUp} />
534534-535535-<div class="relative h-full w-full overflow-hidden">
536536- <canvas bind:this={canvas} class="h-full w-full invert dark:invert-0" ontouchstart={handleTouch}
534534+<!-- svelte-ignore a11y_no_noninteractive_tabindex a11y_no_noninteractive_element_interactions -->
535535+<div
536536+ bind:this={container}
537537+ class="relative h-full w-full overflow-hidden outline-none"
538538+ tabindex="0"
539539+ role="application"
540540+ aria-label="Dino game"
541541+ onkeydown={handleKeyDown}
542542+ onkeyup={handleKeyUp}
543543+>
544544+ <canvas
545545+ bind:this={canvas}
546546+ class="h-full w-full touch-none select-none invert dark:invert-0"
547547+ ontouchstart={handleTouch}
537548 ></canvas>
538549539550 {#if gameState === 'idle' || gameState === 'gameover'}
+95
src/lib/cards/GameCards/README.md
···11+# Game Cards
22+33+This folder contains interactive game cards (Tetris, Dino, etc.).
44+55+## Implementation Requirements
66+77+When creating a new game card, follow these patterns to ensure multiple games on the same page work independently.
88+99+### 1. Container Setup
1010+1111+Make the game container focusable and handle keyboard events on it (not on `svelte:window`):
1212+1313+```svelte
1414+<script lang="ts">
1515+ let container: HTMLDivElement;
1616+</script>
1717+1818+<!-- svelte-ignore a11y_no_noninteractive_tabindex a11y_no_noninteractive_element_interactions -->
1919+<div
2020+ bind:this={container}
2121+ class="relative h-full w-full overflow-hidden outline-none"
2222+ tabindex="0"
2323+ role="application"
2424+ aria-label="Your game name"
2525+ onkeydown={handleKeyDown}
2626+ onkeyup={handleKeyUp}
2727+>
2828+ <!-- game content -->
2929+</div>
3030+```
3131+3232+### 2. Focus on Game Start
3333+3434+When the game starts, focus the container so keyboard events work:
3535+3636+```typescript
3737+function startGame() {
3838+ // ... game initialization
3939+ container?.focus();
4040+}
4141+```
4242+4343+### 3. Keyboard Handlers
4444+4545+Do NOT use `<svelte:window onkeydown={...} />` - this captures all keyboard events globally and causes all games to respond at once.
4646+4747+Instead, attach handlers directly to the container div. The handlers will only fire when that specific game is focused.
4848+4949+```typescript
5050+function handleKeyDown(e: KeyboardEvent) {
5151+ if (e.code === 'Space') {
5252+ e.preventDefault();
5353+ // handle action
5454+ }
5555+}
5656+```
5757+5858+### 4. Canvas Setup
5959+6060+Add `touch-none` and `select-none` classes to prevent scrolling and text selection during gameplay:
6161+6262+```svelte
6363+<canvas
6464+ bind:this={canvas}
6565+ class="h-full w-full touch-none select-none"
6666+ ontouchstart={handleTouchStart}
6767+ ontouchmove={handleTouchMove}
6868+ ontouchend={handleTouchEnd}
6969+></canvas>
7070+```
7171+7272+### 5. Touch Controls
7373+7474+For mobile support, add touch event handlers to the canvas. Prevent default to stop page scrolling:
7575+7676+```typescript
7777+function handleTouchStart(e: TouchEvent) {
7878+ if (gameState === 'playing') {
7979+ e.preventDefault();
8080+ }
8181+ // handle touch
8282+}
8383+```
8484+8585+## Checklist for New Game Cards
8686+8787+- [ ] Container has `bind:this={container}` reference
8888+- [ ] Container has `tabindex="0"` for focusability
8989+- [ ] Container has `role="application"` and `aria-label`
9090+- [ ] Container has `outline-none` class
9191+- [ ] Keyboard handlers are on container, NOT `svelte:window`
9292+- [ ] `startGame()` calls `container?.focus()`
9393+- [ ] Canvas has `touch-none select-none` classes
9494+- [ ] Touch handlers call `e.preventDefault()` when game is active
9595+- [ ] No `isTyping()` check needed (focus handles this automatically)