···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)
···11import { checkAndUploadImage } from '$lib/helper';
22-import type { CardDefinition } from '../types';
22+import type { CardDefinition } from '../../types';
33import ImageCard from './ImageCard.svelte';
44import ImageCardSettings from './ImageCardSettings.svelte';
55
···11import { checkAndUploadImage, validateLink } from '$lib/helper';
22-import type { CardDefinition } from '../types';
22+import type { CardDefinition } from '../../types';
33import CreateLinkCardModal from './CreateLinkCardModal.svelte';
44import EditingLinkCard from './EditingLinkCard.svelte';
55import LinkCard from './LinkCard.svelte';
···11-import type { CardDefinition } from '../types';
11+import type { CardDefinition } from '../../types';
22import CreateMapCardModal from './CreateMapCardModal.svelte';
33import MapCard from './MapCard.svelte';
44import MapCardSettings from './MapCardSettings.svelte';
···11import { COLUMNS } from '$lib';
22-import type { CardDefinition } from '../types';
22+import type { CardDefinition } from '../../types';
33import EditingSectionCard from './EditingSectionCard.svelte';
44import SectionCard from './SectionCard.svelte';
55import SectionCardSettings from './SectionCardSettings.svelte';
···22 import type { Item } from '$lib/types';
33 import type { Editor } from '@tiptap/core';
44 import { textAlignClasses, textSizeClasses, verticalAlignClasses } from '.';
55- import type { ContentComponentProps } from '../types';
55+ import type { ContentComponentProps } from '../../types';
66 import MarkdownTextEditor from '$lib/components/MarkdownTextEditor.svelte';
77 import { cn } from '@foxui/core';
88
···11-import type { CardDefinition } from '../types';
11+import type { CardDefinition } from '../../types';
22import EditingTextCard from './EditingTextCard.svelte';
33import TextCard from './TextCard.svelte';
44import TextCardSettings from './TextCardSettings.svelte';
···11-import type { CardDefinition } from '../types';
11+import type { CardDefinition } from '../../types';
22import CreateAppleMusicCardModal from './CreateAppleMusicCardModal.svelte';
33import AppleMusicCard from './AppleMusicCard.svelte';
44
···11<script lang="ts">
22- import type { ContentComponentProps } from '../types';
22+ import type { ContentComponentProps } from '../../types';
33 import Video from './Video.svelte';
4455 let { item = $bindable() }: ContentComponentProps = $props();
···11-import type { CardDefinition } from '../types';
11+import type { CardDefinition } from '../../types';
22import BlueskyMediaCard from './BlueskyMediaCard.svelte';
33import CreateBlueskyMediaCardModal from './CreateBlueskyMediaCardModal.svelte';
44
···11-import type { CardDefinition } from '../types';
11+import type { CardDefinition } from '../../types';
22import CreateEmbedCardModal from './CreateEmbedCardModal.svelte';
33import EmbedCard from './EmbedCard.svelte';
44
···11<script lang="ts">
22 import type { Item } from '$lib/types';
33- import type { SettingsComponentProps } from '../types';
33+ import type { SettingsComponentProps } from '../../types';
44 import { Button, Label } from '@foxui/core';
55 import GiphySearchModal from './GiphySearchModal.svelte';
66
···11-import type { CardDefinition } from '../types';
11+import type { CardDefinition } from '../../types';
22import CreateGifCardModal from './CreateGifCardModal.svelte';
33import EditingGifCard from './EditingGifCard.svelte';
44import GifCard from './GifCard.svelte';
···11import { user, listRecords, getCDNImageBlobUrl } from '$lib/atproto';
22-import type { CardDefinition } from '../types';
22+import type { CardDefinition } from '../../types';
33import LivestreamCard from './LivestreamCard.svelte';
44import LivestreamEmbedCard from './LivestreamEmbedCard.svelte';
55
···11-import type { CardDefinition } from '../types';
11+import type { CardDefinition } from '../../types';
22import { getRecord, listRecords, parseUri } from '$lib/atproto';
33import PhotoGalleryCard from './PhotoGalleryCard.svelte';
44import type { Did } from '@atcute/lexicons';
···11-import type { CardDefinition } from '../types';
11+import type { CardDefinition } from '../../types';
22import { listRecords } from '$lib/atproto';
33import PopfeedReviewsCard from './PopfeedReviewsCard.svelte';
44
···11-import type { CardDefinition } from '../types';
11+import type { CardDefinition } from '../../types';
22import CreateSpotifyCardModal from './CreateSpotifyCardModal.svelte';
33import SpotifyCard from './SpotifyCard.svelte';
44
···11// animated emojis from
22// https://googlefonts.github.io/noto-emoji-animation/
3344-import type { CardDefinition } from '../types';
44+import type { CardDefinition } from '../../types';
55import { listRecords, putRecord } from '$lib/atproto';
66import StatusphereCard from './StatusphereCard.svelte';
77import EditStatusphereCard from './EditStatusphereCard.svelte';
···11-import type { CardDefinition } from '../types';
11+import type { CardDefinition } from '../../types';
22import { listRecords } from '$lib/atproto';
33import TealFMPlaysCard from './TealFMPlaysCard.svelte';
44
···11-import type { CardDefinition } from '../types';
11+import type { CardDefinition } from '../../types';
22import CreateYoutubeCardModal from './CreateYoutubeCardModal.svelte';
33import YoutubeCard from './YoutubeCard.svelte';
44import YoutubeCardSettings from './YoutubeCardSettings.svelte';
···11import { describeRepo } from '$lib/atproto';
22-import type { CardDefinition } from '../types';
22+import type { CardDefinition } from '../../types';
33import ATProtoCollectionsCard from './ATProtoCollectionsCard.svelte';
4455export const ATProtoCollectionsCardDefinition = {
···11-import type { CardDefinition } from '../types';
11+import type { CardDefinition } from '../../types';
22import BigSocialCard from './BigSocialCard.svelte';
33import CreateBigSocialCardModal from './CreateBigSocialCardModal.svelte';
44
···11-import type { CardDefinition } from '../types';
11+import type { CardDefinition } from '../../types';
22import BlueskyFeedCard from './BlueskyFeedCard.svelte';
33import { getAuthorFeed, resolveHandle } from '$lib/atproto/methods';
44import type { Did, Handle } from '@atcute/lexicons';
···11-import type { CardDefinition } from '../types';
11+import type { CardDefinition } from '../../types';
22import BlueskyPostCard from './BlueskyPostCard.svelte';
33import CreateBlueskyPostCardModal from './CreateBlueskyPostCardModal.svelte';
44import { getPosts } from '$lib/atproto/methods';
···11-import type { CardDefinition } from '../types';
11+import type { CardDefinition } from '../../types';
22import BlueskyProfileCard from './BlueskyProfileCard.svelte';
3344export const BlueskyProfileCardDefinition = {
···11import { parseUri } from '$lib/atproto';
22-import type { CardDefinition } from '../types';
22+import type { CardDefinition } from '../../types';
33import CreateEventCardModal from './CreateEventCardModal.svelte';
44import EventCard from './EventCard.svelte';
55
···11<script lang="ts">
22 import type { Item } from '$lib/types';
33- import type { SettingsComponentProps } from '../types';
33+ import type { SettingsComponentProps } from '../../types';
44 import type { AppBskyActorDefs } from '@atcute/bluesky';
55 import HandleInput from '$lib/atproto/UI/HandleInput.svelte';
66
···11-import type { CardDefinition } from '../types';
11+import type { CardDefinition } from '../../types';
22import type { Did } from '@atcute/lexicons';
33import { getBlentoOrBskyProfile } from '$lib/atproto/methods';
44import FriendsCard from './FriendsCard.svelte';
···11-import type { CardDefinition } from '../types';
11+import type { CardDefinition } from '../../types';
22import GitHubContributorsCard from './GitHubContributorsCard.svelte';
33import CreateGitHubContributorsCardModal from './CreateGitHubContributorsCardModal.svelte';
44import GitHubContributorsCardSettings from './GitHubContributorsCardSettings.svelte';
···11-import type { CardDefinition } from '../types';
11+import type { CardDefinition } from '../../types';
22import CreateGitHubProfileCardModal from './CreateGitHubProfileCardModal.svelte';
33import type GithubContributionsGraph from './GithubContributionsGraph.svelte';
44import GitHubProfileCard from './GitHubProfileCard.svelte';
···11import { getPostThread } from '$lib/atproto/methods';
22-import type { CardDefinition } from '../types';
22+import type { CardDefinition } from '../../types';
33import GuestbookCard from './GuestbookCard.svelte';
44import CreateGuestbookCardModal from './CreateGuestbookCardModal.svelte';
55
···11-import type { CardDefinition } from '../types';
11+import type { CardDefinition } from '../../types';
22import CreateKickstarterCardModal from './CreateKickstarterCardModal.svelte';
33import KickstarterCard from './KickstarterCard.svelte';
44
···11-import type { CardDefinition } from '../types';
11+import type { CardDefinition } from '../../types';
22import LatestBlueskyPostCard from './LatestBlueskyPostCard.svelte';
33import { getAuthorFeed } from '$lib/atproto/methods';
44
···11-import type { CardDefinition } from '../types';
11+import type { CardDefinition } from '../../types';
22import CreateProductHuntCardModal from './CreateProductHuntCardModal.svelte';
33import ProductHuntCard from './ProductHuntCard.svelte';
44
···11import { user } from '$lib/atproto/auth.svelte';
22-import type { CardDefinition } from '../types';
22+import type { CardDefinition } from '../../types';
33import VCardCard from './VCardCard.svelte';
44import VCardCardSettings from './VCardCardSettings.svelte';
55
···11-import type { CardDefinition } from '../types';
11+import type { CardDefinition } from '../../types';
22import ButtonCard from './ButtonCard.svelte';
33import EditingButtonCard from './EditingButtonCard.svelte';
44import ButtonCardSettings from './ButtonCardSettings.svelte';
···11-import type { CardDefinition } from '../types';
11+import type { CardDefinition } from '../../types';
22import ClockCard from './ClockCard.svelte';
33import ClockCardSettings from './ClockCardSettings.svelte';
44
···11-import type { CardDefinition } from '../types';
11+import type { CardDefinition } from '../../types';
22import CountdownCard from './CountdownCard.svelte';
33import CountdownCardSettings from './CountdownCardSettings.svelte';
44
···11-import type { CardDefinition } from '../types';
11+import type { CardDefinition } from '../../types';
22import TimerCard from './TimerCard.svelte';
33import TimerCardSettings from './TimerCardSettings.svelte';
44
···11-import type { CardDefinition } from '../types';
11+import type { CardDefinition } from '../../types';
22import DrawCard from './DrawCard.svelte';
33import EditingDrawCard from './EditingDrawCard.svelte';
44
···11-import type { CardDefinition } from '../types';
11+import type { CardDefinition } from '../../types';
22import CreateFluidTextCardModal from './CreateFluidTextCardModal.svelte';
33import EditingFluidTextCard from './EditingFluidTextCard.svelte';
44import FluidTextCard from './FluidTextCard.svelte';
···11import { uploadBlob } from '$lib/atproto';
22-import type { CardDefinition } from '../types';
22+import type { CardDefinition } from '../../types';
33import CreateModel3DCardModal from './CreateModel3DCardModal.svelte';
44import Model3DCard from './Model3DCard.svelte';
55
+2-2
src/lib/website/EditableWebsite.svelte
···2222 import EditableProfile from './EditableProfile.svelte';
2323 import type { Item, WebsiteData } from '../types';
2424 import { innerWidth } from 'svelte/reactivity/window';
2525- import EditingCard from '../cards/Card/EditingCard.svelte';
2525+ import EditingCard from '../cards/_base/Card/EditingCard.svelte';
2626 import { AllCardDefinitions, CardDefinitionsByType } from '../cards';
2727 import { tick, type Component } from 'svelte';
2828 import type { CardDefinition, CreationModalComponentProps } from '../cards/types';
2929 import { dev } from '$app/environment';
3030 import { setIsCoarse, setIsMobile, setSelectedCardId, setSelectCard } from './context';
3131- import BaseEditingCard from '../cards/BaseCard/BaseEditingCard.svelte';
3131+ import BaseEditingCard from '../cards/_base/BaseCard/BaseEditingCard.svelte';
3232 import Context from './Context.svelte';
3333 import Head from './Head.svelte';
3434 import Account from './Account.svelte';
+2-2
src/lib/website/EmptyState.svelte
···11<script lang="ts">
22- import BaseCard from '$lib/cards/BaseCard/BaseCard.svelte';
33- import Card from '$lib/cards/Card/Card.svelte';
22+ import BaseCard from '$lib/cards/_base/BaseCard/BaseCard.svelte';
33+ import Card from '$lib/cards/_base/Card/Card.svelte';
44 import type { Item, WebsiteData } from '$lib/types';
55 import { text } from '@sveltejs/kit';
66
+2-2
src/lib/website/Website.svelte
···11<script lang="ts">
22- import Card from '../cards/Card/Card.svelte';
22+ import Card from '../cards/_base/Card/Card.svelte';
33 import Profile from './Profile.svelte';
44 import {
55 getDescription,
···1111 } from '../helper';
1212 import { innerWidth } from 'svelte/reactivity/window';
1313 import { setDidContext, setHandleContext, setIsMobile } from './context';
1414- import BaseCard from '../cards/BaseCard/BaseCard.svelte';
1414+ import BaseCard from '../cards/_base/BaseCard/BaseCard.svelte';
1515 import type { WebsiteData } from '$lib/types';
1616 import Context from './Context.svelte';
1717 import MadeWithBlento from './MadeWithBlento.svelte';
+1-1
src/routes/api/github/+server.ts
···11import { json } from '@sveltejs/kit';
22import type { RequestHandler } from './$types';
33-import type { GitHubContributionsData } from '$lib/cards/GitHubProfileCard/types';
33+import type { GitHubContributionsData } from '$lib/cards/social/GitHubProfileCard/types';
4455const GithubAPIURL = 'https://edge-function-github-contribution.vercel.app/api/github-data?user=';
66
+1
src/routes/random/+page.server.ts
···16161717 while (!foundData && i < 20) {
1818 const rando = Math.floor(Math.random() * list.keys.length);
1919+ console.log(list.keys[rando].name);
19202021 foundData = await getCache(list.keys[rando].name, 'self', cache as unknown as UserCache);
2122