<form class="calc">
<div class="calc__logo">
<span class="calc__j">J</span>K
</div>
<div class="calc__memory"></div>
<div class="calc__error"></div>
<input class="calc__screen" type="text" name="output" maxlength="10" value="0" readonly>
<div class="calc__btns">
<button class="calc__btn calc__btn--secondary" type="button" data-fn="%" title="Percent" aria-label="Percent" ontouchstart="">
<span tabindex="-1">%</span>
</button>
<button class="calc__btn calc__btn--secondary" type="button" data-fn="sqrt" title="Square Root" aria-label="Square Root" ontouchstart="">
<span tabindex="-1"><small>√</small></span>
</button>
<button class="calc__btn calc__btn--secondary" type="button" data-fn="+-" title="Negate Value" aria-label="Negate Value" ontouchstart="">
<span tabindex="-1"><small>+/-</small></span>
</button>
<button class="calc__btn calc__btn--primary" type="button" data-fn="C" title="Clear" aria-label="Clear" ontouchstart="">
<span tabindex="-1">C</span>
</button>
<button class="calc__btn calc__btn--secondary" type="button" data-fn="mrc" title="Recall/Clear Memory" aria-label="Recall/Clear Memory" ontouchstart="">
<span tabindex="-1"><small>MRC</small></span>
</button>
<button class="calc__btn calc__btn--secondary" type="button" data-fn="m-" title="Subtract From Memory" aria-label="Subtract From Memory" ontouchstart="">
<span tabindex="-1"><small>M-</small></span>
</button>
<button class="calc__btn calc__btn--secondary" type="button" data-fn="m+" title="Add To Memory" aria-label="Add To Memory" ontouchstart="">
<span tabindex="-1"><small>M+</small></span>
</button>
<button class="calc__btn calc__btn--secondary" type="button" data-fn="/" title="Divided By" aria-label="Divided By" ontouchstart="">
<span tabindex="-1">÷</span>
</button>
<button class="calc__btn" type="button" data-fn="7" ontouchstart="">
<span tabindex="-1">7</span>
</button>
<button class="calc__btn" type="button" data-fn="8" ontouchstart="">
<span tabindex="-1">8</span>
</button>
<button class="calc__btn" type="button" data-fn="9" ontouchstart="">
<span tabindex="-1">9</span>
</button>
<button class="calc__btn calc__btn--secondary" type="button" data-fn="*" title="Times" aria-label="Times" ontouchstart="">
<span tabindex="-1">×</span>
</button>
<button class="calc__btn" type="button" data-fn="4" ontouchstart="">
<span tabindex="-1">4</span>
</button>
<button class="calc__btn" type="button" data-fn="5" ontouchstart="">
<span tabindex="-1">5</span>
</button>
<button class="calc__btn" type="button" data-fn="6" ontouchstart="">
<span tabindex="-1">6</span>
</button>
<button class="calc__btn calc__btn--secondary" type="button" data-fn="-" title="Minus" aria-label="Minus" ontouchstart="">
<span tabindex="-1">-</span>
</button>
<button class="calc__btn" type="button" data-fn="1" ontouchstart="">
<span tabindex="-1">1</span>
</button>
<button class="calc__btn" type="button" data-fn="2" ontouchstart="">
<span tabindex="-1">2</span>
</button>
<button class="calc__btn" type="button" data-fn="3" ontouchstart="">
<span tabindex="-1">3</span>
</button>
<button class="calc__btn calc__btn--secondary calc__btn--tall" type="button" data-fn="+" title="Plus" aria-label="Plus" ontouchstart="">
<span tabindex="-1">+</span>
</button>
<button class="calc__btn" type="button" data-fn="0" ontouchstart="">
<span tabindex="-1">0</span>
</button>
<button class="calc__btn" type="button" data-fn="." aria-label="Point" ontouchstart="">
<span tabindex="-1">.</span>
</button>
<button class="calc__btn" type="button" data-fn="=" title="Equals" aria-label="Equals" ontouchstart="">
<span tabindex="-1">=</span>
</button>
</div>
</form>
* {
border: 0;
box-sizing: border-box;
margin: 0;
padding: 0;
}
:root {
--bg: #c7cad1;
--fg: #17181c;
--calcBg: #e3e4e8;
--fg: #17181c;
--logoBlue: #255ff4;
--transDur: 0.05s;
--buttonTiming: linear;
font-size: calc(20px + (30 - 20) * (100vw - 320px) / (1280 - 320));
}
body, button, input {
color: var(--fg);
font: 1em/1.5 "Hind", sans-serif;
}
body {
background-color: var(--bg);
display: grid;
padding: 1.5em 0;
place-items: center;
height: 100vh;
}
.calc {
background-color: var(--calcBg);
background-image:
linear-gradient(180deg,#0000,#0001),
radial-gradient(0.25em 0.25em at 0.5em 0.25em,#fff7 25%,#fff0 50%),
radial-gradient(95% 0.25em at 50% 0.25em,#fff7 25%,#fff0 50%);
border-radius: 0.75em;
box-shadow:
0 0.5em 0.5em #fff4 inset,
0 -0.125em 0.25em 0.125em #0007 inset,
0 0.25em 0.75em #0007;
padding: 0.25em 1em;
position: relative;
width: 12em;
height: 19.5em;
}
.calc__btns {
display: grid;
grid-template-columns: repeat(4,1fr);
grid-template-rows: repeat(6,1.5em);
grid-gap: 0.5em 0.75em;
}
.calc__btn:focus, .calc__btn span:focus, .calc__screen:focus {
outline: transparent;
}
.calc__btn, .calc__screen {
appearance: none;
appearance: none;
}
.calc__btn, .calc__btn.calc__btn--primary, .calc__btn.calc__btn--secondary {
color: #fff;
}
.calc__btn {
background-color: #2e3138;
background-image:
linear-gradient(180deg,#0000,#0002),
radial-gradient(90% 0.125em at 50% 0.125em,#fff7 25%,#fff0 50%);
border: 0;
border-radius: 0.25em;
box-shadow:
0.125em 0.125em 0.25em #0007,
0 -0.05em 0 0.05em #0004,
0 0.05em 0 0.05em #fff4,
-0.125em 0 0.125em #22252a inset,
0 0.125em 0.125em #fff4 inset,
0.125em 0 0.125em #fff4 inset,
0 -0.125em 0.125em #22252a inset;
cursor: pointer;
text-shadow: 0 0 0.125em #fff7;
transition: box-shadow var(--transDur) var(--buttonTiming);
tap-highlight-color: #0000;
}
.calc__btn:active, .calc__btn.calc__btn--active {
box-shadow:
0 0 0 #0007,
0 -0.05em 0 0.05em #0004,
0 0.05em 0 0.05em #fff4,
-0.125em 0 0.125em #0b0c0e inset,
0 0.125em 0.125em #0004 inset,
0.125em 0 0.125em #0004 inset,
0 -0.125em 0.125em #0b0c0e inset;
}
.calc__btn:active span, .calc__btn.calc__btn--active span {
transform: scale(0.95);
}
.calc__btn:focus {
color: #86a6f9;
text-shadow: 0 0 0.25em #86a6f977;
}
.calc__btn small, .calc__btn span {
pointer-events: none;
}
.calc__btn span {
display: block;
transition: transform var(--transDur) var(--buttonTiming);
}
.calc__btn.calc__btn--primary {
background-color: #255ff4;
box-shadow:
0.125em 0.125em 0.25em #0007,
0 -0.05em 0 0.05em #0004,
0 0.05em 0 0.05em #fff4,
-0.125em 0 0.125em #0936aa inset,
0 0.125em 0.125em #fff4 inset,
0.125em 0 0.125em #fff4 inset,
0 -0.125em 0.125em #0936aa inset;
}
.calc__btn.calc__btn--primary:active, .calc__btn.calc__btn--primary.calc__btn--active {
box-shadow:
0 0 0 #0007,
0 -0.05em 0 0.05em #0004,
0 0.05em 0 0.05em #fff4,
-0.125em 0 0.125em #062779 inset,
0 0.125em 0.125em #0004 inset,
0.125em 0 0.125em #0004 inset,
0 -0.125em 0.125em #062779 inset;
}
.calc__btn.calc__btn--secondary {
background-color: #5c6270;
box-shadow:
0.125em 0.125em 0.25em #0007,
0 -0.05em 0 0.05em #0004,
0 0.05em 0 0.05em #fff4,
-0.125em 0 0.125em #454954 inset,
0 0.125em 0.125em #fff4 inset,
0.125em 0 0.125em #fff4 inset,
0 -0.125em 0.125em #454954 inset;
}
.calc__btn.calc__btn--secondary:active, .calc__btn.calc__btn--secondary.calc__btn--active {
box-shadow:
0 0 0 #0007,
0 -0.05em 0 0.05em #0004,
0 0.05em 0 0.05em #fff4,
-0.125em 0 0.125em #2e3138 inset,
0 0.125em 0.125em #0004 inset,
0.125em 0 0.125em #0004 inset,
0 -0.125em 0.125em #2e3138 inset;
}
.calc__btn--tall {
grid-column: 4 / 5;
grid-row: 5 / 7;
}
.calc__error, .calc__memory, .calc__screen {
color: #17181c;
}
.calc__error, .calc__memory {
font-weight: bold;
position: absolute;
left: 1.5em;
text-shadow: 0.1em 0.1em 0.1em #0004;
}
.calc__error {
top: 4.3em;
}
.calc__memory {
top: 3.4em;
}
.calc__j {
color: var(--logoBlue);
text-shadow: 0 0 0.25em #255ff444;
}
.calc__logo {
font-size: 2em;
text-align: center;
text-shadow: 0 0 0.25em #0004;
height: 3rem;
}
.calc__screen {
background-image: linear-gradient(180deg,#9aa38f,#8d9781);
border-top: 0.25rem solid #abafba;
border-right: 0.25rem solid #abafba;
border-bottom: 0.25rem solid #fff;
border-left: 0.25rem solid #c7cad1;
border-radius: 0.25rem;
box-shadow:
0 0.25rem 0.25rem #0007 inset;
display: block;
font: 2em/1 "VT323", monospace;
margin: 0 auto 1rem auto;
padding: 0 0.25rem;
text-align: right;
text-shadow: 0.1rem 0.1rem 0.1rem #0004;
text-transform: uppercase;
width: 100%;
}
.calc__screen--fade-in {
animation: valueBlink 0.05s linear;
}
/* Dark theme */
@media (prefers-color-scheme: dark) {
:root {
--bg: #454954;
--fg: #e3e4e8;
--calcBg: #17181c;
--logoBlue: #5583f6;
}
.calc__btn {
background-color: #e3e4e8;
background-image:
linear-gradient(180deg,#0000,#0002),
radial-gradient(90% 0.125em at 50% 0.125em,#fff7 25%,#fff0 50%);
box-shadow:
0.125em 0.125em 0.25em #0007,
0 -0.05em 0 0.05em #0004,
0 0.05em 0 0.05em #fff1,
-0.125em 0 0.125em #737a8c inset,
0 0.125em 0.125em #fff4 inset,
0.125em 0 0.125em #fff4 inset,
0 -0.125em 0.125em #737a8c inset;
color: #17181c;
text-shadow: 0 0 0.125em #fff7;
}
.calc__btn:active, .calc__btn.calc__btn--active {
box-shadow:
0 0 0 #0007,
0 -0.05em 0 0.05em #0004,
0 0.05em 0 0.05em #fff1,
-0.125em 0 0.125em #5c6270 inset,
0 0.125em 0.125em #0004 inset,
0.125em 0 0.125em #0004 inset,
0 -0.125em 0.125em #5c6270 inset;
}
.calc__btn.calc__btn--primary {
box-shadow:
0.125em 0.125em 0.25em #0007,
0 -0.05em 0 0.05em #0004,
0 0.05em 0 0.05em #fff1,
-0.125em 0 0.125em #0936aa inset,
0 0.125em 0.125em #fff4 inset,
0.125em 0 0.125em #fff4 inset,
0 -0.125em 0.125em #0936aa inset;
}
.calc__btn.calc__btn--primary:active, .calc__btn.calc__btn--primary.calc__btn--active {
box-shadow:
0 0 0 #0007,
0 -0.05em 0 0.05em #0004,
0 0.05em 0 0.05em #fff1,
-0.125em 0 0.125em #062779 inset,
0 0.125em 0.125em #0004 inset,
0.125em 0 0.125em #0004 inset,
0 -0.125em 0.125em #062779 inset;
}
.calc__btn.calc__btn--secondary {
box-shadow:
0.125em 0.125em 0.25em #0007,
0 -0.05em 0 0.05em #0004,
0 0.05em 0 0.05em #fff1,
-0.125em 0 0.125em #454954 inset,
0 0.125em 0.125em #fff4 inset,
0.125em 0 0.125em #fff4 inset,
0 -0.125em 0.125em #454954 inset;
}
.calc__btn.calc__btn--secondary:active, .calc__btn.calc__btn--secondary.calc__btn--active {
box-shadow:
0 0 0 #0007,
0 -0.05em 0 0.05em #0004,
0 0.05em 0 0.05em #fff1,
-0.125em 0 0.125em #2e3138 inset,
0 0.125em 0.125em #0004 inset,
0.125em 0 0.125em #0004 inset,
0 -0.125em 0.125em #2e3138 inset;
}
.calc__logo {
text-shadow: 0 0 0.25em #fff4;
}
.calc__screen {
border-top: 0.25rem solid #2e3138;
border-right: 0.25rem solid #2e3138;
border-bottom: 0.25rem solid #454954;
border-left: 0.25rem solid #454954;
}
}
/* Animation */
@keyframes valueBlink {
from {
color: #17181c00;
text-shadow: 0.1rem 0.1rem 0.1rem #0000;
}
to {
color: #17181c;
text-shadow: 0.1rem 0.1rem 0.1rem #0004;
}
}
document.addEventListener("DOMContentLoaded",app);
function app() {
let calc = document.forms[0],
isCalcing = false,
error = "",
val = 0,
m = 0,
op = "",
exp = [],
lastPart = "",
lastOperand = 0,
signs = /[\/\*\-\+]/,
debug = false,
action = e => {
let tar = e.target || e,
fn = fnByKey(e.keyCode,e.shiftKey,e.altKey,e.metaKey),
output = calc.output;
// prevent a click right after equals if a button is in focus
if (e.keyCode == 13)
e.preventDefault();
// pressing button on screen
else if (!fn && !e.keyCode)
fn = tar.getAttribute("data-fn");
if (fn && output) {
let maxLen = +output.maxLength,
maxVal = 10 ** maxLen,
fnIsNum = !isNaN(fn),
fnIsDec = fn == ".",
fnIsPct = fn == "%",
fnIsSqrt = fn == "sqrt",
fnIsSign = fn == "+-",
recallOrClearM = fn == "mrc",
subFromM = fn == "m-",
addToM = fn == "m+",
fnIsOp = "/*-+".indexOf(fn) > -1,
fnIsEquals = fn == "=",
fnIsClear = fn == "C";
if (!error) {
if (exp.length)
lastPart = exp[exp.length - 1];
if (fnIsNum || fnIsDec || fnIsPct || fnIsSqrt || fnIsSign || fnIsOp) {
// start a new expression if `=` was previously pressed
if (!isCalcing) {
isCalcing = true;
if (fnIsNum || fnIsDec) {
op = "";
exp = ["0"];
lastPart = exp[0];
lastOperand = 0;
}
}
if (!exp.length)
exp.push("0");
}
// number
if (fnIsNum) {
if (isNaN(lastPart)) {
exp.push(fn);
} else if (lastPart.length < maxLen) {
let numToAdd = lastPart + fn;
// prevent whole numbers beginning with 0
if (numToAdd[0] == "0" && numToAdd[1] != ".")
numToAdd = numToAdd.substr(1);
exp[exp.length - 1] = numToAdd;
}
// decimal point
} else if (fnIsDec) {
if (lastPart.indexOf(".") == -1) {
if (isNaN(lastPart))
exp.push("0.");
else if (lastPart.length < maxLen - 1)
exp[exp.length - 1] += fn;
}
// percent
} else if (fnIsPct) {
if (exp.length) {
if (!isNaN(lastPart)) {
if (op && exp.indexOf(op) > -1) {
exp[exp.length - 1] = nearestLastDecPt(exp[0] * exp[exp.length - 1] / 100);
} else {
exp[exp.length - 1] = nearestLastDecPt(exp[exp.length - 1] / 100);
isCalcing = false;
}
} else {
exp.push(`${exp[0] * (exp[0] / 100)}`);
exp[exp.length - 1] = nearestLastDecPt(exp[exp.length - 1]);
}
}
blink();
// square root
} else if (fnIsSqrt) {
if (exp.length) {
if (!isNaN(lastPart))
exp[exp.length - 1] = String(Math.sqrt(exp[exp.length - 1]));
else
exp.push(String(Math.sqrt(exp[0])));
}
blink();
// toggle sign
} else if (fnIsSign) {
if (exp.length) {
if (!isNaN(lastPart))
exp[exp.length - 1] = String(-exp[exp.length - 1]);
else
exp[0] = String(-exp[0]);
}
// memory
} else if (recallOrClearM) {
if (m != 0 && m == val) {
if (exp.length <= 1) {
isCalcing = false;
m = 0;
displayM(false);
}
} else {
if (!isCalcing)
isCalcing = true;
if (isNaN(lastPart) || !exp.length)
exp.push(String(m));
else
exp[exp.length - 1] = String(m);
lastPart = exp[exp.length - 1];
lastOperand = lastPart;
displayM(m != 0);
}
// operation
} else if (fnIsOp) {
op = fn;
// switch operator
if (isNaN(lastPart)) {
exp[exp.length - 1] = op;
// calculate the current expression before using the operator
} else {
let curExp = exp.join(" ");
if (signs.test(curExp)) {
exp = [`${solve(exp[0],exp[1],exp[2])}`];
exp[0] = nearestLastDecPt(exp[0]);
lastPart = exp[0];
val = lastPart;
}
exp.push(op);
}
blink();
// equals, m-. or m+
} else {
let memAction = subFromM || addToM;
if (fnIsEquals || memAction) {
isCalcing = false;
if (op && exp.indexOf(op) > -1)
lastOperand = lastPart;
let compoundEquals = lastOperand && exp.indexOf(op) == -1,
normalEquals = !isNaN(lastOperand);
if (compoundEquals) {
if (!memAction)
exp = [`${solve(val,op,lastOperand)}`];
} else if (normalEquals) {
exp = [`${solve(exp[0],op,lastOperand)}`];
// equals without a second operand
} else {
lastOperand = exp[0];
exp = [`${solve(val,op,val)}`];
}
exp[0] = nearestLastDecPt(exp[0]);
// decrement/increment memory value by screen value
if (memAction) {
if (subFromM)
m -= +exp[0];
if (addToM)
m += +exp[0];
displayM(m != 0);
if (debug)
console.log(`M: ${m}`);
}
blink();
}
}
if (fn != "C") {
lastPart = exp[exp.length - 1];
if (!isNaN(lastPart) || lastPart == "NaN")
val = lastPart;
// deal with infinity, 0 / 0, square root of a negative number, or integer overflow
if (Math.abs(val) == Infinity || val == "NaN") {
error = "0";
} else if (val <= -maxVal / 10) {
let cutNeg = maxLen - 3;
if (cutNeg < 0)
cutNeg = 0;
error = (val / maxVal).toFixed(cutNeg);
} else if (val >= maxVal) {
let cutPos = maxLen - 2;
if (cutPos < 0)
cutPos = 0;
error = (val / maxVal).toFixed(cutPos);
}
// update the display
let outputVal = error || String(val);
if (!error) {
// fit the value into the screen without ending with a decimal point
if ((val >= -1e8 && val < -1e7) || (val >= 1e8 && val < 1e9))
outputVal = outputVal.substr(0,maxLen - 1);
else
outputVal = outputVal.substr(0,maxLen);
} else {
displayE();
}
output.value = outputVal;
}
}
// clear
if (fnIsClear) {
if (debug)
console.clear();
isCalcing = false;
error = "";
val = 0;
// prevent storage of infinity or non-numbers
if (Math.abs(m) == Infinity || isNaN(m)) {
m = 0;
displayM(false);
}
op = "";
exp = [];
lastPart = "";
lastOperand = 0;
output.value = val;
blink();
displayE(false);
}
if (debug)
console.log(exp);
}
},
blink = () => {
let output = calc.output;
if (output) {
let fadeInClass = "calc__screen--fade-in",
screenCL = output.classList;
screenCL.remove(fadeInClass);
void output.offsetWidth;
setTimeout(() => {
screenCL.add(fadeInClass);
},0);
}
},
displayE = (show = true) => {
let ce = calc.querySelector(".calc__error");
ce.textContent = show ? "E" : "";
},
displayM = (show = true) => {
let cm = calc.querySelector(".calc__memory");
cm.textContent = show ? "M" : "";
},
fnByKey = (keycode,isShift,isAlt,isCmd) => {
let fn = "";
switch (keycode) {
case 12:
case 27:
case 67:
if (!isCmd)
fn = "C";
break;
case 13:
fn = "=";
break;
case 48:
case 96:
fn = "0";
break;
case 49:
case 97:
fn = "1";
break;
case 50:
case 98:
fn = "2";
break;
case 51:
case 99:
fn = "3";
break;
case 52:
case 100:
fn = "4";
break;
case 53:
case 101:
fn = isShift ? "%" : "5";
break;
case 54:
case 102:
fn = "6";
break;
case 55:
case 103:
fn = "7";
break;
case 56:
fn = isShift ? "*" : "8";
break;
case 104:
fn = "8";
break;
case 57:
case 105:
fn = "9";
break;
case 77:
fn = "mrc";
break;
case 83:
fn = "sqrt";
break;
case 106:
fn = "*";
break;
case 107:
fn = "+";
break;
case 187:
fn = isShift ? "+" : "=";
break;
case 188:
fn = isShift ? "m-" : "";
break;
case 109:
case 189:
fn = isAlt ? "+-" : "-";
break;
case 110:
fn = ".";
break;
case 190:
fn = isShift ? "m+" : ".";
break;
case 111:
case 191:
fn = "/";
break;
default:
fn = "";
break;
}
return fn;
},
kbdButtonPress = (e,state = "down") => {
let tar = e.target || e,
fn = fnByKey(e.keyCode,e.shiftKey,e.altKey,e.metaKey),
key = calc.querySelector(`[data-fn="${fn}"]`);
if (key) {
let activeClass = "calc__btn--active",
keyCL = key.classList;
if (state == "down" && !keyCL.contains(activeClass))
keyCL.add(activeClass);
else if (state == "up" && keyCL.contains(activeClass))
keyCL.remove(activeClass);
}
},
nearestLastDecPt = (n,places = 9) => {
let power = 10 ** -places,
r = Math.round(n / power) * power;
return String(+r.toFixed(places));
},
solve = (A,op,B) => {
let r = 0,
a = !isNaN(A) ? +A : r,
b = !isNaN(B) ? +B : a;
switch (op) {
case "/":
r = a / b;
break;
case "*":
r = a * b;
break;
case "-":
r = a - b;
break;
case "+":
r = a + b;
break;
default:
r = a;
break;
}
return r;
};
calc.addEventListener("click",action);
document.addEventListener("keydown",action);
document.addEventListener("keydown",kbdButtonPress);
document.addEventListener("keyup",e => kbdButtonPress(e,"up"));
}
This Pen doesn't use any external JavaScript resources.