Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 41 additions & 11 deletions projects/calculator/index.html
Original file line number Diff line number Diff line change
@@ -1,18 +1,48 @@
<!doctype html>
<html lang="en">

<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Calculator | Vanilla Verse</title>
<link rel="stylesheet" href="styles.css" />
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Calculator</title>
<link rel="stylesheet" href="styles.css">
</head>

<body>
<header><h1>Calculator</h1></header>
<main id="app">
<!-- TODO: Implement basic calculator UI (display + buttons 0-9, ., +, -, ×, ÷, =, C, ±, %) -->
<!-- TODO: Wire up interactions and keyboard support -->
<!-- TODO: Handle edge cases (divide by zero, multiple decimals, operator chaining) -->
</main>
<script defer src="main.js"></script>
<div class="calculator">
<div class="display">
<div class="previous-operand" id="previousOperand"></div>
<div class="current-operand" id="currentOperand">0</div>
</div>

<div class="buttons">
<button class="function" aria-label="All Clear" data-action="clear">AC</button>
<button class="function" aria-label="Backspace" data-action="backspace">⌫</button>
<button class="function" aria-label="Toggle Sign" data-action="toggle-sign">+/-</button>
<button class="operator" aria-label="Divide" data-action="operator" data-operator="÷">÷</button>

<button aria-label="7" data-number="7">7</button>
<button aria-label="8" data-number="8">8</button>
<button aria-label="9" data-number="9">9</button>
<button class="operator" aria-label="Multiply" data-action="operator" data-operator="×">×</button>

<button aria-label="4" data-number="4">4</button>
<button aria-label="5" data-number="5">5</button>
<button aria-label="6" data-number="6">6</button>
<button class="operator" aria-label="Subtract" data-action="operator" data-operator="−">−</button>

<button aria-label="1" data-number="1">1</button>
<button aria-label="2" data-number="2">2</button>
<button aria-label="3" data-number="3">3</button>
<button class="operator" aria-label="Add" data-action="operator" data-operator="+">+</button>

<button class="zero" aria-label="0" data-number="0">0</button>
<button aria-label="Decimal" data-action="decimal">.</button>
<button class="function" aria-label="Percentage" data-action="percentage">%</button>
<button class="equals" aria-label="Equals" data-action="equals">=</button>
</div>
</div>
<script src="main.js"></script>
</body>

</html>
239 changes: 224 additions & 15 deletions projects/calculator/main.js
Original file line number Diff line number Diff line change
@@ -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();
Loading