Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3dcdfb4986 | ||
|
|
0209975af6 | ||
|
|
c9b7e92f39 | ||
|
|
c56a47cb60 | ||
|
|
bdb84f5d90 | ||
|
|
33838b7fa7 | ||
|
|
33e9ad2f79 | ||
|
|
db62519f9b | ||
|
|
ec978de0b3 | ||
|
|
d9a7694031 | ||
|
|
42dcbff857 | ||
|
|
5923d341a0 | ||
|
|
cd4796024e | ||
|
|
cff948708f | ||
|
|
ea10c16811 | ||
|
|
474d31576f | ||
|
|
73ff32c243 | ||
|
|
0a50c733b0 | ||
|
|
1386378ca1 | ||
|
|
30f48ab897 | ||
|
|
d2f6b8b46c | ||
|
|
247377fca3 | ||
|
|
be39401716 | ||
|
|
d2a3b7ae2e | ||
|
|
39ab605279 | ||
|
|
cf9d893f3f |
89
CHANGELOG.md
89
CHANGELOG.md
@@ -1,3 +1,92 @@
|
||||
## [4.13.13](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.13.12...v4.13.13) (2025-10-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **docker:** install Python dependencies for flashcard generation ([c9b7e92](https://github.com/antialias/soroban-abacus-flashcards/commit/c9b7e92f39ee7aa7f13606c2836763144df102e7))
|
||||
|
||||
|
||||
### Code Refactoring
|
||||
|
||||
* **arcade:** standardize game card themes with preset system ([0209975](https://github.com/antialias/soroban-abacus-flashcards/commit/0209975af642944cc5a434c0b44205a87e634e7e)), closes [#99f6e4](https://github.com/antialias/soroban-abacus-flashcards/issues/99f6e4) [#5eead4](https://github.com/antialias/soroban-abacus-flashcards/issues/5eead4)
|
||||
|
||||
## [4.13.12](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.13.11...v4.13.12) (2025-10-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **card-sorting:** use blue gradient matching other game cards ([bdb84f5](https://github.com/antialias/soroban-abacus-flashcards/commit/bdb84f5d909542060fa886a83a5af62c4a785a98))
|
||||
|
||||
## [4.13.11](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.13.10...v4.13.11) (2025-10-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **card-sorting:** match game selector background to other games ([db62519](https://github.com/antialias/soroban-abacus-flashcards/commit/db62519f9beb0b4bc6120e1fd5ec251cfde5c3c1)), closes [#ccfbf1](https://github.com/antialias/soroban-abacus-flashcards/issues/ccfbf1) [#99f6e4](https://github.com/antialias/soroban-abacus-flashcards/issues/99f6e4)
|
||||
* **docker:** copy core package with Python scripts to production image ([33e9ad2](https://github.com/antialias/soroban-abacus-flashcards/commit/33e9ad2f79b591f1c5ee57a6691e1bcf48420859))
|
||||
|
||||
## [4.13.10](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.13.9...v4.13.10) (2025-10-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add Typst to Docker image for flashcard generation ([d9a7694](https://github.com/antialias/soroban-abacus-flashcards/commit/d9a769403187bf70fb069be7ffe77417a62271a5))
|
||||
|
||||
|
||||
### Code Refactoring
|
||||
|
||||
* remove 'Complete Soroban Learning Platform' section ([42dcbff](https://github.com/antialias/soroban-abacus-flashcards/commit/42dcbff85708ad378550634cbf7a3345eccb578e))
|
||||
|
||||
## [4.13.9](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.13.8...v4.13.9) (2025-10-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* set color on abacus container div for numeral visibility ([cd47960](https://github.com/antialias/soroban-abacus-flashcards/commit/cd4796024e41f731ae5d83c82f6582e19d6eaf99)), closes [#1f2937](https://github.com/antialias/soroban-abacus-flashcards/issues/1f2937)
|
||||
|
||||
## [4.13.8](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.13.7...v4.13.8) (2025-10-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* use color instead of fill for numeral styling ([ea10c16](https://github.com/antialias/soroban-abacus-flashcards/commit/ea10c16811eb969b9963417079c330ae9ff295ba))
|
||||
|
||||
## [4.13.7](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.13.6...v4.13.7) (2025-10-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add dark color for abacus numerals ([73ff32c](https://github.com/antialias/soroban-abacus-flashcards/commit/73ff32c2432beb62710e57aa8b3b4793eca43fda)), closes [#1f2937](https://github.com/antialias/soroban-abacus-flashcards/issues/1f2937)
|
||||
* use app-wide abacus config and remove instruction text ([0a50c73](https://github.com/antialias/soroban-abacus-flashcards/commit/0a50c733b089c7c341f0fdef47da78d1c61a3cb5))
|
||||
|
||||
## [4.13.6](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.13.5...v4.13.6) (2025-10-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* simplify abacus pane with light background ([30f48ab](https://github.com/antialias/soroban-abacus-flashcards/commit/30f48ab8976976688e089b07ece7fdae6d7ada79))
|
||||
|
||||
## [4.13.5](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.13.4...v4.13.5) (2025-10-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* correct AbacusReact API usage and add structural styling ([247377f](https://github.com/antialias/soroban-abacus-flashcards/commit/247377fca35ee3433e02ad594ecc1c4f391f0143)), closes [#fbbf24](https://github.com/antialias/soroban-abacus-flashcards/issues/fbbf24) [#a78](https://github.com/antialias/soroban-abacus-flashcards/issues/a78)
|
||||
|
||||
## [4.13.4](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.13.3...v4.13.4) (2025-10-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **card-sorting:** increase card tile sizes to contain abacuses ([d2a3b7a](https://github.com/antialias/soroban-abacus-flashcards/commit/d2a3b7ae2e3f6819b8d9ace32be22f04f748d1bc))
|
||||
|
||||
## [4.13.3](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.13.2...v4.13.3) (2025-10-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **card-sorting:** increase SVG size to fill card containers ([cf9d893](https://github.com/antialias/soroban-abacus-flashcards/commit/cf9d893f3fdbef6e91cd0ba283d602b9215569f1))
|
||||
|
||||
## [4.13.2](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.13.1...v4.13.2) (2025-10-18)
|
||||
|
||||
|
||||
|
||||
10
Dockerfile
10
Dockerfile
@@ -33,8 +33,8 @@ RUN turbo build --filter=@soroban/web
|
||||
FROM node:18-alpine AS runner
|
||||
WORKDIR /app
|
||||
|
||||
# Install Python and build tools for better-sqlite3 (needed at runtime)
|
||||
RUN apk add --no-cache python3 py3-setuptools make g++
|
||||
# Install Python, build tools for better-sqlite3, and Typst (needed at runtime)
|
||||
RUN apk add --no-cache python3 py3-setuptools make g++ typst
|
||||
|
||||
# Create non-root user
|
||||
RUN addgroup --system --gid 1001 nodejs
|
||||
@@ -55,6 +55,12 @@ COPY --from=builder --chown=nextjs:nodejs /app/apps/web/drizzle ./apps/web/drizz
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/node_modules ./node_modules
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/apps/web/node_modules ./apps/web/node_modules
|
||||
|
||||
# Copy core package (needed for Python flashcard generation scripts)
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/packages/core ./packages/core
|
||||
|
||||
# Install Python dependencies for flashcard generation
|
||||
RUN pip3 install --no-cache-dir -r packages/core/requirements.txt
|
||||
|
||||
# Copy package.json files for module resolution
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/package.json ./
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/apps/web/package.json ./apps/web/
|
||||
|
||||
154
apps/web/.claude/GAME_THEMES.md
Normal file
154
apps/web/.claude/GAME_THEMES.md
Normal file
@@ -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<string, GameTheme>
|
||||
```
|
||||
|
||||
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')
|
||||
```
|
||||
@@ -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": []
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
import { useState } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { PageWithNav } from '@/components/PageWithNav'
|
||||
import { AbacusReact } from '@soroban/abacus-react'
|
||||
import { AbacusReact, useAbacusConfig } from '@soroban/abacus-react'
|
||||
import { css } from '../../styled-system/css'
|
||||
import { container, grid, hstack, stack } from '../../styled-system/patterns'
|
||||
|
||||
export default function HomePage() {
|
||||
const [abacusValue, setAbacusValue] = useState(1234567)
|
||||
const appConfig = useAbacusConfig()
|
||||
return (
|
||||
<PageWithNav navTitle="Soroban Mastery Platform" navEmoji="🧮">
|
||||
<div className={css({ bg: 'gray.900', minHeight: '100vh' })}>
|
||||
@@ -53,62 +54,31 @@ export default function HomePage() {
|
||||
<div
|
||||
className={css({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
gap: '4',
|
||||
justifyContent: 'center',
|
||||
my: '10',
|
||||
p: '8',
|
||||
bg: 'rgba(0, 0, 0, 0.4)',
|
||||
bg: 'white',
|
||||
borderRadius: '2xl',
|
||||
border: '2px solid',
|
||||
borderColor: 'purple.500/30',
|
||||
boxShadow: '0 25px 50px -12px rgba(139, 92, 246, 0.25)',
|
||||
color: '#1f2937',
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={css({
|
||||
fontSize: 'lg',
|
||||
fontWeight: 'semibold',
|
||||
color: 'gray.300',
|
||||
mb: '2',
|
||||
})}
|
||||
>
|
||||
Click the beads to interact!
|
||||
</div>
|
||||
<AbacusReact
|
||||
value={abacusValue}
|
||||
columns={7}
|
||||
beadShape="diamond"
|
||||
colorScheme="place-value"
|
||||
hideInactiveBeads={false}
|
||||
beadShape={appConfig.beadShape}
|
||||
colorScheme={appConfig.colorScheme}
|
||||
hideInactiveBeads={appConfig.hideInactiveBeads}
|
||||
interactive={true}
|
||||
animated={true}
|
||||
soundEnabled={true}
|
||||
soundVolume={0.4}
|
||||
scaleFactor={2.2}
|
||||
showNumbers={true}
|
||||
customStyles={{
|
||||
numerals: {
|
||||
fill: '#fbbf24',
|
||||
fontWeight: 'bold',
|
||||
fontSize: '18px',
|
||||
},
|
||||
}}
|
||||
callbacks={{
|
||||
onValueChange: (newValue: number) => setAbacusValue(newValue),
|
||||
}}
|
||||
onValueChange={(newValue: number) => setAbacusValue(newValue)}
|
||||
/>
|
||||
<div
|
||||
className={css({
|
||||
fontSize: '4xl',
|
||||
fontWeight: 'bold',
|
||||
color: 'yellow.400',
|
||||
fontFamily: 'mono',
|
||||
letterSpacing: 'wide',
|
||||
})}
|
||||
>
|
||||
{abacusValue.toLocaleString()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p
|
||||
@@ -192,57 +162,8 @@ export default function HomePage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Color Scheme Showcase */}
|
||||
{/* Main content container */}
|
||||
<div className={container({ maxW: '7xl', px: '4', py: '12' })}>
|
||||
<section className={stack({ gap: '8' })}>
|
||||
<div className={css({ textAlign: 'center' })}>
|
||||
<h2
|
||||
className={css({
|
||||
fontSize: { base: '2xl', md: '3xl' },
|
||||
fontWeight: 'bold',
|
||||
color: 'white',
|
||||
mb: '3',
|
||||
})}
|
||||
>
|
||||
Beautiful Color Schemes
|
||||
</h2>
|
||||
<p className={css({ color: 'gray.400', fontSize: 'lg' })}>
|
||||
Choose from multiple visual styles to enhance learning
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className={grid({ columns: { base: 1, sm: 2, lg: 4 }, gap: '6' })}>
|
||||
<ColorSchemeCard
|
||||
title="Monochrome"
|
||||
description="Classic, minimalist design"
|
||||
colorScheme="monochrome"
|
||||
value={42}
|
||||
beadShape="diamond"
|
||||
/>
|
||||
<ColorSchemeCard
|
||||
title="Place Value"
|
||||
description="Each column has its own color"
|
||||
colorScheme="place-value"
|
||||
value={789}
|
||||
beadShape="circle"
|
||||
/>
|
||||
<ColorSchemeCard
|
||||
title="Heaven & Earth"
|
||||
description="Distinct heaven and earth beads"
|
||||
colorScheme="heaven-earth"
|
||||
value={156}
|
||||
beadShape="square"
|
||||
/>
|
||||
<ColorSchemeCard
|
||||
title="Alternating"
|
||||
description="Alternating column colors"
|
||||
colorScheme="alternating"
|
||||
value={234}
|
||||
beadShape="diamond"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Arcade Games Section */}
|
||||
<section className={stack({ gap: '6', mt: '16' })}>
|
||||
<div className={hstack({ justify: 'space-between', alignItems: 'center' })}>
|
||||
@@ -343,36 +264,6 @@ export default function HomePage() {
|
||||
accentColor="blue"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Stats Banner */}
|
||||
<section
|
||||
className={css({
|
||||
bg: 'linear-gradient(135deg, rgba(139, 92, 246, 0.2) 0%, rgba(59, 130, 246, 0.2) 100%)',
|
||||
rounded: '2xl',
|
||||
p: '10',
|
||||
mt: '16',
|
||||
border: '2px solid',
|
||||
borderColor: 'purple.500/20',
|
||||
})}
|
||||
>
|
||||
<h2
|
||||
className={css({
|
||||
fontSize: { base: 'xl', md: '2xl' },
|
||||
fontWeight: 'bold',
|
||||
mb: '8',
|
||||
textAlign: 'center',
|
||||
color: 'white',
|
||||
})}
|
||||
>
|
||||
Complete Soroban Learning Platform
|
||||
</h2>
|
||||
<div className={grid({ columns: { base: 2, md: 4 }, gap: '8', textAlign: 'center' })}>
|
||||
<StatItem number="4" label="Arcade Games" />
|
||||
<StatItem number="8" label="Max Players" />
|
||||
<StatItem number="3" label="Learning Modes" />
|
||||
<StatItem number="4+" label="Export Formats" />
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</PageWithNav>
|
||||
@@ -572,23 +463,3 @@ function FeaturePanel({
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function StatItem({ number, label }: { number: string; label: string }) {
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
className={css({
|
||||
fontSize: { base: '3xl', md: '4xl' },
|
||||
fontWeight: 'bold',
|
||||
mb: '2',
|
||||
background: 'linear-gradient(135deg, #fbbf24, #f59e0b)',
|
||||
backgroundClip: 'text',
|
||||
color: 'transparent',
|
||||
})}
|
||||
>
|
||||
{number}
|
||||
</div>
|
||||
<div className={css({ fontSize: 'sm', color: 'gray.300' })}>{label}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -280,8 +280,8 @@ export function PlayingPhase() {
|
||||
key={card.id}
|
||||
onClick={() => handleCardClick(card.id)}
|
||||
className={css({
|
||||
width: '90px',
|
||||
height: '90px',
|
||||
width: '140px',
|
||||
height: '140px',
|
||||
padding: '8px',
|
||||
border: '2px solid',
|
||||
borderColor: selectedCardId === card.id ? '#1976d2' : 'transparent',
|
||||
@@ -314,8 +314,8 @@ export function PlayingPhase() {
|
||||
<div
|
||||
dangerouslySetInnerHTML={{ __html: card.svgContent }}
|
||||
className={css({
|
||||
width: '74px',
|
||||
height: '74px',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
@@ -416,8 +416,8 @@ export function PlayingPhase() {
|
||||
key={`slot-${index}`}
|
||||
onClick={() => handleSlotClick(index)}
|
||||
className={css({
|
||||
width: '90px',
|
||||
height: '110px',
|
||||
width: '140px',
|
||||
height: '160px',
|
||||
padding: '0.5rem',
|
||||
borderRadius: '8px',
|
||||
border: '2px solid',
|
||||
@@ -458,13 +458,12 @@ export function PlayingPhase() {
|
||||
__html: card.svgContent,
|
||||
}}
|
||||
className={css({
|
||||
width: '70px',
|
||||
height: '70px',
|
||||
flex: 1,
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
overflow: 'hidden',
|
||||
margin: '0 auto',
|
||||
'& svg': {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
|
||||
@@ -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: 'teal',
|
||||
gradient: 'linear-gradient(135deg, #99f6e4, #5eead4)',
|
||||
borderColor: 'teal.200',
|
||||
...getGameTheme('teal'),
|
||||
available: true,
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
// ============================================================================
|
||||
|
||||
88
apps/web/src/lib/arcade/game-themes.ts
Normal file
88
apps/web/src/lib/arcade/game-themes.ts
Normal file
@@ -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<string, GameTheme>
|
||||
|
||||
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]
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "soroban-monorepo",
|
||||
"version": "4.13.2",
|
||||
"version": "4.13.13",
|
||||
"private": true,
|
||||
"description": "Beautiful Soroban Flashcard Generator - Monorepo",
|
||||
"workspaces": [
|
||||
|
||||
Reference in New Issue
Block a user