Files
2026-03-30 15:42:39 -04:00

672 lines
19 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Scientific Calculator</title>
<link rel="icon" type="image/png" href="images/gigachad.png" />
<link rel="apple-touch-icon" href="images/gigachad.png" />
<style>
:root {
--bg: #0f1419;
--surface: #1a2332;
--key: #243044;
--key-hover: #2d3d54;
--accent: #3d8bfd;
--accent-hover: #5a9dff;
--text: #e8eef7;
--muted: #8a9bb0;
--danger: #e85d5d;
--sci: #1e2a3d;
--sci-hover: #263548;
--radius: 10px;
font-family: "Segoe UI", system-ui, sans-serif;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: radial-gradient(ellipse 120% 80% at 50% -20%, #1e3a5f 0%, var(--bg) 55%);
color: var(--text);
}
.calc {
width: min(100% - 2rem, 680px);
padding: 1.25rem;
background: var(--surface);
border-radius: calc(var(--radius) + 4px);
box-shadow: 0 16px 48px rgba(0, 0, 0, 0.45), 0 0 0 1px rgba(255, 255, 255, 0.06);
display: flex;
flex-direction: column;
align-items: stretch;
}
.hero-wrap {
display: flex;
justify-content: center;
width: 100%;
max-width: 320px;
margin: 0 auto 0.75rem;
border-radius: var(--radius);
overflow: hidden;
border: 3px solid var(--accent);
box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08);
}
.hero-img {
display: block;
width: 100%;
height: auto;
max-height: 250px;
object-fit: contain;
}
.calc-panel {
width: 100%;
min-width: 0;
}
h1 {
margin: 0 0 0.65rem;
font-size: 0.7rem;
font-weight: 600;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--muted);
text-align: center;
}
.display-wrap {
background: var(--bg);
border-radius: var(--radius);
padding: 0.65rem 0.85rem;
margin-bottom: 0.75rem;
min-height: 3.5rem;
display: flex;
flex-direction: column;
align-items: flex-end;
justify-content: center;
border: 1px solid rgba(255, 255, 255, 0.06);
}
.expression {
font-size: 0.7rem;
color: var(--muted);
min-height: 1.15em;
word-break: break-all;
text-align: right;
width: 100%;
line-height: 1.35;
}
.result {
font-size: 1.5rem;
font-variant-numeric: tabular-nums;
font-weight: 500;
word-break: break-all;
text-align: right;
width: 100%;
}
.mode-row {
display: flex;
justify-content: flex-end;
margin-bottom: 0.45rem;
}
.mode-btn {
font: inherit;
font-size: 0.7rem;
font-weight: 600;
letter-spacing: 0.06em;
padding: 0.35rem 0.65rem;
border: 1px solid rgba(255, 255, 255, 0.12);
border-radius: 999px;
cursor: pointer;
background: var(--sci);
color: var(--accent);
}
.mode-btn:hover {
background: var(--sci-hover);
}
.keys {
display: grid;
gap: 0.4rem;
}
.keys.sci {
grid-template-columns: repeat(5, 1fr);
margin-bottom: 0.4rem;
}
.keys.main {
grid-template-columns: repeat(4, 1fr);
}
button {
font: inherit;
font-size: 1rem;
font-variant-numeric: tabular-nums;
border: none;
border-radius: var(--radius);
padding: 0.72rem 0.2rem;
cursor: pointer;
background: var(--key);
color: var(--text);
transition: background 0.12s ease, transform 0.08s ease;
}
button.sci {
font-size: 0.78rem;
padding: 0.55rem 0.15rem;
background: var(--sci);
}
button.sci:hover {
background: var(--sci-hover);
}
button:hover {
background: var(--key-hover);
}
button:active {
transform: scale(0.97);
}
button.op {
background: #2a3548;
color: var(--accent);
}
button.op:hover {
background: #33425a;
}
button.equals {
background: var(--accent);
color: #fff;
grid-row: span 2;
}
button.equals:hover {
background: var(--accent-hover);
}
button.danger {
color: var(--danger);
}
button.zero {
grid-column: span 2;
}
</style>
</head>
<body>
<div class="calc" role="application" aria-label="Scientific calculator">
<div class="hero-wrap">
<img
class="hero-img"
src="images/gigachad.png"
alt="GigaChad meme portrait"
/>
</div>
<div class="calc-panel">
<h1>Scientific calculator</h1>
<div class="mode-row">
<button type="button" class="mode-btn" id="degBtn" title="Toggle angle units for sin/cos/tan and inverse trig">DEG</button>
</div>
<div class="display-wrap">
<div class="expression" id="expr" aria-live="polite"></div>
<div class="result" id="out">0</div>
</div>
<div class="keys sci">
<button type="button" class="sci danger" data-action="clear">C</button>
<button type="button" class="sci" data-action="back"></button>
<button type="button" class="sci op" data-append="(">(</button>
<button type="button" class="sci op" data-append=")">)</button>
<button type="button" class="sci op" data-action="ans">ANS</button>
<button type="button" class="sci" data-append="sin(">sin</button>
<button type="button" class="sci" data-append="cos(">cos</button>
<button type="button" class="sci" data-append="tan(">tan</button>
<button type="button" class="sci" data-append="asin(">asin</button>
<button type="button" class="sci" data-append="acos(">acos</button>
<button type="button" class="sci" data-append="atan(">atan</button>
<button type="button" class="sci" data-append="log(">log</button>
<button type="button" class="sci" data-append="ln(">ln</button>
<button type="button" class="sci" data-append="sqrt("></button>
<button type="button" class="sci op" data-append="^">x^y</button>
<button type="button" class="sci op" data-append="^2"></button>
<button type="button" class="sci op" data-append="^(-1)">1/x</button>
<button type="button" class="sci op" data-append="pi">π</button>
<button type="button" class="sci op" data-append="e">e</button>
<button type="button" class="sci op" data-append="!">!</button>
</div>
<div class="keys main">
<button type="button" class="op" data-append="/">÷</button>
<button type="button" class="op" data-append="*">×</button>
<button type="button" class="op" data-append="-"></button>
<button type="button" class="op" data-append="+">+</button>
<button type="button" data-digit="7">7</button>
<button type="button" data-digit="8">8</button>
<button type="button" data-digit="9">9</button>
<button type="button" class="op equals" data-action="equals">=</button>
<button type="button" data-digit="4">4</button>
<button type="button" data-digit="5">5</button>
<button type="button" data-digit="6">6</button>
<button type="button" data-digit="1">1</button>
<button type="button" data-digit="2">2</button>
<button type="button" data-digit="3">3</button>
<button type="button" class="zero" data-digit="0">0</button>
<button type="button" data-action="dot">.</button>
</div>
</div>
</div>
<script>
(function () {
const exprEl = document.getElementById("expr");
const outEl = document.getElementById("out");
const degBtn = document.getElementById("degBtn");
let exprStr = "";
let lastResult = 0;
let degMode = true;
function formatDisplay(n) {
if (!Number.isFinite(n)) return "Error";
const s = String(n);
if (s.length > 16) return n.toPrecision(12).replace(/\.?0+e/, "e");
return s;
}
function prettyExpr(s) {
if (!s) return "";
return s
.replace(/\*/g, "×")
.replace(/\//g, "÷")
.replace(/pi/g, "π");
}
function updateView() {
exprEl.textContent = prettyExpr(exprStr);
outEl.textContent = formatDisplay(lastResult);
}
function factorial(n) {
if (!Number.isFinite(n)) throw new Error("domain");
if (n < 0 || Math.abs(n - Math.round(n)) > 1e-12) throw new Error("domain");
const k = Math.round(n);
if (k > 170) throw new Error("overflow");
let r = 1;
for (let i = 2; i <= k; i++) r *= i;
return r;
}
function applyFunc(name, x) {
const rad = (d) => (d * Math.PI) / 180;
const deg = (r) => (r * 180) / Math.PI;
switch (name) {
case "sin":
return degMode ? Math.sin(rad(x)) : Math.sin(x);
case "cos":
return degMode ? Math.cos(rad(x)) : Math.cos(x);
case "tan":
return degMode ? Math.tan(rad(x)) : Math.tan(x);
case "asin":
if (degMode) return deg(Math.asin(x));
return Math.asin(x);
case "acos":
if (degMode) return deg(Math.acos(x));
return Math.acos(x);
case "atan":
if (degMode) return deg(Math.atan(x));
return Math.atan(x);
case "log":
return Math.log10(x);
case "ln":
return Math.log(x);
case "sqrt":
return Math.sqrt(x);
case "abs":
return Math.abs(x);
default:
throw new Error("fn");
}
}
function tokenize(str) {
const tokens = [];
let i = 0;
const peek = () => str[i];
const readNum = () => {
let start = i;
if (peek() === ".") {
i++;
while (i < str.length && /[0-9]/.test(str[i])) i++;
} else {
while (i < str.length && /[0-9]/.test(str[i])) i++;
if (peek() === ".") {
i++;
while (i < str.length && /[0-9]/.test(str[i])) i++;
}
}
const raw = str.slice(start, i);
if (raw === "." || raw === "") throw new Error("num");
const v = parseFloat(raw);
if (!Number.isFinite(v)) throw new Error("num");
return { type: "number", value: v };
};
while (i < str.length) {
const c = peek();
if (/\s/.test(c)) {
i++;
continue;
}
if (/[0-9.]/.test(c)) {
tokens.push(readNum());
continue;
}
if (/[a-zA-Z]/.test(c)) {
let j = i;
while (j < str.length && /[a-zA-Z]/.test(str[j])) j++;
const id = str.slice(i, j);
i = j;
if (id === "pi") tokens.push({ type: "const", name: "pi" });
else if (id === "e") tokens.push({ type: "const", name: "e" });
else if (
["sin", "cos", "tan", "asin", "acos", "atan", "log", "ln", "sqrt", "abs"].includes(id)
) {
tokens.push({ type: "func", name: id });
} else throw new Error("id");
continue;
}
if (c === "(") {
tokens.push({ type: "lparen" });
i++;
continue;
}
if (c === ")") {
tokens.push({ type: "rparen" });
i++;
continue;
}
if (c === "!") {
tokens.push({ type: "fact" });
i++;
continue;
}
if ("+-*/^".includes(c)) {
tokens.push({ type: "op", v: c });
i++;
continue;
}
throw new Error("tok");
}
return tokens;
}
function insertImplicitMul(tokens) {
const out = [];
const needs = (a, b) => {
if (!a || !b) return false;
const end = ["number", "rparen", "fact"];
const start = ["number", "lparen", "func", "const"];
return end.includes(a.type) && start.includes(b.type);
};
for (let k = 0; k < tokens.length; k++) {
const t = tokens[k];
if (k > 0 && needs(out[out.length - 1], t)) {
out.push({ type: "op", v: "*" });
}
out.push(t);
}
return out;
}
function parseAndEval(tokens) {
let p = 0;
const peek = () => tokens[p];
const take = () => tokens[p++];
function parseAdditive() {
let left = parseMultiplicative();
while (peek()?.type === "op" && (peek().v === "+" || peek().v === "-")) {
const op = take().v;
const right = parseMultiplicative();
left = op === "+" ? left + right : left - right;
}
return left;
}
function parseMultiplicative() {
let left = parsePower();
while (peek()?.type === "op" && (peek().v === "*" || peek().v === "/")) {
const op = take().v;
const right = parsePower();
left = op === "*" ? left * right : left / right;
}
return left;
}
function parsePower() {
let left = parseUnary();
if (peek()?.type === "op" && peek().v === "^") {
take();
const right = parsePower();
left = Math.pow(left, right);
}
return left;
}
function parseUnary() {
if (peek()?.type === "op" && peek().v === "-") {
take();
return -parseUnary();
}
if (peek()?.type === "op" && peek().v === "+") {
take();
return parseUnary();
}
return parsePostfix();
}
function parsePostfix() {
let val = parsePrimary();
while (peek()?.type === "fact") {
take();
val = factorial(val);
}
return val;
}
function parsePrimary() {
const t = peek();
if (!t) throw new Error("exp");
if (t.type === "number") {
take();
return t.value;
}
if (t.type === "const") {
take();
return t.name === "pi" ? Math.PI : Math.E;
}
if (t.type === "func") {
const name = take().name;
if (peek()?.type !== "lparen") throw new Error("(");
take();
const inner = parseAdditive();
if (peek()?.type !== "rparen") throw new Error(")");
take();
return applyFunc(name, inner);
}
if (t.type === "lparen") {
take();
const inner = parseAdditive();
if (peek()?.type !== "rparen") throw new Error(")");
take();
return inner;
}
throw new Error("prim");
}
const result = parseAdditive();
if (peek()) throw new Error("trail");
if (!Number.isFinite(result)) throw new Error("nan");
return result;
}
function evaluate(raw) {
const sub = String(raw).trim();
if (!sub) throw new Error("empty");
let s = sub.replace(/\bANS\b/g, "(" + String(lastResult) + ")");
const tokens = insertImplicitMul(tokenize(s));
return parseAndEval(tokens);
}
function lastChar() {
return exprStr.slice(-1);
}
function shouldInsertMul(next) {
if (!exprStr) return false;
const a = lastChar();
const b = next[0];
if (/[0-9]/.test(a) && /[0-9]/.test(b)) return false;
if (a === "^") return false;
const aEnd = /[0-9\)\!]/.test(a);
const bStart = /[\(0-9a-zA-Z]/.test(b) && b !== "!";
return aEnd && bStart;
}
function append(s) {
if (shouldInsertMul(s)) exprStr += "*";
exprStr += s;
updateView();
}
function inputDigit(d) {
append(d);
}
function inputDot() {
append(".");
}
function doEval() {
try {
const v = evaluate(exprStr);
lastResult = v;
exprStr = "";
updateView();
} catch {
lastResult = NaN;
updateView();
}
}
function clear() {
exprStr = "";
lastResult = 0;
updateView();
}
function back() {
exprStr = exprStr.slice(0, -1);
updateView();
}
function insertAns() {
append("ANS");
}
degBtn.addEventListener("click", function () {
degMode = !degMode;
degBtn.textContent = degMode ? "DEG" : "RAD";
degBtn.title = degMode
? "Angles in degrees (click for radians)"
: "Angles in radians (click for degrees)";
});
document.querySelector(".calc").addEventListener("click", function (e) {
const btn = e.target.closest("button");
if (!btn) return;
if (btn === degBtn) return;
const ap = btn.getAttribute("data-append");
const d = btn.getAttribute("data-digit");
const action = btn.getAttribute("data-action");
if (action === "equals") {
doEval();
return;
}
if (d !== null) {
inputDigit(d);
return;
}
if (ap !== null) {
append(ap);
return;
}
if (action === "clear") clear();
else if (action === "back") back();
else if (action === "dot") inputDot();
else if (action === "ans") insertAns();
});
document.addEventListener("keydown", function (e) {
if (e.key >= "0" && e.key <= "9") {
e.preventDefault();
inputDigit(e.key);
} else if (e.key === ".") {
e.preventDefault();
inputDot();
} else if (e.key === "+") {
e.preventDefault();
append("+");
} else if (e.key === "-") {
e.preventDefault();
append("-");
} else if (e.key === "*") {
e.preventDefault();
append("*");
} else if (e.key === "/") {
e.preventDefault();
append("/");
} else if (e.key === "^") {
e.preventDefault();
append("^");
} else if (e.key === "(") {
e.preventDefault();
append("(");
} else if (e.key === ")") {
e.preventDefault();
append(")");
} else if (e.key === "Enter" || (e.key === "=" && !e.shiftKey)) {
e.preventDefault();
doEval();
} else if (e.key === "Escape") {
e.preventDefault();
clear();
} else if (e.key === "Backspace") {
e.preventDefault();
back();
}
});
updateView();
})();
</script>
</body>
</html>