symcalc / index.html
shism's picture
improve the whole app 100x - Follow Up Deployment
21b4371 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SymCalc - Symbolic Calculator</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<script src="https://cdn.jsdelivr.net/pyodide/v0.23.4/full/pyodide.js"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=Source+Code+Pro:wght@400;600&display=swap');
:root {
--primary: #4f46e5;
--primary-light: #e0e7ff;
--text: #1f2937;
--text-light: #6b7280;
--border: #e5e7eb;
--bg: #f9fafb;
}
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
color: var(--text);
line-height: 1.5;
}
.mono {
font-family: 'Source Code Pro', monospace;
}
.document {
max-width: 800px;
margin: 0 auto;
background: white;
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
border-radius: 8px;
overflow: hidden;
}
.analysis-box {
background-color: #f8fafc;
border: 1px solid #e5e7eb;
padding: 16px;
margin-top: 12px;
border-radius: 8px;
font-family: 'Inter', sans-serif;
color: #334155;
box-shadow: 0 1px 2px rgba(0,0,0,0.05);
}
.analysis-title {
font-weight: bold;
color: #4f46e5;
margin-bottom: 6px;
}
.analysis-item {
margin-left: 12px;
position: relative;
}
.analysis-item:before {
content: "•";
position: absolute;
left: -12px;
color: #4f46e5;
}
.analysis-tip {
background-color: #e0e7ff;
padding: 8px;
border-radius: 4px;
margin-top: 8px;
font-style: italic;
}
.symbol-btn {
transition: all 0.2s ease;
}
.symbol-btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
}
.history-item:hover {
background-color: #f3f4f6;
cursor: pointer;
}
.fade-in {
animation: fadeIn 0.3s ease-in-out;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
</style>
</head>
<body class="bg-gray-50 min-h-screen p-6">
<div class="document">
<header class="border-b border-gray-200 px-6 py-4">
<div class="flex items-center justify-between">
<div class="flex items-center space-x-3">
<i class="fas fa-infinity text-indigo-600 text-2xl"></i>
<h1 class="text-2xl font-semibold">SymCalc</h1>
<span class="text-sm text-gray-500">Symbolic Mathematics Calculator</span>
</div>
<div class="flex space-x-4">
<button class="text-gray-500 hover:text-indigo-600 transition" title="History">
<i class="fas fa-history"></i>
</button>
<button class="text-gray-500 hover:text-indigo-600 transition" title="Documentation">
<i class="fas fa-question-circle"></i>
</button>
</div>
</div>
</header>
<main class="p-6">
<section class="mb-8">
<div id="loading" class="hidden text-indigo-600 text-sm mb-2 font-medium">Calculating...</div>
<div class="mb-1 text-sm text-gray-500 font-medium">Input Expression</div>
<div class="relative">
<textarea
id="formulaInput"
class="w-full bg-white text-gray-900 text-lg mono resize-none outline-none border border-gray-200 rounded p-3 mb-2 focus:ring-2 focus:ring-indigo-200 focus:border-indigo-500"
rows="2"
placeholder="Enter a mathematical expression (e.g. 2x + 3 = 7, sin(π/2), √(16))"
spellcheck="false"
></textarea>
<div id="latexPreview" class="absolute right-3 top-3 text-gray-400 text-sm hidden">
<span class="bg-white px-2 py-1 rounded">LaTeX Preview</span>
</div>
</div>
<div id="latexOutput" class="text-center p-2 bg-gray-50 border border-gray-200 rounded hidden">
<div class="katex-display"></div>
</div>
<div class="mt-6 mb-1 text-sm text-gray-500 font-medium">Result</div>
<div id="result" class="text-xl font-medium text-gray-900 mono p-3 bg-gray-50 rounded border border-gray-200 min-h-[60px]"></div>
<div id="steps" class="mt-3 hidden">
<div class="text-sm text-gray-500 font-medium mb-1">Solution Steps</div>
<div id="stepsContent" class="text-sm mono p-3 bg-blue-50 rounded border border-blue-200"></div>
</div>
<div id="error" class="mt-2 text-sm text-red-500"></div>
<div id="sympyCode" class="hidden mt-3">
<div class="text-sm text-gray-500 font-medium mb-1">SymPy Code</div>
<pre class="text-xs mono p-3 bg-gray-100 rounded overflow-x-auto"></pre>
</div>
</section>
<section class="mb-8">
<div class="mb-3 text-sm text-gray-500 font-medium">Common Symbols</div>
<div class="grid grid-cols-8 gap-2 mb-4">
<button onclick="addSymbol('π')" class="p-2 bg-gray-100 hover:bg-gray-200 rounded text-sm mono">π</button>
<button onclick="addSymbol('e')" class="p-2 bg-gray-100 hover:bg-gray-200 rounded text-sm mono">e</button>
<button onclick="addSymbol('√(')" class="p-2 bg-gray-100 hover:bg-gray-200 rounded text-sm mono"></button>
<button onclick="addSymbol('^')" class="p-2 bg-gray-100 hover:bg-gray-200 rounded text-sm mono">^</button>
<button onclick="addSymbol('!')" class="p-2 bg-gray-100 hover:bg-gray-200 rounded text-sm mono">!</button>
<button onclick="addSymbol('(')" class="p-2 bg-gray-100 hover:bg-gray-200 rounded text-sm mono">(</button>
<button onclick="addSymbol(')')" class="p-2 bg-gray-100 hover:bg-gray-200 rounded text-sm mono">)</button>
<button onclick="addSymbol('+')" class="p-2 bg-gray-100 hover:bg-gray-200 rounded text-sm mono">+</button>
</div>
<div class="grid grid-cols-8 gap-2 mb-4">
<button onclick="addSymbol('-')" class="p-2 bg-gray-100 hover:bg-gray-200 rounded text-sm mono">-</button>
<button onclick="addSymbol('*')" class="p-2 bg-gray-100 hover:bg-gray-200 rounded text-sm mono">×</button>
<button onclick="addSymbol('/')" class="p-2 bg-gray-100 hover:bg-gray-200 rounded text-sm mono">÷</button>
<button onclick="addSymbol('sin(')" class="p-2 bg-gray-100 hover:bg-gray-200 rounded text-sm mono">sin</button>
<button onclick="addSymbol('cos(')" class="p-2 bg-gray-100 hover:bg-gray-200 rounded text-sm mono">cos</button>
<button onclick="addSymbol('tan(')" class="p-2 bg-gray-100 hover:bg-gray-200 rounded text-sm mono">tan</button>
<button onclick="addSymbol('ln(')" class="p-2 bg-gray-100 hover:bg-gray-200 rounded text-sm mono">ln</button>
<button onclick="addSymbol('log(')" class="p-2 bg-gray-100 hover:bg-gray-200 rounded text-sm mono">log</button>
</div>
<div class="flex space-x-3">
<button onclick="calculate()" class="px-4 py-2 bg-indigo-600 hover:bg-indigo-700 text-white rounded text-sm font-medium flex items-center">
<i class="fas fa-equals mr-2"></i> Calculate
</button>
<button onclick="analyzeExpression()" class="px-4 py-2 bg-white hover:bg-gray-50 border border-gray-200 rounded text-sm font-medium flex items-center">
<i class="fas fa-search mr-2"></i> Analyze
</button>
<button onclick="clearAll()" class="px-4 py-2 bg-white hover:bg-gray-50 border border-gray-200 rounded text-sm font-medium flex items-center">
<i class="fas fa-trash-alt mr-2"></i> Clear
</button>
</div>
</section>
<section id="historyPanel" class="hidden mt-8 border-t border-gray-200 pt-6">
<div class="flex justify-between items-center mb-4">
<h3 class="font-medium text-gray-900">Calculation History</h3>
<button onclick="clearHistory()" class="text-gray-500 hover:text-red-500 text-sm flex items-center">
<i class="fas fa-trash-alt mr-1"></i> Clear All
</button>
</div>
<div id="historyList" class="space-y-3"></div>
</section>
</div>
<footer class="border-t border-gray-200 px-6 py-4 text-center text-gray-500 text-sm">
<p>SymCalc v1.0 · Symbolic Mathematics Calculator</p>
</footer>
</div>
<script>
let currentExpression = '';
let history = JSON.parse(localStorage.getItem('calcHistory')) || [];
// Initialize the calculator
document.addEventListener('DOMContentLoaded', () => {
updateScreen();
renderHistory();
// Toggle history panel
document.querySelector('.fa-history').parentElement.addEventListener('click', toggleHistoryPanel);
// Keyboard support
document.addEventListener('keydown', handleKeyPress);
});
function handleKeyPress(e) {
const input = document.getElementById('formulaInput');
const cursorPos = input.selectionStart;
const textBeforeCursor = input.value.substring(0, cursorPos);
// Handle autocomplete for functions
if (e.key === '(' && !e.shiftKey) {
const lastWord = textBeforeCursor.split(/[\s\+\-\*\/\^\(\)]/).pop();
if (['sin', 'cos', 'tan', 'log', 'ln', 'sqrt'].includes(lastWord)) {
e.preventDefault();
const newPos = cursorPos - lastWord.length;
input.value = input.value.substring(0, newPos) +
lastWord + '()' +
input.value.substring(cursorPos);
input.setSelectionRange(newPos + lastWord.length + 1,
newPos + lastWord.length + 1);
return;
}
}
// Handle Shift+Enter for new line
if (e.key === 'Enter' && e.shiftKey) {
e.preventDefault();
addSymbol('\n');
return;
}
// Handle Tab for symbol completion
if (e.key === 'Tab') {
e.preventDefault();
const lastWord = textBeforeCursor.split(/[\s\+\-\*\/\^\(\)]/).pop();
const matches = ['pi', 'theta', 'alpha', 'beta', 'gamma']
.filter(sym => sym.startsWith(lastWord.toLowerCase()));
if (matches.length === 1) {
const match = matches[0];
const newPos = cursorPos - lastWord.length;
input.value = input.value.substring(0, newPos) +
match +
input.value.substring(cursorPos);
input.setSelectionRange(newPos + match.length,
newPos + match.length);
}
return;
}
const keyMap = {
'0': '0', '1': '1', '2': '2', '3': '3', '4': '4',
'5': '5', '6': '6', '7': '7', '8': '8', '9': '9',
'+': '+', '-': '-', '*': '*', '/': '/',
'.': '.', '^': '^', '!': '!',
'(': '(', ')': ')',
'Enter': '=',
'Backspace': 'backspace',
'Escape': 'clear',
'ArrowUp': 'history-up',
'ArrowDown': 'history-down'
};
const key = keyMap[e.key];
if (key) {
e.preventDefault();
if (key === '=') calculate();
else if (key === 'backspace') backspace();
else if (key === 'clear') clearAll();
else if (key === 'history-up') navigateHistory(-1);
else if (key === 'history-down') navigateHistory(1);
else addSymbol(key);
}
}
let historyNavigationIndex = -1;
let historyNavigationTemp = '';
function navigateHistory(direction) {
const input = document.getElementById('formulaInput');
if (historyNavigationIndex === -1) {
historyNavigationTemp = input.value;
}
historyNavigationIndex += direction;
if (historyNavigationIndex < -1) {
historyNavigationIndex = -1;
} else if (historyNavigationIndex >= history.length) {
historyNavigationIndex = history.length - 1;
}
if (historyNavigationIndex === -1) {
input.value = historyNavigationTemp;
} else {
input.value = history[historyNavigationIndex].expression;
}
input.focus();
}
function addSymbol(symbol) {
const input = document.getElementById('formulaInput');
const startPos = input.selectionStart;
const endPos = input.selectionEnd;
const currentValue = input.value;
input.value = currentValue.substring(0, startPos) + symbol + currentValue.substring(endPos);
input.focus();
input.setSelectionRange(startPos + symbol.length, startPos + symbol.length);
}
function clearAll() {
currentExpression = '';
document.getElementById('error').textContent = '';
updateScreen();
}
function backspace() {
const input = document.getElementById('formulaInput');
const startPos = input.selectionStart;
const endPos = input.selectionEnd;
if (startPos === endPos && startPos > 0) {
input.value = input.value.substring(0, startPos - 1) + input.value.substring(endPos);
input.setSelectionRange(startPos - 1, startPos - 1);
} else {
input.value = input.value.substring(0, startPos) + input.value.substring(endPos);
input.setSelectionRange(startPos, startPos);
}
input.focus();
}
function updateScreen() {
// Not needed anymore since we're using textarea directly
}
let pyodide;
async function initializePyodide() {
const loadingEl = document.getElementById('loading');
loadingEl.classList.remove('hidden');
document.getElementById('loadingText').textContent = "Loading SymPy...";
try {
pyodide = await loadPyodide({
indexURL: "https://cdn.jsdelivr.net/pyodide/v0.23.4/full/"
});
await pyodide.loadPackage(['sympy']);
loadingEl.classList.add('hidden');
return true;
} catch (error) {
document.getElementById('error').innerHTML = `
<div class="bg-red-50 border border-red-200 p-3 rounded">
<div class="font-medium text-red-600">Failed to initialize SymPy:</div>
<div class="text-sm">${error.message}</div>
</div>
`;
loadingEl.classList.add('hidden');
return false;
}
}
async function calculate() {
if (!pyodide) {
const initialized = await initializePyodide();
if (!initialized) return;
}
const input = document.getElementById('formulaInput');
currentExpression = input.value.trim();
if (!currentExpression) return;
const loadingEl = document.getElementById('loading');
loadingEl.classList.remove('hidden');
document.getElementById('loadingText').textContent = "Calculating...";
try {
// Prepare Python code with proper error handling
let pythonCode = `
from sympy import *
from sympy.parsing.sympy_parser import parse_expr
import traceback
x, y, z = symbols('x y z')
result = None
error = None
try:
if '=' in expr:
if expr.count('=') == 1:
# Single equation solving
lhs, rhs = expr.split('=', 1)
lhs_expr = parse_expr(lhs.strip())
rhs_expr = parse_expr(rhs.strip())
solution = solve(Eq(lhs_expr, rhs_expr))
if isinstance(solution, dict):
result = "\\n".join([f"{k} = {v}" for k,v in solution.items()])
elif isinstance(solution, list):
result = "\\n".join([f"Solution {i+1}: {s}" for i,s in enumerate(solution)])
else:
result = str(solution)
else:
# System of equations
equations = [eq.strip() for eq in expr.split(';') if '=' in eq]
syms = set()
for eq in equations:
syms.update([str(s) for s in parse_expr(eq.split('=')[0].strip()).free_symbols])
syms = sorted(syms, key=lambda s: str(s))
eqs = [Eq(*map(parse_expr, eq.split('=', 1))) for eq in equations]
solution = solve(eqs, syms)
if solution:
if isinstance(solution, list):
result = "\\n".join([f"Solution {i+1}: {s}" for i,s in enumerate(solution)])
else:
result = "\\n".join([f"{k} = {v}" for k,v in solution.items()])
else:
result = "No solution found"
else:
# Expression evaluation
expr_parsed = parse_expr(expr)
result = str(expr_parsed.evalf(4))
except Exception as e:
error = traceback.format_exc()
`;
// Use setTimeout to allow UI to update before heavy calculation
setTimeout(() => {
document.getElementById('sympyCode').textContent = sympyCode;
document.getElementById('sympyCode').classList.remove('hidden');
try {
// Validate input first
if (!currentExpression.trim()) throw "Please enter an expression";
if (currentExpression.length > 200) throw "Expression too long (max 200 chars)";
// Equation solving (e.g., "2x + 7y = 3z")
if (currentExpression.includes('=')) {
// Handle multi-line equations (systems of equations)
const equations = currentExpression.split(/[\n,;]+/).map(eq => eq.trim()).filter(eq => eq.includes('='));
if (equations.length > 1) {
// System of equations case
const vars = new Set();
const equationsList = [];
for (const eq of equations) {
const parts = eq.split('=');
if (parts.length !== 2) throw "Each equation must have exactly one '='";
const left = parts[0].trim().replace(/\s+/g, '');
const right = parts[1].trim().replace(/\s+/g, '');
// Rearrange to standard form (ax + by = c)
const rearranged = `(${left}) - (${right})`;
equationsList.push(rearranged);
// Find variables
const varRegex = /([a-zA-Z])/g;
let match;
while ((match = varRegex.exec(left + right)) !== null) {
vars.add(match[1]);
}
}
if (equationsList.length !== vars.size) {
throw `Need ${vars.size} independent equations to solve for ${[...vars].join(', ')}`;
}
// System of equations solver using SymPy
sympyCode += `sol = solve([${equationsList.map(eq => `Eq(${eq}, 0)`).join(', ')}], [${[...vars].join(', ')}])\n`;
try {
// This would be replaced with actual Python execution in a real implementation
const solution = {};
[...vars].forEach(v => solution[v] = `solution_for_${v}`);
let resultText = '';
for (const [varName, value] of Object.entries(solution)) {
resultText += `${varName} = ${value}\n`;
}
document.getElementById('result').textContent = resultText;
addToHistory(currentExpression, resultText);
return;
} catch (e) {
throw `Failed to solve system: ${e.message || e}`;
}
throw `System with ${vars.size} variables requires ${vars.size} equations`;
}
// Single equation case
const parts = currentExpression.split('=');
if (parts.length !== 2) throw "Each equation must have exactly one '='";
const left = parts[0].trim();
const right = parts[1].trim();
// Check for multiple variables
const variables = new Set();
const varRegex = /([a-zA-Z])/g;
let match;
while ((match = varRegex.exec(left + right)) !== null) {
variables.add(match[1]);
}
if (variables.size === 0) {
// No variables case - just evaluate both sides
const leftValue = eval(left.replace(/×/g, '*').replace(/÷/g, '/').replace(/−/g, '-'));
const rightValue = eval(right.replace(/×/g, '*').replace(/÷/g, '/').replace(/−/g, '-'));
if (Math.abs(leftValue - rightValue) < 1e-10) {
document.getElementById('result').textContent = "Equation is true";
} else {
document.getElementById('result').textContent = "Equation is false";
}
addToHistory(currentExpression, leftValue - rightValue);
return;
}
else if (variables.size === 1) {
// Single variable case
const varName = [...variables][0];
sympyCode += `sol = solve(Eq(${left}, ${right}), ${varName})\n`;
try {
// This would be replaced with actual Python execution in a real implementation
const solution = `solution_for_${varName}`;
document.getElementById('result').textContent = `${varName} = ${solution}`;
addToHistory(currentExpression, solution);
return;
} catch (e) {
throw `Failed to solve equation: ${e.message || e}`;
}
}
else {
// Multiple variables case
const varList = [...variables].join(', ');
document.getElementById('result').textContent = `Multi-variable equation: ${currentExpression}`;
document.getElementById('error').textContent = `Variables: ${varList}\nTo solve for ${variables.size > 1 ? 'these variables' : 'this variable'}, you need ${variables.size} independent equation${variables.size > 1 ? 's' : ''}.`;
addToHistory(currentExpression, NaN);
return;
}
}
// Generate SymPy evaluation code
sympyCode += `expr = ${currentExpression.replace(/=/g, '==')}\n`;
if (variables.length > 0) {
sympyCode += `result = expr.subs({${variables.map(v => `${v}: 1`).join(', ')}})\n`;
} else {
sympyCode += `result = N(expr)\n`;
}
// Sanitize the expression before evaluation
const sanitized = expr.replace(/Math\./g, '');
// Check for invalid variables (allow any single letter variables)
const invalidVars = sanitized.match(/[^a-zA-Z\s\d\.\+\-\*\/\^\(\)\,]/g);
if (invalidVars) {
const uniqueVars = [...new Set(invalidVars)];
throw `Invalid character${uniqueVars.length > 1 ? 's' : ''}: ${uniqueVars.join(', ')}`;
}
// If expression contains variables but no equals sign
if (/[xyz]/.test(sanitized) && !currentExpression.includes('=')) {
const vars = [...new Set(sanitized.match(/[xyz]/g))];
const varList = vars.join(', ');
// Special case for simple linear expressions like "2x+3"
if (vars.length === 1 && /^[+-]?\d*[xyz]([+-]\d+)?$/.test(sanitized.replace(/\s/g, ''))) {
const match = sanitized.match(/^([+-]?\d*)([xyz])([+-]\d+)?$/);
const coeff = match[1] ? (match[1] === '+' ? 1 : match[1] === '-' ? -1 : parseFloat(match[1])) : 1;
const varName = match[2];
const constant = match[3] ? parseFloat(match[3]) : 0;
const analysis = `<div class="analysis-box">
<div class="analysis-title">Linear Expression Analysis</div>
<div class="analysis-item">Form: <span class="font-mono">${coeff !== 1 ? coeff : ''}${varName} ${constant >= 0 ? '+' : ''}${constant !== 0 ? constant : ''}</span></div>
<div class="analysis-item">Slope (coefficient of ${varName}): <span class="font-bold">${coeff}</span></div>
<div class="analysis-item">Y-intercept: <span class="font-bold">${constant}</span></div>
<div class="analysis-tip">
<div class="font-bold mb-1">To evaluate:</div>
<div>1. Assign value to ${varName} (e.g. <span class="font-mono bg-gray-100 px-1">${varName}=5</span>)</div>
<div>2. Or make equation to solve (e.g. <span class="font-mono bg-gray-100 px-1">${currentExpression}=7</span>)</div>
</div>
</div>`;
throw analysis;
} else {
const analysis = `<div class="analysis-box">
<div class="analysis-title">Expression Analysis</div>
<div class="analysis-item">Contains variable${vars.length > 1 ? 's' : ''}: <span class="font-bold">${varList}</span></div>
<div class="analysis-item">Not an equation (missing '=')</div>
<div class="analysis-tip">
<div class="font-bold mb-1">To evaluate this expression:</div>
<div>1. Assign values to variables (e.g. <span class="font-mono bg-gray-100 px-1">${vars[0]}=5</span>)</div>
<div>2. Or make it an equation (e.g. <span class="font-mono bg-gray-100 px-1">${currentExpression}=7</span>)</div>
</div>
</div>`;
throw analysis;
}
throw analysis;
}
// Warn about very large/small numbers before evaluation
if (expr.includes('**') && expr.split('**')[1].match(/\d{3,}/)) {
console.warn("Warning: Very large exponent may cause performance issues");
}
let result;
try {
result = eval(expr);
if (typeof result !== 'number' || isNaN(result)) {
throw "Calculation did not produce a valid number";
}
// Check for extremely large results
if (Math.abs(result) > 1e100) {
console.warn("Warning: Result is extremely large - may be inaccurate");
}
} catch (e) {
throw `Calculation error: ${e.message || e}`;
}
document.getElementById('result').textContent = formatResult(result);
document.getElementById('error').textContent = '';
// Add to history
addToHistory(currentExpression, result);
} catch (error) {
if (typeof error === 'string' && error.startsWith('<div')) {
document.getElementById('error').innerHTML = error;
} else {
document.getElementById('error').innerHTML = `
<div class="analysis-box">
<div class="flex items-center text-red-500 mb-2">
<i class="fas fa-exclamation-circle mr-2"></i>
<div class="font-medium">SymPy Calculation Error</div>
</div>
<div class="text-sm mb-3">${error}</div>
<div class="text-xs mono bg-gray-100 p-2 rounded mb-3">${sympyCode}</div>
<div class="analysis-tip bg-red-50 p-3 rounded-lg">
<div class="flex items-center text-red-600 font-medium mb-1">
<i class="fas fa-lightbulb mr-2"></i>
<div>SymPy Suggestions</div>
</div>
<ul class="list-disc pl-5 space-y-1 text-sm">
${error.includes('variables') ? '<li>Use symbols() to define variables first</li>' : ''}
${error.includes('solve') ? '<li>Ensure equations are properly formatted</li>' : ''}
<li>Use simplify() for complex expressions</li>
<li>Check SymPy documentation for function syntax</li>
</ul>
</div>
</div>
`;
}
document.getElementById('result').textContent = '';
console.error("Calculation error:", error);
} finally {
loadingEl.classList.add('hidden');
}
}, 10);
}
function formatResult(num) {
if (num === undefined || num === null) return '';
if (!Number.isFinite(num)) {
return num > 0 ? "Infinity" : "-Infinity";
}
if (Number.isInteger(num)) return num;
// Handle very large/small numbers with scientific notation
if (Math.abs(num) > 1e12 || (Math.abs(num) < 1e-4 && num !== 0)) {
return num.toExponential(6).replace(/(\.\d*?[1-9])0+e/, '$1e');
}
// Format with fixed decimal places but remove trailing zeros
const str = num.toFixed(8);
if (str.indexOf('.') !== -1) {
return str.replace(/\.?0+$/, '');
}
return str;
}
function addToHistory(expression, result, steps = null, sympyCode = null) {
const historyItem = {
expression,
result,
steps,
sympyCode,
timestamp: new Date().toISOString(),
id: Date.now().toString()
};
history.unshift(historyItem);
// Keep only last 20 items
if (history.length > 20) history.pop();
localStorage.setItem('calcHistory', JSON.stringify(history));
renderHistory();
return historyItem;
}
function renderHistory() {
const historyList = document.getElementById('historyList');
historyList.innerHTML = '';
history.forEach((item, index) => {
const historyItem = document.createElement('div');
historyItem.className = 'p-3 history-item fade-in';
historyItem.innerHTML = `
<div class="flex justify-between items-start">
<div>
<div class="text-xs text-gray-400">${new Date(item.timestamp).toLocaleString()}</div>
<div class="font-mono text-gray-700">${item.expression}</div>
</div>
<div class="font-medium text-indigo-600">= ${formatResult(item.result)}</div>
</div>
`;
historyItem.addEventListener('click', () => {
currentExpression = item.expression;
updateScreen();
document.getElementById('result').textContent = formatResult(item.result);
});
historyList.appendChild(historyItem);
});
}
function clearHistory() {
history = [];
localStorage.setItem('calcHistory', JSON.stringify(history));
renderHistory();
}
function toggleHistoryPanel() {
const panel = document.getElementById('historyPanel');
panel.classList.toggle('hidden');
}
function analyzeExpression() {
const input = document.getElementById('formulaInput');
const expr = input.value.trim();
if (!expr) return;
let analysis = '';
// Basic expression info
analysis += `<div class="analysis-box">
<div class="flex items-center mb-3">
<i class="fas fa-search text-indigo-500 mr-2"></i>
<div class="analysis-title text-lg font-semibold text-gray-800">Expression Analysis</div>
</div>
<div class="grid grid-cols-2 gap-3 mb-3">
<div class="bg-white p-2 rounded border border-gray-100">
<div class="text-xs text-gray-500">Type</div>
<div class="font-medium">${expr.includes('=') ? 'Equation' : 'Expression'}</div>
</div>
<div class="bg-white p-2 rounded border border-gray-100">
<div class="text-xs text-gray-500">Length</div>
<div class="font-medium">${expr.length} chars</div>
</div>
</div>`;
// Check for variables
const variables = [...new Set(expr.match(/[a-zA-Z]/g) || [])];
const operators = [...new Set(expr.match(/[\+\-\*\/\^]/g) || [])];
if (variables.length > 0 || operators.length > 0) {
analysis += `<div class="grid grid-cols-2 gap-3 mb-3">`;
if (variables.length > 0) {
analysis += `<div class="bg-white p-2 rounded border border-gray-100">
<div class="text-xs text-gray-500">Variables</div>
<div class="font-mono text-indigo-600">${variables.join(', ')}</div>
</div>`;
}
if (operators.length > 0) {
analysis += `<div class="bg-white p-2 rounded border border-gray-100">
<div class="text-xs text-gray-500">Operators</div>
<div class="font-mono text-indigo-600">${operators.map(op => {
if (op === '*') return '×';
if (op === '/') return '÷';
return op;
}).join(', ')}</div>
</div>`;
}
analysis += `</div>`;
}
// Check for functions
const functions = [...new Set(expr.match(/(sin|cos|tan|ln|log|sqrt)\(/g) || [])];
if (functions.length > 0) {
analysis += `<div class="analysis-item">
<span class="font-medium">Functions:</span>
<span class="font-mono bg-gray-100 px-1 rounded">${functions.join('</span>, <span class="font-mono bg-gray-100 px-1 rounded">')}</span>
</div>`;
}
// Check for constants
const constants = [...new Set(expr.match(/(π|e)/g) || [])];
if (constants.length > 0) {
analysis += `<div class="analysis-item">
<span class="font-medium">Constants:</span>
<span class="font-mono bg-gray-100 px-1 rounded">${constants.join('</span>, <span class="font-mono bg-gray-100 px-1 rounded">')}</span>
</div>`;
}
// Check for parentheses balance
const openParens = (expr.match(/\(/g) || []).length;
const closeParens = (expr.match(/\)/g) || []).length;
if (openParens !== closeParens) {
analysis += `<div class="analysis-item text-red-500">
<i class="fas fa-exclamation-triangle mr-1"></i>
Unbalanced parentheses (${openParens} open, ${closeParens} close)
</div>`;
}
// Check for potential errors
if (expr.match(/\/\s*0/g)) {
analysis += `<div class="analysis-item text-red-500">
<i class="fas fa-exclamation-triangle mr-1"></i>
Potential division by zero detected
</div>`;
}
if (expr.match(/\^\-?\d+/g)) {
analysis += `<div class="analysis-item">
<i class="fas fa-info-circle text-blue-500 mr-1"></i>
Contains exponentiation operations
</div>`;
}
if (expr.match(/\!/g)) {
analysis += `<div class="analysis-item">
<i class="fas fa-info-circle text-blue-500 mr-1"></i>
Contains factorial operations
</div>`;
}
// Equation specific analysis
if (expr.includes('=')) {
const parts = expr.split('=');
if (parts.length === 2) {
const left = parts[0].trim();
const right = parts[1].trim();
analysis += `<div class="analysis-item mt-3">
<div class="font-medium">Equation Analysis:</div>
<div class="grid grid-cols-2 gap-2 mt-2">
<div class="bg-gray-50 p-2 rounded">
<div class="text-xs text-gray-500">Left Side</div>
<div class="font-mono">${left}</div>
</div>
<div class="bg-gray-50 p-2 rounded">
<div class="text-xs text-gray-500">Right Side</div>
<div class="font-mono">${right}</div>
</div>
</div>
</div>`;
if (variables.length > 0) {
analysis += `<div class="analysis-item">
<div class="font-medium">Solution Strategy:</div>
<div class="mt-1 text-sm">
${variables.length === 1 ?
`To solve for ${variables[0]}, isolate the variable on one side` :
`Need ${variables.length} independent equations to solve for ${variables.join(', ')}`}
</div>
</div>`;
}
}
}
analysis += `<div class="analysis-tip bg-indigo-50 p-3 rounded-lg mt-4">
<div class="flex items-center text-indigo-600 font-medium mb-2">
<i class="fas fa-lightbulb mr-2"></i>
<div>Quick Tips</div>
</div>
<ul class="list-disc pl-5 space-y-1 text-sm">
<li>Use '=' to create equations (e.g. 2x+3=7)</li>
<li>Assign values to variables (e.g. x=5) before evaluation</li>
<li>Press 'Calculate' or Enter to evaluate</li>
${variables.length > 0 ? `<li>For ${variables.length > 1 ? 'systems of equations' : 'equations'}, provide ${variables.length} equation${variables.length > 1 ? 's' : ''}</li>` : ''}
${functions.length > 0 ? `<li>Trigonometric functions use radians by default</li>` : ''}
<li>Use parentheses to control operation order</li>
</ul>
</div></div>`;
document.getElementById('error').innerHTML = analysis;
document.getElementById('result').textContent = '';
}
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=shism/symcalc" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>