fix: correct column indexing and add boundary checks for interactive abacus

- Fix column indexing bug where tens/ones columns were swapped
- Add boundary checks to prevent values exceeding abacus capacity
- Improve earth and heaven bead click logic with proper place value mapping

🤖 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 17:56:32 -05:00
parent 697552ecd9
commit bbfb3614a2
4 changed files with 60 additions and 8 deletions

View File

@ -99,7 +99,8 @@
"WebFetch(domain:www.radix-ui.com)",
"Read(//Users/antialias/**)",
"Bash(turbo run:*)",
"Bash(npx turbo run:*)"
"Bash(npx turbo run:*)",
"Bash(open http://localhost:3003/guide)"
],
"deny": [],
"ask": []

View File

@ -17,6 +17,7 @@
"@myriaddreamin/typst-ts-renderer": "0.6.1-rc3",
"@myriaddreamin/typst-ts-web-compiler": "0.6.1-rc3",
"@myriaddreamin/typst.ts": "0.6.1-rc3",
"@number-flow/react": "^0.5.10",
"@pandacss/dev": "^0.20.0",
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-checkbox": "^1.0.4",

View File

@ -24,6 +24,7 @@ export function InteractiveAbacus({
}: InteractiveAbacusProps) {
const [currentValue, setCurrentValue] = useState(initialValue)
const [isChanging, setIsChanging] = useState(false)
const [previousValue, setPreviousValue] = useState(initialValue)
const svgRef = useRef<HTMLDivElement>(null)
// Animated value display
@ -39,6 +40,13 @@ export function InteractiveAbacus({
config: { tension: 400, friction: 25 }
})
// Crossfade animation between old and new SVG states
const crossfadeSpring = useSpring({
opacity: isChanging ? 0.7 : 1,
transform: isChanging ? 'scale(0.98)' : 'scale(1)',
config: { tension: 300, friction: 30 }
})
@ -60,12 +68,15 @@ export function InteractiveAbacus({
if (beadType === 'earth') {
const position = parseInt(beadPosition || '0')
const columnPower = Math.pow(10, beadColumn)
const placeValue = columns - 1 - beadColumn
const columnPower = Math.pow(10, placeValue)
const currentDigit = Math.floor(currentValue / columnPower) % 10
const heavenContribution = Math.floor(currentDigit / 5) * 5
const earthContribution = currentDigit % 5
console.log('Earth bead analysis:', {
position,
beadColumn,
placeValue,
columnPower,
currentDigit,
heavenContribution,
@ -75,20 +86,27 @@ export function InteractiveAbacus({
if (beadType === 'heaven') {
// Toggle heaven bead (worth 5)
const columnPower = Math.pow(10, beadColumn)
// Column indexing: 0=leftmost, but place values are rightmost=ones
// For 3 columns: col 0=hundreds(10^2), col 1=tens(10^1), col 2=ones(10^0)
const placeValue = columns - 1 - beadColumn
const columnPower = Math.pow(10, placeValue)
const heavenValue = 5 * columnPower
const maxValue = Math.pow(10, columns) - 1
if (isActive) {
// Deactivate heaven bead - subtract 5 from this column
setCurrentValue(prev => Math.max(0, prev - heavenValue))
} else {
// Activate heaven bead - add 5 to this column
setCurrentValue(prev => prev + heavenValue)
setCurrentValue(prev => Math.min(prev + heavenValue, maxValue))
}
} else if (beadType === 'earth' && beadPosition) {
// Toggle earth bead (worth 1 each)
const position = parseInt(beadPosition) // 0-3 where 0 is top (closest to bar), 3 is bottom
const columnPower = Math.pow(10, beadColumn)
// Column indexing: 0=leftmost, but place values are rightmost=ones
const placeValue = columns - 1 - beadColumn
const columnPower = Math.pow(10, placeValue)
// Calculate current digit in this column
const currentDigit = Math.floor(currentValue / columnPower) % 10
@ -129,12 +147,19 @@ export function InteractiveAbacus({
const columnContribution = Math.floor(currentValue / columnPower) % 10 * columnPower
const newValue = currentValue - columnContribution + (newDigit * columnPower)
setCurrentValue(Math.max(0, newValue))
// Ensure value doesn't exceed maximum for this number of columns
const maxValue = Math.pow(10, columns) - 1
setCurrentValue(Math.max(0, Math.min(newValue, maxValue)))
}
// Visual feedback
// Visual feedback with extended timing for smoother transition
setIsChanging(true)
setTimeout(() => setIsChanging(false), 150)
// Update previous value for crossfade effect
setPreviousValue(currentValue)
// Extended timing to allow for smoother crossfade
setTimeout(() => setIsChanging(false), 300)
}, [currentValue])
// Add click event listener for bead interactions

View File

@ -37,6 +37,9 @@ importers:
'@myriaddreamin/typst.ts':
specifier: 0.6.1-rc3
version: 0.6.1-rc3(@myriaddreamin/typst-ts-renderer@0.6.1-rc3)(@myriaddreamin/typst-ts-web-compiler@0.6.1-rc3)
'@number-flow/react':
specifier: ^0.5.10
version: 0.5.10(react-dom@18.0.0)(react@18.0.0)
'@pandacss/dev':
specifier: ^0.20.0
version: 0.20.1(jsdom@27.0.0)(typescript@5.0.2)
@ -1377,6 +1380,18 @@ packages:
engines: {node: '>=12.4.0'}
dev: true
/@number-flow/react@0.5.10(react-dom@18.0.0)(react@18.0.0):
resolution: {integrity: sha512-a8Wh5eNITn7Km4xbddAH7QH8eNmnduR6k34ER1hkHSGO4H2yU1DDnuAWLQM99vciGInFODemSc0tdxrXkJEpbA==}
peerDependencies:
react: ^18 || ^19
react-dom: ^18 || ^19
dependencies:
esm-env: 1.2.2
number-flow: 0.5.8
react: 18.0.0
react-dom: 18.0.0(react@18.0.0)
dev: false
/@pandacss/config@0.20.1:
resolution: {integrity: sha512-RG+WWK2NAZhmikChYsER61YIHiR4IsLH52guJdhuerk+nT5dpSrs3RkGM5F8Fs18Pkcj+gWwcS+g+g41ictf1w==}
dependencies:
@ -4388,6 +4403,10 @@ packages:
- supports-color
dev: true
/esm-env@1.2.2:
resolution: {integrity: sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==}
dev: false
/espree@9.6.1:
resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@ -5618,6 +5637,12 @@ packages:
path-key: 4.0.0
dev: true
/number-flow@0.5.8:
resolution: {integrity: sha512-FPr1DumWyGi5Nucoug14bC6xEz70A1TnhgSHhKyfqjgji2SOTz+iLJxKtv37N5JyJbteGYCm6NQ9p1O4KZ7iiA==}
dependencies:
esm-env: 1.2.2
dev: false
/object-assign@4.1.1:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'}