|
Server : Apache/2.4.62 System : FreeBSD fbsdweb2.web.rcn.net 14.1-RELEASE FreeBSD 14.1-RELEASE releng/14.1-n267679-10e31f0946d8 GENERIC amd64 User : www ( 80) PHP Version : 8.3.8 Disable Function : NONE Directory : /domains/drsuper/CalculusMechanics/images/ |
Upload File : |
/* ===== DrSuper Popup Calculator (Injects UI Automatically) ===== */
document.addEventListener("DOMContentLoaded", () => {
// Prevent duplicates if included twice
if (document.getElementById("calcFab") || document.getElementById("calcOverlay")) return;
// --- Inject HTML ---
const wrapper = document.createElement("div");
wrapper.innerHTML = `
<button id="calcFab" type="button" aria-label="Open calculator">🧮</button>
<div id="calcOverlay" class="calc-overlay" role="dialog" aria-modal="true" aria-labelledby="calcTitle">
<div class="calc-modal" role="document">
<div class="calc-header">
<div>
<div id="calcTitle" class="calc-title">Calculator</div>
<div class="calc-sub">Esc to close • Enter to =</div>
</div>
<button id="calcClose" type="button" class="calc-x" aria-label="Close">✕</button>
</div>
<input id="calcDisplay" class="calc-display" inputmode="decimal" autocomplete="off"
placeholder="Type: (3+2)*5 or sin(0.5)" />
<div class="calc-grid" aria-label="Calculator buttons">
<!-- Row 1 -->
<button class="cb" type="button" data-a="7">7</button>
<button class="cb" type="button" data-a="8">8</button>
<button class="cb" type="button" data-a="9">9</button>
<button class="cb op" type="button" data-a="/">÷</button>
<button class="cb fn" type="button" data-a="(">(</button>
<button class="cb fn" type="button" data-a=")">)</button>
<!-- Row 2 -->
<button class="cb" type="button" data-a="4">4</button>
<button class="cb" type="button" data-a="5">5</button>
<button class="cb" type="button" data-a="6">6</button>
<button class="cb op" type="button" data-a="*">×</button>
<button class="cb fn" type="button" data-a="^">^</button>
<button class="cb fn" type="button" data-a="pi">Ï€</button>
<!-- Row 3 -->
<button class="cb" type="button" data-a="1">1</button>
<button class="cb" type="button" data-a="2">2</button>
<button class="cb" type="button" data-a="3">3</button>
<button class="cb op" type="button" data-a="-">−</button>
<button class="cb fn" type="button" data-a="sqrt(">√</button>
<button class="cb fn" type="button" data-a="abs(">abs</button>
<!-- Row 4 -->
<button class="cb" type="button" data-a="0">0</button>
<button class="cb" type="button" data-a=".">.</button>
<button class="cb fn" type="button" data-a="%">%</button>
<button class="cb op" type="button" data-a="+">+</button>
<button class="cb danger" type="button" data-a="back">⌫</button>
<button class="cb danger" type="button" data-a="clear">C</button>
<!-- Row 5 -->
<button class="cb fn" type="button" data-a="sin(">sin</button>
<button class="cb fn" type="button" data-a="cos(">cos</button>
<button class="cb fn" type="button" data-a="tan(">tan</button>
<button class="cb fn" type="button" data-a="ln(">ln</button>
<button class="cb fn" type="button" data-a="log10(">log</button>
<button class="cb eq" type="button" data-a="=">=</button>
</div>
<div class="calc-footer">
<button id="calcCopy" type="button" class="calc-copy">Copy result</button>
<span id="calcMsg" class="calc-msg" aria-live="polite"></span>
</div>
</div>
</div>
`;
document.body.appendChild(wrapper);
// --- Wire up behavior ---
const fab = document.getElementById("calcFab");
const overlay = document.getElementById("calcOverlay");
const closeBtn = document.getElementById("calcClose");
const display = document.getElementById("calcDisplay");
const copyBtn = document.getElementById("calcCopy");
const msg = document.getElementById("calcMsg");
const showMsg = (t) => { msg.textContent = t; setTimeout(() => (msg.textContent = ""), 1400); };
const setOpen = (open) => {
overlay.classList.toggle("is-open", !!open);
if (open) setTimeout(() => display.focus(), 0);
};
const toggleOpen = () => setOpen(!overlay.classList.contains("is-open"));
// Safe-ish evaluator (allowlist + token mapping)
function evaluateExpr(input){
let s = (input || "").trim();
if (!s) return "";
// Normalize symbols
s = s.replaceAll("×","*").replaceAll("÷","/").replaceAll("−","-");
// Allowlist characters (no quotes, no brackets, no semicolons, etc.)
if (!/^[0-9+\-*/().,^% \tA-Za-z]+$/.test(s)) {
throw new Error("Invalid characters");
}
// Map tokens
s = s
.replace(/\bpi\b/gi, "Math.PI")
.replace(/\be\b/g, "Math.E")
.replace(/\^/g, "**")
.replace(/\babs\s*\(/gi, "Math.abs(")
.replace(/\bsqrt\s*\(/gi, "Math.sqrt(")
.replace(/\bsin\s*\(/gi, "Math.sin(")
.replace(/\bcos\s*\(/gi, "Math.cos(")
.replace(/\btan\s*\(/gi, "Math.tan(")
.replace(/\bln\s*\(/gi, "Math.log(")
.replace(/\blog10\s*\(/gi, "Math.log10(");
// percent: 50% -> (50/100)
s = s.replace(/(\d+(?:\.\d+)?)\s*%/g, "($1/100)");
const f = new Function(`"use strict"; return (${s});`);
const result = f();
if (typeof result !== "number" || !isFinite(result)) throw new Error("Bad result");
return result;
}
function doEquals(){
try{
const r = evaluateExpr(display.value);
if (r === "") return;
display.value = String(r);
showMsg("OK");
} catch (e){
showMsg("Error");
}
}
// Events
fab.addEventListener("click", toggleOpen);
closeBtn.addEventListener("click", () => setOpen(false));
// Persistent: NO outside-click close
document.addEventListener("keydown", (e) => {
if (!overlay.classList.contains("is-open")) return;
if (e.key === "Escape") setOpen(false);
if (e.key === "Enter") { e.preventDefault(); doEquals(); }
});
document.querySelectorAll(".cb").forEach(btn => {
btn.addEventListener("click", () => {
const a = btn.getAttribute("data-a");
if (a === "clear") { display.value = ""; display.focus(); return; }
if (a === "back") { display.value = display.value.slice(0, -1); display.focus(); return; }
if (a === "=") { doEquals(); return; }
if (a === "pi") { display.value += "pi"; display.focus(); return; }
display.value += a;
display.focus();
});
});
copyBtn.addEventListener("click", async () => {
try{
await navigator.clipboard.writeText(display.value || "");
showMsg("Copied");
} catch(e){
showMsg("Copy failed");
}
});
// Start closed
setOpen(false);
});