diff --git a/apps/web/src/components/InteractiveAbacus.tsx b/apps/web/src/components/InteractiveAbacus.tsx index 324fb458..1b091425 100644 --- a/apps/web/src/components/InteractiveAbacus.tsx +++ b/apps/web/src/components/InteractiveAbacus.tsx @@ -42,6 +42,112 @@ export function InteractiveAbacus({ + // Handle bead clicks to toggle values + const handleBeadClick = useCallback((event: Event) => { + const target = event.target as Element + + // Find the closest element with bead data attributes + const beadElement = target.closest('[data-bead-type]') + if (!beadElement) return + + const beadType = beadElement.getAttribute('data-bead-type') + const beadColumn = parseInt(beadElement.getAttribute('data-bead-column') || '0') + const beadPosition = beadElement.getAttribute('data-bead-position') + const isActive = beadElement.getAttribute('data-bead-active') === '1' + + console.log('Bead clicked:', { beadType, beadColumn, beadPosition, isActive }) + console.log('Current value before click:', currentValue) + + if (beadType === 'earth') { + const position = parseInt(beadPosition || '0') + const columnPower = Math.pow(10, beadColumn) + const currentDigit = Math.floor(currentValue / columnPower) % 10 + const heavenContribution = Math.floor(currentDigit / 5) * 5 + const earthContribution = currentDigit % 5 + console.log('Earth bead analysis:', { + position, + columnPower, + currentDigit, + heavenContribution, + earthContribution + }) + } + + if (beadType === 'heaven') { + // Toggle heaven bead (worth 5) + const columnPower = Math.pow(10, beadColumn) + const heavenValue = 5 * columnPower + + 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) + } + } 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) + + // Calculate current digit in this column + const currentDigit = Math.floor(currentValue / columnPower) % 10 + const heavenContribution = Math.floor(currentDigit / 5) * 5 + const earthContribution = currentDigit % 5 + + let newEarthContribution: number + + // Earth beads are numbered 0-3 from top to bottom (0 is closest to bar) + // In traditional abacus logic: + // - earthContribution represents how many beads are active (0-4) + // - Active beads are positions 0, 1, 2, ... up to (earthContribution - 1) + // - When you click a bead: toggle that "level" of activation + + if (isActive) { + // This bead is currently active, so we deactivate it and all beads below it + // If position 2 is clicked and active, we want positions 0,1 to remain active + // So earthContribution should be position (2) + newEarthContribution = position + } else { + // This bead is currently inactive, so we activate it and all beads above it + // If position 2 is clicked and inactive, we want positions 0,1,2 to be active + // So earthContribution should be position + 1 (3) + newEarthContribution = position + 1 + } + + console.log('Earth bead calculation:', { + position, + isActive, + currentEarthContribution: earthContribution, + newEarthContribution + }) + + // Calculate the new digit for this column + const newDigit = heavenContribution + newEarthContribution + + // Calculate the new total value + const columnContribution = Math.floor(currentValue / columnPower) % 10 * columnPower + const newValue = currentValue - columnContribution + (newDigit * columnPower) + + setCurrentValue(Math.max(0, newValue)) + } + + // Visual feedback + setIsChanging(true) + setTimeout(() => setIsChanging(false), 150) + }, [currentValue]) + + // Add click event listener for bead interactions + useEffect(() => { + const svgContainer = svgRef.current + if (!svgContainer) return + + svgContainer.addEventListener('click', handleBeadClick) + return () => { + svgContainer.removeEventListener('click', handleBeadClick) + } + }, [handleBeadClick]) + // Notify parent of value changes useMemo(() => { onValueChange?.(currentValue) @@ -101,7 +207,16 @@ export function InteractiveAbacus({ className={css({ width: '100%', height: '100%', - transition: 'all 0.3s ease' + 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)' + } + } })} /> @@ -213,8 +328,9 @@ export function InteractiveAbacus({ border: '1px solid', borderColor: 'gray.200' })}> - How to use: Use the preset buttons below to set different values. - The abacus will display the number using traditional soroban bead positions. + How to use: Click on the beads to activate or deactivate them! + Heaven beads (top) are worth 5 each, earth beads (bottom) are worth 1 each. + You can also use the preset buttons below. diff --git a/apps/web/styled-system/chunks/src__app__games__memory-quiz__page.css b/apps/web/styled-system/chunks/src__app__games__memory-quiz__page.css index 5b07831d..f87e9424 100644 --- a/apps/web/styled-system/chunks/src__app__games__memory-quiz__page.css +++ b/apps/web/styled-system/chunks/src__app__games__memory-quiz__page.css @@ -855,18 +855,6 @@ } @media (max-width: 768px) { - .\[\@media_\(max-width\:_768px\)\]\:gap_10px { - gap: 10px - } - } - @media (max-width: 480px) { - - .\[\@media_\(max-width\:_480px\)\]\:gap_8px { - gap: 8px - } - } - @media (max-width: 768px) { - .\[\@media_\(max-width\:_768px\)\]\:h_130px { height: 130px } @@ -899,18 +887,6 @@ min-width: 90px } } - @media (max-width: 768px) { - - .\[\@media_\(max-width\:_768px\)\]\:fs_40px { - font-size: 40px - } - } - @media (max-width: 480px) { - - .\[\@media_\(max-width\:_480px\)\]\:fs_32px { - font-size: 32px - } - } @media screen and (min-width: 48em) { .md\:px_4 { @@ -933,6 +909,9 @@ } @media (max-width: 768px) { + .\[\@media_\(max-width\:_768px\)\]\:gap_10px { + gap: 10px + } .\[\@media_\(max-width\:_768px\)\]\:h_130px { height: 130px } @@ -940,6 +919,9 @@ .\[\@media_\(max-width\:_768px\)\]\:min-w_100px { min-width: 100px } + .\[\@media_\(max-width\:_768px\)\]\:fs_40px { + font-size: 40px + } .\[\@media_\(max-width\:_768px\)\]\:gap_10px { gap: 10px @@ -959,6 +941,9 @@ } @media (max-width: 480px) { + .\[\@media_\(max-width\:_480px\)\]\:gap_8px { + gap: 8px + } .\[\@media_\(max-width\:_480px\)\]\:h_120px { height: 120px } @@ -966,6 +951,9 @@ .\[\@media_\(max-width\:_480px\)\]\:min-w_90px { min-width: 90px } + .\[\@media_\(max-width\:_480px\)\]\:fs_32px { + font-size: 32px + } .\[\@media_\(max-width\:_480px\)\]\:gap_8px { gap: 8px diff --git a/apps/web/styled-system/chunks/src__components__InteractiveAbacus.css b/apps/web/styled-system/chunks/src__components__InteractiveAbacus.css index 9fd3ff4e..f5bcf234 100644 --- a/apps/web/styled-system/chunks/src__components__InteractiveAbacus.css +++ b/apps/web/styled-system/chunks/src__components__InteractiveAbacus.css @@ -1,185 +1,5 @@ @layer utilities { - .border_blue\.600 { - border-color: var(--colors-blue-600) - } - - .bg_blue\.25 { - background: blue.25 - } - - .w_6 { - width: var(--sizes-6) - } - - .h_6 { - height: var(--sizes-6) - } - - .border-t_blue\.500 { - border-top-color: var(--colors-blue-500) - } - - .rounded_full { - border-radius: var(--radii-full) - } - - .animation_spin_1s_linear_infinite { - animation: spin 1s linear infinite - } - - .bg_red\.25 { - background: red.25 - } - - .rounded_md { - border-radius: var(--radii-md) - } - - .min-h_300px { - min-height: 300px - } - - .opacity_0\.6 { - opacity: 0.6 - } - - .w_full { - width: var(--sizes-full) - } - - .h_full { - height: var(--sizes-full) - } - - .overflow_hidden { - overflow: hidden - } - - .bg_white { - background: var(--colors-white) - } - - .\[\&_svg\]\:w_100\% svg { - width: 100% - } - - .\[\&_svg\]\:h_100\% svg { - height: 100% - } - - .\[\&_svg\]\:max-w_100\% svg { - max-width: 100% - } - - .\[\&_svg\]\:max-h_100\% svg { - max-height: 100% - } - - .\[\&_svg\]\:object_contain svg { - object-fit: contain - } - - .\[\&_svg\]\:transition_all_0\.2s_ease svg { - transition: all 0.2s ease - } - - .gap_4 { - gap: var(--spacing-4) - } - - .mt_2 { - margin-top: var(--spacing-2) - } - - .min-w_80px { - min-width: 80px - } - - .max-w_300px { - max-width: 300px - } - - .w_24px { - width: 24px - } - - .h_8px { - height: 8px - } - - .rounded_8px { - border-radius: 8px - } - - .border_green\.600 { - border-color: var(--colors-green-600) - } - - .border_gray\.400 { - border-color: var(--colors-gray-400) - } - - .w_20px { - width: 20px - } - - .h_6px { - height: 6px - } - - .rounded_6px { - border-radius: 6px - } - - .transition_border-color_0\.2s_ease { - transition: border-color 0.2s ease - } - - .p_16px_8px { - padding: 16px 8px - } - - .gap_8px { - gap: 8px - } - - .min-h_60px { - min-height: 60px - } - - .justify_flex-end { - justify-content: flex-end - } - - .w_50px { - width: 50px - } - - .h_4px { - height: 4px - } - - .bg_gray\.800 { - background-color: var(--colors-gray-800) - } - - .rounded_2px { - border-radius: 2px - } - - .gap_4px { - gap: 4px - } - - .min-h_80px { - min-height: 80px - } - - .justify_flex-start { - justify-content: flex-start - } - .flex_column { flex-direction: column } @@ -188,58 +8,6 @@ gap: var(--spacing-6) } - .gap_2 { - gap: var(--spacing-2) - } - - .border_amber\.800 { - border-color: var(--colors-amber-800) - } - - .max-w_400px { - max-width: 400px - } - - .border_\#d97706 { - border-color: #d97706 - } - - .pos_absolute { - position: absolute - } - - .top_50\% { - top: 50% - } - - .left_50\% { - left: 50% - } - - .transform_translate\(-50\%\,_-50\%\) { - transform: translate(-50%, -50%) - } - - .pointer-events_none { - pointer-events: none - } - - .fs_2xl { - font-size: var(--font-sizes-2xl) - } - - .text_orange\.600 { - color: var(--colors-orange-600) - } - - .text-shadow_0_2px_4px_rgba\(0\,0\,0\,0\.2\) { - text-shadow: 0 2px 4px rgba(0,0,0,0.2) - } - - .z_10 { - z-index: 10 - } - .w_300px { width: 300px } @@ -291,6 +59,12 @@ .transition_all_0\.3s_ease { transition: all 0.3s ease } + .\[\&_\[data-bead-type\]\]\:cursor_pointer [data-bead-type] { + cursor: pointer + } + .\[\&_\[data-bead-type\]\]\:transition_all_0\.2s_ease [data-bead-type] { + transition: all 0.2s ease + } .fs_3xl { font-size: var(--font-sizes-3xl) @@ -452,34 +226,6 @@ box-shadow: 0 12px 32px rgba(0, 0, 0, 0.15) } - .hover\:border_blue\.700:is(:hover, [data-hover]) { - border-color: var(--colors-blue-700) - } - - .hover\:border_green\.700:is(:hover, [data-hover]) { - border-color: var(--colors-green-700) - } - - .hover\:border_gray\.500:is(:hover, [data-hover]) { - border-color: var(--colors-gray-500) - } - - .hover\:bg_blue\.200:is(:hover, [data-hover]) { - background: var(--colors-blue-200) - } - - .hover\:transform_translateY\(-1px\):is(:hover, [data-hover]) { - transform: translateY(-1px) - } - - .hover\:border_blue\.400:is(:hover, [data-hover]) { - border-color: var(--colors-blue-400) - } - - .hover\:shadow_0_4px_12px_rgba\(0\,_0\,_0\,_0\.1\):is(:hover, [data-hover]) { - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1) - } - .hover\:bg_gray\.200:is(:hover, [data-hover]) { background: var(--colors-gray-200) } @@ -488,14 +234,24 @@ border-color: var(--colors-gray-400) } - .\[\&\:hover\]\:border_amber\.700:hover { - border-color: var(--colors-amber-700) + .hover\:bg_blue\.200:is(:hover, [data-hover]) { + background: var(--colors-blue-200) } - .\[\&\:hover\]\:transform_scale\(1\.01\):hover { - transform: scale(1.01) + .hover\:border_blue\.400:is(:hover, [data-hover]) { + border-color: var(--colors-blue-400) } + .hover\:transform_translateY\(-1px\):is(:hover, [data-hover]) { + transform: translateY(-1px) + } + .\[\&_\[data-bead-type\]\]\:hover\:filter_brightness\(1\.2\) [data-bead-type]:is(:hover, [data-hover]) { + filter: brightness(1.2) + } + .\[\&_\[data-bead-type\]\]\:hover\:transform_scale\(1\.05\) [data-bead-type]:is(:hover, [data-hover]) { + transform: scale(1.05) + } + .active\:transform_scale\(0\.95\):is(:active, [data-active]) { transform: scale(0.95) } diff --git a/apps/web/styled-system/styles.css b/apps/web/styled-system/styles.css index 47f4c528..8b25550f 100644 --- a/apps/web/styled-system/styles.css +++ b/apps/web/styled-system/styles.css @@ -25,6 +25,10 @@ padding: 12px 16px } + .gap_4px { + gap: 4px + } + .fs_11px { font-size: 11px } @@ -37,6 +41,10 @@ padding: 10px 20px } + .max-w_300px { + max-width: 300px + } + .min-w_50px { min-width: 50px } @@ -77,6 +85,14 @@ transition: width 0.5s ease } + .justify_flex-end { + justify-content: flex-end + } + + .rounded_6px { + border-radius: 6px + } + .p_8px_16px { padding: 8px 16px } @@ -318,6 +334,10 @@ box-shadow: 0 8px 25px rgba(40, 167, 69, 0.2) } + .min-h_60px { + min-height: 60px + } + .p_16px_20px { padding: 16px 20px } @@ -407,10 +427,26 @@ width: 100vw } + .pointer-events_none { + pointer-events: none + } + .z_1000 { z-index: 1000 } + .top_50\% { + top: 50% + } + + .left_50\% { + left: 50% + } + + .transform_translate\(-50\%\,_-50\%\) { + transform: translate(-50%, -50%) + } + .fs_72px { font-size: 72px } @@ -447,6 +483,10 @@ margin: 0 auto } + .justify_flex-start { + justify-content: flex-start + } + .text_gray\.800 { color: var(--colors-gray-800) } @@ -511,6 +551,10 @@ padding: 12px 24px } + .rounded_8px { + border-radius: 8px + } + .fs_16px { font-size: 16px } @@ -619,6 +663,10 @@ --gradient-from: var(--colors-indigo-400) } + .text_orange\.600 { + color: var(--colors-orange-600) + } + .ml_2 { margin-left: var(--spacing-2) } @@ -895,6 +943,10 @@ width: var(--sizes-20) } + .min-h_300px { + min-height: 300px + } + .max-w_sm { max-width: var(--sizes-sm) } @@ -951,6 +1003,10 @@ padding: var(--spacing-6) } + .max-w_400px { + max-width: 400px + } + .max-h_80vh { max-height: 80vh } @@ -1306,146 +1362,6 @@ gap: var(--spacing-0) } - .border_blue\.600 { - border-color: var(--colors-blue-600) - } - - .min-h_300px { - min-height: 300px - } - - .\[\&_svg\]\:transition_all_0\.2s_ease svg { - transition: all 0.2s ease - } - - .mt_2 { - margin-top: var(--spacing-2) - } - - .min-w_80px { - min-width: 80px - } - - .max-w_300px { - max-width: 300px - } - - .w_24px { - width: 24px - } - - .h_8px { - height: 8px - } - - .rounded_8px { - border-radius: 8px - } - - .border_green\.600 { - border-color: var(--colors-green-600) - } - - .border_gray\.400 { - border-color: var(--colors-gray-400) - } - - .w_20px { - width: 20px - } - - .h_6px { - height: 6px - } - - .rounded_6px { - border-radius: 6px - } - - .transition_border-color_0\.2s_ease { - transition: border-color 0.2s ease - } - - .p_16px_8px { - padding: 16px 8px - } - - .gap_8px { - gap: 8px - } - - .min-h_60px { - min-height: 60px - } - - .justify_flex-end { - justify-content: flex-end - } - - .w_50px { - width: 50px - } - - .h_4px { - height: 4px - } - - .bg_gray\.800 { - background-color: var(--colors-gray-800) - } - - .rounded_2px { - border-radius: 2px - } - - .gap_4px { - gap: 4px - } - - .min-h_80px { - min-height: 80px - } - - .justify_flex-start { - justify-content: flex-start - } - - .border_amber\.800 { - border-color: var(--colors-amber-800) - } - - .max-w_400px { - max-width: 400px - } - - .border_\#d97706 { - border-color: #d97706 - } - - .top_50\% { - top: 50% - } - - .left_50\% { - left: 50% - } - - .transform_translate\(-50\%\,_-50\%\) { - transform: translate(-50%, -50%) - } - - .pointer-events_none { - pointer-events: none - } - - .text_orange\.600 { - color: var(--colors-orange-600) - } - - .text-shadow_0_2px_4px_rgba\(0\,0\,0\,0\.2\) { - text-shadow: 0 2px 4px rgba(0,0,0,0.2) - } - .w_300px { width: 300px } @@ -1494,6 +1410,14 @@ transition: all 0.3s ease } + .\[\&_\[data-bead-type\]\]\:cursor_pointer [data-bead-type] { + cursor: pointer + } + + .\[\&_\[data-bead-type\]\]\:transition_all_0\.2s_ease [data-bead-type] { + transition: all 0.2s ease + } + .px_6 { padding-inline: var(--spacing-6) } @@ -2196,34 +2120,6 @@ box-shadow: 0 12px 32px rgba(0, 0, 0, 0.15) } - .hover\:border_blue\.700:is(:hover, [data-hover]) { - border-color: var(--colors-blue-700) - } - - .hover\:border_green\.700:is(:hover, [data-hover]) { - border-color: var(--colors-green-700) - } - - .hover\:border_gray\.500:is(:hover, [data-hover]) { - border-color: var(--colors-gray-500) - } - - .hover\:bg_blue\.200:is(:hover, [data-hover]) { - background: var(--colors-blue-200) - } - - .hover\:transform_translateY\(-1px\):is(:hover, [data-hover]) { - transform: translateY(-1px) - } - - .hover\:border_blue\.400:is(:hover, [data-hover]) { - border-color: var(--colors-blue-400) - } - - .hover\:shadow_0_4px_12px_rgba\(0\,_0\,_0\,_0\.1\):is(:hover, [data-hover]) { - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1) - } - .hover\:bg_gray\.200:is(:hover, [data-hover]) { background: var(--colors-gray-200) } @@ -2232,14 +2128,26 @@ border-color: var(--colors-gray-400) } - .\[\&\:hover\]\:border_amber\.700:hover { - border-color: var(--colors-amber-700) + .hover\:bg_blue\.200:is(:hover, [data-hover]) { + background: var(--colors-blue-200) } - .\[\&\:hover\]\:transform_scale\(1\.01\):hover { - transform: scale(1.01) + .hover\:border_blue\.400:is(:hover, [data-hover]) { + border-color: var(--colors-blue-400) } + .hover\:transform_translateY\(-1px\):is(:hover, [data-hover]) { + transform: translateY(-1px) + } + + .\[\&_\[data-bead-type\]\]\:hover\:filter_brightness\(1\.2\) [data-bead-type]:is(:hover, [data-hover]) { + filter: brightness(1.2) + } + + .\[\&_\[data-bead-type\]\]\:hover\:transform_scale\(1\.05\) [data-bead-type]:is(:hover, [data-hover]) { + transform: scale(1.05) + } + .hover\:border_brand\.300:is(:hover, [data-hover]) { border-color: var(--colors-brand-300) }