From eb027c9a66345001d3f22dd3efddf62b98b51cae Mon Sep 17 00:00:00 2001 From: Pramod-Munoli Date: Mon, 27 Oct 2025 18:59:31 +0530 Subject: [PATCH] feat: implement calculator UI, functionality, and keyboard support --- projects/calculator/index.html | 52 +++++-- projects/calculator/main.js | 239 ++++++++++++++++++++++++++++++--- projects/calculator/styles.css | 130 +++++++++++++++--- 3 files changed, 377 insertions(+), 44 deletions(-) diff --git a/projects/calculator/index.html b/projects/calculator/index.html index ada16de..d6626a4 100644 --- a/projects/calculator/index.html +++ b/projects/calculator/index.html @@ -1,18 +1,48 @@ + - - - Calculator | Vanilla Verse - + + + Calculator + + -

Calculator

-
- - - -
- +
+
+
+
0
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + diff --git a/projects/calculator/main.js b/projects/calculator/main.js index 7c489e5..1347028 100644 --- a/projects/calculator/main.js +++ b/projects/calculator/main.js @@ -1,15 +1,224 @@ -/** - * TODO: Calculator logic - * - Render/display current input and result - * - Support operations: +, -, *, /, %, ±, decimal - * - Keyboard support (numbers, operators, Enter for =, Backspace) - * - Prevent invalid inputs (multiple decimals, divide by zero) - * Optional: - * - Expression history - * - Theme toggle - */ - -document.addEventListener('DOMContentLoaded', () => { - console.log('Calculator app ready'); - // TODO: Build UI or select existing DOM nodes and attach event listeners -}); + +const currentOperandElement = document.getElementById('currentOperand'); +const previousOperandElement = document.getElementById('previousOperand'); +const buttons = document.querySelectorAll('button'); + +let currentOperand = '0'; +let previousOperand = ''; +let operation = null; +let shouldResetScreen = false; + +function init() { + buttons.forEach(button => { + button.addEventListener('click', () => { + button.classList.add('press-animation'); + setTimeout(() => button.classList.remove('press-animation'), 200); + + handleButtonClick(button); + }); + }); + + document.addEventListener('keydown', handleKeyboardInput); + + updateDisplay(); +} + +function handleButtonClick(button) { + if (button.dataset.number !== undefined) { + appendNumber(button.dataset.number); + } else if (button.dataset.action === 'operator') { + chooseOperation(button.dataset.operator); + } else if (button.dataset.action === 'decimal') { + appendDecimal(); + } else if (button.dataset.action === 'equals') { + compute(); + } else if (button.dataset.action === 'clear') { + clear(); + } else if (button.dataset.action === 'backspace') { + backspace(); + } else if (button.dataset.action === 'toggle-sign') { + toggleSign(); + } else if (button.dataset.action === 'percentage') { + percentage(); + } +} + +function handleKeyboardInput(e) { + if (e.key === 'Enter' || e.key === 'Escape' || e.key === 'Backspace') { + e.preventDefault(); + } + + if (e.key >= '0' && e.key <= '9') { + appendNumber(e.key); + } + else if (e.key === '.') { + appendDecimal(); + } + else if (e.key === '+' || e.key === '-' || e.key === '*' || e.key === '/') { + const operatorMap = { + '+': '+', + '-': '−', + '*': '×', + '/': '÷' + }; + chooseOperation(operatorMap[e.key]); + } + else if (e.key === 'Enter' || e.key === '=') { + compute(); + } + else if (e.key === 'Escape') { + clear(); + } + else if (e.key === 'Backspace') { + backspace(); + } +} + +function appendNumber(number) { + if (shouldResetScreen) { + currentOperand = ''; + shouldResetScreen = false; + } + + if (currentOperand === '0') { + currentOperand = number; + } else if (currentOperand.length < 12) { + currentOperand += number; + } + + updateDisplay(); +} + +function appendDecimal() { + if (shouldResetScreen) { + currentOperand = '0'; + shouldResetScreen = false; + } + + if (!currentOperand.includes('.')) { + currentOperand += '.'; + } + + updateDisplay(); +} + +function chooseOperation(op) { + if (currentOperand === '') return; + + if (previousOperand !== '') { + compute(); + } + + operation = op; + previousOperand = currentOperand; + shouldResetScreen = true; + updateDisplay(); +} + +function compute() { + if (operation === null || previousOperand === '') return; + + let computation; + const prev = parseFloat(previousOperand); + const current = parseFloat(currentOperand); + + if (isNaN(prev) || isNaN(current)) { + clear(); + return; + } + + switch (operation) { + case '+': computation = prev + current; break; + case '−': computation = prev - current; break; + case '×': computation = prev * current; break; + case '÷': + if (current === 0) { + currentOperand = 'Error'; + previousOperand = ''; + operation = null; + shouldResetScreen = true; + updateDisplay(); + return; + } + computation = prev / current; + break; + default: return; + } + + currentOperand = roundResult(computation); + operation = null; + previousOperand = ''; + shouldResetScreen = true; + updateDisplay(); +} + +function clear() { + currentOperand = '0'; + previousOperand = ''; + operation = null; + shouldResetScreen = false; + updateDisplay(); +} + +function backspace() { + if (currentOperand.length > 1) { + currentOperand = currentOperand.slice(0, -1); + } else { + currentOperand = '0'; + } + updateDisplay(); +} + +function toggleSign() { + if (currentOperand !== '0') { + currentOperand = currentOperand.startsWith('-') + ? currentOperand.slice(1) + : '-' + currentOperand; + } + updateDisplay(); +} + +function percentage() { + currentOperand = (parseFloat(currentOperand) / 100).toString(); + shouldResetScreen = true; + updateDisplay(); +} + +function roundResult(number) { + const strNumber = number.toExponential(12); + const roundedNumber = parseFloat(strNumber); + + return Number.isInteger(roundedNumber) + ? roundedNumber.toString() + : roundedNumber.toString(); +} + +function formatNumber(number) { + if (number === 'Error') return number; + + const stringNumber = number.toString(); + + if (stringNumber.length > 12) { + return parseFloat(number).toExponential(6); + } + + return stringNumber; +} + +function updateDisplay() { + currentOperandElement.textContent = formatNumber(currentOperand); + + if (operation != null) { + previousOperandElement.textContent = `${previousOperand} ${operation}`; + } else { + previousOperandElement.textContent = ''; + } + + if (currentOperand === 'Error') { + currentOperandElement.classList.add('error'); + } else { + currentOperandElement.classList.remove('error'); + } +} + +init(); diff --git a/projects/calculator/styles.css b/projects/calculator/styles.css index 93a483b..8e71aa5 100644 --- a/projects/calculator/styles.css +++ b/projects/calculator/styles.css @@ -1,18 +1,112 @@ -/* TODO: Style the Calculator UI (grid of buttons, responsive layout, accessible focus states) */ -:root { - --bg: #0f172a; - --fg: #e2e8f0; - --accent: #22d3ee; - --panel: #111827; -} -* { box-sizing: border-box; } -body { - margin: 0; - font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; - background: var(--bg); - color: var(--fg); - min-height: 100vh; - display: grid; - place-items: center; -} -#app { width: min(92vw, 420px); } +* { margin: 0; padding: 0; box-sizing: border-box; } + + body { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + background: #1a1a1a; + color: #f0f0f0; + display: flex; + justify-content: center; + align-items: center; + min-height: 100vh; + padding: 20px; + } + + /* ===== CALCULATOR CONTAINER ===== */ + .calculator { + background: #2d2d2d; + border-radius: 20px; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5); + width: 100%; + max-width: 360px; + padding: 25px; + } + + /* ===== DISPLAY ===== */ + .display { + background: #1a1a1a; + border-radius: 10px; + padding: 20px; + margin-bottom: 20px; + text-align: right; + min-height: 80px; + display: flex; + flex-direction: column; + justify-content: flex-end; + } + + .previous-operand { + color: #888; + font-size: 1.2rem; + margin-bottom: 5px; + height: 1.5rem; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .current-operand { + color: #f0f0f0; + font-size: 2.5rem; + font-weight: 300; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + /* ===== BUTTON GRID ===== */ + .buttons { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 12px; + } + + /* ===== BUTTON STYLES ===== */ + button { + background: #333; + border: none; + border-radius: 50%; + color: #f0f0f0; + cursor: pointer; + font-size: 1.5rem; + height: 70px; + width: 70px; + transition: all 0.2s ease; + outline: none; + } + + button:focus { + box-shadow: 0 0 0 3px rgba(255, 165, 0, 0.5); + } + + button:hover { background: #444; } + button:active { transform: scale(0.95); } + + /* ===== SPECIAL BUTTONS ===== */ + .operator { background: #ff9500; color: white; } + .operator:hover { background: #ffad33; } + + .equals { background: #4caf50; color: white; } + .equals:hover { background: #66bb6a; } + + .function { background: #a5a5a5; color: #333; } + .function:hover { background: #bfbfbf; } + + /* ===== ANIMATIONS ===== */ + @keyframes press { + 0% { transform: scale(1); } + 50% { transform: scale(0.95); } + 100% { transform: scale(1); } + } + + .press-animation { animation: press 0.2s ease; } + + /* ===== RESPONSIVE ===== */ + @media (max-width: 400px) { + .calculator { padding: 15px; } + button { height: 60px; width: 60px; font-size: 1.3rem; } + .current-operand { font-size: 2rem; } + .previous-operand { font-size: 1rem; } + } + + /* ===== ERROR STATE ===== */ + .error { color: #ff5252; } \ No newline at end of file