Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 14x 14x 14x 10x 10x 10x 10x 14x 1x 1x 9x 9x 9x 4x 4x 2x 2x 2x 2x 4x 1x 1x 1x 1x 4x 1x 1x 9x 9x 14x 14x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 33x 33x 33x 33x 33x 33x 33x 33x 33x 109x 109x 109x 109x 2x 2x 2x 109x 2x 2x 2x 105x 105x 105x 109x 11x 11x 109x 109x 109x 1x 1x 109x 104x 104x 104x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 6x 6x 6x 6x 6x 6x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 6x 6x 6x 1x 1x 1x 1x 1x 1x 1x 3x 3x 3x 3x 3x 3x 3x 6x 6x 6x 6x 6x 6x 6x 6x | /**
* Bead Tooltip Utilities
*
* Extracted from TutorialPlayer for reuse in practice help overlay.
* Handles smart tooltip positioning to avoid covering active beads.
*/
import type { StepBeadHighlight } from '@soroban/abacus-react'
/**
* Target specification for tooltip overlay
*/
export interface TooltipTarget {
type: 'bead'
columnIndex: number
beadType: 'heaven' | 'earth'
beadPosition: number | undefined
}
/**
* Result of tooltip positioning calculation
*/
export interface TooltipPositioning {
/** Which side to show tooltip on */
side: 'top' | 'left'
/** Target bead for the tooltip */
target: TooltipTarget
/** The topmost bead that was selected */
topmostBead: StepBeadHighlight
/** Column index of the target */
targetColumnIndex: number
}
/**
* Find the topmost bead with arrows in a step
*
* Priority order:
* 1. Higher place value (leftmost columns = more significant)
* 2. Heaven beads before earth beads
* 3. Lower position number for earth beads (higher on abacus)
*
* @param stepBeadHighlights - Array of bead highlights for the current step
* @returns The topmost bead, or null if none have arrows
*/
export function findTopmostBeadWithArrows(
stepBeadHighlights: StepBeadHighlight[] | undefined
): StepBeadHighlight | null {
if (!stepBeadHighlights || stepBeadHighlights.length === 0) return null
// Filter only beads that have direction arrows
const beadsWithArrows = stepBeadHighlights.filter((bead) => !!bead.direction)
if (beadsWithArrows.length === 0) {
return null
}
// Sort by priority
const sortedBeads = [...beadsWithArrows].sort((a, b) => {
// First sort by place value (higher place value = more significant = topmost priority)
if (a.placeValue !== b.placeValue) {
return b.placeValue - a.placeValue
}
// If same place value, heaven beads come before earth beads
if (a.beadType !== b.beadType) {
return a.beadType === 'heaven' ? -1 : 1
}
// If both earth beads in same column, lower position number = higher on abacus
if (a.beadType === 'earth' && b.beadType === 'earth') {
return (a.position || 0) - (b.position || 0)
}
return 0
})
return sortedBeads[0] || null
}
/**
* Check if there are active beads to the left of a target column
*
* Active beads are:
* - Beads against the reckoning bar (based on current value)
* - Beads with direction arrows in the current step
*
* @param currentValue - Current abacus value
* @param stepBeadHighlights - Bead highlights for current step
* @param abacusColumns - Number of columns on the abacus
* @param targetColumnIndex - Column index to check left of
* @returns True if there are active beads to the left
*/
export function hasActiveBeadsToLeft(
currentValue: number,
stepBeadHighlights: StepBeadHighlight[] | undefined,
abacusColumns: number,
targetColumnIndex: number
): boolean {
// Get current abacus state - check which beads are against the reckoning bar
const abacusDigits = currentValue.toString().padStart(abacusColumns, '0').split('').map(Number)
for (let col = 0; col < targetColumnIndex; col++) {
const digitValue = abacusDigits[col]
// Check if any beads are active (against reckoning bar) in this column
if (digitValue >= 5) {
// Heaven bead is active
return true
}
if (digitValue % 5 > 0) {
// Earth beads are active
return true
}
// Also check if this column has beads with direction arrows
const hasArrowsInColumn =
stepBeadHighlights?.some((bead) => {
const beadColumnIndex = abacusColumns - 1 - bead.placeValue
return beadColumnIndex === col && !!bead.direction
}) ?? false
if (hasArrowsInColumn) {
return true
}
}
return false
}
/**
* Calculate smart tooltip positioning
*
* Determines which side to show the tooltip on and which bead to target,
* avoiding covering active beads.
*
* @param currentValue - Current abacus value
* @param stepBeadHighlights - Bead highlights for current step
* @param abacusColumns - Number of columns on the abacus
* @returns Positioning info, or null if no beads with arrows
*/
export function calculateTooltipPositioning(
currentValue: number,
stepBeadHighlights: StepBeadHighlight[] | undefined,
abacusColumns: number
): TooltipPositioning | null {
const topmostBead = findTopmostBeadWithArrows(stepBeadHighlights)
if (!topmostBead) return null
// Convert placeValue to columnIndex
const targetColumnIndex = abacusColumns - 1 - topmostBead.placeValue
// Check if there are active beads to the left
const activeToLeft = hasActiveBeadsToLeft(
currentValue,
stepBeadHighlights,
abacusColumns,
targetColumnIndex
)
// Determine tooltip position and target
const shouldPositionAbove = activeToLeft
const side = shouldPositionAbove ? 'top' : 'left'
const target: TooltipTarget = shouldPositionAbove
? {
// Target the heaven bead position for the column
type: 'bead',
columnIndex: targetColumnIndex,
beadType: 'heaven',
beadPosition: 0, // Heaven beads are always at position 0
}
: {
// Target the actual bead
type: 'bead',
columnIndex: targetColumnIndex,
beadType: topmostBead.beadType,
beadPosition: topmostBead.position,
}
return {
side,
target,
topmostBead,
targetColumnIndex,
}
}
|