feat: polish interactive abacus with column-based digit display

- Remove pencil icon since input is always available
- Position number display closer to abacus (-35px/-40px vs -50px/-60px)
- Fix SVG cropping with proper scaling (2.0x/2.4x) and overflow handling
- Position each digit underneath its corresponding abacus column
- Maintain seamless keyboard input and NumberFlow animations
- Preserve all existing interactive functionality

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Thomas Hallock
2025-09-15 18:34:26 -05:00
parent fe38bfc8ad
commit ad11e3dc90

View File

@@ -319,115 +319,133 @@ export function InteractiveAbacus({
}
})}
>
<TypstSoroban
number={currentValue}
width={compact ? "144pt" : "180pt"}
height={compact ? "192pt" : "240pt"}
className={css({
width: '100%',
height: '100%',
transition: 'all 0.3s ease',
cursor: 'pointer',
'& [data-bead-type]': {
cursor: 'pointer',
transition: 'all 0.2s ease',
_hover: {
filter: 'brightness(1.2)',
transform: 'scale(1.05)'
}
}
})}
/>
{/* Aspect ratio container for proper SVG cropping */}
<div className={css({
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
overflow: 'hidden'
})}>
<div className={css({
transform: compact ? 'scale(2.0)' : 'scale(2.4)',
transformOrigin: 'center'
})}>
<TypstSoroban
number={currentValue}
width="120pt"
height="200pt"
className={css({
transition: 'all 0.3s ease',
cursor: 'pointer',
'& [data-bead-type]': {
cursor: 'pointer',
transition: 'all 0.2s ease',
_hover: {
filter: 'brightness(1.2)',
transform: 'scale(1.05)'
}
},
'& svg': {
width: '100%',
height: '100%',
maxWidth: '100%',
maxHeight: '100%',
objectFit: 'contain'
}
})}
/>
</div>
</div>
</animated.div>
{/* Integrated Value Display with Always-Available Input */}
{/* Column-based Value Display */}
{showValue && (
<div
className={css({
position: 'absolute',
bottom: compact ? '-50px' : '-60px',
bottom: compact ? '-35px' : '-40px',
left: '50%',
transform: 'translateX(-50%)',
display: 'flex',
alignItems: 'center',
gap: '2',
bg: 'white',
border: '2px solid',
borderColor: 'blue.200',
rounded: 'xl',
boxShadow: '0 4px 12px rgba(59, 130, 246, 0.15)',
px: '3',
py: '2',
minW: '160px'
gap: '0',
width: compact ? '240px' : '300px',
justifyContent: 'center'
})}
>
{/* Single Unified Number Display/Input */}
<div
ref={numberRef}
tabIndex={showManualInput ? 0 : -1}
onClick={handleNumberClick}
onKeyDown={handleKeyDown}
onBlur={handleNumberBlur}
className={css({
position: 'relative',
display: 'flex',
alignItems: 'center',
flex: '1',
outline: 'none',
cursor: showManualInput ? 'pointer' : 'default'
})}
title={showManualInput ? "Click to edit, type numbers, Enter to confirm" : undefined}
>
<NumberFlow
value={isEditing ? parseInt(editingValue) || 0 : currentValue}
animated={!disableAnimation}
style={{
fontSize: compact ? '1.5rem' : '1.875rem',
fontWeight: 'bold',
color: isEditing ? '#1d4ed8' : '#2563eb', // Slightly darker when editing
textAlign: 'center',
minWidth: '80px',
width: '100%'
}}
/>
{/* Position digits above their respective columns */}
{Array.from({ length: columns }, (_, colIndex) => {
const placeValue = columns - 1 - colIndex // rightmost is 0 (ones), leftmost is highest
const digit = Math.floor((isEditing ? parseInt(editingValue) || 0 : currentValue) / Math.pow(10, placeValue)) % 10
const columnWidth = compact ? (240 / columns) : (300 / columns)
{/* Visual editing indicator */}
{isEditing && (
<div className={css({
position: 'absolute',
bottom: '-2px',
left: '50%',
transform: 'translateX(-50%)',
width: '80%',
height: '2px',
bg: 'blue.400',
rounded: 'full',
animation: 'pulse 1s infinite'
})} />
)}
</div>
return (
<div
key={colIndex}
className={css({
position: 'relative',
width: `${columnWidth}px`,
display: 'flex',
justifyContent: 'center',
alignItems: 'center'
})}
>
<div
ref={colIndex === 0 && showManualInput ? numberRef : undefined}
tabIndex={showManualInput && colIndex === 0 ? 0 : -1}
onClick={showManualInput ? handleNumberClick : undefined}
onKeyDown={showManualInput && colIndex === 0 ? handleKeyDown : undefined}
onBlur={showManualInput && colIndex === 0 ? handleNumberBlur : undefined}
className={css({
bg: 'white',
border: '2px solid',
borderColor: 'blue.200',
rounded: 'xl',
boxShadow: '0 4px 12px rgba(59, 130, 246, 0.15)',
px: '2',
py: '1',
minW: compact ? '32px' : '40px',
position: 'relative',
outline: 'none',
cursor: showManualInput ? 'pointer' : 'default',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
})}
title={showManualInput && colIndex === 0 ? "Click to edit, type numbers, Enter to confirm" : undefined}
>
<NumberFlow
value={digit}
animated={!disableAnimation && !isEditing}
style={{
fontSize: compact ? '1.25rem' : '1.5rem',
fontWeight: 'bold',
color: isEditing ? '#1d4ed8' : '#2563eb',
textAlign: 'center',
minWidth: compact ? '20px' : '24px'
}}
/>
{/* Input Indicator */}
{showManualInput && (
<div
className={css({
p: '1',
rounded: 'full',
bg: 'blue.50',
color: 'blue.600',
fontSize: 'xs',
w: '6',
h: '6',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexShrink: 0
})}
title="Click the number to edit directly"
>
</div>
)}
{/* Visual editing indicator - only show on first column when editing */}
{isEditing && colIndex === 0 && (
<div className={css({
position: 'absolute',
bottom: '-2px',
left: '50%',
transform: 'translateX(-50%)',
width: '80%',
height: '2px',
bg: 'blue.400',
rounded: 'full',
animation: 'pulse 1s infinite'
})} />
)}
</div>
</div>
)
})}
</div>
)}
</div>