Compare commits

...

8 Commits

Author SHA1 Message Date
semantic-release-bot
291bcc581d chore(release): 4.11.1 [skip ci]
## [4.11.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.11.0...v4.11.1) (2025-10-18)

### Bug Fixes

* **card-sorting:** center AbacusReact SVGs in card tiles ([26edec1](26edec1bbf))
2025-10-18 22:41:08 +00:00
Thomas Hallock
26edec1bbf fix(card-sorting): center AbacusReact SVGs in card tiles
Improve centering of abacus SVGs within both available cards and position slots.

**Issue:**
AbacusReact SVGs were not properly centered within their card containers,
appearing off-center or misaligned.

**Fix:**
- **Available cards**: Added maxHeight: '100%' constraint and display: 'block'
  with margin: '0 auto' to SVG styling
- **Position slots**: Changed container to use flex: 1 and proper flex centering,
  constrained SVG to maxWidth: '70px' with centering styles

**Changes:**
- Available card SVG container: Added display flex with center alignment
- Available card SVG: maxHeight: '100%', display: 'block', margin: '0 auto'
- Position slot SVG container: width: '100%', flex: 1, flex centering
- Position slot SVG: maxWidth: '70px', maxHeight: '100%', display: 'block', margin: '0 auto'

Now AbacusReact SVGs render centered within their card tiles regardless of
the actual SVG dimensions.

src/arcade-games/card-sorting/components/PlayingPhase.tsx:314-330,457-475

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-18 17:40:03 -05:00
semantic-release-bot
da4fdc90e0 chore(release): 4.11.0 [skip ci]
## [4.11.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.10.6...v4.11.0) (2025-10-18)

### Features

* **home:** redesign home page to showcase complete platform ([ee6c4f2](ee6c4f2f4f))
2025-10-18 22:34:07 +00:00
Thomas Hallock
ee6c4f2f4f feat(home): redesign home page to showcase complete platform
Replace outdated "flashcard generator" landing page with comprehensive
platform showcase highlighting all three pillars: arcade games,
interactive learning, and flashcard creation.

**New Home Page Structure:**
- Compact hero with 3 CTAs: Play Games, Learn, Create
- 4 arcade game cards with player counts and mode tags
- Two-column feature sections for Learning & Flashcards
- Multiplayer features grid (4 cards)
- Stats banner: 4 games, 8 max players, 3 learning modes, 4+ formats

**Visual Design:**
- Smaller, denser components to fit more content
- Information-rich showcase vs marketing fluff
- Purple gradient hero matching guide branding
- Responsive grid layouts for all screen sizes

**Result:**
Home page now accurately represents the full platform:
multiplayer arcade games + interactive tutorials + flashcard tools.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-18 17:33:01 -05:00
semantic-release-bot
9b9f0cdbcb chore(release): 4.10.6 [skip ci]
## [4.10.6](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.10.5...v4.10.6) (2025-10-18)

### Bug Fixes

* **card-sorting:** position slots flow horizontally with wrap ([e14ffe4](e14ffe44d6))
2025-10-18 22:24:03 +00:00
Thomas Hallock
e14ffe44d6 fix(card-sorting): position slots flow horizontally with wrap
Fix position slots to match Python original - they should flow horizontally
and wrap, not stack vertically.

**Before:**
- Container: display: flex, flexDirection: 'column' (vertical stack)
- Each slot + insert button pair wrapped in a div
- One slot per line with insert button below it

**After (matching Python lines 3101-3111):**
- Container: display: flex, flexWrap: 'wrap' (horizontal flow)
- Slots and insert buttons flow together as siblings
- Wraps naturally based on container width
- Dashed border container with semi-transparent background

**Changes:**
- Position slots container: flex-wrap instead of flex-direction: column
- Removed wrapper div around slot+button pairs
- Added React keys to slots and buttons
- Added container styling (padding, background, border)

Now matches the Python original where all slots and + buttons flow
together horizontally and wrap as needed.

src/arcade-games/card-sorting/components/PlayingPhase.tsx:362-524

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-18 17:22:56 -05:00
semantic-release-bot
d5bc0bb27c chore(release): 4.10.5 [skip ci]
## [4.10.5](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.10.4...v4.10.5) (2025-10-18)

### Code Refactoring

* **arcade:** merge /arcade/room into /arcade route ([0790074](0790074ffc))
2025-10-18 22:11:17 +00:00
Thomas Hallock
0790074ffc refactor(arcade): merge /arcade/room into /arcade route
Simplify arcade routing by moving room page to main arcade route.
The /arcade route now handles both game selection and gameplay,
eliminating the need for a separate /arcade/room route.

**Changes:**
- Move /arcade/room/page.tsx → /arcade/page.tsx
- Update import paths for styled-system (room/ → arcade/)
- Remove legacy GAMES_CONFIG handling (now registry-only)
- Delete obsolete EnhancedChampionArena component

**Result:**
- Users navigate directly to /arcade for all arcade features
- Game selection UI shows when no game selected in room
- Selected game renders when room has gameName set
- Simpler, more intuitive URL structure

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-18 17:10:10 -05:00
6 changed files with 434 additions and 176 deletions

View File

@@ -1,3 +1,31 @@
## [4.11.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.11.0...v4.11.1) (2025-10-18)
### Bug Fixes
* **card-sorting:** center AbacusReact SVGs in card tiles ([26edec1](https://github.com/antialias/soroban-abacus-flashcards/commit/26edec1bbf038264405ec9d161edcd18f67a6fc6))
## [4.11.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.10.6...v4.11.0) (2025-10-18)
### Features
* **home:** redesign home page to showcase complete platform ([ee6c4f2](https://github.com/antialias/soroban-abacus-flashcards/commit/ee6c4f2f4f39e3b30f59c54866c3857c218fb80f))
## [4.10.6](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.10.5...v4.10.6) (2025-10-18)
### Bug Fixes
* **card-sorting:** position slots flow horizontally with wrap ([e14ffe4](https://github.com/antialias/soroban-abacus-flashcards/commit/e14ffe44d66d0c97bc0cc4e0c255698e88ce723a))
## [4.10.5](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.10.4...v4.10.5) (2025-10-18)
### Code Refactoring
* **arcade:** merge /arcade/room into /arcade route ([0790074](https://github.com/antialias/soroban-abacus-flashcards/commit/0790074ffc5008bce9a162fe0ddbd1d5c214c4f7))
## [4.10.4](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.10.3...v4.10.4) (2025-10-18)

View File

@@ -97,7 +97,8 @@
"Bash(pnpm exec turbo build --filter=@soroban/web)",
"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\"\")\"\"')"
"Bash(do gh run list --limit 1 --workflow=\"Build and Deploy\" --json conclusion,status --jq '.[0] | \"\"\\(.status) - \\(.conclusion // \"\"running\"\")\"\"')",
"WebFetch(domain:abaci.one)"
],
"deny": [],
"ask": []

View File

@@ -5,16 +5,9 @@ import { useRoomData, useSetRoomGame } from '@/hooks/useRoomData'
import { GAMES_CONFIG } from '@/components/GameSelector'
import type { GameType } from '@/components/GameSelector'
import { PageWithNav } from '@/components/PageWithNav'
import { css } from '../../../../styled-system/css'
import { css } from '../../../styled-system/css'
import { getAllGames, getGame, hasGame } from '@/lib/arcade/game-registry'
// Map GameType keys to internal game names
// Note: "battle-arena" removed - now handled by game registry as "matching"
const GAME_TYPE_TO_NAME: Record<GameType, string> = {
'complement-race': 'complement-race',
'master-organizer': 'master-organizer',
}
/**
* /arcade - Renders the game for the user's current room
* Since users can only be in one room at a time, this is a simple singular route
@@ -84,7 +77,7 @@ export default function RoomPage() {
const handleGameSelect = (gameType: GameType) => {
console.log('[RoomPage] handleGameSelect called with gameType:', gameType)
// Check if it's a registry game first
// All games are now in the registry
if (hasGame(gameType)) {
const gameDef = getGame(gameType)
if (!gameDef?.manifest.available) {
@@ -95,47 +88,12 @@ export default function RoomPage() {
console.log('[RoomPage] Selecting registry game:', gameType)
setRoomGame({
roomId: roomData.id,
gameName: gameType, // Use the game name directly for registry games
gameName: gameType,
})
return
}
// Legacy game handling
const gameConfig = GAMES_CONFIG[gameType as keyof typeof GAMES_CONFIG]
if (!gameConfig) {
console.log('[RoomPage] Unknown game type:', gameType)
return
}
console.log('[RoomPage] Game config:', {
name: gameConfig.name,
available: 'available' in gameConfig ? gameConfig.available : true,
})
if ('available' in gameConfig && gameConfig.available === false) {
console.log('[RoomPage] Game not available, blocking selection')
return // Don't allow selecting unavailable games
}
// Map GameType to internal game name
const internalGameName = GAME_TYPE_TO_NAME[gameType]
console.log('[RoomPage] Mapping:', {
gameType,
internalGameName,
mappingExists: !!internalGameName,
})
console.log('[RoomPage] Calling setRoomGame with:', {
roomId: roomData.id,
gameName: internalGameName,
preservingGameConfig: true,
})
// Don't pass gameConfig - we want to preserve existing settings for all games
setRoomGame({
roomId: roomData.id,
gameName: internalGameName,
})
console.log('[RoomPage] Unknown game type:', gameType)
}
return (
@@ -178,7 +136,7 @@ export default function RoomPage() {
})}
>
{/* Legacy games */}
{Object.entries(GAMES_CONFIG).map(([gameType, config]) => {
{Object.entries(GAMES_CONFIG).map(([gameType, config]: [string, any]) => {
const isAvailable = !('available' in config) || config.available !== false
return (
<button

View File

@@ -3,133 +3,400 @@
import Link from 'next/link'
import { PageWithNav } from '@/components/PageWithNav'
import { css } from '../../styled-system/css'
import { container, hstack, stack } from '../../styled-system/patterns'
import { container, grid, hstack, stack } from '../../styled-system/patterns'
export default function HomePage() {
return (
<PageWithNav navTitle="Soroban Flashcards" navEmoji="🧮">
<div
className={css({
minHeight: '100vh',
bg: 'gradient-to-br from-brand.50 to-brand.100',
})}
>
{/* Hero Section */}
<main className={container({ maxW: '6xl', px: '4' })}>
<div
className={stack({
gap: '12',
py: '16',
align: 'center',
textAlign: 'center',
})}
>
{/* Hero Content */}
<div className={stack({ gap: '6', maxW: '4xl' })}>
<PageWithNav navTitle="Soroban Mastery Platform" navEmoji="🧮">
<div className={css({ bg: 'gray.50', minHeight: '100vh' })}>
{/* Compact Hero */}
<div
className={css({
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
color: 'white',
py: { base: '8', md: '12' },
})}
>
<div className={container({ maxW: '6xl', px: '4' })}>
<div className={css({ textAlign: 'center', maxW: '4xl', mx: 'auto' })}>
<h1
className={css({
fontSize: { base: '4xl', md: '6xl' },
fontSize: { base: '3xl', md: '5xl' },
fontWeight: 'bold',
color: 'gray.900',
mb: '4',
lineHeight: 'tight',
})}
>
Beautiful Soroban <span className={css({ color: 'brand.600' })}>Flashcards</span>
Master Soroban Through{' '}
<span className={css({ color: 'yellow.300' })}>Play & Practice</span>
</h1>
<p
className={css({
fontSize: { base: 'lg', md: 'xl' },
color: 'gray.600',
maxW: '2xl',
mx: 'auto',
})}
>
Create stunning, educational flashcards with authentic Japanese abacus
representations. Perfect for teachers, students, and mental math enthusiasts.
<p className={css({ fontSize: { base: 'md', md: 'lg' }, opacity: 0.95, mb: '6' })}>
Interactive tutorials, multiplayer games, and beautiful flashcardsyour complete
soroban learning ecosystem
</p>
<div className={hstack({ gap: '4', justify: 'center', mt: '8' })}>
<div className={hstack({ gap: '3', justify: 'center', flexWrap: 'wrap' })}>
<Link
href="/create"
href="/arcade"
className={css({
px: '8',
py: '4',
bg: 'brand.600',
color: 'white',
fontSize: 'lg',
fontWeight: 'semibold',
rounded: 'xl',
shadow: 'card',
px: '6',
py: '3',
bg: 'yellow.400',
color: 'gray.900',
fontWeight: 'bold',
rounded: 'lg',
shadow: 'lg',
_hover: { bg: 'yellow.300', transform: 'translateY(-2px)' },
transition: 'all',
_hover: {
bg: 'brand.700',
transform: 'translateY(-2px)',
shadow: 'modal',
},
})}
>
Start Creating
🎮 Play Games
</Link>
<Link
href="/guide"
className={css({
px: '8',
py: '4',
px: '6',
py: '3',
bg: 'white',
color: 'brand.700',
fontSize: 'lg',
fontWeight: 'semibold',
rounded: 'xl',
shadow: 'card',
border: '2px solid',
borderColor: 'brand.200',
color: 'purple.700',
fontWeight: 'bold',
rounded: 'lg',
shadow: 'lg',
_hover: { bg: 'gray.100', transform: 'translateY(-2px)' },
transition: 'all',
_hover: {
borderColor: 'brand.400',
transform: 'translateY(-2px)',
},
})}
>
📚 Learn Soroban
📚 Learn
</Link>
<Link
href="/create"
className={css({
px: '6',
py: '3',
bg: 'purple.600',
color: 'white',
fontWeight: 'bold',
rounded: 'lg',
shadow: 'lg',
_hover: { bg: 'purple.700', transform: 'translateY(-2px)' },
transition: 'all',
})}
>
🎨 Create
</Link>
</div>
</div>
</div>
</div>
{/* Features Grid */}
<div
{/* Main Content Grid */}
<div className={container({ maxW: '7xl', px: '4', py: '8' })}>
<div className={stack({ gap: '8' })}>
{/* Arcade Games Section */}
<section>
<div className={hstack({ justify: 'space-between', mb: '4' })}>
<h2 className={css({ fontSize: '2xl', fontWeight: 'bold', color: 'gray.900' })}>
🕹 Multiplayer Arcade
</h2>
<Link
href="/arcade"
className={css({
fontSize: 'sm',
color: 'purple.600',
fontWeight: 'semibold',
_hover: { color: 'purple.700' },
})}
>
View All
</Link>
</div>
<div className={grid({ columns: { base: 1, sm: 2, lg: 4 }, gap: '4' })}>
<GameCard
icon="🧠"
title="Memory Lightning"
description="Memorize soroban numbers"
players="1-8 players"
tags={['Co-op', 'Competitive']}
/>
<GameCard
icon="⚔️"
title="Matching Pairs"
description="Turn-based card battles"
players="1-4 players"
tags={['Pattern Recognition']}
/>
<GameCard
icon="🏁"
title="Speed Race"
description="Race AI with complements"
players="1-4 players + AI"
tags={['Practice', 'Sprint', 'Survival']}
/>
<GameCard
icon="🔢"
title="Card Sorting"
description="Arrange cards visually"
players="Solo challenge"
tags={['Visual Literacy']}
/>
</div>
</section>
{/* Two Column Layout */}
<div className={grid({ columns: { base: 1, lg: 2 }, gap: '6' })}>
{/* Interactive Learning */}
<section
className={css({
bg: 'white',
rounded: 'xl',
p: '6',
shadow: 'sm',
border: '1px solid',
borderColor: 'gray.200',
})}
>
<h2
className={css({
fontSize: 'xl',
fontWeight: 'bold',
color: 'gray.900',
mb: '4',
})}
>
📚 Interactive Learning
</h2>
<div className={stack({ gap: '3' })}>
<FeatureItem
icon="🔍"
title="Reading Numbers"
description="Visual tutorials on interpreting bead positions"
/>
<FeatureItem
icon="🧮"
title="Arithmetic Operations"
description="Step-by-step interactive practice: +, , ×, ÷"
/>
<FeatureItem
icon="🎯"
title="Guided Tutorials"
description="Hands-on exercises with instant feedback"
/>
</div>
<Link
href="/guide"
className={css({
display: 'block',
mt: '4',
py: '2',
textAlign: 'center',
bg: 'purple.50',
color: 'purple.700',
fontWeight: 'semibold',
rounded: 'lg',
_hover: { bg: 'purple.100' },
})}
>
Start Learning
</Link>
</section>
{/* Flashcard Creator */}
<section
className={css({
bg: 'white',
rounded: 'xl',
p: '6',
shadow: 'sm',
border: '1px solid',
borderColor: 'gray.200',
})}
>
<h2
className={css({
fontSize: 'xl',
fontWeight: 'bold',
color: 'gray.900',
mb: '4',
})}
>
🎨 Flashcard Creator
</h2>
<div className={stack({ gap: '3' })}>
<FeatureItem
icon="📄"
title="Multiple Formats"
description="PDF, PNG, SVG, interactive HTML"
/>
<FeatureItem
icon="🎨"
title="Custom Styling"
description="Bead shapes, color schemes, fonts, layouts"
/>
<FeatureItem
icon="📐"
title="Paper Options"
description="A3, A4, A5, US Letter • Portrait/Landscape"
/>
</div>
<Link
href="/create"
className={css({
display: 'block',
mt: '4',
py: '2',
textAlign: 'center',
bg: 'blue.50',
color: 'blue.700',
fontWeight: 'semibold',
rounded: 'lg',
_hover: { bg: 'blue.100' },
})}
>
Create Flashcards
</Link>
</section>
</div>
{/* Multiplayer Features */}
<section>
<h2
className={css({ fontSize: '2xl', fontWeight: 'bold', color: 'gray.900', mb: '4' })}
>
🌐 Multiplayer Features
</h2>
<div className={grid({ columns: { base: 1, sm: 2, md: 4 }, gap: '4' })}>
<FeatureCard
icon="🎭"
title="Player Characters"
description="Custom names, emojis, and colors for each player"
/>
<FeatureCard
icon="🏠"
title="Private Rooms"
description="Create rooms with codes, passwords, or approval-only access"
/>
<FeatureCard
icon="⚡"
title="Real-time Play"
description="Socket.io powered instant multiplayer sync"
/>
<FeatureCard
icon="📊"
title="Stats & Progress"
description="Track wins, accuracy, and performance across games"
/>
</div>
</section>
{/* Quick Stats */}
<section
className={css({
display: 'grid',
gridTemplateColumns: { base: '1', md: '3' },
gap: '8',
mt: '16',
w: 'full',
bg: 'gradient-to-r',
gradientFrom: 'purple.600',
gradientTo: 'indigo.600',
rounded: 'xl',
p: '6',
color: 'white',
})}
>
<FeatureCard
icon="🎨"
title="Beautiful Design"
description="Vector graphics, color schemes, authentic bead positioning"
/>
<FeatureCard
icon="⚡"
title="Instant Generation"
description="Create PDFs, interactive HTML, PNGs, and SVGs in seconds"
/>
<FeatureCard
icon="🎯"
title="Educational Focus"
description="Perfect for teachers, students, and soroban enthusiasts"
/>
</div>
<h2
className={css({
fontSize: 'xl',
fontWeight: 'bold',
mb: '4',
textAlign: 'center',
})}
>
Complete Soroban Learning Platform
</h2>
<div className={grid({ columns: { base: 2, md: 4 }, gap: '6', 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>
</main>
</div>
</div>
</PageWithNav>
)
}
function GameCard({
icon,
title,
description,
players,
tags,
}: {
icon: string
title: string
description: string
players: string
tags: string[]
}) {
return (
<div
className={css({
bg: 'white',
rounded: 'lg',
p: '4',
shadow: 'sm',
border: '1px solid',
borderColor: 'gray.200',
transition: 'all',
_hover: { shadow: 'md', transform: 'translateY(-2px)' },
})}
>
<div className={css({ fontSize: '2xl', mb: '2' })}>{icon}</div>
<h3 className={css({ fontSize: 'md', fontWeight: 'bold', color: 'gray.900', mb: '1' })}>
{title}
</h3>
<p className={css({ fontSize: 'sm', color: 'gray.600', mb: '2' })}>{description}</p>
<p className={css({ fontSize: 'xs', color: 'gray.500', mb: '2' })}>{players}</p>
<div className={hstack({ gap: '1', flexWrap: 'wrap' })}>
{tags.map((tag) => (
<span
key={tag}
className={css({
fontSize: 'xs',
px: '2',
py: '0.5',
bg: 'purple.100',
color: 'purple.700',
rounded: 'full',
})}
>
{tag}
</span>
))}
</div>
</div>
)
}
function FeatureItem({
icon,
title,
description,
}: {
icon: string
title: string
description: string
}) {
return (
<div className={hstack({ gap: '3', alignItems: 'flex-start' })}>
<div className={css({ fontSize: 'xl', flexShrink: 0 })}>{icon}</div>
<div>
<h4 className={css({ fontSize: 'sm', fontWeight: 'semibold', color: 'gray.900' })}>
{title}
</h4>
<p className={css({ fontSize: 'xs', color: 'gray.600' })}>{description}</p>
</div>
</div>
)
}
function FeatureCard({
icon,
title,
@@ -142,44 +409,31 @@ function FeatureCard({
return (
<div
className={css({
p: '8',
bg: 'white',
rounded: '2xl',
shadow: 'card',
rounded: 'lg',
p: '4',
shadow: 'sm',
border: '1px solid',
borderColor: 'gray.200',
textAlign: 'center',
transition: 'all',
_hover: {
transform: 'translateY(-4px)',
shadow: 'modal',
},
})}
>
<div
className={css({
fontSize: '4xl',
mb: '4',
})}
>
{icon}
</div>
<h3
className={css({
fontSize: 'xl',
fontWeight: 'bold',
color: 'gray.900',
mb: '3',
})}
>
<div className={css({ fontSize: '2xl', mb: '2' })}>{icon}</div>
<h3 className={css({ fontSize: 'sm', fontWeight: 'bold', color: 'gray.900', mb: '1' })}>
{title}
</h3>
<p
className={css({
color: 'gray.600',
lineHeight: 'relaxed',
})}
>
<p className={css({ fontSize: 'xs', color: 'gray.600', lineHeight: 'relaxed' })}>
{description}
</p>
</div>
)
}
function StatItem({ number, label }: { number: string; label: string }) {
return (
<div>
<div className={css({ fontSize: '3xl', fontWeight: 'bold', mb: '1' })}>{number}</div>
<div className={css({ fontSize: 'sm', opacity: 0.9 })}>{label}</div>
</div>
)
}

View File

@@ -321,7 +321,10 @@ export function PlayingPhase() {
justifyContent: 'center',
'& svg': {
maxWidth: '100%',
maxHeight: '100%',
height: 'auto',
display: 'block',
margin: '0 auto',
},
})}
/>
@@ -362,8 +365,14 @@ export function PlayingPhase() {
<div
className={css({
display: 'flex',
flexDirection: 'column',
gap: '0.25rem',
flexWrap: 'wrap',
gap: '8px',
justifyContent: 'center',
alignItems: 'center',
padding: '15px',
background: 'rgba(255,255,255,0.7)',
borderRadius: '8px',
border: '2px dashed #2c5f76',
})}
>
{/* Insert button before first position */}
@@ -402,9 +411,10 @@ export function PlayingPhase() {
const isEmpty = card === null
return (
<div key={index}>
<>
{/* Position slot */}
<div
key={`slot-${index}`}
onClick={() => handleSlotClick(index)}
className={css({
width: '90px',
@@ -449,10 +459,17 @@ export function PlayingPhase() {
__html: card.svgContent,
}}
className={css({
width: '70px',
width: '100%',
flex: 1,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
'& svg': {
width: '100%',
maxWidth: '70px',
maxHeight: '100%',
height: 'auto',
display: 'block',
margin: '0 auto',
},
})}
/>
@@ -483,6 +500,7 @@ export function PlayingPhase() {
{/* Insert button after this position */}
<button
key={`insert-${index + 1}`}
type="button"
onClick={() => handleInsertClick(index + 1)}
disabled={!selectedCardId}
@@ -498,7 +516,6 @@ export function PlayingPhase() {
cursor: selectedCardId ? 'pointer' : 'default',
opacity: selectedCardId ? 1 : 0.3,
transition: 'all 0.2s',
marginTop: '0.25rem',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
@@ -511,7 +528,7 @@ export function PlayingPhase() {
>
+
</button>
</div>
</>
)
})}
</div>

View File

@@ -1,6 +1,6 @@
{
"name": "soroban-monorepo",
"version": "4.10.4",
"version": "4.11.1",
"private": true,
"description": "Beautiful Soroban Flashcard Generator - Monorepo",
"workspaces": [