From a29c54c690a10d40413528f88b09c77907cf5e59 Mon Sep 17 00:00:00 2001 From: delta student <04.sainiKhushi@gmail.com> Date: Fri, 24 Oct 2025 12:54:19 +0530 Subject: [PATCH 1/4] Feature: Implemented calculator layout with responsive UI and functional arithmetic operations --- Projects/calculator-js/README.md | 20 ++++++++ Projects/calculator-js/index.html | 44 ++++++++++++++++++ Projects/calculator-js/script.js | 77 +++++++++++++++++++++++++++++++ Projects/calculator-js/style.css | 73 +++++++++++++++++++++++++++++ 4 files changed, 214 insertions(+) create mode 100644 Projects/calculator-js/README.md create mode 100644 Projects/calculator-js/index.html create mode 100644 Projects/calculator-js/script.js create mode 100644 Projects/calculator-js/style.css diff --git a/Projects/calculator-js/README.md b/Projects/calculator-js/README.md new file mode 100644 index 00000000..7e2e60fa --- /dev/null +++ b/Projects/calculator-js/README.md @@ -0,0 +1,20 @@ +# Calculator (mini project) + +Simple calculator implemented with HTML, CSS and JavaScript. + +## Features +- Basic operations: +, -, *, / +- Decimal support +- Percent handling (e.g., 50% → 0.5) +- Handles division by zero gracefully (shows "Error") +- Input and result displayed separately + +## How to use +1. Open `index.html` in a browser. +2. Use the buttons or keyboard to enter the expression. +3. Press `=` or Enter to evaluate. +4. Input remains visible; result appears below input. + +## Notes for maintainers +- Files added under `projects/calculator-js/` +- No external dependencies diff --git a/Projects/calculator-js/index.html b/Projects/calculator-js/index.html new file mode 100644 index 00000000..26292d24 --- /dev/null +++ b/Projects/calculator-js/index.html @@ -0,0 +1,44 @@ + + + + + + Calculator + + + +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + diff --git a/Projects/calculator-js/script.js b/Projects/calculator-js/script.js new file mode 100644 index 00000000..96f5433c --- /dev/null +++ b/Projects/calculator-js/script.js @@ -0,0 +1,77 @@ +const display = document.getElementById('display'); +const resultDisplay = document.getElementById('result'); +const buttons = document.querySelectorAll('.buttons button'); + +let currentExpression = ''; + +function updateDisplay() { + display.value = currentExpression; +} + +function sanitizeExpression(expr) { + return expr.replace(/[^0-9+\-*/().%]/g, ''); +} + +function calculateExpression(expr) { + const withPercent = expr.replace(/(\d+(\.\d+)?)%/g, '($1/100)'); + try { + const result = eval(withPercent); + if (typeof result === 'number') { + if (!isFinite(result)) return 'Error'; + return String(Math.round(result * 1e10) / 1e10); + } + return 'Error'; + } catch (e) { + return 'Error'; + } +} + +buttons.forEach(btn => { + btn.addEventListener('click', () => { + const val = btn.dataset.value; + const action = btn.dataset.action; + + if (action === 'clear') { + currentExpression = ''; + updateDisplay(); + resultDisplay.value = ''; + return; + } + if (action === 'back') { + currentExpression = currentExpression.slice(0, -1); + updateDisplay(); + resultDisplay.value = ''; + return; + } + if (action === 'equals') { + const sanitized = sanitizeExpression(currentExpression); + const result = calculateExpression(sanitized); + resultDisplay.value = result; + return; + } + + currentExpression += val; + updateDisplay(); + resultDisplay.value = ''; + }); +}); + +// Keyboard support +window.addEventListener('keydown', (e) => { + if ((e.key >= '0' && e.key <= '9') || '+-*/().%'.includes(e.key)) { + currentExpression += e.key; + updateDisplay(); + resultDisplay.value = ''; + } else if (e.key === 'Enter') { + const sanitized = sanitizeExpression(currentExpression); + resultDisplay.value = calculateExpression(sanitized); + } else if (e.key === 'Backspace') { + currentExpression = currentExpression.slice(0, -1); + updateDisplay(); + resultDisplay.value = ''; + } else if (e.key.toLowerCase() === 'c') { + currentExpression = ''; + updateDisplay(); + resultDisplay.value = ''; + } +}); diff --git a/Projects/calculator-js/style.css b/Projects/calculator-js/style.css new file mode 100644 index 00000000..2e9f7e3f --- /dev/null +++ b/Projects/calculator-js/style.css @@ -0,0 +1,73 @@ +:root{ + --bg:#f4f7fb; + --panel:#ffffff; + --accent:#3b82f6; + --text:#111827; + --muted:#6b7280; +} + +*{box-sizing:border-box} +html,body{height:100%} +body{ + margin:0; + display:flex; + align-items:center; + justify-content:center; + background:linear-gradient(180deg,#e6eefc,#f8fafc); + font-family:Inter,system-ui,-apple-system,Segoe UI,Roboto,"Helvetica Neue",Arial; + color:var(--text); + padding:24px; +} + +.calculator{ + width:320px; + background:var(--panel); + border-radius:12px; + box-shadow:0 6px 24px rgba(16,24,40,0.08); + padding:18px; +} + +.display{ + width:100%; + height:56px; + border-radius:8px; + border:1px solid #e6eefc; + padding:8px 12px; + font-size:20px; + text-align:right; + margin-bottom:6px; + background:linear-gradient(180deg,#fff,#fbfdff); +} + +.display + .display { + background: #e5e7eb; + color: #111827; + font-weight: 600; + margin-bottom:12px; +} + +.buttons{ + display:grid; + grid-template-columns:repeat(4,1fr); + gap:10px; +} + +button{ + height:48px; + border-radius:8px; + border:none; + background:#f3f4f6; + font-size:16px; + cursor:pointer; + transition:transform .08s ease, box-shadow .08s; +} +button:active{transform:translateY(1px)} +button[data-action="equals"]{ + background:var(--accent); + color:#fff; + grid-column:4; + grid-row:5; + height:100%; +} +button.zero{grid-column:1 / span 2} +button[data-action="clear"]{background:#fef3f2;color:#991b1b} From 3c3cebfaad8853c6c7ba91dd41bcf54bb9dbdd8a Mon Sep 17 00:00:00 2001 From: delta student <04.sainiKhushi@gmail.com> Date: Sat, 25 Oct 2025 17:39:17 +0530 Subject: [PATCH 2/4] Fix keyboard handler: Escape clears input, remove parentheses input --- Projects/calculator-js/script.js | 46 +++++++++++++++++++------------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/Projects/calculator-js/script.js b/Projects/calculator-js/script.js index 96f5433c..360ceaf1 100644 --- a/Projects/calculator-js/script.js +++ b/Projects/calculator-js/script.js @@ -56,22 +56,32 @@ buttons.forEach(btn => { }); }); -// Keyboard support -window.addEventListener('keydown', (e) => { - if ((e.key >= '0' && e.key <= '9') || '+-*/().%'.includes(e.key)) { - currentExpression += e.key; - updateDisplay(); - resultDisplay.value = ''; - } else if (e.key === 'Enter') { - const sanitized = sanitizeExpression(currentExpression); - resultDisplay.value = calculateExpression(sanitized); - } else if (e.key === 'Backspace') { - currentExpression = currentExpression.slice(0, -1); - updateDisplay(); - resultDisplay.value = ''; - } else if (e.key.toLowerCase() === 'c') { - currentExpression = ''; - updateDisplay(); - resultDisplay.value = ''; - } +document.addEventListener('keydown', (e) => { + // Only digits, operators, and decimal + if ("0123456789+-*/.%".includes(e.key)) { + currentExpression += e.key; + updateDisplay(); + resultDisplay.value = ''; + } + // Evaluate on Enter + else if (e.key === 'Enter') { + e.preventDefault(); // Prevent form submission + const sanitized = sanitizeExpression(currentExpression); + resultDisplay.value = calculateExpression(sanitized); + } + // Delete last character + else if (e.key === 'Backspace') { + e.preventDefault(); // Prevent browser back navigation + currentExpression = currentExpression.slice(0, -1); + updateDisplay(); + resultDisplay.value = ''; + } + // Clear using Escape key + else if (e.key === 'Escape') { + e.preventDefault(); + currentExpression = ''; + updateDisplay(); + resultDisplay.value = ''; + } }); + From 6365615c5d531e79775beec822df1543e83f13b4 Mon Sep 17 00:00:00 2001 From: delta student <04.sainiKhushi@gmail.com> Date: Sat, 25 Oct 2025 18:13:08 +0530 Subject: [PATCH 3/4] fix: correct directory casing in README.md --- Projects/calculator-js/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Projects/calculator-js/README.md b/Projects/calculator-js/README.md index 7e2e60fa..f004e504 100644 --- a/Projects/calculator-js/README.md +++ b/Projects/calculator-js/README.md @@ -16,5 +16,5 @@ Simple calculator implemented with HTML, CSS and JavaScript. 4. Input remains visible; result appears below input. ## Notes for maintainers -- Files added under `projects/calculator-js/` +- Files added under `Projects/calculator-js/` - No external dependencies From bef740d554e457d92c05d4759e847074b1887382 Mon Sep 17 00:00:00 2001 From: delta student <04.sainiKhushi@gmail.com> Date: Sat, 25 Oct 2025 18:53:57 +0530 Subject: [PATCH 4/4] replace eval() with custom safe expression parse --- Projects/calculator-js/script.js | 324 ++++++++++++++++++++++++++----- 1 file changed, 278 insertions(+), 46 deletions(-) diff --git a/Projects/calculator-js/script.js b/Projects/calculator-js/script.js index 360ceaf1..05cbd4a5 100644 --- a/Projects/calculator-js/script.js +++ b/Projects/calculator-js/script.js @@ -1,3 +1,102 @@ +// const display = document.getElementById('display'); +// const resultDisplay = document.getElementById('result'); +// const buttons = document.querySelectorAll('.buttons button'); + +// let currentExpression = ''; + +// function updateDisplay() { +// display.value = currentExpression; +// } + +// function sanitizeExpression(expr) { +// return expr.replace(/[^0-9+\-*/().%]/g, ''); +// } + +// // function calculateExpression(expr) { +// // const withPercent = expr.replace(/(\d+(\.\d+)?)%/g, '($1/100)'); +// // try { +// // const result = eval(withPercent); +// // if (typeof result === 'number') { +// // if (!isFinite(result)) return 'Error'; +// // return String(Math.round(result * 1e10) / 1e10); +// // } +// // return 'Error'; +// // } catch (e) { +// // return 'Error'; +// // } +// // } + +// +// Option 1: Use math.js library (add to index.html: ) +// +// Or install via npm: npm install mathjs +// + + +// +// Option 1: Use math.js library (add to index.html: ) +// +// Or install via npm: npm install mathjs +// + + + + + + +// buttons.forEach(btn => { +// btn.addEventListener('click', () => { +// const val = btn.dataset.value; +// const action = btn.dataset.action; + +// if (action === 'clear') { +// currentExpression = ''; +// updateDisplay(); +// resultDisplay.value = ''; +// return; +// } +// if (action === 'back') { +// currentExpression = currentExpression.slice(0, -1); +// updateDisplay(); +// resultDisplay.value = ''; +// return; +// } +// if (action === 'equals') { +// const sanitized = sanitizeExpression(currentExpression); +// const result = calculateExpression(sanitized); +// resultDisplay.value = result; +// return; +// } + +// currentExpression += val; +// updateDisplay(); +// resultDisplay.value = ''; +// }); +// }); + +// document.addEventListener('keydown', (e) => { +// // Only digits, operators, and decimal +// if ("0123456789+-*/.%".includes(e.key)) { +// currentExpression += e.key; +// updateDisplay(); +// resultDisplay.value = ''; +// } +// // Evaluate on Enter +// else if (e.key === 'Enter') { +// e.preventDefault(); // Prevent form submission +// const sanitized = sanitizeExpression(currentExpression); +// resultDisplay.value = calculateExpression(sanitized); +// } +// // Delete last character +// else if (e.key === 'Backspace') { +// e.preventDefault(); // Prevent browser back navigation +// currentExpression = currentExpression.slice(0, -1); +// updateDisplay(); +// resultDisplay.value = ''; +// } +// // Clear using Escape key +// else if (e.key === 'Escape') { +// e.preventDefault(); +// currentExpression = ''; +// updateDisplay(); +// resultDisplay.value = ''; +// } +// }); + const display = document.getElementById('display'); const resultDisplay = document.getElementById('result'); const buttons = document.querySelectorAll('.buttons button'); @@ -12,10 +111,13 @@ function sanitizeExpression(expr) { return expr.replace(/[^0-9+\-*/().%]/g, ''); } +// Custom expression parser - replaces eval() function calculateExpression(expr) { + // Convert percentages to division by 100 const withPercent = expr.replace(/(\d+(\.\d+)?)%/g, '($1/100)'); + try { - const result = eval(withPercent); + const result = evaluateExpression(withPercent); if (typeof result === 'number') { if (!isFinite(result)) return 'Error'; return String(Math.round(result * 1e10) / 1e10); @@ -26,62 +128,192 @@ function calculateExpression(expr) { } } -buttons.forEach(btn => { - btn.addEventListener('click', () => { - const val = btn.dataset.value; - const action = btn.dataset.action; +// Tokenizer: converts string expression into tokens +function tokenize(expr) { + const tokens = []; + let i = 0; + + while (i < expr.length) { + const char = expr[i]; + + // Skip whitespace + if (char === ' ') { + i++; + continue; + } + + // Handle numbers (including decimals) + if (char >= '0' && char <= '9' || char === '.') { + let num = ''; + while (i < expr.length && (expr[i] >= '0' && expr[i] <= '9' || expr[i] === '.')) { + num += expr[i]; + i++; + } + tokens.push({ type: 'number', value: parseFloat(num) }); + continue; + } + + // Handle operators + if (char === '+' || char === '-' || char === '*' || char === '/') { + tokens.push({ type: 'operator', value: char }); + i++; + continue; + } + + // Handle parentheses + if (char === '(') { + tokens.push({ type: 'leftParen' }); + i++; + continue; + } + + if (char === ')') { + tokens.push({ type: 'rightParen' }); + i++; + continue; + } + + // Unknown character - skip it + i++; + } + + return tokens; +} + +// Evaluator: uses Shunting Yard algorithm to handle operator precedence +function evaluateExpression(expr) { + const tokens = tokenize(expr); + const outputQueue = []; + const operatorStack = []; + + // Define operator precedence + const precedence = { + '+': 1, + '-': 1, + '*': 2, + '/': 2 + }; + + // Convert to Reverse Polish Notation (RPN) using Shunting Yard + for (const token of tokens) { + if (token.type === 'number') { + outputQueue.push(token); + } else if (token.type === 'operator') { + while ( + operatorStack.length > 0 && + operatorStack[operatorStack.length - 1].type === 'operator' && + precedence[operatorStack[operatorStack.length - 1].value] >= precedence[token.value] + ) { + outputQueue.push(operatorStack.pop()); + } + operatorStack.push(token); + } else if (token.type === 'leftParen') { + operatorStack.push(token); + } else if (token.type === 'rightParen') { + while ( + operatorStack.length > 0 && + operatorStack[operatorStack.length - 1].type !== 'leftParen' + ) { + outputQueue.push(operatorStack.pop()); + } + // Pop the left parenthesis + if (operatorStack.length > 0) { + operatorStack.pop(); + } + } + } + + // Pop remaining operators + while (operatorStack.length > 0) { + outputQueue.push(operatorStack.pop()); + } + + // Evaluate RPN expression + const evalStack = []; + + for (const token of outputQueue) { + if (token.type === 'number') { + evalStack.push(token.value); + } else if (token.type === 'operator') { + if (evalStack.length < 2) { + throw new Error('Invalid expression'); + } + + const b = evalStack.pop(); + const a = evalStack.pop(); + + let result; + switch (token.value) { + case '+': + result = a + b; + break; + case '-': + result = a - b; + break; + case '*': + result = a * b; + break; + case '/': + if (b === 0) { + return Infinity; // Will be caught as 'Error' by !isFinite check + } + result = a / b; + break; + default: + throw new Error('Unknown operator'); + } + + evalStack.push(result); + } + } + + if (evalStack.length !== 1) { + throw new Error('Invalid expression'); + } + + return evalStack[0]; +} + +// Rest of your button handling code remains the same +buttons.forEach(button => { + button.addEventListener('click', () => { + const action = button.dataset.action; + const value = button.dataset.value; if (action === 'clear') { currentExpression = ''; - updateDisplay(); resultDisplay.value = ''; - return; - } - if (action === 'back') { + updateDisplay(); + } else if (action === 'backspace') { currentExpression = currentExpression.slice(0, -1); updateDisplay(); - resultDisplay.value = ''; - return; - } - if (action === 'equals') { + } else if (action === 'equals') { const sanitized = sanitizeExpression(currentExpression); const result = calculateExpression(sanitized); resultDisplay.value = result; - return; + } else { + currentExpression += value; + updateDisplay(); } - - currentExpression += val; - updateDisplay(); - resultDisplay.value = ''; }); }); +// Keyboard support document.addEventListener('keydown', (e) => { - // Only digits, operators, and decimal - if ("0123456789+-*/.%".includes(e.key)) { - currentExpression += e.key; - updateDisplay(); - resultDisplay.value = ''; - } - // Evaluate on Enter - else if (e.key === 'Enter') { - e.preventDefault(); // Prevent form submission - const sanitized = sanitizeExpression(currentExpression); - resultDisplay.value = calculateExpression(sanitized); - } - // Delete last character - else if (e.key === 'Backspace') { - e.preventDefault(); // Prevent browser back navigation - currentExpression = currentExpression.slice(0, -1); - updateDisplay(); - resultDisplay.value = ''; - } - // Clear using Escape key - else if (e.key === 'Escape') { - e.preventDefault(); - currentExpression = ''; - updateDisplay(); - resultDisplay.value = ''; - } -}); - + if (e.key >= '0' && e.key <= '9' || '+-*/.()%'.includes(e.key)) { + currentExpression += e.key; + updateDisplay(); + } else if (e.key === 'Enter') { + const sanitized = sanitizeExpression(currentExpression); + const result = calculateExpression(sanitized); + resultDisplay.value = result; + } else if (e.key === 'Backspace') { + e.preventDefault(); + currentExpression = currentExpression.slice(0, -1); + updateDisplay(); + } else if (e.key.toLowerCase() === 'c') { + currentExpression = ''; + resultDisplay.value = ''; + updateDisplay(); + } +}); \ No newline at end of file