Skip to content
Draft
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
29 changes: 23 additions & 6 deletions api/controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,17 @@ 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 },
'power': function(a, b) { return Math.pow(a, b) },
'sqrt': function(a) { return Math.sqrt(a) },
'sin': function(a) { return Math.sin(a) },
'cos': function(a) { return Math.cos(a) },
'tan': function(a) { return Math.tan(a) },
'log': function(a) { return Math.log10(a) },
'ln': function(a) { return Math.log(a) },
};

var singleOperandOps = ['sqrt', 'sin', 'cos', 'tan', 'log', 'ln'];

if (!req.query.operation) {
throw new Error("Unspecified operation");
}
Expand All @@ -29,16 +38,24 @@ 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);
// Only validate operand2 for operations that require two operands
if (!singleOperandOps.includes(req.query.operation)) {
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) });
// Call operation with appropriate number of operands
var result = singleOperandOps.includes(req.query.operation)
? operation(req.query.operand1)
: operation(req.query.operand1, req.query.operand2);

res.json({ result: result });
};
39 changes: 38 additions & 1 deletion public/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ var operand1 = 0;
var operand2 = 0;
var operation = null;

// Single operand operations that execute immediately
var SINGLE_OPERAND_OPS = ['√', 'sin', 'cos', 'tan', 'log', 'ln'];

function calculate(operand1, operand2, operation) {
var uri = location.origin + "/arithmetic";

Expand All @@ -33,13 +36,38 @@ function calculate(operand1, operand2, operation) {
case '/':
uri += "?operation=divide";
break;
case '^':
uri += "?operation=power";
break;
case '√':
uri += "?operation=sqrt";
break;
case 'sin':
uri += "?operation=sin";
break;
case 'cos':
uri += "?operation=cos";
break;
case 'tan':
uri += "?operation=tan";
break;
case 'log':
uri += "?operation=log";
break;
case 'ln':
uri += "?operation=ln";
break;
default:
setError();
return;
}

uri += "&operand1=" + encodeURIComponent(operand1);
uri += "&operand2=" + encodeURIComponent(operand2);

// Only add operand2 for binary operations
if (!SINGLE_OPERAND_OPS.includes(operation)) {
uri += "&operand2=" + encodeURIComponent(operand2);
}

setLoading(true);

Expand Down Expand Up @@ -116,6 +144,15 @@ function operationPressed(op) {
state = states.operator;
}

function scientificPressed(op) {
// Single operand operations execute immediately
if (SINGLE_OPERAND_OPS.includes(op)) {
operand1 = getValue();
calculate(operand1, null, op);
state = states.complete;
}
}

function equalPressed() {
if (state < states.operand2) {
state = states.complete;
Expand Down
70 changes: 42 additions & 28 deletions public/default.css
Original file line number Diff line number Diff line change
@@ -1,44 +1,44 @@
/* CSS Custom Properties for Modern Theming */
/* CSS Custom Properties for The Green Theme of the Shire */
:root {
--primary-bg: #f0f2f5;
--calculator-bg: #ffffff;
--display-bg: #1e1e1e;
--display-text: #ffffff;
--button-bg: #f8f9fa;
--button-hover: #e9ecef;
--button-active: #dee2e6;
--button-operator: #007bff;
--button-operator-hover: #0056b3;
--button-operator-active: #004085;
--primary-bg: #d4f1d4;
--calculator-bg: #f0fff0;
--display-bg: #1a4d1a;
--display-text: #c8ffc8;
--button-bg: #e8f5e8;
--button-hover: #c8e6c8;
--button-active: #a8d6a8;
--button-operator: #2d8659;
--button-operator-hover: #257047;
--button-operator-active: #1d5a38;
--button-equals: #28a745;
--button-equals-hover: #1e7e34;
--button-equals-active: #155724;
--button-clear: #dc3545;
--button-clear-hover: #c82333;
--button-clear-active: #bd2130;
--text-primary: #212529;
--text-secondary: #6c757d;
--shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
--shadow-md: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
--shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175);
--text-primary: #1a4d1a;
--text-secondary: #4a7c4a;
--shadow-sm: 0 0.125rem 0.25rem rgba(0, 100, 0, 0.1);
--shadow-md: 0 0.5rem 1rem rgba(0, 100, 0, 0.2);
--shadow-lg: 0 1rem 3rem rgba(0, 100, 0, 0.25);
--border-radius: 0.5rem;
--border-radius-sm: 0.375rem;
--transition-duration: 0.15s;
--font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', sans-serif;
}

/* Dark mode support */
/* Dark mode support - Green theme of Fangorn Forest */
@media (prefers-color-scheme: dark) {
:root {
--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;
--primary-bg: #0a2f0a;
--calculator-bg: #1a3d1a;
--display-bg: #0d1f0d;
--display-text: #90ee90;
--button-bg: #2d4d2d;
--button-hover: #3d6d3d;
--button-active: #4d8d4d;
--text-primary: #90ee90;
--text-secondary: #6b9d6b;
}
}

Expand All @@ -47,7 +47,7 @@ BODY {
width: 100vw;
padding: 0;
margin: 0;
background: linear-gradient(135deg, var(--primary-bg) 0%, #e3e6ea 100%);
background: linear-gradient(135deg, var(--primary-bg) 0%, #b8e6b8 100%);
font-family: var(--font-family);
display: flex;
justify-content: center;
Expand Down Expand Up @@ -226,7 +226,21 @@ BODY {
height: 200px;
}

/* Button type specific styling */
/* Button type specific styling - Scientific buttons like the leaves of Lothlórien */
.btn-scientific {
background: #4a9d5f;
color: white;
font-size: 1rem;
}

.btn-scientific:hover {
background: #3d8550;
}

.btn-scientific:active {
background: #2f6d40;
}

.btn[onclick*="operationPressed"] {
background: var(--button-operator);
color: white;
Expand Down
22 changes: 16 additions & 6 deletions public/index.html
Original file line number Diff line number Diff line change
@@ -1,25 +1,35 @@
<html lang="en">
<head>
<title>Modern Calculator</title>
<title>Scientific Palantír of Rivendell</title>
<link rel="stylesheet" href="default.css">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
<meta name="description" content="A modern, accessible calculator application">
<meta name="description" content="A scientific calculator forged in the halls of Rivendell">
<script src="client.js"></script>
</head>
<body onLoad="setValue(0)">
<div class="container">
<main class="calculator" role="application" aria-label="Calculator">
<main class="calculator" role="application" aria-label="Scientific Calculator">
<div id="results">
<div id="result" role="textbox" aria-label="Calculator display" aria-live="polite" aria-readonly="true" tabindex="0"><span class="digit0">0</span></div>
</div>

<div id="loading" style="visibility:hidden" aria-hidden="true"></div>

<div class="calculator-buttons" role="grid" aria-label="Calculator buttons">
<button class="btn span-2" aria-label="Empty button" disabled></button>
<button class="btn" onClick="clearPressed()" aria-label="Clear all">C</button>
<button class="btn" onClick="clearPressed()" aria-label="Clear entry">CE</button>
<!-- Scientific functions row -->
<button class="btn btn-scientific" onClick="scientificPressed('sin')" aria-label="Sine">sin</button>
<button class="btn btn-scientific" onClick="scientificPressed('cos')" aria-label="Cosine">cos</button>
<button class="btn btn-scientific" onClick="scientificPressed('tan')" aria-label="Tangent">tan</button>
<button class="btn btn-scientific" onClick="operationPressed('^')" aria-label="Power">x^y</button>

<!-- More scientific functions row -->
<button class="btn btn-scientific" onClick="scientificPressed('√')" aria-label="Square root">√</button>
<button class="btn btn-scientific" onClick="scientificPressed('log')" aria-label="Logarithm base 10">log</button>
<button class="btn btn-scientific" onClick="scientificPressed('ln')" aria-label="Natural logarithm">ln</button>
<button class="btn" onClick="clearEntryPressed()" aria-label="Clear entry">CE</button>

<!-- Clear and basic operations -->
<button class="btn" onClick="clearPressed()" aria-label="Clear all">C</button>
<button class="btn" onClick="numberPressed(7)" aria-label="Seven">7</button>
<button class="btn" onClick="numberPressed(8)" aria-label="Eight">8</button>
<button class="btn" onClick="numberPressed(9)" aria-label="Nine">9</button>
Expand Down
59 changes: 59 additions & 0 deletions test/arithmetic.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -205,4 +205,63 @@ describe('Arithmetic', function () {
});
});
});

describe('Scientific Operations - Power of the Istari', function () {
it('raises number to power', 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 square root', 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 sine of zero', function (done) {
request.get('/arithmetic?operation=sin&operand1=0')
.expect(200)
.end(function (err, res) {
expect(res.body).to.eql({ result: 0 });
done();
});
});
it('calculates cosine of zero', function (done) {
request.get('/arithmetic?operation=cos&operand1=0')
.expect(200)
.end(function (err, res) {
expect(res.body).to.eql({ result: 1 });
done();
});
});
it('calculates tangent of zero', function (done) {
request.get('/arithmetic?operation=tan&operand1=0')
.expect(200)
.end(function (err, res) {
expect(res.body).to.eql({ result: 0 });
done();
});
});
it('calculates log10 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();
});
});
it('calculates natural log of e', function (done) {
request.get('/arithmetic?operation=ln&operand1=2.718281828459045')
.expect(200)
.end(function (err, res) {
expect(res.body.result).to.be.closeTo(1, 0.0001);
done();
});
});
});
});