From 0209975af642944cc5a434c0b44205a87e634e7e Mon Sep 17 00:00:00 2001 From: Thomas Hallock Date: Sat, 18 Oct 2025 19:00:38 -0500 Subject: [PATCH] refactor(arcade): standardize game card themes with preset system MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Create a centralized theme system to prevent inconsistent game card styling. All games now use standard color presets instead of manually specifying gradients. ## Changes **New Theme System:** - Created `/src/lib/arcade/game-themes.ts` with 10 standard themes - All themes use Tailwind 100-200 colors for consistent pastel appearance - Exported `getGameTheme()` helper and `GAME_THEMES` constants via SDK - Added comprehensive documentation in `.claude/GAME_THEMES.md` **Migrated All Games:** - card-sorting: Uses `getGameTheme('teal')` - memory-quiz: Uses `getGameTheme('blue')` - matching: Uses `getGameTheme('purple')` - complement-race: Uses `getGameTheme('blue')` **Benefits:** - ✅ Prevents future styling inconsistencies - ✅ One-line theme setup instead of three properties - ✅ TypeScript autocomplete for available themes - ✅ Centralized maintenance - update all games by changing theme definition - ✅ Clear documentation prevents mistakes **Before:** ```typescript color: 'teal', gradient: 'linear-gradient(135deg, #99f6e4, #5eead4)', // Manual, error-prone borderColor: 'teal.200', ``` **After:** ```typescript ...getGameTheme('teal') // Simple, consistent, discoverable ``` This fixes the root cause where card-sorting needed manual gradient adjustments - now all games automatically get professional styling. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- apps/web/.claude/GAME_THEMES.md | 154 ++++++++++++++++++ apps/web/.claude/settings.local.json | 4 +- .../src/arcade-games/card-sorting/index.ts | 6 +- .../arcade-games/complement-race/index.tsx | 6 +- apps/web/src/arcade-games/matching/index.ts | 6 +- .../web/src/arcade-games/memory-quiz/index.ts | 6 +- apps/web/src/lib/arcade/game-sdk/index.ts | 7 + apps/web/src/lib/arcade/game-themes.ts | 88 ++++++++++ 8 files changed, 260 insertions(+), 17 deletions(-) create mode 100644 apps/web/.claude/GAME_THEMES.md create mode 100644 apps/web/src/lib/arcade/game-themes.ts diff --git a/apps/web/.claude/GAME_THEMES.md b/apps/web/.claude/GAME_THEMES.md new file mode 100644 index 00000000..7101f1bd --- /dev/null +++ b/apps/web/.claude/GAME_THEMES.md @@ -0,0 +1,154 @@ +# Game Theme Standardization + +## Problem + +Previously, each game manually specified `color`, `gradient`, and `borderColor` in their manifest. This led to: +- Inconsistent appearance across game cards +- No guidance on what colors/gradients to use +- Easy to choose saturated colors that don't match the pastel style +- Duplication and maintenance burden + +## Solution + +**Standard theme presets** in `/src/lib/arcade/game-themes.ts` + +All games now use predefined color themes that ensure consistent, professional appearance. + +## Usage + +### 1. Import from the Game SDK + +```typescript +import { defineGame, getGameTheme } from '@/lib/arcade/game-sdk' +import type { GameManifest } from '@/lib/arcade/game-sdk' +``` + +### 2. Use the Theme Spread Operator + +```typescript +const manifest: GameManifest = { + name: 'my-game', + displayName: 'My Awesome Game', + icon: '🎮', + description: 'A fun game', + longDescription: 'More details...', + maxPlayers: 4, + difficulty: 'Intermediate', + chips: ['🎯 Feature 1', '⚡ Feature 2'], + ...getGameTheme('blue'), // ← Just add this! + available: true, +} +``` + +That's it! The theme automatically provides: +- `color: 'blue'` +- `gradient: 'linear-gradient(135deg, #dbeafe, #bfdbfe)'` +- `borderColor: 'blue.200'` + +## Available Themes + +All themes use Tailwind's 100-200 color range for soft pastel appearance: + +| Theme | Color Range | Use Case | +|-------|-------------|----------| +| `blue` | blue-100 to blue-200 | Memory, puzzle games | +| `purple` | purple-100 to purple-200 | Strategic, battle games | +| `green` | green-100 to green-200 | Growth, achievement games | +| `teal` | teal-100 to teal-200 | Creative, sorting games | +| `indigo` | indigo-100 to indigo-200 | Deep thinking games | +| `pink` | pink-100 to pink-200 | Fun, casual games | +| `orange` | orange-100 to orange-200 | Speed, energy games | +| `yellow` | yellow-100 to yellow-200 | Bright, happy games | +| `red` | red-100 to red-200 | Competition, challenge | +| `gray` | gray-100 to gray-200 | Neutral games | + +## Examples + +### Current Games + +```typescript +// Memory Lightning - blue theme +...getGameTheme('blue') + +// Matching Pairs Battle - purple theme +...getGameTheme('purple') + +// Card Sorting Challenge - teal theme +...getGameTheme('teal') + +// Speed Complement Race - blue theme +...getGameTheme('blue') +``` + +## Benefits + +✅ **Consistency** - All games have the same professional pastel look +✅ **Simple** - One line instead of three properties +✅ **Maintainable** - Update all games by changing the theme definition +✅ **Discoverable** - TypeScript autocomplete shows available themes +✅ **No mistakes** - Can't accidentally use wrong color values + +## Advanced Usage + +If you need to inspect or customize a theme: + +```typescript +import { GAME_THEMES } from '@/lib/arcade/game-sdk' +import type { GameTheme } from '@/lib/arcade/game-sdk' + +// Access a specific theme +const blueTheme: GameTheme = GAME_THEMES.blue + +// Use it +const manifest: GameManifest = { + // ... other fields + ...blueTheme, + // Or customize: + color: blueTheme.color, + gradient: 'linear-gradient(135deg, #custom, #values)', // override + borderColor: blueTheme.borderColor, +} +``` + +## Adding New Themes + +To add a new theme, edit `/src/lib/arcade/game-themes.ts`: + +```typescript +export const GAME_THEMES = { + // ... existing themes + mycolor: { + color: 'mycolor', + gradient: 'linear-gradient(135deg, #lighter, #darker)', // Use Tailwind 100-200 + borderColor: 'mycolor.200', + }, +} as const satisfies Record +``` + +Then update the TypeScript type: +```typescript +export type GameThemeName = keyof typeof GAME_THEMES +``` + +## Migration Checklist + +When creating a new game: + +- [x] Import `getGameTheme` from `@/lib/arcade/game-sdk` +- [x] Use `...getGameTheme('theme-name')` in manifest +- [x] Remove manual `color`, `gradient`, `borderColor` properties +- [x] Choose a theme that matches your game's vibe + +## Summary + +**Old way** (error-prone, inconsistent): +```typescript +color: 'teal', +gradient: 'linear-gradient(135deg, #99f6e4, #5eead4)', // Too saturated! +borderColor: 'teal.200', +``` + +**New way** (simple, consistent): +```typescript +...getGameTheme('teal') +``` diff --git a/apps/web/.claude/settings.local.json b/apps/web/.claude/settings.local.json index 54a63283..3bb5fe97 100644 --- a/apps/web/.claude/settings.local.json +++ b/apps/web/.claude/settings.local.json @@ -98,7 +98,9 @@ "Bash(do gh run list --limit 1 --json conclusion,status,name,databaseId --jq '.[0] | \"\"\\(.status) - \\(.conclusion // \"\"running\"\")\"\"')", "Bash(do gh run list --limit 1 --json conclusion,status,name --jq '.[0] | \"\"\\(.status) - \\(.conclusion // \"\"running\"\") - \\(.name)\"\"')", "Bash(do gh run list --limit 1 --workflow=\"Build and Deploy\" --json conclusion,status --jq '.[0] | \"\"\\(.status) - \\(.conclusion // \"\"running\"\")\"\"')", - "WebFetch(domain:abaci.one)" + "WebFetch(domain:abaci.one)", + "Bash(do gh run list --limit 1 --workflow=\"Build and Deploy\" --json conclusion,status,databaseId --jq '.[0] | \"\"\\(.status) - \\(.conclusion // \"\"running\"\") - Run ID: \\(.databaseId)\"\"')", + "Bash(node -e:*)" ], "deny": [], "ask": [] diff --git a/apps/web/src/arcade-games/card-sorting/index.ts b/apps/web/src/arcade-games/card-sorting/index.ts index 4c31d132..58d02b2a 100644 --- a/apps/web/src/arcade-games/card-sorting/index.ts +++ b/apps/web/src/arcade-games/card-sorting/index.ts @@ -5,7 +5,7 @@ * in ascending order using only visual patterns (no numbers shown). */ -import { defineGame } from '@/lib/arcade/game-sdk' +import { defineGame, getGameTheme } from '@/lib/arcade/game-sdk' import type { GameManifest } from '@/lib/arcade/game-sdk' import { GameComponent } from './components/GameComponent' import { CardSortingProvider } from './Provider' @@ -24,9 +24,7 @@ const manifest: GameManifest = { maxPlayers: 1, // Single player only difficulty: 'Intermediate', chips: ['🧠 Pattern Recognition', '🎯 Solo Challenge', '📊 Smart Scoring'], - color: 'blue', - gradient: 'linear-gradient(135deg, #dbeafe, #bfdbfe)', - borderColor: 'blue.200', + ...getGameTheme('teal'), available: true, } diff --git a/apps/web/src/arcade-games/complement-race/index.tsx b/apps/web/src/arcade-games/complement-race/index.tsx index 38ff150e..7cdeb9c1 100644 --- a/apps/web/src/arcade-games/complement-race/index.tsx +++ b/apps/web/src/arcade-games/complement-race/index.tsx @@ -3,7 +3,7 @@ * Complete integration into the arcade system with multiplayer support */ -import { defineGame } from '@/lib/arcade/game-sdk' +import { defineGame, getGameTheme } from '@/lib/arcade/game-sdk' import type { GameManifest } from '@/lib/arcade/game-sdk' import { complementRaceValidator } from './Validator' import { ComplementRaceProvider } from './Provider' @@ -20,9 +20,7 @@ const manifest: GameManifest = { maxPlayers: 4, icon: '🏁', chips: ['👥 1-4 Players', '🚂 Sprint Mode', '🤖 AI Opponents', '🔥 Speed Challenge'], - color: 'blue', - gradient: 'linear-gradient(135deg, #dbeafe, #bfdbfe)', - borderColor: 'blue.200', + ...getGameTheme('blue'), difficulty: 'Intermediate', available: true, } diff --git a/apps/web/src/arcade-games/matching/index.ts b/apps/web/src/arcade-games/matching/index.ts index e52c2c66..cf22837f 100644 --- a/apps/web/src/arcade-games/matching/index.ts +++ b/apps/web/src/arcade-games/matching/index.ts @@ -5,7 +5,7 @@ * Supports both abacus-numeral matching and complement pairs modes. */ -import { defineGame } from '@/lib/arcade/game-sdk' +import { defineGame, getGameTheme } from '@/lib/arcade/game-sdk' import type { GameManifest } from '@/lib/arcade/game-sdk' import { MemoryPairsGame } from './components/MemoryPairsGame' import { MatchingProvider } from './Provider' @@ -23,9 +23,7 @@ const manifest: GameManifest = { maxPlayers: 4, difficulty: 'Intermediate', chips: ['👥 Multiplayer', '🎯 Strategic', '🏆 Competitive'], - color: 'purple', - gradient: 'linear-gradient(135deg, #e9d5ff, #ddd6fe)', - borderColor: 'purple.200', + ...getGameTheme('purple'), available: true, } diff --git a/apps/web/src/arcade-games/memory-quiz/index.ts b/apps/web/src/arcade-games/memory-quiz/index.ts index b69e6092..54a7671d 100644 --- a/apps/web/src/arcade-games/memory-quiz/index.ts +++ b/apps/web/src/arcade-games/memory-quiz/index.ts @@ -5,7 +5,7 @@ * Supports both cooperative and competitive multiplayer modes. */ -import { defineGame } from '@/lib/arcade/game-sdk' +import { defineGame, getGameTheme } from '@/lib/arcade/game-sdk' import type { GameManifest } from '@/lib/arcade/game-sdk' import { MemoryQuizGame } from './components/MemoryQuizGame' import { MemoryQuizProvider } from './Provider' @@ -23,9 +23,7 @@ const manifest: GameManifest = { maxPlayers: 8, difficulty: 'Intermediate', chips: ['👥 Multiplayer', '🧠 Memory', '🧮 Soroban'], - color: 'blue', - gradient: 'linear-gradient(135deg, #dbeafe, #bfdbfe)', - borderColor: 'blue.200', + ...getGameTheme('blue'), available: true, } diff --git a/apps/web/src/lib/arcade/game-sdk/index.ts b/apps/web/src/lib/arcade/game-sdk/index.ts index fd3dc4d1..8468b742 100644 --- a/apps/web/src/lib/arcade/game-sdk/index.ts +++ b/apps/web/src/lib/arcade/game-sdk/index.ts @@ -82,6 +82,13 @@ export { loadManifest } from './load-manifest' */ export { defineGame } from './define-game' +/** + * Standard color themes for game cards + * Use these to ensure consistent appearance across all games + */ +export { getGameTheme, GAME_THEMES } from '../game-themes' +export type { GameTheme, GameThemeName } from '../game-themes' + // ============================================================================ // Re-exports for convenience // ============================================================================ diff --git a/apps/web/src/lib/arcade/game-themes.ts b/apps/web/src/lib/arcade/game-themes.ts new file mode 100644 index 00000000..a370dc4e --- /dev/null +++ b/apps/web/src/lib/arcade/game-themes.ts @@ -0,0 +1,88 @@ +/** + * Standard color themes for arcade game cards + * + * Use these presets to ensure consistent, professional appearance + * across all game cards on the /arcade game chooser. + * + * All gradients use Tailwind's 100-200 color range for soft pastel appearance. + */ + +export interface GameTheme { + color: string + gradient: string + borderColor: string +} + +/** + * Standard theme presets + * These match Tailwind's color system and provide consistent styling + */ +export const GAME_THEMES = { + blue: { + color: 'blue', + gradient: 'linear-gradient(135deg, #dbeafe, #bfdbfe)', // blue-100 to blue-200 + borderColor: 'blue.200', + }, + purple: { + color: 'purple', + gradient: 'linear-gradient(135deg, #e9d5ff, #ddd6fe)', // purple-100 to purple-200 + borderColor: 'purple.200', + }, + green: { + color: 'green', + gradient: 'linear-gradient(135deg, #d1fae5, #a7f3d0)', // green-100 to green-200 + borderColor: 'green.200', + }, + teal: { + color: 'teal', + gradient: 'linear-gradient(135deg, #ccfbf1, #99f6e4)', // teal-100 to teal-200 + borderColor: 'teal.200', + }, + indigo: { + color: 'indigo', + gradient: 'linear-gradient(135deg, #e0e7ff, #c7d2fe)', // indigo-100 to indigo-200 + borderColor: 'indigo.200', + }, + pink: { + color: 'pink', + gradient: 'linear-gradient(135deg, #fce7f3, #fbcfe8)', // pink-100 to pink-200 + borderColor: 'pink.200', + }, + orange: { + color: 'orange', + gradient: 'linear-gradient(135deg, #ffedd5, #fed7aa)', // orange-100 to orange-200 + borderColor: 'orange.200', + }, + yellow: { + color: 'yellow', + gradient: 'linear-gradient(135deg, #fef3c7, #fde68a)', // yellow-100 to yellow-200 + borderColor: 'yellow.200', + }, + red: { + color: 'red', + gradient: 'linear-gradient(135deg, #fee2e2, #fecaca)', // red-100 to red-200 + borderColor: 'red.200', + }, + gray: { + color: 'gray', + gradient: 'linear-gradient(135deg, #f3f4f6, #e5e7eb)', // gray-100 to gray-200 + borderColor: 'gray.200', + }, +} as const satisfies Record + +export type GameThemeName = keyof typeof GAME_THEMES + +/** + * Get a standard theme by name + * Use this in your game manifest instead of hardcoding gradients + * + * @example + * const manifest: GameManifest = { + * name: 'my-game', + * // ... other fields + * ...getGameTheme('blue') + * } + */ +export function getGameTheme(themeName: GameThemeName): GameTheme { + return GAME_THEMES[themeName] +}