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