// This file is responsible for all calculator animations and logic. const buttons = document.querySelectorAll('.button'); const displayText = document.querySelector('.display-text'); let currentInput = ''; let characterSpans = []; // Array to track all spans for proper positioning let isResultDisplayed = false; // Track if the result is displayed let resultSpan; // For showing the result // Add on-click event listeners to the buttons. buttons.forEach(button => { button.addEventListener('click', () => { const value = button.textContent; if (value === 'AC') { resetCalculator(); } else if (value === 'H') { toggleHistory(); } else if (value === 'settings') { toggleSettings(); } else if (value === 'backspace') { popCharacter(); } else if (value === '=') { calculateResult(); } else { if (isResultDisplayed) { resetCalculator(); // Reset after "=" is pressed } if (currentInput === 'Error') currentInput = ''; // Clear on error currentInput += value; appendCharacter(value); // Append with animation } }); }); // This function is used to append a character to the calculator display. It's called when user clicks a button. function appendCharacter(char) { // Create new span element for the character const newChar = document.createElement('span'); newChar.textContent = char; displayText.appendChild(newChar); characterSpans.push(newChar); // Add the new span to the array // Move all characters smoothly updateCharacterPositions(); // Animate the new character in from the right anime({ targets: newChar, opacity: [0, 1], translateY: [0, -25], // Animating into place scale: [0, 1], duration: 500, easing: 'easeOutExpo' }); } // This function moves already input characters to left to make space for a new one. function updateCharacterPositions() { const totalWidth = displayText.offsetWidth; // Width of the display let currentOffset = totalWidth; // Start from the rightmost edge // Loop through characters in reverse (so newest appears at the right) for (let i = characterSpans.length - 1; i >= 0; i--) { const span = characterSpans[i]; const spanWidth = span.offsetWidth; // Get the width of each character currentOffset -= spanWidth; // Move left by the width of the character + some margin anime({ targets: span, right: (totalWidth - currentOffset - spanWidth) + 'px', // Keep them aligned right duration: 300, easing: 'easeOutQuint' }); } } // This function calculates the result based on characters input. function calculateResult() { const equation = characterSpans.map(span => span.textContent).join(''); let result; try { result = eval(equation.replaceAll('÷', '/').replaceAll('×', '*').replaceAll('−', '-')).toString(); } catch (error) { result = 'Error'; } // We should add 'result' to the history here. characterSpans.forEach(span => { span.style.fontSize = '1.5rem'; }); const totalWidth = displayText.offsetWidth; // Width of the display let currentOffset = totalWidth; // Start from the rightmost edge // Animate the equation upwards // Loop through characters in reverse (so newest appears at the right) for (let i = characterSpans.length - 1; i >= 0; i--) { const span = characterSpans[i]; const spanWidth = span.offsetWidth; // Get the width of each character currentOffset -= spanWidth; // Move left by the width of the character + some margin anime({ targets: span, translateY: -45, opacity: [1, 0.4], right: (totalWidth - currentOffset - spanWidth) + 'px', // Keep them aligned right duration: 800, easing: 'easeOutExpo' }); } resultSpan = document.createElement('span'); // Create a new span for the result resultSpan.textContent = `= ${result}`; displayText.appendChild(resultSpan); // Animate the result to appear from below anime({ targets: resultSpan, opacity: [0, 1], translateY: [50, -10], // Animating upwards translateX: -5, scale: [0, 1], duration: 500, easing: 'easeOutExpo' }); // Set a flag to reset on the next key press isResultDisplayed = true; } // This function toggles the history menu. function toggleHistory() { $('body').toggleClass('history-open'); if ($('body').hasClass('history-open')) { // If the history was just opened // Animate it's elements anime({ targets: '.history-container span', translateY: [-100, 0], opacity: [0, 1], duration: 400, delay: anime.stagger(100, { start: 200, direction: 'reverse' }), easing: 'easeOutQuart' }) } } // This function toggles the settings menu. function toggleSettings() { $('body').toggleClass('settings-open'); } $('#history').on('click', toggleHistory); // Resets everything, the result and display. function resetCalculator() { currentInput = ''; characterSpans = []; displayText.innerHTML = ''; // Clear the display isResultDisplayed = false; // Reset the result display flag } // popLock (funny name) is true when a cbaracter pop animation is being progress. let popLock = false; // This function pops the last character input to the calculator. function popCharacter() { if (popLock) return; popLock = true; if (isResultDisplayed) { resetCalculator(); popLock = false; return; } currentInput = currentInput.slice(0, -1); characterSpans.pop(); if (displayText.innerHTML.length === 0) { isResultDisplayed = false; popLock = false; return; } // Animate character disappearing anime({ targets: displayText.lastChild, opacity: [1, 0], duration: 150, easing: 'easeInExpo', complete: () => { displayText.removeChild(displayText.lastChild); updateCharacterPositions(); popLock = false; } }); } // This sets the calculator display value to some string. I'm pretty sure it's not used, but I'll leave it here just in case. function updateDisplay(value) { // Clear existing spans and reset positions displayText.innerHTML = ''; characterSpans = []; [...value].forEach(char => { appendCharacter(char); // Append each character }); }