Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8de4015250 | ||
|
|
4c338726c1 | ||
|
|
7e4d32460a | ||
|
|
f0bb411573 |
10
.github/workflows/deploy-storybook.yml
vendored
10
.github/workflows/deploy-storybook.yml
vendored
@@ -31,7 +31,7 @@ jobs:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '18'
|
||||
node-version: '20'
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
@@ -44,7 +44,7 @@ jobs:
|
||||
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||
|
||||
- name: Setup pnpm cache
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
@@ -173,15 +173,15 @@ jobs:
|
||||
|
||||
- name: Setup Pages
|
||||
if: github.ref == 'refs/heads/main'
|
||||
uses: actions/configure-pages@v3
|
||||
uses: actions/configure-pages@v4
|
||||
|
||||
- name: Upload artifact
|
||||
if: github.ref == 'refs/heads/main'
|
||||
uses: actions/upload-pages-artifact@v2
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: ./pages
|
||||
|
||||
- name: Deploy to GitHub Pages
|
||||
if: github.ref == 'refs/heads/main'
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v2
|
||||
uses: actions/deploy-pages@v3
|
||||
14
CHANGELOG.md
14
CHANGELOG.md
@@ -1,3 +1,17 @@
|
||||
## [1.1.2](https://github.com/antialias/soroban-abacus-flashcards/compare/v1.1.1...v1.1.2) (2025-09-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* upgrade Node.js to version 20 for Storybook compatibility ([4c33872](https://github.com/antialias/soroban-abacus-flashcards/commit/4c338726c13af623b1536f75fe6a18e0ab529377))
|
||||
|
||||
## [1.1.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v1.1.0...v1.1.1) (2025-09-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* update GitHub Actions to use latest action versions for Storybook deployment ([f0bb411](https://github.com/antialias/soroban-abacus-flashcards/commit/f0bb411573c8496d11d560fa7efe9324015412b2))
|
||||
|
||||
# [1.1.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v1.0.0...v1.1.0) (2025-09-28)
|
||||
|
||||
|
||||
|
||||
@@ -690,7 +690,7 @@ export function EnhancedChampionArena({ onGameModeChange, onConfigurePlayer, cla
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Prominent Game Selector - takes remaining space */}
|
||||
{/* Prominent Game Selector - constrained to available space */}
|
||||
<div className={css({
|
||||
flex: 1,
|
||||
mt: { base: '1', md: '2' },
|
||||
@@ -698,7 +698,9 @@ export function EnhancedChampionArena({ onGameModeChange, onConfigurePlayer, cla
|
||||
borderTop: '2px solid',
|
||||
borderColor: 'gray.200',
|
||||
minHeight: 0,
|
||||
overflow: 'auto'
|
||||
overflow: 'hidden',
|
||||
display: 'flex',
|
||||
flexDirection: 'column'
|
||||
})}>
|
||||
<GameSelector
|
||||
variant="detailed"
|
||||
|
||||
@@ -42,7 +42,7 @@ export function GameCard({
|
||||
className={css({
|
||||
background: config.gradient || 'white',
|
||||
rounded: variant === 'compact' ? 'xl' : '2xl',
|
||||
p: variant === 'compact' ? '4' : '8',
|
||||
p: variant === 'compact' ? '3' : { base: '3', md: '4', lg: '6' },
|
||||
border: '2px solid',
|
||||
borderColor: available ? config.borderColor || 'blue.200' : 'gray.200',
|
||||
boxShadow: variant === 'compact'
|
||||
@@ -52,7 +52,10 @@ export function GameCard({
|
||||
cursor: available ? 'pointer' : 'not-allowed',
|
||||
transition: 'all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275)',
|
||||
position: 'relative',
|
||||
overflow: 'visible',
|
||||
overflow: 'hidden',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '100%',
|
||||
_hover: available ? {
|
||||
transform: variant === 'compact' ? 'translateY(-2px) scale(1.02)' : 'translateY(-8px) scale(1.02)',
|
||||
boxShadow: variant === 'compact'
|
||||
@@ -65,100 +68,125 @@ export function GameCard({
|
||||
{/* Game icon with enhanced styling */}
|
||||
<div className={css({
|
||||
textAlign: 'center',
|
||||
mb: variant === 'compact' ? '2' : '4'
|
||||
flex: 1,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'space-between',
|
||||
minHeight: 0
|
||||
})}>
|
||||
{/* Top section - icon and title */}
|
||||
<div className={css({
|
||||
fontSize: variant === 'compact' ? '2xl' : '4xl',
|
||||
mb: variant === 'compact' ? '2' : '3',
|
||||
display: 'inline-block',
|
||||
transform: 'perspective(1000px)',
|
||||
transition: 'all 0.3s ease'
|
||||
flexShrink: 0
|
||||
})}>
|
||||
{config.icon}
|
||||
<div className={css({
|
||||
fontSize: variant === 'compact' ? 'xl' : { base: 'xl', md: '2xl', lg: '3xl' },
|
||||
mb: variant === 'compact' ? '1' : { base: '1', md: '2' },
|
||||
display: 'inline-block',
|
||||
transform: 'perspective(1000px)',
|
||||
transition: 'all 0.3s ease'
|
||||
})}>
|
||||
{config.icon}
|
||||
</div>
|
||||
|
||||
<h4 className={css({
|
||||
fontSize: variant === 'compact' ? 'md' : { base: 'lg', md: 'xl', lg: '2xl' },
|
||||
fontWeight: 'bold',
|
||||
color: 'gray.900',
|
||||
mb: variant === 'compact' ? '0.5' : { base: '1', md: '2' }
|
||||
})}>
|
||||
{variant === 'detailed' ? config.fullName || config.name : config.name}
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
<h4 className={css({
|
||||
fontSize: variant === 'compact' ? 'lg' : '2xl',
|
||||
fontWeight: 'bold',
|
||||
color: 'gray.900',
|
||||
mb: variant === 'compact' ? '1' : '3'
|
||||
})}>
|
||||
{variant === 'detailed' ? config.fullName || config.name : config.name}
|
||||
</h4>
|
||||
|
||||
{/* Middle section - description (flexible) */}
|
||||
{variant === 'detailed' && (
|
||||
<p className={css({
|
||||
fontSize: 'base',
|
||||
color: 'gray.600',
|
||||
mb: '4',
|
||||
lineHeight: 'relaxed'
|
||||
})}>
|
||||
{config.longDescription || config.description}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* Feature chips */}
|
||||
{variant === 'detailed' && config.chips && (
|
||||
<div className={css({
|
||||
flex: 1,
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
gap: '2',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
mb: '4'
|
||||
minHeight: 0
|
||||
})}>
|
||||
{config.chips.map((chip, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className={css({
|
||||
px: '3',
|
||||
py: '1',
|
||||
background: config.color === 'green'
|
||||
? 'linear-gradient(135deg, #d1fae5, #a7f3d0)'
|
||||
: config.color === 'purple'
|
||||
? 'linear-gradient(135deg, #e0e7ff, #c7d2fe)'
|
||||
: 'linear-gradient(135deg, #dbeafe, #bfdbfe)',
|
||||
color: config.color === 'green'
|
||||
? 'green.800'
|
||||
: config.color === 'purple'
|
||||
? 'indigo.800'
|
||||
: 'blue.800',
|
||||
rounded: 'full',
|
||||
fontSize: 'xs',
|
||||
fontWeight: 'semibold',
|
||||
border: '1px solid',
|
||||
borderColor: config.color === 'green'
|
||||
? 'green.200'
|
||||
: config.color === 'purple'
|
||||
? 'indigo.200'
|
||||
: 'blue.200',
|
||||
opacity: available ? 1 : 0.8
|
||||
})}
|
||||
>
|
||||
{chip}
|
||||
</span>
|
||||
))}
|
||||
<p className={css({
|
||||
fontSize: { base: 'xs', md: 'sm', lg: 'base' },
|
||||
color: 'gray.600',
|
||||
lineHeight: 'relaxed',
|
||||
display: { base: 'none', sm: 'block' },
|
||||
overflow: 'hidden'
|
||||
})}>
|
||||
{config.description}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Player availability indicator */}
|
||||
{/* Bottom section - chips and status */}
|
||||
<div className={css({
|
||||
fontSize: variant === 'compact' ? 'xs' : 'sm',
|
||||
color: available ? 'green.600' : 'red.600',
|
||||
fontWeight: 'semibold',
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
gap: '1',
|
||||
px: '2',
|
||||
py: '1',
|
||||
background: available ? 'rgba(16, 185, 129, 0.1)' : 'rgba(239, 68, 68, 0.1)',
|
||||
rounded: 'full',
|
||||
border: '1px solid',
|
||||
borderColor: available ? 'green.200' : 'red.200'
|
||||
flexShrink: 0,
|
||||
mt: 'auto'
|
||||
})}>
|
||||
{activePlayerCount <= config.maxPlayers
|
||||
? `✓ ${activePlayerCount}/${config.maxPlayers} ${activePlayerCount === 1 ? 'player' : 'players'}`
|
||||
: `✗ Too many players (max ${config.maxPlayers})`
|
||||
}
|
||||
{/* Feature chips */}
|
||||
{variant === 'detailed' && config.chips && (
|
||||
<div className={css({
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
gap: { base: '1', md: '2' },
|
||||
justifyContent: 'center',
|
||||
mb: { base: '2', md: '3' }
|
||||
})}>
|
||||
{config.chips.slice(0, 2).map((chip, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className={css({
|
||||
px: { base: '2', md: '3' },
|
||||
py: { base: '0.5', md: '1' },
|
||||
background: config.color === 'green'
|
||||
? 'linear-gradient(135deg, #d1fae5, #a7f3d0)'
|
||||
: config.color === 'purple'
|
||||
? 'linear-gradient(135deg, #e0e7ff, #c7d2fe)'
|
||||
: 'linear-gradient(135deg, #dbeafe, #bfdbfe)',
|
||||
color: config.color === 'green'
|
||||
? 'green.800'
|
||||
: config.color === 'purple'
|
||||
? 'indigo.800'
|
||||
: 'blue.800',
|
||||
rounded: 'full',
|
||||
fontSize: { base: '2xs', md: 'xs' },
|
||||
fontWeight: 'semibold',
|
||||
border: '1px solid',
|
||||
borderColor: config.color === 'green'
|
||||
? 'green.200'
|
||||
: config.color === 'purple'
|
||||
? 'indigo.200'
|
||||
: 'blue.200',
|
||||
opacity: available ? 1 : 0.8
|
||||
})}
|
||||
>
|
||||
{chip}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Player availability indicator */}
|
||||
<div className={css({
|
||||
fontSize: variant === 'compact' ? '2xs' : { base: '2xs', md: 'xs', lg: 'sm' },
|
||||
color: available ? 'green.600' : 'red.600',
|
||||
fontWeight: 'semibold',
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
gap: '1',
|
||||
px: { base: '1.5', md: '2' },
|
||||
py: { base: '0.5', md: '1' },
|
||||
background: available ? 'rgba(16, 185, 129, 0.1)' : 'rgba(239, 68, 68, 0.1)',
|
||||
rounded: 'full',
|
||||
border: '1px solid',
|
||||
borderColor: available ? 'green.200' : 'red.200'
|
||||
})}>
|
||||
{activePlayerCount <= config.maxPlayers
|
||||
? `✓ ${activePlayerCount}/${config.maxPlayers} ${activePlayerCount === 1 ? 'player' : 'players'}`
|
||||
: `✗ Too many players (max ${config.maxPlayers})`
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -84,14 +84,20 @@ export function GameSelector({
|
||||
const { activePlayerCount } = useGameMode()
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className={css({
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
overflow: 'hidden'
|
||||
}, className)}>
|
||||
{showHeader && (
|
||||
<h3 className={css({
|
||||
fontSize: variant === 'compact' ? 'lg' : 'xl',
|
||||
fontSize: variant === 'compact' ? 'lg' : { base: 'lg', md: 'xl' },
|
||||
fontWeight: 'bold',
|
||||
color: 'gray.800',
|
||||
mb: '4',
|
||||
textAlign: 'center'
|
||||
mb: { base: '2', md: '3' },
|
||||
textAlign: 'center',
|
||||
flexShrink: 0
|
||||
})}>
|
||||
🎮 Available Games
|
||||
</h3>
|
||||
@@ -111,8 +117,12 @@ export function GameSelector({
|
||||
) : (
|
||||
<div className={css({
|
||||
display: 'grid',
|
||||
gridTemplateColumns: { base: '1fr', md: variant === 'compact' ? 'repeat(2, 1fr)' : 'repeat(2, 1fr)' },
|
||||
gap: variant === 'compact' ? '3' : '4'
|
||||
gridTemplateColumns: { base: '1fr', md: 'repeat(2, 1fr)' },
|
||||
gridTemplateRows: { base: 'repeat(4, 1fr)', md: 'repeat(2, 1fr)' },
|
||||
gap: variant === 'compact' ? '2' : { base: '2', md: '3' },
|
||||
flex: 1,
|
||||
minHeight: 0,
|
||||
overflow: 'hidden'
|
||||
})}>
|
||||
{Object.entries(GAMES_CONFIG).map(([gameType, config]) => (
|
||||
<GameCard
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "soroban-monorepo",
|
||||
"version": "1.1.0",
|
||||
"version": "1.1.2",
|
||||
"private": true,
|
||||
"description": "Beautiful Soroban Flashcard Generator - Monorepo",
|
||||
"workspaces": [
|
||||
|
||||
Reference in New Issue
Block a user