diff --git a/api/controller.js b/api/controller.js index 949731c..b43da2d 100644 --- a/api/controller.js +++ b/api/controller.js @@ -16,6 +16,12 @@ exports.calculate = function(req, res) { 'subtract': function(a, b) { return a - b }, 'multiply': function(a, b) { return a * b }, 'divide': function(a, b) { return a / b }, + 'sin': function(a) { return Math.sin(a) }, + 'cos': function(a) { return Math.cos(a) }, + 'tan': function(a) { return Math.tan(a) }, + 'sqrt': function(a) { return Math.sqrt(a) }, + 'power': function(a, b) { return Math.pow(a, b) }, + 'log': function(a) { return Math.log10(a) }, }; if (!req.query.operation) { @@ -29,16 +35,23 @@ exports.calculate = function(req, res) { } if (!req.query.operand1 || - !req.query.operand1.match(/^(-)?[0-9\.]+(e(-)?[0-9]+)?$/) || + !req.query.operand1.match(/^(-)?[0-9.]+(e(-)?[0-9]+)?$/) || req.query.operand1.replace(/[-0-9e]/g, '').length > 1) { throw new Error("Invalid operand1: " + req.query.operand1); } - if (!req.query.operand2 || - !req.query.operand2.match(/^(-)?[0-9\.]+(e(-)?[0-9]+)?$/) || - req.query.operand2.replace(/[-0-9e]/g, '').length > 1) { - throw new Error("Invalid operand2: " + req.query.operand2); - } + // Check if operation requires two operands + var singleOperandOps = ['sin', 'cos', 'tan', 'sqrt', 'log']; + var requiresTwoOperands = singleOperandOps.indexOf(req.query.operation) === -1; - res.json({ result: operation(req.query.operand1, req.query.operand2) }); + if (requiresTwoOperands) { + if (!req.query.operand2 || + !req.query.operand2.match(/^(-)?[0-9.]+(e(-)?[0-9]+)?$/) || + req.query.operand2.replace(/[-0-9e]/g, '').length > 1) { + throw new Error("Invalid operand2: " + req.query.operand2); + } + res.json({ result: operation(req.query.operand1, req.query.operand2) }); + } else { + res.json({ result: operation(req.query.operand1) }); + } }; diff --git a/public/client.js b/public/client.js index 1d21fd9..7c301b0 100644 --- a/public/client.js +++ b/public/client.js @@ -33,13 +33,28 @@ function calculate(operand1, operand2, operation) { case '/': uri += "?operation=divide"; break; + case 'power': + uri += "?operation=power"; + break; + case 'sin': + case 'cos': + case 'tan': + case 'sqrt': + case 'log': + uri += "?operation=" + operation; + break; default: setError(); return; } uri += "&operand1=" + encodeURIComponent(operand1); - uri += "&operand2=" + encodeURIComponent(operand2); + + // Only add operand2 for operations that need it + var singleOperandOps = ['sin', 'cos', 'tan', 'sqrt', 'log']; + if (singleOperandOps.indexOf(operation) === -1) { + uri += "&operand2=" + encodeURIComponent(operand2); + } setLoading(true); @@ -132,6 +147,44 @@ function equalPressed() { calculate(operand1, operand2, operation); } +function scientificOperation(op) { + // Scientific operations work on the current value immediately + var currentValue = getValue(); + + // Save state for potential chaining + operand1 = currentValue; + operation = op; + state = states.complete; + + // Calculate immediately (operand2 not needed for these operations) + calculate(currentValue, null, op); +} + +function toggleTheme() { + var body = document.body; + var themeIcon = document.querySelector('.theme-icon'); + + if (body.classList.contains('dark-theme')) { + body.classList.remove('dark-theme'); + body.classList.add('light-theme'); + themeIcon.textContent = '🌙'; + } else if (body.classList.contains('light-theme')) { + body.classList.remove('light-theme'); + body.classList.add('dark-theme'); + themeIcon.textContent = '☀️'; + } else { + // First time toggle - check system preference + var prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; + if (prefersDark) { + body.classList.add('light-theme'); + themeIcon.textContent = '🌙'; + } else { + body.classList.add('dark-theme'); + themeIcon.textContent = '☀️'; + } + } +} + // Enhanced keyboard support document.addEventListener('keydown', (event) => { // Handle keydown for special keys diff --git a/public/default.css b/public/default.css index bd14feb..d91f51e 100644 --- a/public/default.css +++ b/public/default.css @@ -10,6 +10,9 @@ --button-operator: #007bff; --button-operator-hover: #0056b3; --button-operator-active: #004085; + --button-scientific: #6f42c1; + --button-scientific-hover: #5a32a3; + --button-scientific-active: #4c2a8a; --button-equals: #28a745; --button-equals-hover: #1e7e34; --button-equals-active: #155724; @@ -42,6 +45,32 @@ } } +/* Light theme override */ +body.light-theme { + --primary-bg: #f0f2f5; + --calculator-bg: #ffffff; + --display-bg: #1e1e1e; + --display-text: #ffffff; + --button-bg: #f8f9fa; + --button-hover: #e9ecef; + --button-active: #dee2e6; + --text-primary: #212529; + --text-secondary: #6c757d; +} + +/* Dark theme override */ +body.dark-theme { + --primary-bg: #121212; + --calculator-bg: #1e1e1e; + --display-bg: #000000; + --display-text: #ffffff; + --button-bg: #2d2d2d; + --button-hover: #3d3d3d; + --button-active: #4d4d4d; + --text-primary: #ffffff; + --text-secondary: #adb5bd; +} + BODY { height: 100vh; width: 100vw; @@ -108,6 +137,36 @@ BODY { min-height: auto; } +/* Theme toggle button */ +.theme-toggle { + position: absolute; + top: 1rem; + right: 1rem; + background: var(--button-bg); + border: none; + border-radius: 50%; + width: 50px; + height: 50px; + font-size: 1.5rem; + cursor: pointer; + box-shadow: var(--shadow-md); + transition: all var(--transition-duration) ease; + display: flex; + align-items: center; + justify-content: center; + z-index: 100; +} + +.theme-toggle:hover { + background: var(--button-hover); + transform: scale(1.1); + box-shadow: var(--shadow-lg); +} + +.theme-toggle:active { + transform: scale(0.95); +} + #results { margin-bottom: 1.5rem; width: 100%; @@ -266,6 +325,21 @@ BODY { background: var(--button-clear-active); } +/* Scientific button styling */ +.btn-scientific { + background: var(--button-scientific); + color: white; + font-size: 1rem; +} + +.btn-scientific:hover { + background: var(--button-scientific-hover); +} + +.btn-scientific:active { + background: var(--button-scientific-active); +} + .calculator { background: var(--calculator-bg); border-radius: var(--border-radius); @@ -306,6 +380,10 @@ BODY { background: var(--button-clear-active); } +.btn-scientific.pressed { + background: var(--button-scientific-active); +} + /* Improved mobile touch targets */ @media (max-width: 767px) { .btn { diff --git a/public/index.html b/public/index.html index eaa7da7..dab3ea2 100644 --- a/public/index.html +++ b/public/index.html @@ -8,6 +8,9 @@
+
0
@@ -16,7 +19,13 @@
- + + + + + + + diff --git a/test/arithmetic.test.js b/test/arithmetic.test.js index deded48..1a774d3 100644 --- a/test/arithmetic.test.js +++ b/test/arithmetic.test.js @@ -205,4 +205,88 @@ describe('Arithmetic', function () { }); }); }); + + describe('Scientific Operations', function () { + describe('Sine', function () { + it('calculates sine of 0', function (done) { + request.get('/arithmetic?operation=sin&operand1=0') + .expect(200) + .end(function (err, res) { + expect(res.body).to.eql({ result: 0 }); + done(); + }); + }); + }); + + describe('Cosine', function () { + it('calculates cosine of 0', function (done) { + request.get('/arithmetic?operation=cos&operand1=0') + .expect(200) + .end(function (err, res) { + expect(res.body).to.eql({ result: 1 }); + done(); + }); + }); + }); + + describe('Square Root', function () { + it('calculates square root of 9', function (done) { + request.get('/arithmetic?operation=sqrt&operand1=9') + .expect(200) + .end(function (err, res) { + expect(res.body).to.eql({ result: 3 }); + done(); + }); + }); + + it('calculates square root of 0', function (done) { + request.get('/arithmetic?operation=sqrt&operand1=0') + .expect(200) + .end(function (err, res) { + expect(res.body).to.eql({ result: 0 }); + done(); + }); + }); + }); + + describe('Power', function () { + it('calculates 2 to the power of 3', function (done) { + request.get('/arithmetic?operation=power&operand1=2&operand2=3') + .expect(200) + .end(function (err, res) { + expect(res.body).to.eql({ result: 8 }); + done(); + }); + }); + + it('calculates 10 to the power of 2', function (done) { + request.get('/arithmetic?operation=power&operand1=10&operand2=2') + .expect(200) + .end(function (err, res) { + expect(res.body).to.eql({ result: 100 }); + done(); + }); + }); + }); + + describe('Logarithm', function () { + it('calculates log of 10', function (done) { + request.get('/arithmetic?operation=log&operand1=10') + .expect(200) + .end(function (err, res) { + expect(res.body).to.eql({ result: 1 }); + done(); + }); + }); + + it('calculates log of 100', function (done) { + request.get('/arithmetic?operation=log&operand1=100') + .expect(200) + .end(function (err, res) { + expect(res.body).to.eql({ result: 2 }); + done(); + }); + }); + }); + }); });