advent of code 2025 in ts and nix

feat: update day 6 vis

dunkirk.sh af2b8821 35a4c7d0

verified
Changed files
+563 -189
vis
+282 -95
vis/06/generate.ts
···
opacity: 0.5;
cursor: not-allowed;
}
+
.speed-control {
+
display: flex;
+
align-items: center;
+
gap: 8px;
+
font-size: 13px;
+
color: #a6adc8;
+
}
+
.speed-control input[type="range"] {
+
width: 120px;
+
height: 6px;
+
background: #313244;
+
outline: none;
+
-webkit-appearance: none;
+
}
+
.speed-control input[type="range"]::-webkit-slider-thumb {
+
-webkit-appearance: none;
+
appearance: none;
+
width: 14px;
+
height: 14px;
+
background: #a6e3a1;
+
cursor: pointer;
+
border: 1px solid #313244;
+
}
+
.speed-control input[type="range"]::-moz-range-thumb {
+
width: 14px;
+
height: 14px;
+
background: #a6e3a1;
+
cursor: pointer;
+
border: 1px solid #313244;
+
}
.info {
color: #f9e2af;
font-size: 14px;
···
<button id="play">▶ Play</button>
<button id="next">Next →</button>
<button id="reset">↺ Reset</button>
+
<div class="speed-control">
+
<label for="speed">Speed:</label>
+
<input type="range" id="speed" min="1" max="25" value="5" step="1">
+
<span id="speedValue">5x</span>
+
</div>
</div>
<div class="info" id="infoBar">
-
Group: <span id="groupNum">1</span> / <span id="totalGroups">${Math.ceil(problemsArray.length / 10)}</span>
+
Group: <span id="groupNum">1</span> / <span id="totalGroups">100</span>
| Grand Total: <span id="grandTotal">0</span>
</div>
···
<script>
const problems = ${JSON.stringify(problemsArray)};
-
const GROUP_SIZE = 10;
-
const totalGroups = Math.ceil(problems.length / GROUP_SIZE);
+
+
// Calculate group size based on how many cards fit in a row
+
function calculateGroupSize() {
+
const containerWidth = window.innerWidth * 0.95; // 95vw max
+
const cardMinWidth = 150; // minmax(150px, 1fr)
+
const gap = 15;
+
const containerPadding = 40; // 20px on each side
+
+
const availableWidth = containerWidth - containerPadding;
+
const cardsPerRow = Math.floor((availableWidth + gap) / (cardMinWidth + gap));
+
+
// Calculate rows that fit on screen (approximate)
+
const viewportHeight = window.innerHeight;
+
const headerHeight = 300; // Approximate space for header, controls, info
+
const availableHeight = viewportHeight - headerHeight;
+
const cardHeight = 150; // Approximate card height
+
const rowsPerScreen = Math.max(1, Math.floor((availableHeight + gap) / (cardHeight + gap)));
+
+
return Math.max(cardsPerRow, cardsPerRow * rowsPerScreen);
+
}
+
+
let GROUP_SIZE = calculateGroupSize();
+
let totalGroups = Math.ceil(problems.length / GROUP_SIZE);
let currentGroup = 0;
let isPart2 = false;
let isPlaying = false;
let shouldStop = false;
let runningTotal = 0;
+
let speed = 5;
+
+
// Step state for fine-grained navigation
+
let currentProblemIdx = 0; // Which problem in the group (0-9)
+
let currentStepIdx = 0; // Which number/column within that problem
+
let problemAccumulators = []; // Track accumulator for each problem
+
let problemData = []; // Calculated data for each problem in group
const groupNumEl = document.getElementById('groupNum');
const totalGroupsEl = document.getElementById('totalGroups');
···
const resetBtn = document.getElementById('reset');
const problemContainer = document.getElementById('problemContainer');
const calculation = document.getElementById('calculation');
+
const speedSlider = document.getElementById('speed');
+
const speedValue = document.getElementById('speedValue');
function updateModeLabels() {
part1Label.classList.toggle('active', !isPart2);
···
problemContainer.innerHTML = '';
calculation.innerHTML = '<span class="nums">Group Total: <span class="result">0</span></span>';
+
// Reset step state
+
currentProblemIdx = 0;
+
currentStepIdx = 0;
+
problemAccumulators = [];
+
problemData = [];
+
groupProblems.forEach((problem, i) => {
const item = document.createElement('div');
item.className = 'problem-item';
···
const localProblem = [...problem];
const operator = localProblem.pop()?.trim();
const maxWidth = localProblem.reduce((m, s) => Math.max(m, s.length), 0);
+
+
// Calculate and store problem data
+
const data = calculateProblemData(problem);
+
problemData.push(data);
+
problemAccumulators.push(operator === '*' ? 1 : 0);
let gridHtml = '<div class="problem-grid" id="grid-' + i + '">';
···
groupNumEl.textContent = currentGroup + 1;
totalGroupsEl.textContent = totalGroups;
-
prevBtn.disabled = currentGroup === 0 || isPlaying;
-
nextBtn.disabled = currentGroup === totalGroups - 1 || isPlaying;
+
updateButtons();
+
}
+
+
function updateButtons() {
+
const atStart = currentGroup === 0 && currentProblemIdx === 0 && currentStepIdx === 0;
+
const atEnd = currentGroup === totalGroups - 1 &&
+
currentProblemIdx === problemData.length - 1 &&
+
currentStepIdx === problemData[currentProblemIdx]?.nums.length;
+
+
prevBtn.disabled = isPlaying || atStart;
+
nextBtn.disabled = isPlaying || atEnd;
}
-
async function animateProblem(problemIdx, problemData, problem) {
-
const { nums, operator } = problemData;
+
function performStep(problemIdx, stepIdx) {
+
const data = problemData[problemIdx];
+
const { nums, operator } = data;
const grid = document.getElementById(\`grid-\${problemIdx}\`);
const acc = document.getElementById(\`acc-\${problemIdx}\`);
-
-
let accumulator = operator === '*' ? 1 : 0;
+
+
if (!grid || !acc || stepIdx >= nums.length) return;
+
const startIdx = currentGroup * GROUP_SIZE;
+
const problem = problems[startIdx + problemIdx];
+
if (isPart2) {
// Part 2: Highlight columns
const localProblem = [...problem];
localProblem.pop(); // Remove operator
const maxWidth = localProblem.reduce((m, s) => Math.max(m, s.length), 0);
+
const colIdx = maxWidth - 2 - stepIdx;
+
+
// Highlight all digits in this column
+
const digitsInCol = grid.querySelectorAll(\`[data-col="\${colIdx}"]\`);
+
digitsInCol.forEach(d => d.classList.add('highlight'));
+
} else {
+
// Part 1: Highlight rows
+
const numberElements = grid.querySelectorAll('.number');
+
if (numberElements[stepIdx]) {
+
numberElements[stepIdx].classList.add('highlight');
+
}
+
}
-
for (let i = 0; i < nums.length; i++) {
-
// Column index: rightmost is maxWidth-1, then maxWidth-2, etc.
-
const colIdx = maxWidth - 2 - i;
-
-
// Highlight all digits in this column
-
const digitsInCol = grid.querySelectorAll(\`[data-col="\${colIdx}"]\`);
-
digitsInCol.forEach(d => d.classList.add('highlight'));
-
-
await new Promise(resolve => setTimeout(resolve, 400));
-
-
// Perform operation
-
if (operator === '*') {
-
accumulator *= nums[i];
-
} else {
-
accumulator += nums[i];
-
}
-
acc.textContent = accumulator.toLocaleString();
+
// Perform operation
+
if (operator === '*') {
+
problemAccumulators[problemIdx] *= nums[stepIdx];
+
} else {
+
problemAccumulators[problemIdx] += nums[stepIdx];
+
}
+
acc.textContent = problemAccumulators[problemIdx].toLocaleString();
+
}
-
// Fade out the column
-
digitsInCol.forEach(d => {
-
d.classList.remove('highlight');
-
d.classList.add('fade');
-
});
+
function fadeStep(problemIdx, stepIdx) {
+
const grid = document.getElementById(\`grid-\${problemIdx}\`);
+
if (!grid) return;
-
await new Promise(resolve => setTimeout(resolve, 200));
-
}
+
const startIdx = currentGroup * GROUP_SIZE;
+
const problem = problems[startIdx + problemIdx];
+
+
if (isPart2) {
+
const localProblem = [...problem];
+
localProblem.pop();
+
const maxWidth = localProblem.reduce((m, s) => Math.max(m, s.length), 0);
+
const colIdx = maxWidth - 2 - stepIdx;
+
+
const digitsInCol = grid.querySelectorAll(\`[data-col="\${colIdx}"]\`);
+
digitsInCol.forEach(d => {
+
d.classList.remove('highlight');
+
d.classList.add('fade');
+
});
} else {
-
// Part 1: Highlight rows
const numberElements = grid.querySelectorAll('.number');
+
if (numberElements[stepIdx]) {
+
numberElements[stepIdx].classList.remove('highlight');
+
numberElements[stepIdx].classList.add('fade');
+
}
+
}
+
}
-
for (let i = 0; i < nums.length; i++) {
-
// Highlight current number
-
numberElements[i].classList.add('highlight');
+
function stepForward(fromPlayback = false) {
+
if (isPlaying && !fromPlayback) return;
+
+
// Perform current step
+
performStep(currentProblemIdx, currentStepIdx);
+
+
// Advance step
+
currentStepIdx++;
+
+
// Check if we've finished this problem
+
if (currentStepIdx >= problemData[currentProblemIdx].nums.length) {
+
// Fade the last step
+
fadeStep(currentProblemIdx, currentStepIdx - 1);
+
+
// Update grand total
+
runningTotal += problemAccumulators[currentProblemIdx];
+
grandTotalEl.textContent = runningTotal.toLocaleString();
+
+
// Move to next problem
+
currentProblemIdx++;
+
currentStepIdx = 0;
+
+
// Check if we've finished the group
+
if (currentProblemIdx >= problemData.length) {
+
// Update group total
+
const groupTotal = problemAccumulators.reduce((sum, val) => sum + val, 0);
+
calculation.innerHTML = \`<span class="nums">Group Total: <span class="result">\${groupTotal.toLocaleString()}</span></span>\`;
-
await new Promise(resolve => setTimeout(resolve, 400));
+
// Move to next group
+
if (currentGroup < totalGroups - 1) {
+
currentGroup++;
+
renderGroup();
+
}
+
}
+
} else {
+
// Fade previous step
+
if (currentStepIdx > 0) {
+
fadeStep(currentProblemIdx, currentStepIdx - 1);
+
}
+
}
+
+
if (!fromPlayback) updateButtons();
+
}
-
// Perform operation
-
if (operator === '*') {
-
accumulator *= nums[i];
+
function stepBackward() {
+
if (isPlaying) return;
+
+
// Move back one step
+
currentStepIdx--;
+
+
// If we're before the start of this problem, go to previous problem
+
if (currentStepIdx < 0) {
+
currentProblemIdx--;
+
+
// If we're before the start of the group, go to previous group
+
if (currentProblemIdx < 0) {
+
if (currentGroup > 0) {
+
currentGroup--;
+
renderGroup();
+
// Set to end of this group
+
currentProblemIdx = problemData.length - 1;
+
currentStepIdx = problemData[currentProblemIdx].nums.length - 1;
} else {
-
accumulator += nums[i];
+
// Already at the very start
+
currentProblemIdx = 0;
+
currentStepIdx = 0;
}
-
acc.textContent = accumulator.toLocaleString();
-
-
// Fade out the number
-
numberElements[i].classList.remove('highlight');
-
numberElements[i].classList.add('fade');
-
-
await new Promise(resolve => setTimeout(resolve, 200));
+
} else {
+
// Go to end of previous problem
+
currentStepIdx = problemData[currentProblemIdx].nums.length - 1;
+
+
// Revert the grand total
+
runningTotal -= problemAccumulators[currentProblemIdx + 1];
+
grandTotalEl.textContent = runningTotal.toLocaleString();
}
}
-
-
return accumulator;
+
+
// Clear current state and rebuild up to current step
+
renderGroupState();
+
updateButtons();
}
-
async function animateGroup() {
+
function renderGroupState() {
+
// Re-render the group with current state
const startIdx = currentGroup * GROUP_SIZE;
const endIdx = Math.min(startIdx + GROUP_SIZE, problems.length);
const groupProblems = problems.slice(startIdx, endIdx);
-
-
let groupTotal = 0;
-
-
// Animate all problems in parallel but update totals as each completes
-
const results = await Promise.all(
-
groupProblems.map(async (problem, i) => {
-
const data = calculateProblemData(problem);
-
const result = await animateProblem(i, data, problem);
-
-
// Update totals cumulatively as each problem finishes
-
groupTotal += result;
-
runningTotal += result;
-
grandTotalEl.textContent = runningTotal.toLocaleString();
-
-
return result;
-
})
-
);
-
-
// Show final group total
-
calculation.innerHTML = \`<span class="nums">Group Total: <span class="result">\${groupTotal.toLocaleString()}</span></span>\`;
+
+
// Reset accumulators
+
for (let i = 0; i < problemAccumulators.length; i++) {
+
const data = problemData[i];
+
problemAccumulators[i] = data.operator === '*' ? 1 : 0;
+
const acc = document.getElementById(\`acc-\${i}\`);
+
if (acc) acc.textContent = '';
+
}
+
+
// Clear all highlights and fades
+
document.querySelectorAll('.highlight, .fade').forEach(el => {
+
el.classList.remove('highlight', 'fade');
+
});
+
+
// Replay all steps up to current position
+
for (let p = 0; p <= currentProblemIdx; p++) {
+
const maxStep = p === currentProblemIdx ? currentStepIdx : problemData[p].nums.length;
+
for (let s = 0; s < maxStep; s++) {
+
performStep(p, s);
+
fadeStep(p, s);
+
}
+
}
}
async function playAll() {
isPlaying = true;
shouldStop = false;
playBtn.textContent = '⏸ Pause';
-
prevBtn.disabled = true;
-
nextBtn.disabled = true;
+
updateButtons();
-
for (let i = currentGroup; i < totalGroups; i++) {
-
if (shouldStop) break;
+
while (!shouldStop) {
+
// Check if we're at the end
+
const atEnd = currentGroup === totalGroups - 1 &&
+
currentProblemIdx === problemData.length - 1 &&
+
currentStepIdx >= problemData[currentProblemIdx].nums.length;
-
currentGroup = i;
-
renderGroup();
-
await animateGroup();
+
if (atEnd) break;
-
if (shouldStop) break;
-
await new Promise(resolve => setTimeout(resolve, 1000));
+
stepForward(true);
+
// Speed: 1 = 1000ms, 5 = 600ms, 10 = 200ms, 25 = 20ms
+
const delay = Math.max(20, 1050 - (speed * 50));
+
await new Promise(resolve => setTimeout(resolve, delay));
}
isPlaying = false;
+
shouldStop = false;
playBtn.textContent = '▶ Play';
-
prevBtn.disabled = currentGroup === 0;
-
nextBtn.disabled = currentGroup === totalGroups - 1;
+
updateButtons();
}
function stopPlaying() {
shouldStop = true;
+
isPlaying = false;
+
playBtn.textContent = '▶ Play';
+
updateButtons();
}
function resetAnimation() {
-
if (isPlaying) return;
+
if (isPlaying) stopPlaying();
currentGroup = 0;
runningTotal = 0;
grandTotalEl.textContent = '0';
···
});
prevBtn.addEventListener('click', () => {
-
if (!isPlaying && currentGroup > 0) {
-
currentGroup--;
-
renderGroup();
-
}
+
stepBackward();
});
nextBtn.addEventListener('click', () => {
-
if (!isPlaying && currentGroup < totalGroups - 1) {
-
currentGroup++;
-
renderGroup();
-
}
+
stepForward();
});
resetBtn.addEventListener('click', resetAnimation);
···
}
});
+
speedSlider.addEventListener('input', (e) => {
+
speed = parseInt(e.target.value);
+
speedValue.textContent = speed + 'x';
+
});
+
document.addEventListener('keydown', (e) => {
-
if (isPlaying) return;
if (e.key === 'ArrowLeft') prevBtn.click();
if (e.key === 'ArrowRight') nextBtn.click();
if (e.key === ' ') {
e.preventDefault();
playBtn.click();
}
-
if (e.key === 'r' || e.key === 'R') resetBtn.click();
+
if (e.key === 'r' || e.key === 'R') {
+
if (!isPlaying) resetBtn.click();
+
}
if (e.key === 't' || e.key === 'T') {
-
toggleMode();
+
if (!isPlaying) toggleMode();
+
}
+
});
+
+
// Handle window resize
+
window.addEventListener('resize', () => {
+
const newGroupSize = calculateGroupSize();
+
if (newGroupSize !== GROUP_SIZE && !isPlaying) {
+
GROUP_SIZE = newGroupSize;
+
totalGroups = Math.ceil(problems.length / GROUP_SIZE);
+
// Adjust current group to maintain position
+
const currentProblemGlobal = currentGroup * GROUP_SIZE + currentProblemIdx;
+
currentGroup = Math.floor(currentProblemGlobal / GROUP_SIZE);
+
currentProblemIdx = currentProblemGlobal % GROUP_SIZE;
+
totalGroupsEl.textContent = totalGroups;
+
renderGroup();
}
});
+281 -94
vis/06/index.html
···
opacity: 0.5;
cursor: not-allowed;
}
+
.speed-control {
+
display: flex;
+
align-items: center;
+
gap: 8px;
+
font-size: 13px;
+
color: #a6adc8;
+
}
+
.speed-control input[type="range"] {
+
width: 120px;
+
height: 6px;
+
background: #313244;
+
outline: none;
+
-webkit-appearance: none;
+
}
+
.speed-control input[type="range"]::-webkit-slider-thumb {
+
-webkit-appearance: none;
+
appearance: none;
+
width: 14px;
+
height: 14px;
+
background: #a6e3a1;
+
cursor: pointer;
+
border: 1px solid #313244;
+
}
+
.speed-control input[type="range"]::-moz-range-thumb {
+
width: 14px;
+
height: 14px;
+
background: #a6e3a1;
+
cursor: pointer;
+
border: 1px solid #313244;
+
}
.info {
color: #f9e2af;
font-size: 14px;
···
<button id="play">▶ Play</button>
<button id="next">Next →</button>
<button id="reset">↺ Reset</button>
+
<div class="speed-control">
+
<label for="speed">Speed:</label>
+
<input type="range" id="speed" min="1" max="25" value="5" step="1">
+
<span id="speedValue">5x</span>
+
</div>
</div>
<div class="info" id="infoBar">
···
<script>
const problems = [[" 6 ","187 ","635 ","552 ","* "],[" 8 ","49 ","82 ","33 ","* "],["99 ","5422 ","2573 ","5463 ","+ "],["58 ","49 ","27 ","92 ","+ "],["62 ","75 ","43 ","88 ","+ "],[" 9 ","874 ","218 ","798 ","* "],["497 ","545 ","479 ","629 ","* "],["52 ","23 ","38 ","66 ","* "],["586 ","151 ","721 ","514 ","+ "],["58 ","27 ","23 ","25 ","+ "],["1728 "," 745 "," 678 "," 95 ","+ "],["786 ","152 ","998 ","977 ","+ "],["46 ","176 ","158 ","587 ","+ "],[" 6 "," 1 ","24 ","84 ","* "],["39 ","64 ","46 ","82 ","* "],["445 ","252 ","713 "," 58 ","* "],["44 ","76 ","92 ","77 ","+ "],["9 ","54 ","88 ","83 ","* "],[" 2 ","78 ","15 ","36 ","+ "],["65 ","49 ","98 ","26 ","* "],["738 ","991 "," 28 "," 2 ","* "],["741 ","858 ","121 ","3643 ","+ "],["66 ","376 ","896 ","911 ","* "],["23 ","35 ","8 ","1 ","+ "],["656 ","193 ","647 ","617 ","* "],["18 ","68 ","6898 ","9999 ","+ "],["83 ","24 ","18 ","34 ","* "],["87 ","27 ","16 ","85 ","+ "],["75 ","98 ","74 ","24 ","* "],["929 ","651 ","8 ","1 ","+ "],["693 ","323 ","2767 ","2544 ","+ "],["2743 ","6366 "," 194 "," 1 ","+ "],["26 ","87 ","47 ","26 ","+ "],[" 122 "," 851 ","8654 ","7296 ","+ "],["2 ","3 ","78 ","67 ","+ "],[" 518 "," 228 ","7418 ","1683 ","+ "],["56 ","146 ","5555 ","6376 ","+ "],["6779 "," 515 "," 11 "," 15 ","+ "],["934 ","514 ","931 "," 9 ","* "],["19 ","42 ","73 ","3 ","* "],["96 ","11 ","98 "," 9 ","* "],["65 ","32 "," 9 "," 9 ","* "],["7 ","83 ","93 ","26 ","* "],["791 ","724 "," 35 "," 2 ","+ "],[" 8 ","717 ","365 ","452 ","* "],["9 ","37 ","32 ","14 ","+ "],["261 ","213 ","841 ","346 ","+ "],["5849 ","8115 ","7793 "," 245 ","+ "],[" 1 "," 2 ","35 ","53 ","+ "],["8 ","9 ","83 ","86 ","* "],["5 ","82 ","47 ","44 ","+ "],["28 ","87 "," 8 "," 4 ","* "],["38 ","212 ","193 ","736 ","+ "],["27 ","191 ","814 ","696 ","+ "],["819 ","95 ","99 ","43 ","+ "],["89 ","41 ","86 ","82 ","* "],["111 ","737 ","982 ","5 ","+ "],["828 ","762 ","433 ","86 ","+ "],["4993 ","2869 "," 724 "," 495 ","+ "],["241 ","227 ","755 ","89 ","+ "],["255 ","592 ","739 "," 62 ","* "],["249 ","291 ","651 "," 42 ","+ "],["27 ","61 ","13 ","7 ","* "],["256 ","689 ","222 "," 43 ","+ "],["71 ","66 ","17 "," 2 ","* "],[" 31 "," 657 ","7694 ","6832 ","+ "],["8964 ","8899 ","6457 ","2951 ","+ "],["44 ","89 ","45 ","7 ","+ "],["71 ","68 ","67 ","12 ","* "],["49 ","43 ","84 ","82 ","* "],["37 ","39 "," 5 "," 9 ","+ "],["3368 "," 578 "," 149 "," 391 ","+ "],["28 ","32 ","46 ","78 ","* "],["586 ","537 "," 68 "," 57 ","* "],["53 ","74 ","75 ","6 ","* "],["7117 ","2449 ","4776 ","979 ","+ "],["98 ","86 ","59 ","58 ","+ "],["765 ","383 ","941 ","724 ","* "],["2 ","9 ","362 ","522 ","* "],[" 83 ","827 ","239 ","795 ","* "],["6 ","93 ","139 ","8893 ","+ "],["325 ","982 ","691 "," 1 ","* "],["727 ","461 ","217 ","994 ","+ "],["62 ","25 ","48 ","2 ","+ "],["73 ","83 ","64 ","23 ","* "],[" 7 "," 3 ","96 ","84 ","+ "],["5238 ","3563 ","1173 ","6937 ","+ "],["37 ","19 ","11 "," 4 ","+ "],["431 ","851 ","824 ","448 ","+ "],["9 ","35 ","131 ","241 ","+ "],["3 ","52 ","631 ","422 ","* "],["358 ","798 ","366 ","146 ","* "],["7 ","35 ","95 ","58 ","* "],["727 ","767 ","977 ","445 ","* "],["94 ","17 ","57 ","63 ","* "],["7846 ","9933 ","7366 "," 1 ","+ "],["129 ","693 "," 64 "," 27 ","+ "],["331 ","823 ","714 ","814 ","* "],["757 ","358 ","443 ","148 ","+ "],[" 23 ","5992 ","4482 ","1517 ","+ "],["7 ","8 ","18 ","64 ","* "],["82 ","75 ","64 ","66 ","* "],[" 91 "," 92 ","537 ","752 ","+ "],[" 1 "," 2 ","66 ","13 ","+ "],["1 ","7 ","99 ","82 ","* "],["92 ","77 ","25 ","45 ","* "],["963 ","614 ","283 "," 1 ","+ "],["124 "," 83 "," 55 "," 54 ","+ "],["82 ","23 ","29 ","91 ","* "],["6 ","2 ","21 ","84 ","+ "],["592 ","335 "," 33 "," 71 ","* "],["96 ","173 ","637 ","915 ","* "],["657 ","552 ","2 ","5 ","* "],["61 ","81 ","73 ","56 ","+ "],["8 ","5 ","9 ","18 ","* "],[" 6 "," 9 ","23 ","94 ","+ "],["88 ","483 ","166 ","845 ","* "],["284 ","277 ","544 ","335 ","* "],["6465 ","5839 ","6478 ","12 ","+ "],["1813 ","5663 ","5522 ","1 ","+ "],["75 ","64 ","28 ","9 ","* "],["2198 ","2831 ","7339 "," 183 ","+ "],["1169 ","5168 ","1419 ","8825 ","+ "],["45 ","74 ","11 ","52 ","+ "],["7 ","7 ","34 ","86 ","* "],["22 ","52 ","13 ","9 ","* "],[" 2 ","95 ","66 ","52 ","* "],["379 ","341 ","961 ","536 ","* "],["25 ","32 ","12 ","29 ","* "],["8858 ","9115 ","8176 ","4996 ","+ "],["74 ","29 ","41 ","73 ","* "],["5122 ","8431 ","869 ","131 ","+ "],["82 "," 9 "," 6 "," 9 ","+ "],["774 ","65 ","18 ","77 ","* "],["52 ","36 ","62 ","94 ","* "],["31 ","37 ","1 ","3 ","+ "],["26 ","43 ","91 ","192 ","+ "],["2 ","25 ","43 ","95 ","* "],[" 2 "," 99 ","679 ","935 ","* "],["3 ","43 ","859 ","873 ","+ "],[" 2 "," 8 ","81 ","71 ","* "],[" 5 "," 44 ","985 ","296 ","* "],["3697 ","8295 ","5379 ","5435 ","+ "],[" 27 ","8799 ","6312 ","9951 ","+ "],["129 ","714 ","278 ","22 ","+ "],["9 ","31 ","93 ","46 ","+ "],["4 ","9 ","29 ","89 ","* "],["44 ","677 ","1613 ","3419 ","+ "],["99 ","48 ","68 "," 6 ","* "],["874 ","164 "," 34 "," 9 ","* "],[" 68 ","955 ","522 ","883 ","* "],["21 ","49 ","98 ","94 ","* "],["86 "," 8 "," 9 "," 3 ","+ "],["77 ","75 ","4 ","7 ","+ "],[" 1 "," 65 ","864 ","451 ","* "],["456 ","133 ","438 "," 81 ","* "],[" 29 "," 66 ","271 ","141 ","* "],["785 ","367 ","548 "," 22 ","* "],["72 ","91 ","87 ","86 ","+ "],["132 ","245 ","127 ","8 ","* "],["558 ","564 ","755 ","488 ","* "],["645 ","115 ","259 ","754 ","* "],["14 ","392 ","981 ","718 ","* "],["38 ","69 ","72 ","23 ","* "],["13 ","58 ","49 ","65 ","* "],["14 ","92 ","45 "," 1 ","* "],["15 ","19 ","92 ","32 ","* "],["16 ","54 ","5 ","1 ","+ "],["8 ","66 ","78 ","61 ","* "],["5898 ","827 ","492 ","61 ","+ "],["61 ","67 ","55 ","93 ","* "],["22 ","81 ","98 ","25 ","* "],["54 ","79 ","173 ","9733 ","+ "],["15 ","86 ","23 "," 3 ","+ "],[" 599 "," 119 ","5155 ","9881 ","+ "],["741 ","165 ","337 ","1 ","+ "],["548 ","688 ","946 ","621 ","+ "],["99 ","68 ","59 ","52 ","+ "],["41 ","43 ","78 ","4 ","* "],["43 ","18 ","62 ","14 ","* "],[" 21 "," 29 ","439 ","335 ","* "],["281 ","552 ","516 "," 4 ","* "],["78 ","53 ","88 ","2 ","* "],[" 69 ","1598 ","1655 ","6852 ","+ "],[" 7 "," 9 ","53 ","23 ","+ "],["35 ","16 ","35 ","56 ","* "],["385 ","242 ","972 ","285 ","+ "],["667 ","173 ","823 ","44 ","* "],["46 ","65 ","12 ","43 ","* "],["64 ","56 ","165 ","258 ","* "],["99 ","65 ","24 ","24 ","+ "],["97 ","26 ","79 "," 8 ","* "],["46 ","89 ","6 ","2 ","* "],["418 ","995 ","917 "," 72 ","+ "],["58 ","71 "," 3 "," 8 ","+ "],["43 ","87 ","61 "," 3 ","+ "],["392 ","854 ","487 ","6 ","* "],["28 ","66 ","12 ","6 ","* "],[" 5 ","55 ","63 ","35 ","* "],["9754 ","2842 ","598 ","734 ","+ "],["4328 ","3228 ","9493 ","47 ","+ "],["372 ","899 ","935 ","586 ","* "],["6156 ","8899 ","1427 ","474 ","+ "],["79 ","55 ","22 ","64 ","* "],[" 88 ","148 ","278 ","318 ","+ "],["5248 ","792 ","782 ","54 ","+ "],[" 3 "," 1 ","26 ","54 ","* "],["472 ","981 ","593 "," 78 ","+ "],["7 ","47 ","25 ","678 ","* "],["97 ","33 ","834 ","256 ","* "],["37 ","21 ","83 ","82 ","* "],["28 ","25 ","83 ","89 ","* "],["32 ","88 "," 5 "," 5 ","* "],["398 ","951 ","846 "," 7 ","+ "],["74 ","44 ","13 ","93 ","* "],["8979 ","6767 ","8992 ","4627 ","+ "],["396 ","336 "," 18 "," 1 ","+ "],["229 ","595 ","964 ","96 ","* "],["168 "," 64 "," 42 "," 3 ","* "],["338 ","597 ","571 ","1 ","* "],["68 ","99 ","62 ","57 ","+ "],["3762 ","3676 ","945 ","9 ","+ "],["83 ","99 ","62 ","11 ","* "],["979 ","615 ","744 ","936 ","+ "],["363 ","116 ","418 ","483 ","+ "],["68 ","174 ","571 ","382 ","+ "],["4277 ","9429 ","2773 ","291 ","+ "],["111 ","379 ","396 ","288 ","+ "],["8198 ","6684 ","1434 ","72 ","+ "],[" 98 "," 26 ","984 ","488 ","* "],[" 3 "," 44 ","3999 ","1518 ","+ "],["88 ","17 ","32 ","16 ","* "],[" 84 ","511 ","592 ","382 ","* "],["7867 ","2764 ","8337 "," 678 ","+ "],[" 483 "," 337 ","1611 ","2329 ","+ "],["465 ","6842 ","9792 ","5738 ","+ "],["938 ","653 ","287 ","6 ","* "],["36 ","89 ","15 ","28 ","* "],["85 ","79 ","77 ","5 ","* "],["28 ","89 ","23 ","27 ","* "],["57 ","367 ","1482 ","8221 ","+ "],[" 28 "," 33 ","685 ","187 ","* "],["15 ","15 ","72 "," 6 ","* "],["123 ","73 ","17 ","31 ","+ "],["95 ","21 ","61 ","23 ","* "],["148 ","966 ","858 "," 46 ","* "],["4 ","5 ","897 ","495 ","* "],["798 ","458 ","671 "," 7 ","* "],[" 75 ","7569 ","8219 ","6513 ","+ "],[" 2 ","4539 ","4965 ","1633 ","+ "],["85 ","71 ","34 ","52 ","* "],["8 ","86 ","44 ","385 ","* "],["964 ","734 ","567 ","31 ","+ "],["648 ","594 ","45 ","78 ","* "],["6 ","7 ","18 ","5724 ","+ "],["21 ","23 ","83 ","15 ","* "],["38 ","94 ","63 ","67 ","* "],[" 7 "," 6 "," 1 ","88 ","* "],["62 ","93 ","72 ","14 ","+ "],["75 ","7 ","8 ","7 ","+ "],["258 ","651 ","322 ","212 ","+ "],["374 ","536 "," 6 "," 4 ","* "],["42 ","361 ","614 ","744 ","+ "],[" 65 ","676 ","587 ","993 ","* "],["61 ","21 ","16 ","68 ","+ "],["94 ","5 ","1 ","3 ","* "],["595 ","779 ","782 ","132 ","* "],[" 6 ","44 ","72 ","99 ","* "],["342 ","953 ","24 ","47 ","+ "],["56 ","58 ","9 ","7 ","+ "],["65 ","52 ","14 ","133 ","* "],["3 ","745 ","671 ","398 ","* "],["64 ","82 ","27 ","43 ","+ "],["999 ","347 "," 73 "," 7 ","* "],[" 1 ","263 ","298 ","395 ","* "],["9375 ","9827 ","3896 ","7978 ","+ "],["46 ","52 ","472 ","1958 ","+ "],["16 ","758 ","914 ","3196 ","+ "],["266 ","7722 ","4558 ","9786 ","+ "],["5 ","53 ","23 ","52 ","* "],[" 39 ","9861 ","9769 ","3316 ","+ "],["2 ","46 ","57 ","84 ","* "],["9848 ","3154 "," 475 "," 28 ","+ "],[" 2 ","14 ","41 ","33 ","+ "],[" 1 "," 415 ","6754 ","6516 ","+ "],["2955 ","9124 "," 765 "," 446 ","+ "],["823 ","536 ","177 "," 27 ","+ "],["88 ","27 ","96 ","18 ","* "],["525 ","251 ","483 ","923 ","+ "],["64 ","92 ","291 ","926 ","* "],["624 ","453 ","732 ","848 ","* "],["525 "," 49 "," 9 "," 1 ","+ "],["11 ","51 ","4 ","1 ","* "],["8 ","97 ","934 ","744 ","* "],["284 ","59 ","16 ","39 ","* "],[" 79 "," 58 ","314 ","765 ","+ "],["24 ","88 ","63 ","8 ","* "],["752 ","88 ","44 ","78 ","* "],["123 ","37 ","6 ","8 ","+ "],["7 ","47 ","18 ","21 ","* "],["283 ","318 ","989 ","275 ","* "],[" 98 ","569 ","364 ","338 ","+ "],["151 ","778 "," 45 "," 4 ","+ "],["729 ","677 ","12 ","6 ","* "],[" 79 ","5598 ","7359 ","3373 ","+ "],["49 ","83 ","39 ","57 ","* "],["12 ","81 ","36 ","35 ","+ "],["68 ","85 ","61 ","74 ","+ "],["78 ","44 ","3 ","4 ","* "],["1824 ","2978 ","9182 ","9983 ","+ "],["196 ","576 "," 39 "," 27 ","* "],[" 8 ","14 ","34 ","54 ","* "],["246 ","947 "," 24 "," 66 ","* "],[" 8 "," 11 ","578 ","552 ","* "],["519 ","215 ","461 "," 16 ","+ "],["97 ","51 ","62 ","25 ","+ "],["182 ","841 ","391 "," 33 ","* "],["16 ","6574 ","4924 ","1972 ","+ "],["7 ","53 ","12 ","45 ","+ "],["295 "," 42 "," 97 "," 61 ","+ "],["68 ","36 ","54 ","43 ","+ "],["69 ","71 "," 4 "," 5 ","+ "],["5 ","4 ","866 ","322 ","+ "],["96 ","25 ","19 "," 7 ","* "],[" 8 "," 6 ","16 ","46 ","+ "],["5 ","22 ","81 ","77 ","* "],[" 47 "," 91 ","3876 ","5276 ","+ "],["5 ","9 ","7 ","33 ","+ "],["92 ","22 ","75 ","78 ","+ "],[" 5 "," 2 ","34 ","75 ","+ "],["66 "," 9 "," 8 "," 8 ","* "],["11 ","87 ","84 ","51 ","* "],["79 ","63 ","72 ","99 ","* "],["863 ","871 ","395 ","936 ","* "],["2875 ","7489 ","5238 ","427 ","+ "],[" 71 ","166 ","588 ","659 ","* "],["89 ","87 ","34 ","46 ","* "],["133 "," 12 "," 17 "," 7 ","* "],["75 ","75 ","97 ","36 ","* "],[" 93 "," 63 ","7819 ","6948 ","+ "],["64 ","98 ","96 ","38 ","* "],["37 ","36 ","21 ","81 ","* "],[" 8 "," 27 ","339 ","569 ","+ "],["2846 ","1562 ","4664 ","2 ","+ "],["4188 ","8446 ","1145 "," 936 ","+ "],["681 ","161 "," 28 "," 2 ","* "],[" 1 ","89 ","22 ","12 ","+ "],["6 ","99 ","79 ","11 ","* "],["845 ","429 "," 15 "," 1 ","+ "],[" 7 "," 9 "," 8 ","65 ","* "],["2255 ","8757 ","8821 ","561 ","+ "],["246 ","761 ","974 ","368 ","+ "],["71 ","84 ","51 ","75 ","+ "],["347 ","241 ","42 ","4 ","* "],["77 ","29 ","26 ","9 ","* "],["25 ","16 ","74 ","53 ","* "],["355 ","766 ","447 "," 75 ","* "],["225 ","622 "," 12 "," 87 ","* "],["569 ","837 ","875 ","239 ","* "],["53 ","25 ","54 "," 7 ","* "],["17 ","42 ","58 "," 1 ","* "],["61 ","329 ","441 ","7627 ","+ "],["257 ","924 ","179 ","232 ","+ "],["5 ","554 ","675 ","984 ","* "],["476 ","192 ","33 ","23 ","* "],["3648 ","4493 ","493 ","68 ","+ "],["57 ","38 ","86 ","61 ","* "],[" 8 ","71 ","65 ","34 ","+ "],[" 3 "," 1 "," 75 ","455 ","* "],[" 6 ","627 ","364 ","488 ","+ "],[" 3 "," 1 ","325 ","241 ","+ "],["515 ","464 ","22 ","5 ","+ "],["492 ","897 ","75 ","68 ","* "],["77 ","44 ","37 ","89 ","+ "],[" 1 "," 64 ","351 ","135 ","* "],["73 ","86 ","82 "," 6 ","+ "],[" 77 "," 866 "," 215 ","3576 ","+ "],[" 67 ","924 ","242 ","247 ","+ "],["4 ","973 ","656 ","357 ","* "],["7 ","7 ","4 ","73 ","* "],["779 ","859 ","978 ","84 ","+ "],["98 ","1398 ","2394 ","3114 ","+ "],["56 ","55 ","73 ","9 ","* "],[" 2 ","81 ","59 ","36 ","+ "],["748 ","881 ","382 ","2 ","* "],["77 ","47 ","86 ","91 ","* "],["957 ","695 ","338 ","879 ","+ "],["56 ","671 ","488 ","927 ","+ "],[" 49 "," 229 ","7495 ","7669 ","+ "],[" 519 ","1363 ","8248 ","3249 ","+ "],["1285 ","7337 ","4326 "," 879 ","+ "],["64 ","62 ","5 ","7 ","* "],["748 ","332 ","25 ","4 ","* "],["45 ","85 ","13 "," 3 ","* "],["19 ","25 ","51 ","97 ","* "],[" 7 ","6661 ","7373 ","7664 ","+ "],["26 "," 2 "," 5 "," 4 ","* "],[" 5 ","45 ","88 ","89 ","* "],["8 ","4 ","41 ","79 ","* "],["86 ","37 ","47 ","73 ","* "],["11 ","75 ","67 ","24 ","+ "],["3 ","3 ","1 ","61 ","* "],["295 ","3324 ","4171 ","1235 ","+ "],["62 ","174 ","657 ","313 ","+ "],["151 ","58 ","1 ","9 ","+ "],["123 ","97 ","19 ","18 ","+ "],["3965 ","5319 ","453 ","2 ","+ "],["174 ","149 ","272 "," 11 ","+ "],["71 ","69 "," 4 "," 8 ","+ "],["72 ","49 ","23 ","74 ","* "],[" 96 ","827 ","738 ","467 ","+ "],["92 ","99 ","287 ","963 ","* "],["9 ","4 ","169 ","358 ","+ "],[" 7 "," 61 ","783 ","395 ","+ "],["73 ","42 ","94 ","89 ","+ "],["7927 ","5472 "," 64 "," 37 ","+ "],["368 ","325 ","168 ","629 ","* "],["72 ","97 ","15 ","33 ","+ "],["722 ","627 ","754 ","79 ","* "],["494 ","334 ","943 ","34 ","+ "],["16 ","6 ","2 ","7 ","* "],["88 ","41 ","83 ","16 ","* "],["21 ","84 ","6 ","4 ","* "],["495 ","111 ","922 ","459 ","+ "],["5 ","4 ","2 ","52 ","* "],["7 ","29 ","192 ","396 ","+ "],["9194 ","5232 "," 85 "," 47 ","+ "],["52 ","56 ","86 ","75 ","* "],["33 ","74 ","665 ","985 ","* "],["19 ","64 ","27 ","41 ","* "],["4267 ","7517 ","4128 "," 123 ","+ "],["36 ","88 ","23 ","83 ","+ "],[" 1 ","76 ","91 ","15 ","* "],["54 ","26 ","75 ","77 ","+ "],[" 55 ","459 ","328 ","421 ","+ "],["66 ","81 ","55 "," 4 ","* "],["76 ","72 ","98 ","66 ","+ "],["94 ","29 ","41 ","84 ","* "],["8 ","78 ","32 ","78 ","* "],[" 51 "," 32 ","412 ","659 ","* "],["78 ","49 ","93 "," 1 ","+ "],["538 ","863 ","996 ","318 ","* "],[" 7 "," 8 ","93 ","48 ","* "],["3 ","2 ","96 ","53 ","* "],["67 ","75 ","52 ","69 ","+ "],["78 ","72 ","77 ","32 ","* "],["63 ","28 ","8 ","6 ","* "],[" 2 ","26 ","77 ","94 ","* "],["722 ","551 ","482 "," 54 ","+ "],["97 ","56 ","21 ","13 ","* "],["484 ","197 ","546 ","168 ","+ "],[" 4 "," 49 "," 839 ","3482 ","+ "],[" 4 ","147 ","232 ","836 ","+ "],["6789 ","6244 ","778 ","5 ","+ "],["47 ","37 ","89 ","27 ","* "],["7677 ","8456 ","3318 ","869 ","+ "],["176 ","929 ","541 ","45 ","* "],[" 5 "," 13 "," 88 ","118 ","+ "],["1 ","92 ","13 ","86 ","* "],[" 3 "," 1 ","71 ","86 ","* "],["722 "," 12 "," 28 "," 68 ","* "],["28 ","37 ","97 ","16 ","* "],["44 ","66 ","58 ","48 ","+ "],["51 ","51 ","9687 ","2927 ","+ "],["87 ","57 ","93 ","98 ","+ "],["27 ","54 ","78 ","48 ","+ "],["22 ","66 ","37 ","22 ","+ "],["8145 ","2234 ","4444 "," 456 ","+ "],["143 ","391 ","816 ","31 ","+ "],[" 7 ","41 ","91 ","21 ","* "],["6816 ","1647 "," 725 "," 74 ","+ "],["679 ","384 ","435 ","278 ","* "],["76 ","27 ","49 ","48 ","+ "],["9 ","6238 ","8573 ","9737 ","+ "],["22 ","581 ","688 ","559 ","* "],["936 ","774 "," 42 "," 22 ","+ "],["163 ","464 ","848 ","775 ","* "],["372 ","226 ","259 ","11 ","* "],["93 ","245 ","234 ","157 ","* "],["878 ","694 ","45 ","8 ","+ "],[" 93 ","457 ","922 ","525 ","+ "],["6 ","91 ","939 ","146 ","* "],["52 ","97 ","94 ","9 ","* "],["981 ","593 ","389 "," 38 ","+ "],["54 ","44 ","28 ","44 ","+ "],["53 "," 5 "," 4 "," 6 ","+ "],["99 ","72 ","24 ","38 ","+ "],["9671 ","9498 ","7156 "," 452 ","+ "],["9954 ","4914 ","8616 ","4 ","+ "],["45 ","11 ","85 ","91 ","+ "],["9 ","9 ","26 ","87 ","+ "],["22 ","6 ","2 ","5 ","+ "],["833 ","958 ","348 ","53 ","* "],["26 ","87 ","61 ","7 ","* "],["787 ","734 "," 66 "," 1 ","* "],["8 ","873 ","744 ","196 ","* "],["45 ","34 ","32 ","9 ","+ "],["6 ","1 ","8 ","95 ","* "],["88 ","96 ","56 ","42 ","* "],["48 ","776 ","926 ","319 ","* "],[" 6 ","48 ","88 ","31 ","+ "],["988 ","521 ","456 "," 64 ","* "],["5383 ","5887 "," 767 "," 39 ","+ "],[" 7 "," 8 ","43 ","98 ","* "],["6 ","58 ","451 ","195 ","* "],["1 ","768 ","665 ","681 ","* "],["24 ","91 ","328 ","454 ","* "],["6 ","98 ","865 ","931 ","* "],["9 ","9 ","57 ","49 ","* "],["18 ","81 ","8 ","1 ","+ "],["742 ","992 ","798 ","928 ","+ "],["57 ","78 ","62 ","75 ","* "],["22 ","46 ","72 ","51 ","* "],["591 ","862 ","97 ","17 ","+ "],["14 ","71 ","87 ","58 ","* "],[" 3 "," 3 "," 8 ","49 ","+ "],[" 58 "," 965 ","2955 ","1248 ","+ "],[" 4 ","76 ","95 ","89 ","* "],["26 ","52 ","42 ","5 ","+ "],[" 5 "," 9 ","33 ","75 ","* "],["5647 ","3686 ","4533 "," 67 ","+ "],["55 ","27 ","78 ","48 ","* "],["796 ","372 ","28 ","46 ","+ "],["23 ","33 ","283 ","648 ","+ "],["84 ","54 ","97 ","12 ","* "],["35 ","47 ","46 ","62 ","* "],[" 36 ","727 ","336 ","526 ","* "],["54 ","87 ","86 ","42 ","+ "],["8599 ","1135 ","9551 ","7626 ","+ "],["48 ","33 ","49 "," 7 ","* "],["66 ","622 ","6693 ","3614 ","+ "],["199 ","877 ","395 ","827 ","* "],["4 ","696 ","133 ","917 ","* "],["71 ","516 ","259 ","443 ","+ "],["92 ","79 ","55 ","6 ","* "],[" 7 ","44 ","12 ","42 ","+ "],["15 ","54 ","39 ","75 ","* "],["339 ","289 "," 89 "," 2 ","* "],["6 ","27 ","47 ","75 ","* "],["2883 ","2737 ","9459 "," 154 ","+ "],["19 ","73 ","42 ","3 ","+ "],["18 ","99 ","63 ","26 ","* "],["29 ","3128 ","4433 ","6492 ","+ "],[" 75 ","8558 ","6759 ","3937 ","+ "],["172 ","6 ","6 ","1 ","* "],["87 ","96 ","4 ","2 ","* "],["49 ","65 ","42 ","41 ","+ "],[" 64 "," 573 "," 314 ","3749 ","+ "],["514 ","346 ","259 ","129 ","* "],["127 ","795 ","989 ","932 ","* "],["29 ","83 ","79 "," 9 ","+ "],["4145 ","8997 ","1961 ","2979 ","+ "],["35 ","45 ","39 ","7 ","* "],["66 ","69 ","11 ","88 ","* "],["875 ","763 ","28 ","1 ","* "],["2959 ","6634 "," 879 "," 727 ","+ "],["6824 "," 996 "," 943 "," 23 ","+ "],["5 ","1 ","44 ","81 ","+ "],["686 ","262 ","176 ","982 ","* "],["14 ","61 ","286 ","128 ","+ "],[" 67 "," 76 ","968 ","931 ","+ "],["461 ","811 ","682 "," 38 ","* "],["992 ","963 ","2519 ","7434 ","+ "],["2498 ","9473 ","127 ","959 ","+ "],["2 ","1 ","5 ","54 ","* "],[" 1 "," 4 "," 4 ","74 ","+ "],[" 98 "," 79 ","977 ","128 ","+ "],[" 7 ","25 ","61 ","98 ","* "],["628 ","858 ","8 ","4 ","* "],["3927 ","2718 "," 775 "," 27 ","+ "],["61 ","71 ","45 ","18 ","+ "],[" 32 "," 512 "," 617 ","8755 ","+ "],["7 ","12 ","27 ","71 ","+ "],[" 93 ","845 ","183 ","415 ","+ "],[" 124 ","4191 ","7789 ","8452 ","+ "],["49 ","93 ","59 ","13 ","+ "],[" 1 ","945 ","399 ","741 ","* "],[" 978 ","5238 ","1224 ","4523 ","+ "],[" 96 "," 95 ","249 ","544 ","* "],["165 ","314 ","353 "," 7 ","+ "],["54 ","56 ","21 ","3 ","* "],["8 ","46 ","19 ","69 ","* "],[" 6 "," 388 ","6856 ","6959 ","+ "],["892 ","158 ","294 ","384 ","* "],[" 5 "," 212 ","3143 ","4395 ","+ "],["75 ","22 ","94 ","43 ","* "],["8341 ","6139 ","1272 "," 27 ","+ "],["939 ","697 "," 6 "," 9 ","* "],[" 1 ","53 ","81 ","46 ","* "],["344 ","12 ","8 ","9 ","+ "],["77 ","32 ","17 ","8 ","* "],["173 ","558 ","216 "," 51 ","+ "],["85 ","94 ","49 ","78 ","+ "],[" 5 ","65 ","62 ","78 ","* "],["727 ","919 ","2 ","9 ","* "],["565 ","737 ","426 ","459 ","+ "],["71 "," 5 "," 3 "," 7 ","+ "],["664 "," 19 "," 47 "," 7 ","* "],["6177 ","4829 ","6657 ","3389 ","+ "],["6725 ","6725 ","5965 "," 63 ","+ "],[" 9 "," 89 "," 58 ","359 ","+ "],["275 ","547 ","579 ","635 ","+ "],["245 ","524 ","98 ","8 ","* "],["5 ","535 ","515 ","356 ","+ "],[" 34 ","464 ","698 ","367 ","+ "],["884 ","33 ","99 ","84 ","* "],["787 ","291 "," 11 "," 25 ","+ "],["427 ","296 ","818 ","672 ","* "],["36 ","11 ","88 ","1 ","* "],["63 ","76 ","2 ","7 ","* "],["81 ","17 ","29 ","48 ","+ "],[" 4 "," 4 "," 5 ","28 ","+ "],["13 ","84 ","9 ","2 ","* "],["7 ","5797 ","4726 ","9158 ","+ "],["8 ","321 ","741 ","148 ","* "],["24 ","57 ","52 ","55 ","* "],["64 ","65 ","6 ","8 ","* "],["61 ","111 ","671 ","451 ","+ "],[" 2 ","763 ","897 ","533 ","+ "],["326 ","858 "," 4 "," 7 ","+ "],["2 ","26 ","45 ","41 ","* "],["333 ","676 ","331 ","5 ","+ "],["7 ","42 ","29 ","34 ","+ "],[" 19 ","979 ","399 ","177 ","+ "],[" 97 ","9299 ","6719 ","9591 ","+ "],["234 "," 43 "," 48 "," 28 ","* "],["93 ","64 ","52 ","42 ","+ "],["41 ","53 ","67 ","1 ","+ "],["74 ","476 ","739 ","672 ","* "],["196 ","563 ","878 ","439 ","* "],["9975 ","6376 ","9882 ","5337 ","+ "],["6 ","21 ","14 ","665 ","* "],["48 ","27 ","16 ","6 ","* "],["391 ","646 ","474 ","429 ","* "],["425 "," 74 "," 77 "," 24 ","* "],["752 ","483 ","547 ","371 ","+ "],[" 22 "," 14 ","965 ","886 ","* "],["833 ","553 ","23 ","97 ","* "],["812 ","174 ","893 ","227 ","* "],["526 ","684 ","997 ","876 ","+ "],["63 ","94 ","11 ","35 ","* "],["4397 ","3785 ","9537 ","3148 ","+ "],[" 98 "," 83 ","191 ","363 ","+ "],["55 ","55 ","25 ","61 ","+ "],["362 ","928 ","627 "," 5 ","+ "],[" 57 ","161 ","236 ","975 ","* "],["21 ","21 "," 9 "," 5 ","+ "],["37 ","11 ","87 "," 2 ","+ "],["687 ","852 ","645 "," 27 ","+ "],["181 ","85 ","7 ","2 ","+ "],[" 194 ","2627 ","6585 ","4488 ","+ "],["4 ","45 ","61 ","92 ","* "],["4745 ","2934 ","72 ","66 ","+ "],["13 ","47 ","16 ","45 ","+ "],["2 ","87 ","148 ","173 ","* "],["763 ","644 ","6252 ","1416 ","+ "],["77 ","1698 ","6244 ","7518 ","+ "],["589 ","192 ","357 ","41 ","+ "],["3 ","38 ","51 ","77 ","+ "],["136 ","423 ","85 ","4 ","* "],[" 54 ","641 ","655 ","368 ","* "],["76 ","27 "," 9 "," 9 ","* "],[" 24 ","729 ","599 ","884 ","* "],["3596 ","3473 "," 79 "," 57 ","+ "],["73 ","94 ","99 ","53 ","+ "],[" 48 ","896 ","534 ","538 ","+ "],["8719 ","2561 ","7951 "," 23 ","+ "],["52 ","54 ","981 ","471 ","+ "],["534 ","267 ","867 ","662 ","* "],["874 ","97 ","6 ","9 ","+ "],["961 ","742 ","112 ","838 ","* "],[" 5 ","59 ","97 ","14 ","* "],["14 ","61 ","89 "," 7 ","+ "],["935 ","22 ","4 ","5 ","+ "],[" 979 ","5128 ","8697 ","9968 ","+ "],["36 ","13 ","13 ","35 ","+ "],["242 ","353 ","645 "," 56 ","+ "],["48 ","35 ","95 ","53 ","* "],["19 ","35 ","56 ","86 ","* "],["4255 ","6946 ","4342 "," 857 ","+ "],["46 ","74 ","89 ","58 ","* "],["18 ","16 ","58 ","77 ","* "],["55 ","64 ","864 ","388 ","* "],["8793 ","9454 ","6826 "," 4 ","+ "],[" 4 ","28 ","82 ","24 ","* "],["31 ","86 ","12 ","74 ","+ "],["16 ","75 ","89 "," 5 ","+ "],["58 ","38 ","41 ","22 ","* "],["5 ","3 ","6 ","21 ","* "],["479 ","134 ","225 ","456 ","+ "],["912 ","425 "," 82 "," 9 ","* "],["6313 ","135 ","677 ","5 ","+ "],["793 ","734 ","786 ","3 ","* "],["94 ","72 ","54 ","33 ","* "],["2227 ","9844 ","3885 ","759 ","+ "],["799 ","624 ","363 ","455 ","* "],["446 ","2815 ","8528 ","9778 ","+ "],["564 ","766 ","3 ","7 ","+ "],["597 ","875 "," 56 "," 8 ","+ "],["327 ","656 ","593 ","673 ","+ "],[" 5 "," 72 ","2625 ","7933 ","+ "],["4 ","23 ","66 ","69 ","+ "],[" 9 ","589 ","758 ","285 ","+ "],[" 54 ","832 ","128 ","898 ","* "],["66 ","28 ","44 ","28 ","+ "],[" 3 ","39 ","37 ","84 ","* "],[" 45 "," 357 ","3759 ","6313 ","+ "],["665 ","496 ","994 ","26 ","* "],[" 69 "," 639 ","5825 ","2558 ","+ "],["1 ","634 ","148 ","5461 ","+ "],["49 ","19 ","76 ","58 ","+ "],["1 ","549 ","547 ","836 ","* "],[" 64 ","744 ","872 ","244 ","* "],["6653 ","6522 ","5435 ","811 ","+ "],["51 ","16 ","22 ","57 ","* "],["431 ","143 ","281 ","819 ","* "],["894 "," 73 "," 3 "," 1 ","+ "],["2987 ","7159 ","4143 ","653 ","+ "],["835 ","223 ","64 ","6 ","+ "],["8 ","3 ","87 ","54 ","+ "],["96 ","73 ","39 ","82 ","+ "],["868 ","969 ","945 ","159 ","* "],["3419 ","9449 ","7394 ","9915 ","+ "],["34 "," 5 "," 8 "," 3 ","* "],["1 ","55 ","434 ","189 ","+ "],["88 ","53 ","82 ","64 ","+ "],["91 ","56 ","5 ","4 ","* "],[" 4 "," 9 ","41 ","12 ","+ "],[" 65 ","458 ","685 ","284 ","* "],["41 ","53 ","59 ","42 ","* "],["834 ","579 "," 18 "," 5 ","* "],["129 ","776 ","667 "," 87 ","* "],["51 ","1 ","5 ","9 ","* "],[" 69 "," 88 ","652 ","981 ","+ "],["937 ","597 "," 12 "," 76 ","+ "],["5 ","3 ","51 ","45 ","* "],["58 ","82 ","16 ","45 ","+ "],["52 ","55 ","19 ","21 ","+ "],["166 ","893 ","45 ","53 ","+ "],["496 ","499 ","459 ","62 ","+ "],[" 592 "," 291 ","6712 ","8468 ","+ "],["72 ","36 ","69 ","29 ","+ "],["34 ","222 ","565 ","578 ","* "],["915 ","275 "," 39 "," 2 ","+ "],["892 ","393 ","257 ","371 ","* "],["47 ","75 ","25 ","46 ","+ "],["294 ","729 ","695 ","4 ","+ "],["257 ","663 ","8 ","9 ","+ "],["893 "," 15 "," 23 "," 17 ","* "],["79 ","79 ","83 "," 7 ","* "],["699 ","894 ","275 ","276 ","+ "],["183 ","744 ","177 ","437 ","* "],["54 ","13 ","15 ","39 ","* "],["92 ","73 ","58 ","37 ","+ "],["52 ","68 ","88 ","69 ","+ "],["89 ","31 ","14 ","79 ","+ "],["34 ","89 ","5 ","4 ","* "],[" 9 "," 5 ","43 ","99 ","* "],["358 ","328 "," 38 "," 53 ","+ "],["14 ","25 ","22 ","33 ","+ "],["34 ","76 ","16 ","22 ","* "],["43 ","84 ","41 ","71 ","* "],[" 7 "," 3 "," 1 ","77 ","* "],[" 66 ","687 ","465 ","818 ","+ "],["863 ","274 ","451 ","698 ","* "],["691 ","151 ","186 ","67 ","+ "],[" 6 ","97 ","84 ","59 ","+ "],["432 ","224 ","232 ","12 ","* "],["661 ","727 ","723 ","931 ","* "],["998 ","191 ","482 ","9 ","* "],["73 ","59 ","12 ","4 ","+ "],["831 ","742 ","741 "," 2 ","* "],["98 ","56 ","36 ","89 ","* "],["1 ","486 ","733 ","836 ","* "],["878 ","959 ","366 "," 6 ","* "],["77 ","679 ","2789 ","1761 ","+ "],["15 ","67 ","19 ","18 ","* "],["46 ","75 ","84 ","64 ","+ "],["37 ","17 ","17 ","92 ","+ "],["69 ","88 ","72 ","7 ","* "],["362 ","148 ","465 ","544 ","* "],["744 ","731 "," 56 "," 81 ","* "],["5857 ","238 ","418 ","111 ","+ "],["363 ","866 ","75 ","3 ","* "],["67 ","47 ","8749 ","7282 ","+ "],["398 ","383 ","465 ","227 ","+ "],["71 ","99 ","65 ","87 ","* "],[" 82 ","914 ","722 ","991 ","* "],["2 ","572 ","395 ","256 ","+ "],[" 7 "," 31 ","847 ","264 ","* "],["4655 ","4957 "," 562 "," 9 ","+ "],["39 ","858 ","235 ","996 ","+ "],["2 ","1 ","59 ","65 ","+ "],["721 ","239 ","536 "," 6 ","* "],["64 ","42 ","61 ","66 ","+ "],["431 ","326 ","216 ","924 ","* "],["168 ","886 "," 36 "," 8 ","+ "],["962 ","255 ","228 ","49 ","* "],["296 ","541 ","863 "," 95 ","+ "],["3 ","38 ","274 ","969 ","+ "],["871 ","443 ","985 ","538 ","+ "],["16 ","76 ","64 ","16 ","* "],["5 ","41 ","43 ","66 ","* "],["824 ","983 ","79 ","71 ","* "],["51 ","93 ","41 ","24 ","* "],["98 ","82 ","69 ","12 ","+ "],["6 ","8 ","137 ","353 ","+ "],["1881 ","1865 "," 393 "," 5 ","+ "],["674 ","369 ","278 "," 6 ","+ "],["219 ","572 ","797 ","966 ","* "],["62 ","26 ","6 ","2 ","* "],["49 ","75 "," 5 "," 5 ","* "],["351 ","772 "," 22 "," 29 ","* "],["655 ","916 ","151 ","69 ","+ "],["838 ","265 ","657 ","859 ","* "],["7 ","3 ","94 ","81 ","* "],["5499 ","5227 "," 512 "," 484 ","+ "],[" 93 "," 36 ","634 ","884 ","+ "],[" 2 ","987 ","528 ","425 ","+ "],["92 ","66 ","83 ","2 ","+ "],["41 ","477 ","524 ","5419 ","+ "],["42 ","56 ","66 ","17 ","* "],["73 ","89 ","31 ","48 ","+ "],["87 ","96 ","92 ","96 ","* "],[" 675 "," 992 "," 474 ","2233 ","+ "],["621 ","327 ","617 ","336 ","+ "],["45 ","47 ","91 ","23 ","* "],["987 ","611 ","731 ","579 ","* "],[" 21 "," 37 "," 13 ","725 ","* "],["678 ","649 ","37 ","53 ","+ "],["55 ","86 ","99 ","8 ","+ "],["95 ","36 ","35 ","57 ","* "],["19 ","46 ","799 ","812 ","* "],["93 ","39 ","316 ","224 ","* "],["516 "," 28 "," 23 "," 5 ","* "],["288 "," 66 "," 44 "," 83 ","* "],["32 ","59 ","48 ","4 ","* "],["87 ","588 ","367 ","483 ","+ "],["6 ","2 ","5 ","39 ","+ "],["11 ","13 ","83 ","77 ","* "],["88 ","43 ","58 ","83 ","+ "],[" 12 "," 47 ","925 ","622 ","* "],["5458 ","7958 ","8949 ","48 ","+ "],["6 ","56 ","22 ","79 ","+ "],["756 ","323 ","237 ","47 ","* "],["52 ","15 ","66 ","88 ","* "],[" 11 "," 51 "," 12 ","122 ","* "],[" 6 "," 8 "," 2 ","86 ","* "],["184 ","879 "," 27 "," 9 ","* "],["72 ","68 ","78 ","87 ","* "],["74 ","87 ","45 ","46 ","* "],["83 ","59 ","78 ","5 ","+ "],["99 ","89 ","33 ","8 ","+ "],["652 ","383 ","363 "," 78 ","* "],["7 ","76 ","21 ","64 ","* "],["28 ","54 "," 5 "," 1 ","* "],["2 ","3 ","12 ","24 ","+ "],["32 ","97 ","11 ","93 ","+ "],["42 ","44 ","56 ","23 ","+ "],["688 ","431 ","686 ","729 ","+ "],["426 ","189 ","166 "," 25 ","* "],["599 ","275 "," 25 "," 95 ","* "],["351 ","768 ","633 ","211 ","+ "],["498 ","686 "," 87 "," 35 ","+ "],["5 ","3 ","295 ","445 ","* "],["449 ","135 ","692 ","16 ","* "],["49 ","696 ","1366 ","5392 ","+ "],["124 ","778 "," 44 "," 14 ","* "],["94 ","74 ","38 "," 3 ","* "],["7 ","12 ","71 ","21 ","+ "],["2241 ","3469 "," 772 "," 41 ","+ "],["257 ","491 "," 87 "," 48 ","+ "],["624 ","688 ","285 "," 98 ","* "],["9685 ","46 ","47 ","97 ","+ "],["9 ","3 ","92 ","68 ","* "],["9617 "," 46 "," 31 "," 78 ","+ "],[" 58 "," 55 ","198 ","281 ","* "],["27 ","94 ","86 ","32 ","* "],[" 77 ","528 ","981 ","725 ","* "],[" 77 ","437 ","629 ","864 ","* "],["82 ","23 ","15 ","8 ","* "],["3 ","5 ","175 ","826 ","* "],[" 67 "," 69 ","788 ","544 ","+ "],["91 ","17 ","17 ","14 ","* "],["61 ","55 ","4 ","4 ","+ "],["34 ","27 ","73 ","37 ","+ "],["7 ","91 ","162 ","2471 ","+ "],["889 ","151 ","648 ","144 ","+ "],["65 ","86 ","53 ","64 ","+ "],["67 ","85 ","88 ","78 ","* "],["5973 ","5838 ","543 ","89 ","+ "],["957 ","153 ","927 "," 97 ","+ "],[" 7 "," 9 ","71 ","16 ","+ "],[" 3 ","87 ","29 ","52 ","* "],["3493 ","4687 ","6232 ","8474 ","+ "],["776 ","934 ","152 ","634 ","* "],["61 ","26 "," 2 "," 9 ","* "],[" 5 "," 3 ","946 ","137 ","* "],["447 ","333 ","38 ","99 ","* "],[" 9 ","35 ","71 ","33 ","* "],["5 ","23 ","75 ","21 ","+ "],[" 25 "," 444 "," 297 ","9136 ","+ "],["536 ","871 ","614 ","58 ","* "],["854 ","589 ","48 ","25 ","+ "],["43 ","69 ","59 ","47 ","* "],["21 ","958 ","498 ","687 ","* "],[" 69 ","221 ","293 ","741 ","* "],["717 ","642 ","456 ","694 ","* "],["65 ","64 ","53 ","18 ","* "],["13 ","56 ","21 ","88 ","* "],["656 ","859 ","25 ","66 ","+ "],["2 ","4 ","49 ","94 ","* "],[" 27 "," 36 ","591 ","671 ","* "],["652 ","351 ","522 ","217 ","+ "],[" 8 ","44 ","83 ","33 ","* "],[" 56 ","715 ","394 ","195 ","+ "],["85 ","59 ","97 ","44 ","+ "],["73 ","39 ","88 ","95 ","* "],["133 ","124 ","4674 ","3455 ","+ "],["587 ","388 ","983 ","64 ","+ "],["177 ","616 ","783 ","733 ","* "],["9898 ","2241 "," 839 "," 445 ","+ "],["361 ","232 ","618 ","63 ","+ "],["58 ","37 ","29 ","9 ","* "],["87 ","81 ","591 ","512 ","+ "],["3988 ","7773 ","382 ","7 ","+ "],["9967 ","5577 ","4685 "," 281 ","+ "],["276 ","719 ","495 ","619 ","* "],["56 ","81 ","98 ","83 ","+ "],["648 ","828 ","596 "," 56 ","* "],["39 ","49 ","45 ","17 ","* "],["77 ","89 ","21 "," 4 ","+ "],["3255 ","2357 ","5379 ","66 ","+ "],["849 ","967 ","37 ","9 ","* "],["493 ","343 ","712 ","658 ","* "],["51 ","36 ","43 ","84 ","+ "],["314 ","558 ","45 ","82 ","* "],[" 992 "," 685 ","6794 ","7319 ","+ "],["29 ","72 "," 6 "," 7 ","+ "],["261 ","738 "," 78 "," 5 ","+ "],["3 ","6 ","67 ","624 ","* "],["181 ","758 ","842 ","57 ","* "],[" 15 ","692 ","338 ","245 ","* "],[" 9 "," 2 "," 51 ","887 ","* "],["536 ","888 ","613 ","57 ","+ "],["4 ","89 ","62 ","18 ","* "],["8322 ","825 ","1 ","3 ","+ "],["1993 ","1883 ","8949 ","2752 ","+ "],["62 ","68 ","96 ","29 ","* "],[" 94 "," 46 ","956 ","163 ","* "],["865 ","427 ","329 ","2 ","+ "],["11 ","287 ","6188 ","6789 ","+ "],["56 ","78 ","23 ","82 ","* "],["816 ","733 ","216 ","248 ","+ "],["28 ","37 ","11 "," 4 ","* "],["58 ","17 ","55 ","93 ","+ "],["1349 ","9574 ","146 ","799 ","+ "],["36 ","88 ","75 ","44 ","* "],["68 ","32 ","69 ","91 ","* "],["913 ","678 ","369 "," 74 ","* "],["23 ","19 ","68 "," 4 ","* "],["69 ","748 ","322 ","735 ","* "],["45 ","83 ","65 ","985 ","+ "],["59 ","48 ","97 ","92 ","* "],["481 ","535 ","652 "," 71 ","+ "],["38 ","63 ","49 ","85 ","+ "],["45 ","47 ","39 ","86 ","* "],["8 ","277 ","456 ","778 ","+ "],["425 ","978 "," 57 "," 36 ","+ "],["9 ","8 ","37 ","79 ","* "],[" 4 "," 2 ","151 ","368 ","* "],["739 ","658 ","682 ","853 ","* "],["6 ","5 ","2 ","56 ","* "],["86 ","64 ","16 ","22 ","* "],["41 ","77 ","43 ","85 ","* "],["28 ","88 ","77 "," 6 ","+ "],["326 ","232 ","41 ","1 ","* "],[" 519 ","6914 ","3587 ","5977 ","+ "],["8 ","4 ","81 ","33 ","+ "],["141 ","568 ","228 ","484 ","+ "],[" 919 "," 839 "," 314 ","2737 ","+ "],["874 ","644 ","293 ","2 ","* "],[" 5 ","176 ","187 ","724 ","+ "],["42 ","72 ","35 ","58 ","+ "],[" 84 ","473 ","319 ","626 ","* "],[" 3 ","89 ","35 ","46 ","* "],["99 ","58 ","73 ","41 ","+ "],["455 ","818 ","886 ","65 ","* "],["268 ","888 ","656 ","71 ","* "],["9 ","79 ","933 ","454 ","* "],["338 ","761 ","461 ","733 ","* "],["497 ","748 ","739 ","923 ","+ "],["7875 ","1748 ","4914 ","97 ","+ "],["893 ","293 "," 39 "," 13 ","+ "],["614 ","657 "," 81 "," 3 ","* "],["88 ","92 ","73 "," 1 ","+ "],["64 ","87 "," 3 "," 9 ","* "],["158 ","895 ","12 ","81 ","* "],["2415 ","7875 ","9256 ","1141 ","+ "],[" 64"," 16","1512","8719","+"]];
-
const GROUP_SIZE = 10;
-
const totalGroups = Math.ceil(problems.length / GROUP_SIZE);
+
+
// Calculate group size based on how many cards fit in a row
+
function calculateGroupSize() {
+
const containerWidth = window.innerWidth * 0.95; // 95vw max
+
const cardMinWidth = 150; // minmax(150px, 1fr)
+
const gap = 15;
+
const containerPadding = 40; // 20px on each side
+
+
const availableWidth = containerWidth - containerPadding;
+
const cardsPerRow = Math.floor((availableWidth + gap) / (cardMinWidth + gap));
+
+
// Calculate rows that fit on screen (approximate)
+
const viewportHeight = window.innerHeight;
+
const headerHeight = 300; // Approximate space for header, controls, info
+
const availableHeight = viewportHeight - headerHeight;
+
const cardHeight = 150; // Approximate card height
+
const rowsPerScreen = Math.max(1, Math.floor((availableHeight + gap) / (cardHeight + gap)));
+
+
return Math.max(cardsPerRow, cardsPerRow * rowsPerScreen);
+
}
+
+
let GROUP_SIZE = calculateGroupSize();
+
let totalGroups = Math.ceil(problems.length / GROUP_SIZE);
let currentGroup = 0;
let isPart2 = false;
let isPlaying = false;
let shouldStop = false;
let runningTotal = 0;
+
let speed = 5;
+
+
// Step state for fine-grained navigation
+
let currentProblemIdx = 0; // Which problem in the group (0-9)
+
let currentStepIdx = 0; // Which number/column within that problem
+
let problemAccumulators = []; // Track accumulator for each problem
+
let problemData = []; // Calculated data for each problem in group
const groupNumEl = document.getElementById('groupNum');
const totalGroupsEl = document.getElementById('totalGroups');
···
const resetBtn = document.getElementById('reset');
const problemContainer = document.getElementById('problemContainer');
const calculation = document.getElementById('calculation');
+
const speedSlider = document.getElementById('speed');
+
const speedValue = document.getElementById('speedValue');
function updateModeLabels() {
part1Label.classList.toggle('active', !isPart2);
···
problemContainer.innerHTML = '';
calculation.innerHTML = '<span class="nums">Group Total: <span class="result">0</span></span>';
+
// Reset step state
+
currentProblemIdx = 0;
+
currentStepIdx = 0;
+
problemAccumulators = [];
+
problemData = [];
+
groupProblems.forEach((problem, i) => {
const item = document.createElement('div');
item.className = 'problem-item';
···
const localProblem = [...problem];
const operator = localProblem.pop()?.trim();
const maxWidth = localProblem.reduce((m, s) => Math.max(m, s.length), 0);
+
+
// Calculate and store problem data
+
const data = calculateProblemData(problem);
+
problemData.push(data);
+
problemAccumulators.push(operator === '*' ? 1 : 0);
let gridHtml = '<div class="problem-grid" id="grid-' + i + '">';
···
groupNumEl.textContent = currentGroup + 1;
totalGroupsEl.textContent = totalGroups;
-
prevBtn.disabled = currentGroup === 0 || isPlaying;
-
nextBtn.disabled = currentGroup === totalGroups - 1 || isPlaying;
+
updateButtons();
}
-
async function animateProblem(problemIdx, problemData, problem) {
-
const { nums, operator } = problemData;
+
function updateButtons() {
+
const atStart = currentGroup === 0 && currentProblemIdx === 0 && currentStepIdx === 0;
+
const atEnd = currentGroup === totalGroups - 1 &&
+
currentProblemIdx === problemData.length - 1 &&
+
currentStepIdx === problemData[currentProblemIdx]?.nums.length;
+
+
prevBtn.disabled = isPlaying || atStart;
+
nextBtn.disabled = isPlaying || atEnd;
+
}
+
+
function performStep(problemIdx, stepIdx) {
+
const data = problemData[problemIdx];
+
const { nums, operator } = data;
const grid = document.getElementById(`grid-${problemIdx}`);
const acc = document.getElementById(`acc-${problemIdx}`);
-
-
let accumulator = operator === '*' ? 1 : 0;
+
+
if (!grid || !acc || stepIdx >= nums.length) return;
+
const startIdx = currentGroup * GROUP_SIZE;
+
const problem = problems[startIdx + problemIdx];
+
if (isPart2) {
// Part 2: Highlight columns
const localProblem = [...problem];
localProblem.pop(); // Remove operator
const maxWidth = localProblem.reduce((m, s) => Math.max(m, s.length), 0);
+
const colIdx = maxWidth - 2 - stepIdx;
+
+
// Highlight all digits in this column
+
const digitsInCol = grid.querySelectorAll(`[data-col="${colIdx}"]`);
+
digitsInCol.forEach(d => d.classList.add('highlight'));
+
} else {
+
// Part 1: Highlight rows
+
const numberElements = grid.querySelectorAll('.number');
+
if (numberElements[stepIdx]) {
+
numberElements[stepIdx].classList.add('highlight');
+
}
+
}
-
for (let i = 0; i < nums.length; i++) {
-
// Column index: rightmost is maxWidth-1, then maxWidth-2, etc.
-
const colIdx = maxWidth - 2 - i;
-
-
// Highlight all digits in this column
-
const digitsInCol = grid.querySelectorAll(`[data-col="${colIdx}"]`);
-
digitsInCol.forEach(d => d.classList.add('highlight'));
-
-
await new Promise(resolve => setTimeout(resolve, 400));
-
-
// Perform operation
-
if (operator === '*') {
-
accumulator *= nums[i];
-
} else {
-
accumulator += nums[i];
-
}
-
acc.textContent = accumulator.toLocaleString();
+
// Perform operation
+
if (operator === '*') {
+
problemAccumulators[problemIdx] *= nums[stepIdx];
+
} else {
+
problemAccumulators[problemIdx] += nums[stepIdx];
+
}
+
acc.textContent = problemAccumulators[problemIdx].toLocaleString();
+
}
-
// Fade out the column
-
digitsInCol.forEach(d => {
-
d.classList.remove('highlight');
-
d.classList.add('fade');
-
});
+
function fadeStep(problemIdx, stepIdx) {
+
const grid = document.getElementById(`grid-${problemIdx}`);
+
if (!grid) return;
-
await new Promise(resolve => setTimeout(resolve, 200));
-
}
+
const startIdx = currentGroup * GROUP_SIZE;
+
const problem = problems[startIdx + problemIdx];
+
+
if (isPart2) {
+
const localProblem = [...problem];
+
localProblem.pop();
+
const maxWidth = localProblem.reduce((m, s) => Math.max(m, s.length), 0);
+
const colIdx = maxWidth - 2 - stepIdx;
+
+
const digitsInCol = grid.querySelectorAll(`[data-col="${colIdx}"]`);
+
digitsInCol.forEach(d => {
+
d.classList.remove('highlight');
+
d.classList.add('fade');
+
});
} else {
-
// Part 1: Highlight rows
const numberElements = grid.querySelectorAll('.number');
+
if (numberElements[stepIdx]) {
+
numberElements[stepIdx].classList.remove('highlight');
+
numberElements[stepIdx].classList.add('fade');
+
}
+
}
+
}
-
for (let i = 0; i < nums.length; i++) {
-
// Highlight current number
-
numberElements[i].classList.add('highlight');
+
function stepForward(fromPlayback = false) {
+
if (isPlaying && !fromPlayback) return;
+
+
// Perform current step
+
performStep(currentProblemIdx, currentStepIdx);
+
+
// Advance step
+
currentStepIdx++;
+
+
// Check if we've finished this problem
+
if (currentStepIdx >= problemData[currentProblemIdx].nums.length) {
+
// Fade the last step
+
fadeStep(currentProblemIdx, currentStepIdx - 1);
+
+
// Update grand total
+
runningTotal += problemAccumulators[currentProblemIdx];
+
grandTotalEl.textContent = runningTotal.toLocaleString();
+
+
// Move to next problem
+
currentProblemIdx++;
+
currentStepIdx = 0;
+
+
// Check if we've finished the group
+
if (currentProblemIdx >= problemData.length) {
+
// Update group total
+
const groupTotal = problemAccumulators.reduce((sum, val) => sum + val, 0);
+
calculation.innerHTML = `<span class="nums">Group Total: <span class="result">${groupTotal.toLocaleString()}</span></span>`;
-
await new Promise(resolve => setTimeout(resolve, 400));
+
// Move to next group
+
if (currentGroup < totalGroups - 1) {
+
currentGroup++;
+
renderGroup();
+
}
+
}
+
} else {
+
// Fade previous step
+
if (currentStepIdx > 0) {
+
fadeStep(currentProblemIdx, currentStepIdx - 1);
+
}
+
}
+
+
if (!fromPlayback) updateButtons();
+
}
-
// Perform operation
-
if (operator === '*') {
-
accumulator *= nums[i];
+
function stepBackward() {
+
if (isPlaying) return;
+
+
// Move back one step
+
currentStepIdx--;
+
+
// If we're before the start of this problem, go to previous problem
+
if (currentStepIdx < 0) {
+
currentProblemIdx--;
+
+
// If we're before the start of the group, go to previous group
+
if (currentProblemIdx < 0) {
+
if (currentGroup > 0) {
+
currentGroup--;
+
renderGroup();
+
// Set to end of this group
+
currentProblemIdx = problemData.length - 1;
+
currentStepIdx = problemData[currentProblemIdx].nums.length - 1;
} else {
-
accumulator += nums[i];
+
// Already at the very start
+
currentProblemIdx = 0;
+
currentStepIdx = 0;
}
-
acc.textContent = accumulator.toLocaleString();
-
-
// Fade out the number
-
numberElements[i].classList.remove('highlight');
-
numberElements[i].classList.add('fade');
-
-
await new Promise(resolve => setTimeout(resolve, 200));
+
} else {
+
// Go to end of previous problem
+
currentStepIdx = problemData[currentProblemIdx].nums.length - 1;
+
+
// Revert the grand total
+
runningTotal -= problemAccumulators[currentProblemIdx + 1];
+
grandTotalEl.textContent = runningTotal.toLocaleString();
}
}
-
-
return accumulator;
+
+
// Clear current state and rebuild up to current step
+
renderGroupState();
+
updateButtons();
}
-
async function animateGroup() {
+
function renderGroupState() {
+
// Re-render the group with current state
const startIdx = currentGroup * GROUP_SIZE;
const endIdx = Math.min(startIdx + GROUP_SIZE, problems.length);
const groupProblems = problems.slice(startIdx, endIdx);
-
-
let groupTotal = 0;
-
-
// Animate all problems in parallel but update totals as each completes
-
const results = await Promise.all(
-
groupProblems.map(async (problem, i) => {
-
const data = calculateProblemData(problem);
-
const result = await animateProblem(i, data, problem);
-
-
// Update totals cumulatively as each problem finishes
-
groupTotal += result;
-
runningTotal += result;
-
grandTotalEl.textContent = runningTotal.toLocaleString();
-
-
return result;
-
})
-
);
-
-
// Show final group total
-
calculation.innerHTML = `<span class="nums">Group Total: <span class="result">${groupTotal.toLocaleString()}</span></span>`;
+
+
// Reset accumulators
+
for (let i = 0; i < problemAccumulators.length; i++) {
+
const data = problemData[i];
+
problemAccumulators[i] = data.operator === '*' ? 1 : 0;
+
const acc = document.getElementById(`acc-${i}`);
+
if (acc) acc.textContent = '';
+
}
+
+
// Clear all highlights and fades
+
document.querySelectorAll('.highlight, .fade').forEach(el => {
+
el.classList.remove('highlight', 'fade');
+
});
+
+
// Replay all steps up to current position
+
for (let p = 0; p <= currentProblemIdx; p++) {
+
const maxStep = p === currentProblemIdx ? currentStepIdx : problemData[p].nums.length;
+
for (let s = 0; s < maxStep; s++) {
+
performStep(p, s);
+
fadeStep(p, s);
+
}
+
}
}
async function playAll() {
isPlaying = true;
shouldStop = false;
playBtn.textContent = '⏸ Pause';
-
prevBtn.disabled = true;
-
nextBtn.disabled = true;
+
updateButtons();
-
for (let i = currentGroup; i < totalGroups; i++) {
-
if (shouldStop) break;
+
while (!shouldStop) {
+
// Check if we're at the end
+
const atEnd = currentGroup === totalGroups - 1 &&
+
currentProblemIdx === problemData.length - 1 &&
+
currentStepIdx >= problemData[currentProblemIdx].nums.length;
-
currentGroup = i;
-
renderGroup();
-
await animateGroup();
+
if (atEnd) break;
-
if (shouldStop) break;
-
await new Promise(resolve => setTimeout(resolve, 1000));
+
stepForward(true);
+
// Speed: 1 = 1000ms, 5 = 600ms, 10 = 200ms, 25 = 20ms
+
const delay = Math.max(20, 1050 - (speed * 50));
+
await new Promise(resolve => setTimeout(resolve, delay));
}
isPlaying = false;
+
shouldStop = false;
playBtn.textContent = '▶ Play';
-
prevBtn.disabled = currentGroup === 0;
-
nextBtn.disabled = currentGroup === totalGroups - 1;
+
updateButtons();
}
function stopPlaying() {
shouldStop = true;
+
isPlaying = false;
+
playBtn.textContent = '▶ Play';
+
updateButtons();
}
function resetAnimation() {
-
if (isPlaying) return;
+
if (isPlaying) stopPlaying();
currentGroup = 0;
runningTotal = 0;
grandTotalEl.textContent = '0';
···
});
prevBtn.addEventListener('click', () => {
-
if (!isPlaying && currentGroup > 0) {
-
currentGroup--;
-
renderGroup();
-
}
+
stepBackward();
});
nextBtn.addEventListener('click', () => {
-
if (!isPlaying && currentGroup < totalGroups - 1) {
-
currentGroup++;
-
renderGroup();
-
}
+
stepForward();
});
resetBtn.addEventListener('click', resetAnimation);
···
}
});
+
speedSlider.addEventListener('input', (e) => {
+
speed = parseInt(e.target.value);
+
speedValue.textContent = speed + 'x';
+
});
+
document.addEventListener('keydown', (e) => {
-
if (isPlaying) return;
if (e.key === 'ArrowLeft') prevBtn.click();
if (e.key === 'ArrowRight') nextBtn.click();
if (e.key === ' ') {
e.preventDefault();
playBtn.click();
}
-
if (e.key === 'r' || e.key === 'R') resetBtn.click();
+
if (e.key === 'r' || e.key === 'R') {
+
if (!isPlaying) resetBtn.click();
+
}
if (e.key === 't' || e.key === 'T') {
-
toggleMode();
+
if (!isPlaying) toggleMode();
+
}
+
});
+
+
// Handle window resize
+
window.addEventListener('resize', () => {
+
const newGroupSize = calculateGroupSize();
+
if (newGroupSize !== GROUP_SIZE && !isPlaying) {
+
GROUP_SIZE = newGroupSize;
+
totalGroups = Math.ceil(problems.length / GROUP_SIZE);
+
// Adjust current group to maintain position
+
const currentProblemGlobal = currentGroup * GROUP_SIZE + currentProblemIdx;
+
currentGroup = Math.floor(currentProblemGlobal / GROUP_SIZE);
+
currentProblemIdx = currentProblemGlobal % GROUP_SIZE;
+
totalGroupsEl.textContent = totalGroups;
+
renderGroup();
}
});