···1-# Game Cards
2-3-This folder contains interactive game cards (Tetris, Dino, etc.).
4-5-## Implementation Requirements
6-7-When creating a new game card, follow these patterns to ensure multiple games on the same page work independently.
8-9-### 1. Container Setup
10-11-Make the game container focusable and handle keyboard events on it (not on `svelte:window`):
12-13-```svelte
14-<script lang="ts">
15- let container: HTMLDivElement;
16-</script>
17-18-<!-- svelte-ignore a11y_no_noninteractive_tabindex a11y_no_noninteractive_element_interactions -->
19-<div
20- bind:this={container}
21- class="relative h-full w-full overflow-hidden outline-none"
22- tabindex="0"
23- role="application"
24- aria-label="Your game name"
25- onkeydown={handleKeyDown}
26- onkeyup={handleKeyUp}
27->
28- <!-- game content -->
29-</div>
30-```
31-32-### 2. Focus on Game Start
33-34-When the game starts, focus the container so keyboard events work:
35-36-```typescript
37-function startGame() {
38- // ... game initialization
39- container?.focus();
40-}
41-```
42-43-### 3. Keyboard Handlers
44-45-Do NOT use `<svelte:window onkeydown={...} />` - this captures all keyboard events globally and causes all games to respond at once.
46-47-Instead, attach handlers directly to the container div. The handlers will only fire when that specific game is focused.
48-49-```typescript
50-function handleKeyDown(e: KeyboardEvent) {
51- if (e.code === 'Space') {
52- e.preventDefault();
53- // handle action
54- }
55-}
56-```
57-58-### 4. Canvas Setup
59-60-Add `touch-none` and `select-none` classes to prevent scrolling and text selection during gameplay:
61-62-```svelte
63-<canvas
64- bind:this={canvas}
65- class="h-full w-full touch-none select-none"
66- ontouchstart={handleTouchStart}
67- ontouchmove={handleTouchMove}
68- ontouchend={handleTouchEnd}
69-></canvas>
70-```
71-72-### 5. Touch Controls
73-74-For mobile support, add touch event handlers to the canvas. Prevent default to stop page scrolling:
75-76-```typescript
77-function handleTouchStart(e: TouchEvent) {
78- if (gameState === 'playing') {
79- e.preventDefault();
80- }
81- // handle touch
82-}
83-```
84-85-## Checklist for New Game Cards
86-87-- [ ] Container has `bind:this={container}` reference
88-- [ ] Container has `tabindex="0"` for focusability
89-- [ ] Container has `role="application"` and `aria-label`
90-- [ ] Container has `outline-none` class
91-- [ ] Keyboard handlers are on container, NOT `svelte:window`
92-- [ ] `startGame()` calls `container?.focus()`
93-- [ ] Canvas has `touch-none select-none` classes
94-- [ ] Touch handlers call `e.preventDefault()` when game is active
95-- [ ] No `isTyping()` check needed (focus handles this automatically)
···1import { checkAndUploadImage } from '$lib/helper';
2-import type { CardDefinition } from '../types';
3import ImageCard from './ImageCard.svelte';
4import ImageCardSettings from './ImageCardSettings.svelte';
5
···1import { checkAndUploadImage } from '$lib/helper';
2+import type { CardDefinition } from '../../types';
3import ImageCard from './ImageCard.svelte';
4import ImageCardSettings from './ImageCardSettings.svelte';
5
···1import { checkAndUploadImage, validateLink } from '$lib/helper';
2-import type { CardDefinition } from '../types';
3import CreateLinkCardModal from './CreateLinkCardModal.svelte';
4import EditingLinkCard from './EditingLinkCard.svelte';
5import LinkCard from './LinkCard.svelte';
···1import { checkAndUploadImage, validateLink } from '$lib/helper';
2+import type { CardDefinition } from '../../types';
3import CreateLinkCardModal from './CreateLinkCardModal.svelte';
4import EditingLinkCard from './EditingLinkCard.svelte';
5import LinkCard from './LinkCard.svelte';
···1-import type { CardDefinition } from '../types';
2import CreateMapCardModal from './CreateMapCardModal.svelte';
3import MapCard from './MapCard.svelte';
4import MapCardSettings from './MapCardSettings.svelte';
···1+import type { CardDefinition } from '../../types';
2import CreateMapCardModal from './CreateMapCardModal.svelte';
3import MapCard from './MapCard.svelte';
4import MapCardSettings from './MapCardSettings.svelte';
···1import { COLUMNS } from '$lib';
2-import type { CardDefinition } from '../types';
3import EditingSectionCard from './EditingSectionCard.svelte';
4import SectionCard from './SectionCard.svelte';
5import SectionCardSettings from './SectionCardSettings.svelte';
···1import { COLUMNS } from '$lib';
2+import type { CardDefinition } from '../../types';
3import EditingSectionCard from './EditingSectionCard.svelte';
4import SectionCard from './SectionCard.svelte';
5import SectionCardSettings from './SectionCardSettings.svelte';
···1-import type { CardDefinition } from '../types';
2import EditingTextCard from './EditingTextCard.svelte';
3import TextCard from './TextCard.svelte';
4import TextCardSettings from './TextCardSettings.svelte';
···1+import type { CardDefinition } from '../../types';
2import EditingTextCard from './EditingTextCard.svelte';
3import TextCard from './TextCard.svelte';
4import TextCardSettings from './TextCardSettings.svelte';
···1-import type { CardDefinition } from '../types';
2import CreateAppleMusicCardModal from './CreateAppleMusicCardModal.svelte';
3import AppleMusicCard from './AppleMusicCard.svelte';
4
···1+import type { CardDefinition } from '../../types';
2import CreateAppleMusicCardModal from './CreateAppleMusicCardModal.svelte';
3import AppleMusicCard from './AppleMusicCard.svelte';
4
···1<script lang="ts">
2- import type { ContentComponentProps } from '../types';
3 import Video from './Video.svelte';
45 let { item = $bindable() }: ContentComponentProps = $props();
···1<script lang="ts">
2+ import type { ContentComponentProps } from '../../types';
3 import Video from './Video.svelte';
45 let { item = $bindable() }: ContentComponentProps = $props();
···1-import type { CardDefinition } from '../types';
2import BlueskyMediaCard from './BlueskyMediaCard.svelte';
3import CreateBlueskyMediaCardModal from './CreateBlueskyMediaCardModal.svelte';
4
···1+import type { CardDefinition } from '../../types';
2import BlueskyMediaCard from './BlueskyMediaCard.svelte';
3import CreateBlueskyMediaCardModal from './CreateBlueskyMediaCardModal.svelte';
4
···1-import type { CardDefinition } from '../types';
2import CreateEmbedCardModal from './CreateEmbedCardModal.svelte';
3import EmbedCard from './EmbedCard.svelte';
4
···1+import type { CardDefinition } from '../../types';
2import CreateEmbedCardModal from './CreateEmbedCardModal.svelte';
3import EmbedCard from './EmbedCard.svelte';
4
···1-import type { CardDefinition } from '../types';
2import CreateGifCardModal from './CreateGifCardModal.svelte';
3import EditingGifCard from './EditingGifCard.svelte';
4import GifCard from './GifCard.svelte';
···1+import type { CardDefinition } from '../../types';
2import CreateGifCardModal from './CreateGifCardModal.svelte';
3import EditingGifCard from './EditingGifCard.svelte';
4import GifCard from './GifCard.svelte';
···1import { user, listRecords, getCDNImageBlobUrl } from '$lib/atproto';
2-import type { CardDefinition } from '../types';
3import LivestreamCard from './LivestreamCard.svelte';
4import LivestreamEmbedCard from './LivestreamEmbedCard.svelte';
5
···1import { user, listRecords, getCDNImageBlobUrl } from '$lib/atproto';
2+import type { CardDefinition } from '../../types';
3import LivestreamCard from './LivestreamCard.svelte';
4import LivestreamEmbedCard from './LivestreamEmbedCard.svelte';
5
···1-import type { CardDefinition } from '../types';
2import { getRecord, listRecords, parseUri } from '$lib/atproto';
3import PhotoGalleryCard from './PhotoGalleryCard.svelte';
4import type { Did } from '@atcute/lexicons';
···1+import type { CardDefinition } from '../../types';
2import { getRecord, listRecords, parseUri } from '$lib/atproto';
3import PhotoGalleryCard from './PhotoGalleryCard.svelte';
4import type { Did } from '@atcute/lexicons';
···1-import type { CardDefinition } from '../types';
2import { listRecords } from '$lib/atproto';
3import PopfeedReviewsCard from './PopfeedReviewsCard.svelte';
4
···1+import type { CardDefinition } from '../../types';
2import { listRecords } from '$lib/atproto';
3import PopfeedReviewsCard from './PopfeedReviewsCard.svelte';
4
···1-import type { CardDefinition } from '../types';
2import CreateSpotifyCardModal from './CreateSpotifyCardModal.svelte';
3import SpotifyCard from './SpotifyCard.svelte';
4
···1+import type { CardDefinition } from '../../types';
2import CreateSpotifyCardModal from './CreateSpotifyCardModal.svelte';
3import SpotifyCard from './SpotifyCard.svelte';
4
···1// animated emojis from
2// https://googlefonts.github.io/noto-emoji-animation/
34-import type { CardDefinition } from '../types';
5import { listRecords, putRecord } from '$lib/atproto';
6import StatusphereCard from './StatusphereCard.svelte';
7import EditStatusphereCard from './EditStatusphereCard.svelte';
···1// animated emojis from
2// https://googlefonts.github.io/noto-emoji-animation/
34+import type { CardDefinition } from '../../types';
5import { listRecords, putRecord } from '$lib/atproto';
6import StatusphereCard from './StatusphereCard.svelte';
7import EditStatusphereCard from './EditStatusphereCard.svelte';
···1-import type { CardDefinition } from '../types';
2import { listRecords } from '$lib/atproto';
3import TealFMPlaysCard from './TealFMPlaysCard.svelte';
4
···1+import type { CardDefinition } from '../../types';
2import { listRecords } from '$lib/atproto';
3import TealFMPlaysCard from './TealFMPlaysCard.svelte';
4
···1-import type { CardDefinition } from '../types';
2import CreateYoutubeCardModal from './CreateYoutubeCardModal.svelte';
3import YoutubeCard from './YoutubeCard.svelte';
4import YoutubeCardSettings from './YoutubeCardSettings.svelte';
···1+import type { CardDefinition } from '../../types';
2import CreateYoutubeCardModal from './CreateYoutubeCardModal.svelte';
3import YoutubeCard from './YoutubeCard.svelte';
4import YoutubeCardSettings from './YoutubeCardSettings.svelte';
···1-import type { CardDefinition } from '../types';
2import BigSocialCard from './BigSocialCard.svelte';
3import CreateBigSocialCardModal from './CreateBigSocialCardModal.svelte';
4
···1+import type { CardDefinition } from '../../types';
2import BigSocialCard from './BigSocialCard.svelte';
3import CreateBigSocialCardModal from './CreateBigSocialCardModal.svelte';
4
···1-import type { CardDefinition } from '../types';
2import BlueskyFeedCard from './BlueskyFeedCard.svelte';
3import { getAuthorFeed, resolveHandle } from '$lib/atproto/methods';
4import type { Did, Handle } from '@atcute/lexicons';
···1+import type { CardDefinition } from '../../types';
2import BlueskyFeedCard from './BlueskyFeedCard.svelte';
3import { getAuthorFeed, resolveHandle } from '$lib/atproto/methods';
4import type { Did, Handle } from '@atcute/lexicons';
···1-import type { CardDefinition } from '../types';
2import BlueskyPostCard from './BlueskyPostCard.svelte';
3import CreateBlueskyPostCardModal from './CreateBlueskyPostCardModal.svelte';
4import { getPosts } from '$lib/atproto/methods';
···1+import type { CardDefinition } from '../../types';
2import BlueskyPostCard from './BlueskyPostCard.svelte';
3import CreateBlueskyPostCardModal from './CreateBlueskyPostCardModal.svelte';
4import { getPosts } from '$lib/atproto/methods';
···1import { parseUri } from '$lib/atproto';
2-import type { CardDefinition } from '../types';
3import CreateEventCardModal from './CreateEventCardModal.svelte';
4import EventCard from './EventCard.svelte';
5
···1import { parseUri } from '$lib/atproto';
2+import type { CardDefinition } from '../../types';
3import CreateEventCardModal from './CreateEventCardModal.svelte';
4import EventCard from './EventCard.svelte';
5
···1<script lang="ts">
2 import type { Item } from '$lib/types';
3- import type { SettingsComponentProps } from '../types';
4 import type { AppBskyActorDefs } from '@atcute/bluesky';
5 import HandleInput from '$lib/atproto/UI/HandleInput.svelte';
6
···1<script lang="ts">
2 import type { Item } from '$lib/types';
3+ import type { SettingsComponentProps } from '../../types';
4 import type { AppBskyActorDefs } from '@atcute/bluesky';
5 import HandleInput from '$lib/atproto/UI/HandleInput.svelte';
6
···1-import type { CardDefinition } from '../types';
2import type { Did } from '@atcute/lexicons';
3import { getBlentoOrBskyProfile } from '$lib/atproto/methods';
4import FriendsCard from './FriendsCard.svelte';
···1+import type { CardDefinition } from '../../types';
2import type { Did } from '@atcute/lexicons';
3import { getBlentoOrBskyProfile } from '$lib/atproto/methods';
4import FriendsCard from './FriendsCard.svelte';
···1-import type { CardDefinition } from '../types';
2import GitHubContributorsCard from './GitHubContributorsCard.svelte';
3import CreateGitHubContributorsCardModal from './CreateGitHubContributorsCardModal.svelte';
4import GitHubContributorsCardSettings from './GitHubContributorsCardSettings.svelte';
···1+import type { CardDefinition } from '../../types';
2import GitHubContributorsCard from './GitHubContributorsCard.svelte';
3import CreateGitHubContributorsCardModal from './CreateGitHubContributorsCardModal.svelte';
4import GitHubContributorsCardSettings from './GitHubContributorsCardSettings.svelte';
···1-import type { CardDefinition } from '../types';
2import CreateGitHubProfileCardModal from './CreateGitHubProfileCardModal.svelte';
3import type GithubContributionsGraph from './GithubContributionsGraph.svelte';
4import GitHubProfileCard from './GitHubProfileCard.svelte';
···1+import type { CardDefinition } from '../../types';
2import CreateGitHubProfileCardModal from './CreateGitHubProfileCardModal.svelte';
3import type GithubContributionsGraph from './GithubContributionsGraph.svelte';
4import GitHubProfileCard from './GitHubProfileCard.svelte';
···1import { getPostThread } from '$lib/atproto/methods';
2-import type { CardDefinition } from '../types';
3import GuestbookCard from './GuestbookCard.svelte';
4import CreateGuestbookCardModal from './CreateGuestbookCardModal.svelte';
5
···1import { getPostThread } from '$lib/atproto/methods';
2+import type { CardDefinition } from '../../types';
3import GuestbookCard from './GuestbookCard.svelte';
4import CreateGuestbookCardModal from './CreateGuestbookCardModal.svelte';
5
···1-import type { CardDefinition } from '../types';
2import CreateKickstarterCardModal from './CreateKickstarterCardModal.svelte';
3import KickstarterCard from './KickstarterCard.svelte';
4
···1+import type { CardDefinition } from '../../types';
2import CreateKickstarterCardModal from './CreateKickstarterCardModal.svelte';
3import KickstarterCard from './KickstarterCard.svelte';
4
···1-import type { CardDefinition } from '../types';
2import LatestBlueskyPostCard from './LatestBlueskyPostCard.svelte';
3import { getAuthorFeed } from '$lib/atproto/methods';
4
···1+import type { CardDefinition } from '../../types';
2import LatestBlueskyPostCard from './LatestBlueskyPostCard.svelte';
3import { getAuthorFeed } from '$lib/atproto/methods';
4
···1-import type { CardDefinition } from '../types';
2import CreateProductHuntCardModal from './CreateProductHuntCardModal.svelte';
3import ProductHuntCard from './ProductHuntCard.svelte';
4
···1+import type { CardDefinition } from '../../types';
2import CreateProductHuntCardModal from './CreateProductHuntCardModal.svelte';
3import ProductHuntCard from './ProductHuntCard.svelte';
4
···1import { user } from '$lib/atproto/auth.svelte';
2-import type { CardDefinition } from '../types';
3import VCardCard from './VCardCard.svelte';
4import VCardCardSettings from './VCardCardSettings.svelte';
5
···1import { user } from '$lib/atproto/auth.svelte';
2+import type { CardDefinition } from '../../types';
3import VCardCard from './VCardCard.svelte';
4import VCardCardSettings from './VCardCardSettings.svelte';
5
···1-import type { CardDefinition } from '../types';
2import ButtonCard from './ButtonCard.svelte';
3import EditingButtonCard from './EditingButtonCard.svelte';
4import ButtonCardSettings from './ButtonCardSettings.svelte';
···1+import type { CardDefinition } from '../../types';
2import ButtonCard from './ButtonCard.svelte';
3import EditingButtonCard from './EditingButtonCard.svelte';
4import ButtonCardSettings from './ButtonCardSettings.svelte';
···1-import type { CardDefinition } from '../types';
2import ClockCard from './ClockCard.svelte';
3import ClockCardSettings from './ClockCardSettings.svelte';
4
···1+import type { CardDefinition } from '../../types';
2import ClockCard from './ClockCard.svelte';
3import ClockCardSettings from './ClockCardSettings.svelte';
4
···1-import type { CardDefinition } from '../types';
2import CountdownCard from './CountdownCard.svelte';
3import CountdownCardSettings from './CountdownCardSettings.svelte';
4
···1+import type { CardDefinition } from '../../types';
2import CountdownCard from './CountdownCard.svelte';
3import CountdownCardSettings from './CountdownCardSettings.svelte';
4
···1-import type { CardDefinition } from '../types';
2import TimerCard from './TimerCard.svelte';
3import TimerCardSettings from './TimerCardSettings.svelte';
4
···1+import type { CardDefinition } from '../../types';
2import TimerCard from './TimerCard.svelte';
3import TimerCardSettings from './TimerCardSettings.svelte';
4
···1-import type { CardDefinition } from '../types';
2import DrawCard from './DrawCard.svelte';
3import EditingDrawCard from './EditingDrawCard.svelte';
4
···1+import type { CardDefinition } from '../../types';
2import DrawCard from './DrawCard.svelte';
3import EditingDrawCard from './EditingDrawCard.svelte';
4
···1-import type { CardDefinition } from '../types';
2import CreateFluidTextCardModal from './CreateFluidTextCardModal.svelte';
3import EditingFluidTextCard from './EditingFluidTextCard.svelte';
4import FluidTextCard from './FluidTextCard.svelte';
···1+import type { CardDefinition } from '../../types';
2import CreateFluidTextCardModal from './CreateFluidTextCardModal.svelte';
3import EditingFluidTextCard from './EditingFluidTextCard.svelte';
4import FluidTextCard from './FluidTextCard.svelte';
···1import { uploadBlob } from '$lib/atproto';
2-import type { CardDefinition } from '../types';
3import CreateModel3DCardModal from './CreateModel3DCardModal.svelte';
4import Model3DCard from './Model3DCard.svelte';
5
···1import { uploadBlob } from '$lib/atproto';
2+import type { CardDefinition } from '../../types';
3import CreateModel3DCardModal from './CreateModel3DCardModal.svelte';
4import Model3DCard from './Model3DCard.svelte';
5
+2-2
src/lib/website/EditableWebsite.svelte
···22 import EditableProfile from './EditableProfile.svelte';
23 import type { Item, WebsiteData } from '../types';
24 import { innerWidth } from 'svelte/reactivity/window';
25- import EditingCard from '../cards/Card/EditingCard.svelte';
26 import { AllCardDefinitions, CardDefinitionsByType } from '../cards';
27 import { tick, type Component } from 'svelte';
28 import type { CardDefinition, CreationModalComponentProps } from '../cards/types';
29 import { dev } from '$app/environment';
30 import { setIsCoarse, setIsMobile, setSelectedCardId, setSelectCard } from './context';
31- import BaseEditingCard from '../cards/BaseCard/BaseEditingCard.svelte';
32 import Context from './Context.svelte';
33 import Head from './Head.svelte';
34 import Account from './Account.svelte';
···22 import EditableProfile from './EditableProfile.svelte';
23 import type { Item, WebsiteData } from '../types';
24 import { innerWidth } from 'svelte/reactivity/window';
25+ import EditingCard from '../cards/_base/Card/EditingCard.svelte';
26 import { AllCardDefinitions, CardDefinitionsByType } from '../cards';
27 import { tick, type Component } from 'svelte';
28 import type { CardDefinition, CreationModalComponentProps } from '../cards/types';
29 import { dev } from '$app/environment';
30 import { setIsCoarse, setIsMobile, setSelectedCardId, setSelectCard } from './context';
31+ import BaseEditingCard from '../cards/_base/BaseCard/BaseEditingCard.svelte';
32 import Context from './Context.svelte';
33 import Head from './Head.svelte';
34 import Account from './Account.svelte';
+2-2
src/lib/website/EmptyState.svelte
···1<script lang="ts">
2- import BaseCard from '$lib/cards/BaseCard/BaseCard.svelte';
3- import Card from '$lib/cards/Card/Card.svelte';
4 import type { Item, WebsiteData } from '$lib/types';
5 import { text } from '@sveltejs/kit';
6
···1<script lang="ts">
2+ import BaseCard from '$lib/cards/_base/BaseCard/BaseCard.svelte';
3+ import Card from '$lib/cards/_base/Card/Card.svelte';
4 import type { Item, WebsiteData } from '$lib/types';
5 import { text } from '@sveltejs/kit';
6
+2-2
src/lib/website/Website.svelte
···1<script lang="ts">
2- import Card from '../cards/Card/Card.svelte';
3 import Profile from './Profile.svelte';
4 import {
5 getDescription,
···11 } from '../helper';
12 import { innerWidth } from 'svelte/reactivity/window';
13 import { setDidContext, setHandleContext, setIsMobile } from './context';
14- import BaseCard from '../cards/BaseCard/BaseCard.svelte';
15 import type { WebsiteData } from '$lib/types';
16 import Context from './Context.svelte';
17 import MadeWithBlento from './MadeWithBlento.svelte';
···1<script lang="ts">
2+ import Card from '../cards/_base/Card/Card.svelte';
3 import Profile from './Profile.svelte';
4 import {
5 getDescription,
···11 } from '../helper';
12 import { innerWidth } from 'svelte/reactivity/window';
13 import { setDidContext, setHandleContext, setIsMobile } from './context';
14+ import BaseCard from '../cards/_base/BaseCard/BaseCard.svelte';
15 import type { WebsiteData } from '$lib/types';
16 import Context from './Context.svelte';
17 import MadeWithBlento from './MadeWithBlento.svelte';
+1-1
src/routes/api/github/+server.ts
···1import { json } from '@sveltejs/kit';
2import type { RequestHandler } from './$types';
3-import type { GitHubContributionsData } from '$lib/cards/GitHubProfileCard/types';
45const GithubAPIURL = 'https://edge-function-github-contribution.vercel.app/api/github-data?user=';
6
···1import { json } from '@sveltejs/kit';
2import type { RequestHandler } from './$types';
3+import type { GitHubContributionsData } from '$lib/cards/social/GitHubProfileCard/types';
45const GithubAPIURL = 'https://edge-function-github-contribution.vercel.app/api/github-data?user=';
6
+1
src/routes/random/+page.server.ts
···1617 while (!foundData && i < 20) {
18 const rando = Math.floor(Math.random() * list.keys.length);
01920 foundData = await getCache(list.keys[rando].name, 'self', cache as unknown as UserCache);
21
···1617 while (!foundData && i < 20) {
18 const rando = Math.floor(Math.random() * list.keys.length);
19+ console.log(list.keys[rando].name);
2021 foundData = await getCache(list.keys[rando].name, 'self', cache as unknown as UserCache);
22