feat(tutorial): add dark theme and column control props
Add `theme` and `abacusColumns` props to TutorialPlayer for better customization:
- theme: 'light' | 'dark' controls all color schemes
- abacusColumns: number controls abacus column count (default 5)
Updated homepage to use:
- abacusColumns={2} for simpler 2+3 demo
- theme="dark" for cohesive integration with dark page design
- Vertical layout with "What You'll Learn" below tutorial
Dark theme styling:
- Transparent dark backgrounds for all containers
- Muted text colors (gray.200-gray.400)
- Subtle borders and shadows
- Removed bright yellow/amber gradients
All changes maintain backward compatibility - defaults to light theme with 5 columns.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -292,10 +292,10 @@ export default function RoomPage() {
|
||||
disabled={isDisabled}
|
||||
style={{
|
||||
background: gameDef.manifest.gradient,
|
||||
borderColor: gameDef.manifest.borderColor,
|
||||
}}
|
||||
className={css({
|
||||
border: '2px solid',
|
||||
borderColor: gameDef.manifest.borderColor,
|
||||
borderRadius: '2xl',
|
||||
padding: '6',
|
||||
cursor: isDisabled ? 'not-allowed' : 'pointer',
|
||||
|
||||
@@ -206,122 +206,49 @@ export default function HomePage() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className={grid({ columns: { base: 1, lg: 2 }, gap: '8' })}>
|
||||
{/* Live demo */}
|
||||
{/* Live demo and learning objectives */}
|
||||
<div
|
||||
className={css({
|
||||
bg: 'rgba(0, 0, 0, 0.4)',
|
||||
rounded: 'xl',
|
||||
p: '8',
|
||||
border: '1px solid',
|
||||
borderColor: 'gray.700',
|
||||
shadow: 'lg',
|
||||
maxW: '900px',
|
||||
mx: 'auto',
|
||||
})}
|
||||
>
|
||||
<TutorialPlayer
|
||||
tutorial={friendsOf5Tutorial}
|
||||
isDebugMode={false}
|
||||
showDebugPanel={false}
|
||||
hideNavigation={true}
|
||||
abacusColumns={2}
|
||||
theme="dark"
|
||||
/>
|
||||
|
||||
{/* What you'll learn - below tutorial */}
|
||||
<div
|
||||
className={`homepage-tutorial-demo ${css({
|
||||
bg: 'rgba(0, 0, 0, 0.4)',
|
||||
rounded: 'xl',
|
||||
p: '6',
|
||||
border: '1px solid',
|
||||
className={css({
|
||||
mt: '8',
|
||||
pt: '6',
|
||||
borderTop: '1px solid',
|
||||
borderColor: 'gray.700',
|
||||
shadow: 'lg',
|
||||
})}`}
|
||||
>
|
||||
<style jsx>{`
|
||||
/* Hide close button on homepage tutorial */
|
||||
:global(.homepage-tutorial-demo .coachbar__hide) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Soften white backgrounds to light gray */
|
||||
:global(.homepage-tutorial-demo) {
|
||||
/* Override abacus container white background */
|
||||
& :global(> div > div > div) {
|
||||
background: #f9fafb !important; /* gray.50 equivalent */
|
||||
}
|
||||
}
|
||||
|
||||
/* Mute text colors to darker grays */
|
||||
:global(.homepage-tutorial-demo h2) {
|
||||
color: #1f2937 !important; /* gray.800 */
|
||||
}
|
||||
|
||||
:global(.homepage-tutorial-demo p) {
|
||||
color: #4b5563 !important; /* gray.600 - muted from gray.700 */
|
||||
}
|
||||
|
||||
/* Soften guidance box - add more transparency and muted colors */
|
||||
:global(.homepage-tutorial-demo) :global(div[style*='linear-gradient'][style*='255,248,225']) {
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
rgba(255, 248, 225, 0.7) 0%,
|
||||
rgba(254, 252, 232, 0.75) 50%,
|
||||
rgba(255, 245, 157, 0.1) 100%
|
||||
) !important;
|
||||
padding: 1.75rem !important; /* Increase from p-5 (1.25rem) */
|
||||
box-shadow: 0 4px 16px rgba(251, 191, 36, 0.06),
|
||||
0 1px 4px rgba(0, 0, 0, 0.03),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.4) !important;
|
||||
}
|
||||
|
||||
/* Soften pedagogical decomposition inner box */
|
||||
:global(.homepage-tutorial-demo) :global(div[style*='linear-gradient'][style*='248,250,252']) {
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
rgba(249, 250, 251, 0.6) 0%,
|
||||
rgba(243, 244, 246, 0.7) 100%
|
||||
) !important; /* Softer gray gradient */
|
||||
padding: 1rem !important; /* Increase from 0.75rem */
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04), inset 0 1px 0 rgba(255, 255, 255, 0.5) !important;
|
||||
}
|
||||
|
||||
/* Soften shadows on abacus container */
|
||||
:global(.homepage-tutorial-demo) :global(div[style*='box-shadow']) {
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05),
|
||||
0 2px 4px -1px rgba(0, 0, 0, 0.03) !important;
|
||||
}
|
||||
|
||||
/* Mute amber text colors */
|
||||
:global(.homepage-tutorial-demo) :global([style*='color'][style*='amber']) {
|
||||
color: #78716c !important; /* stone.500 - more muted than amber */
|
||||
}
|
||||
|
||||
/* Mute slate text colors in decomposition */
|
||||
:global(.homepage-tutorial-demo) :global([style*='color'][style*='slate']) {
|
||||
color: #64748b !important; /* slate.600 - muted from slate.800 */
|
||||
}
|
||||
|
||||
/* Soften CoachBar if visible */
|
||||
:global(.homepage-tutorial-demo .coachbar) {
|
||||
background: #f9fafb !important;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.05) !important;
|
||||
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.02) !important;
|
||||
padding: 10px 14px !important; /* Slight increase */
|
||||
}
|
||||
|
||||
/* Soften reason tooltip backgrounds */
|
||||
:global(.homepage-tutorial-demo .reason-tooltip) {
|
||||
background: #f9fafb !important;
|
||||
border: 1px solid #e5e7eb !important;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.06) !important;
|
||||
padding: 14px !important; /* Increase from 12px */
|
||||
}
|
||||
`}</style>
|
||||
<TutorialPlayer
|
||||
tutorial={friendsOf5Tutorial}
|
||||
isDebugMode={false}
|
||||
showDebugPanel={false}
|
||||
hideNavigation={true}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* What you'll learn */}
|
||||
<div
|
||||
className={stack({
|
||||
gap: '4',
|
||||
bg: 'rgba(0, 0, 0, 0.4)',
|
||||
p: '6',
|
||||
borderRadius: 'xl',
|
||||
border: '1px solid',
|
||||
borderColor: 'gray.700',
|
||||
justifyContent: 'center',
|
||||
})}
|
||||
>
|
||||
<h3 className={css({ fontSize: 'xl', fontWeight: 'bold', color: 'white' })}>
|
||||
<h3
|
||||
className={css({
|
||||
fontSize: 'xl',
|
||||
fontWeight: 'bold',
|
||||
color: 'white',
|
||||
mb: '4',
|
||||
textAlign: 'center',
|
||||
})}
|
||||
>
|
||||
What You'll Learn
|
||||
</h3>
|
||||
<div className={stack({ gap: '3' })}>
|
||||
<div className={grid({ columns: { base: 1, sm: 2 }, gap: '3' })}>
|
||||
{[
|
||||
'Read and set numbers on an abacus',
|
||||
'Add and subtract with "friends" techniques',
|
||||
@@ -330,7 +257,7 @@ export default function HomePage() {
|
||||
].map((skill, i) => (
|
||||
<div key={i} className={hstack({ gap: '3' })}>
|
||||
<span className={css({ color: 'yellow.400', fontSize: 'lg' })}>✓</span>
|
||||
<span className={css({ color: 'gray.300', fontSize: 'md' })}>{skill}</span>
|
||||
<span className={css({ color: 'gray.300', fontSize: 'sm' })}>{skill}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -216,6 +216,8 @@ interface TutorialPlayerProps {
|
||||
isDebugMode?: boolean
|
||||
showDebugPanel?: boolean
|
||||
hideNavigation?: boolean
|
||||
abacusColumns?: number
|
||||
theme?: 'light' | 'dark'
|
||||
onStepChange?: (stepIndex: number, step: TutorialStep) => void
|
||||
onStepComplete?: (stepIndex: number, step: TutorialStep, success: boolean) => void
|
||||
onTutorialComplete?: (score: number, timeSpent: number) => void
|
||||
@@ -229,6 +231,8 @@ function TutorialPlayerContent({
|
||||
isDebugMode = false,
|
||||
showDebugPanel = false,
|
||||
hideNavigation = false,
|
||||
abacusColumns = 5,
|
||||
theme = 'light',
|
||||
onStepChange,
|
||||
onStepComplete,
|
||||
onTutorialComplete,
|
||||
@@ -1319,11 +1323,18 @@ function TutorialPlayerContent({
|
||||
fontSize: '2xl',
|
||||
fontWeight: 'bold',
|
||||
mb: 2,
|
||||
color: theme === 'dark' ? 'gray.200' : 'gray.900',
|
||||
})}
|
||||
>
|
||||
{currentStep.problem}
|
||||
</h2>
|
||||
<p className={css({ fontSize: 'lg', color: 'gray.700', mb: 4 })}>
|
||||
<p
|
||||
className={css({
|
||||
fontSize: 'lg',
|
||||
color: theme === 'dark' ? 'gray.400' : 'gray.700',
|
||||
mb: 4,
|
||||
})}
|
||||
>
|
||||
{currentStep.description}
|
||||
</p>
|
||||
{/* Hide action description for multi-step problems since it duplicates pedagogical decomposition */}
|
||||
@@ -1341,12 +1352,19 @@ function TutorialPlayerContent({
|
||||
className={css({
|
||||
p: 5,
|
||||
background:
|
||||
'linear-gradient(135deg, rgba(255,248,225,0.95) 0%, rgba(254,252,232,0.95) 50%, rgba(255,245,157,0.15) 100%)',
|
||||
theme === 'dark'
|
||||
? 'linear-gradient(135deg, rgba(40,40,50,0.6) 0%, rgba(50,50,60,0.6) 50%, rgba(60,50,70,0.3) 100%)'
|
||||
: 'linear-gradient(135deg, rgba(255,248,225,0.95) 0%, rgba(254,252,232,0.95) 50%, rgba(255,245,157,0.15) 100%)',
|
||||
backdropFilter: 'blur(10px)',
|
||||
border: '1px solid rgba(251,191,36,0.3)',
|
||||
border:
|
||||
theme === 'dark'
|
||||
? '1px solid rgba(255,255,255,0.1)'
|
||||
: '1px solid rgba(251,191,36,0.3)',
|
||||
borderRadius: 'xl',
|
||||
boxShadow:
|
||||
'0 8px 32px rgba(251,191,36,0.1), 0 2px 8px rgba(0,0,0,0.05), inset 0 1px 0 rgba(255,255,255,0.6)',
|
||||
theme === 'dark'
|
||||
? '0 4px 16px rgba(0,0,0,0.3), inset 0 1px 0 rgba(255,255,255,0.05)'
|
||||
: '0 8px 32px rgba(251,191,36,0.1), 0 2px 8px rgba(0,0,0,0.05), inset 0 1px 0 rgba(255,255,255,0.6)',
|
||||
position: 'relative',
|
||||
maxW: '600px',
|
||||
w: 'full',
|
||||
@@ -1356,7 +1374,9 @@ function TutorialPlayerContent({
|
||||
inset: '0',
|
||||
borderRadius: 'xl',
|
||||
background:
|
||||
'linear-gradient(135deg, rgba(251,191,36,0.1) 0%, rgba(168,85,247,0.05) 100%)',
|
||||
theme === 'dark'
|
||||
? 'linear-gradient(135deg, rgba(100,100,120,0.1) 0%, rgba(80,60,100,0.05) 100%)'
|
||||
: 'linear-gradient(135deg, rgba(251,191,36,0.1) 0%, rgba(168,85,247,0.05) 100%)',
|
||||
zIndex: -1,
|
||||
},
|
||||
})}
|
||||
@@ -1365,10 +1385,10 @@ function TutorialPlayerContent({
|
||||
className={css({
|
||||
fontSize: 'base',
|
||||
fontWeight: '600',
|
||||
color: 'amber.900',
|
||||
color: theme === 'dark' ? 'gray.300' : 'amber.900',
|
||||
mb: 4,
|
||||
letterSpacing: 'wide',
|
||||
textShadow: '0 1px 2px rgba(0,0,0,0.1)',
|
||||
textShadow: theme === 'dark' ? 'none' : '0 1px 2px rgba(0,0,0,0.1)',
|
||||
})}
|
||||
>
|
||||
Guidance
|
||||
@@ -1381,18 +1401,25 @@ function TutorialPlayerContent({
|
||||
mb: 4,
|
||||
p: 3,
|
||||
background:
|
||||
'linear-gradient(135deg, rgba(255,255,255,0.8) 0%, rgba(248,250,252,0.9) 100%)',
|
||||
border: '1px solid rgba(203,213,225,0.4)',
|
||||
theme === 'dark'
|
||||
? 'linear-gradient(135deg, rgba(50,50,60,0.4) 0%, rgba(40,40,50,0.5) 100%)'
|
||||
: 'linear-gradient(135deg, rgba(255,255,255,0.8) 0%, rgba(248,250,252,0.9) 100%)',
|
||||
border:
|
||||
theme === 'dark'
|
||||
? '1px solid rgba(255,255,255,0.1)'
|
||||
: '1px solid rgba(203,213,225,0.4)',
|
||||
borderRadius: 'lg',
|
||||
boxShadow:
|
||||
'0 2px 8px rgba(0,0,0,0.06), inset 0 1px 0 rgba(255,255,255,0.7)',
|
||||
theme === 'dark'
|
||||
? '0 1px 4px rgba(0,0,0,0.3), inset 0 1px 0 rgba(255,255,255,0.05)'
|
||||
: '0 2px 8px rgba(0,0,0,0.06), inset 0 1px 0 rgba(255,255,255,0.7)',
|
||||
backdropFilter: 'blur(4px)',
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={css({
|
||||
fontSize: 'base',
|
||||
color: 'slate.800',
|
||||
color: theme === 'dark' ? 'gray.300' : 'slate.800',
|
||||
fontFamily: 'mono',
|
||||
fontWeight: '500',
|
||||
letterSpacing: 'tight',
|
||||
@@ -1411,7 +1438,7 @@ function TutorialPlayerContent({
|
||||
<div
|
||||
className={css({
|
||||
fontSize: 'sm',
|
||||
color: 'amber.800',
|
||||
color: theme === 'dark' ? 'gray.400' : 'amber.800',
|
||||
fontWeight: '500',
|
||||
lineHeight: '1.6',
|
||||
})}
|
||||
@@ -1489,17 +1516,17 @@ function TutorialPlayerContent({
|
||||
{/* Abacus */}
|
||||
<div
|
||||
className={css({
|
||||
bg: 'white',
|
||||
bg: theme === 'dark' ? 'rgba(30, 30, 40, 0.4)' : 'white',
|
||||
border: '2px solid',
|
||||
borderColor: 'gray.200',
|
||||
borderColor: theme === 'dark' ? 'rgba(255, 255, 255, 0.1)' : 'gray.200',
|
||||
borderRadius: 'lg',
|
||||
p: 6,
|
||||
shadow: 'lg',
|
||||
shadow: theme === 'dark' ? '0 4px 6px rgba(0, 0, 0, 0.3)' : 'lg',
|
||||
})}
|
||||
>
|
||||
<AbacusReact
|
||||
value={currentValue}
|
||||
columns={5}
|
||||
columns={abacusColumns}
|
||||
interactive={true}
|
||||
animated={true}
|
||||
scaleFactor={2.5}
|
||||
|
||||
@@ -21,52 +21,52 @@ export const GAME_THEMES = {
|
||||
blue: {
|
||||
color: 'blue',
|
||||
gradient: 'linear-gradient(135deg, #dbeafe, #bfdbfe)', // blue.100 to blue.200
|
||||
borderColor: 'blue.200',
|
||||
borderColor: '#bfdbfe', // blue.200
|
||||
},
|
||||
purple: {
|
||||
color: 'purple',
|
||||
gradient: 'linear-gradient(135deg, #e9d5ff, #ddd6fe)', // purple.100 to purple.200
|
||||
borderColor: 'purple.200',
|
||||
borderColor: '#ddd6fe', // purple.200
|
||||
},
|
||||
green: {
|
||||
color: 'green',
|
||||
gradient: 'linear-gradient(135deg, #d1fae5, #a7f3d0)', // green.100 to green.200
|
||||
borderColor: 'green.200',
|
||||
borderColor: '#a7f3d0', // green.200
|
||||
},
|
||||
teal: {
|
||||
color: 'teal',
|
||||
gradient: 'linear-gradient(135deg, #ccfbf1, #99f6e4)', // teal.100 to teal.200
|
||||
borderColor: 'teal.200',
|
||||
borderColor: '#99f6e4', // teal.200
|
||||
},
|
||||
indigo: {
|
||||
color: 'indigo',
|
||||
gradient: 'linear-gradient(135deg, #e0e7ff, #c7d2fe)', // indigo.100 to indigo.200
|
||||
borderColor: 'indigo.200',
|
||||
borderColor: '#c7d2fe', // indigo.200
|
||||
},
|
||||
pink: {
|
||||
color: 'pink',
|
||||
gradient: 'linear-gradient(135deg, #fce7f3, #fbcfe8)', // pink.100 to pink.200
|
||||
borderColor: 'pink.200',
|
||||
borderColor: '#fbcfe8', // pink.200
|
||||
},
|
||||
orange: {
|
||||
color: 'orange',
|
||||
gradient: 'linear-gradient(135deg, #ffedd5, #fed7aa)', // orange.100 to orange.200
|
||||
borderColor: 'orange.200',
|
||||
borderColor: '#fed7aa', // orange.200
|
||||
},
|
||||
yellow: {
|
||||
color: 'yellow',
|
||||
gradient: 'linear-gradient(135deg, #fef3c7, #fde68a)', // yellow.100 to yellow.200
|
||||
borderColor: 'yellow.200',
|
||||
borderColor: '#fde68a', // yellow.200
|
||||
},
|
||||
red: {
|
||||
color: 'red',
|
||||
gradient: 'linear-gradient(135deg, #fee2e2, #fecaca)', // red.100 to red.200
|
||||
borderColor: 'red.200',
|
||||
borderColor: '#fecaca', // red.200
|
||||
},
|
||||
gray: {
|
||||
color: 'gray',
|
||||
gradient: 'linear-gradient(135deg, #f3f4f6, #e5e7eb)', // gray.100 to gray.200
|
||||
borderColor: 'gray.200',
|
||||
borderColor: '#e5e7eb', // gray.200
|
||||
},
|
||||
} as const satisfies Record<string, GameTheme>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user