<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Calculadora Científica Cuántica</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&family=Roboto+Mono:wght@300;500&display=swap');
:root {
--primary-glow: #00f3ff;
--secondary-glow: #bc13fe;
--bg-color: #050510;
--glass-bg: rgba(20, 20, 35, 0.7);
--glass-border: rgba(255, 255, 255, 0.1);
--text-main: #e0e0e0;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
user-select: none; /* Evita selección de texto al tocar rápido */
}
body {
background-color: var(--bg-color);
color: var(--text-main);
font-family: 'Roboto Mono', monospace;
overflow: hidden; /* Para el canvas de fondo */
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
/* Fondo Animado (Canvas) */
#bg-canvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 0;
}
/* Contenedor Principal */
.calculator-container {
position: relative;
z-index: 10;
width: 100%;
max-width: 400px;
background: var(--glass-bg);
backdrop-filter: blur(15px);
-webkit-backdrop-filter: blur(15px);
border: 1px solid var(--glass-border);
border-radius: 20px;
box-shadow: 0 0 30px rgba(0, 243, 255, 0.1), inset 0 0 20px rgba(0, 0, 0, 0.5);
padding: 20px;
animation: slideUp 0.8s ease-out;
}
@keyframes slideUp {
from { transform: translateY(50px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
/* Pantalla */
.display-screen {
background: rgba(0, 0, 0, 0.6);
border: 1px solid var(--primary-glow);
border-radius: 10px;
padding: 15px;
margin-bottom: 20px;
text-align: right;
box-shadow: inset 0 0 10px rgba(0, 243, 255, 0.2);
position: relative;
overflow: hidden;
}
.history {
font-size: 0.8rem;
color: #888;
min-height: 1.2rem;
margin-bottom: 5px;
font-family: 'Orbitron', sans-serif;
}
.current-input {
font-size: 2rem;
font-weight: 500;
color: var(--primary-glow);
word-wrap: break-word;
text-shadow: 0 0 5px var(--primary-glow);
font-family: 'Orbitron', sans-serif;
min-height: 2.4rem;
}
/* Grid de Botones */
.buttons-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 12px;
}
button {
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
color: #fff;
padding: 15px 0;
font-size: 1.1rem;
border-radius: 12px;
cursor: pointer;
transition: all 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275);
font-family: 'Roboto Mono', monospace;
position: relative;
overflow: hidden;
}
button:active {
transform: scale(0.95);
}
button::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 0;
height: 0;
background: rgba(255, 255, 255, 0.2);
border-radius: 50%;
transform: translate(-50%, -50%);
transition: width 0.3s ease, height 0.3s ease;
}
button:active::after {
width: 100px;
height: 100px;
}
/* Estilos específicos de botones */
.btn-op {
color: var(--primary-glow);
font-weight: bold;
}
.btn-sci {
font-size: 0.9rem;
color: var(--secondary-glow);
}
.btn-eq {
background: linear-gradient(135deg, rgba(0, 243, 255, 0.2), rgba(188, 19, 254, 0.2));
border-color: var(--primary-glow);
grid-column: span 2;
color: #fff;
text-shadow: 0 0 5px #fff;
}
.btn-clear {
color: #ff4d4d;
border-color: rgba(255, 77, 77, 0.3);
}
.btn-del {
color: #ff9e4d;
}
/* Indicador de modo (DEG/RAD) */
.mode-indicator {
position: absolute;
top: 5px;
left: 10px;
font-size: 0.6rem;
color: var(--secondary-glow);
cursor: pointer;
border: 1px solid var(--secondary-glow);
padding: 2px 5px;
border-radius: 4px;
}
/* Efecto de Scanline en pantalla */
.display-screen::before {
content: " ";
display: block;
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
background: linear-gradient(rgba(18, 16, 16, 0) 50%, rgba(0, 0, 0, 0.25) 50%), linear-gradient(90deg, rgba(255, 0, 0, 0.06), rgba(0, 255, 0, 0.02), rgba(0, 0, 255, 0.06));
z-index: 2;
background-size: 100% 2px, 3px 100%;
pointer-events: none;
}
/* Responsivo */
@media (max-width: 400px) {
.calculator-container {
width: 95%;
padding: 15px;
}
button {
padding: 12px 0;
font-size: 1rem;
}
}
</style>
</head>
<body>
<canvas id="bg-canvas"></canvas>
<div class="calculator-container">
<div class="display-screen">
<div id="mode-toggle" class="mode-indicator" onclick="toggleMode()">RAD</div>
<div class="history" id="history"></div>
<div class="current-input" id="display">0</div>
</div>
<div class="buttons-grid">
<!-- Fila 1 -->
<button class="btn-sci" onclick="handleSci('sin')">sin</button>
<button class="btn-sci" onclick="handleSci('cos')">cos</button>
<button class="btn-sci" onclick="handleSci('tan')">tan</button>
<button class="btn-clear" onclick="clearDisplay()">AC</button>
<!-- Fila 2 -->
<button class="btn-sci" onclick="handleSci('log')">log</button>
<button class="btn-sci" onclick="handleSci('ln')">ln</button>
<button class="btn-sci" onclick="insert('(')">(</button>
<button class="btn-sci" onclick="insert(')')">)</button>
<!-- Fila 3 -->
<button class="btn-sci" onclick="handleSci('pow')">x^y</button>
<button class="btn-sci" onclick="handleSci('sqrt')">√</button>
<button class="btn-sci" onclick="insert('Math.PI')">π</button>
<button class="btn-op" onclick="insert('/')">÷</button>
<!-- Fila 4 -->
<button onclick="insert('7')">7</button>
<button onclick="insert('8')">8</button>
<button onclick="insert('9')">9</button>
<button class="btn-op" onclick="insert('*')">×</button>
<!-- Fila 5 -->
<button onclick="insert('4')">4</button>
<button onclick="insert('5')">5</button>
<button onclick="insert('6')">6</button>
<button class="btn-op" onclick="insert('-')">-</button>
<!-- Fila 6 -->
<button onclick="insert('1')">1</button>
<button onclick="insert('2')">2</button>
<button onclick="insert('3')">3</button>
<button class="btn-op" onclick="insert('+')">+</button>
<!-- Fila 7 -->
<button onclick="insert('0')">0</button>
<button onclick="insert('.')">.</button>
<button class="btn-del" onclick="backspace()">⌫</button>
<button class="btn-eq" onclick="calculate()">CALCULAR</button>
</div>
</div>
<script>
/* ----------------------------------------------------
1. SISTEMA DE AUDIO (Web Audio API)
Genera sonidos de ciencia ficción sin archivos mp3
---------------------------------------------------- */
const AudioContext = window.AudioContext || window.webkitAudioContext;
const audioCtx = new AudioContext();
function playSound(type) {
if (audioCtx.state === 'suspended') {
audioCtx.resume();
}
const oscillator = audioCtx.createOscillator();
const gainNode = audioCtx.createGain();
oscillator.connect(gainNode);
gainNode.connect(audioCtx.destination);
const now = audioCtx.currentTime;
if (type === 'number') {
// Tono corto y agudo (High-tech blip)
oscillator.type = 'sine';
oscillator.frequency.setValueAtTime(800, now);
oscillator.frequency.exponentialRampToValueAtTime(300, now + 0.1);
gainNode.gain.setValueAtTime(0.1, now);
gainNode.gain.exponentialRampToValueAtTime(0.01, now + 0.1);
oscillator.start(now);
oscillator.stop(now + 0.1);
}
else if (type === 'operator') {
// Tono metálico suave
oscillator.type = 'square';
oscillator.frequency.setValueAtTime(200, now);
gainNode.gain.setValueAtTime(0.05, now);
gainNode.gain.linearRampToValueAtTime(0, now + 0.15);
oscillator.start(now);
oscillator.stop(now + 0.15);
}
else if (type === 'clear') {
// Sonido de borrado (barrido hacia abajo)
oscillator.type = 'sawtooth';
oscillator.frequency.setValueAtTime(400, now);
oscillator.frequency.exponentialRampToValueAtTime(50, now + 0.3);
gainNode.gain.setValueAtTime(0.1, now);
gainNode.gain.linearRampToValueAtTime(0, now + 0.3);
oscillator.start(now);
oscillator.stop(now + 0.3);
}
else if (type === 'calc') {
// Sonido de éxito/computación
oscillator.type = 'triangle';
oscillator.frequency.setValueAtTime(440, now);
oscillator.frequency.setValueAtTime(880, now + 0.1);
gainNode.gain.setValueAtTime(0.1, now);
gainNode.gain.linearRampToValueAtTime(0, now + 0.4);
oscillator.start(now);
oscillator.stop(now + 0.4);
}
else if (type === 'error') {
// Sonido de error
oscillator.type = 'sawtooth';
oscillator.frequency.setValueAtTime(150, now);
gainNode.gain.setValueAtTime(0.2, now);
gainNode.gain.linearRampToValueAtTime(0, now + 0.3);
oscillator.start(now);
oscillator.stop(now + 0.3);
}
}
/* ----------------------------------------------------
2. LÓGICA DE LA CALCULADORA
---------------------------------------------------- */
let displayValue = '0';
let isResult = false;
let angleMode = 'RAD'; // RAD o DEG
const display = document.getElementById('display');
const historyDisplay = document.getElementById('history');
const modeBtn = document.getElementById('mode-toggle');
function updateDisplay() {
display.innerText = displayValue;
}
function toggleMode() {
angleMode = angleMode === 'RAD' ? 'DEG' : 'RAD';
modeBtn.innerText = angleMode;
playSound('operator');
}
function insert(val) {
playSound(isNaN(val) ? 'operator' : 'number');
if (isResult) {
// Si acabamos de calcular, y presionan un número, reseteamos.
// Si es un operador, continuamos con el resultado anterior.
if (!isNaN(val) || val === 'Math.PI') {
displayValue = '';
}
isResult = false;
}
if (displayValue === '0' && val !== '.') {
displayValue = '';
}
displayValue += val;
updateDisplay();
}
function clearDisplay() {
playSound('clear');
displayValue = '0';
historyDisplay.innerText = '';
isResult = false;
updateDisplay();
}
function backspace() {
playSound('operator');
if (isResult) {
clearDisplay();
return;
}
if (displayValue.length > 1) {
displayValue = displayValue.slice(0, -1);
} else {
displayValue = '0';
}
updateDisplay();
}
function handleSci(func) {
playSound('operator');
if (isResult) {
isResult = false;
displayValue = '';
}
if (displayValue === '0') displayValue = '';
switch(func) {
case 'sin': displayValue += 'sin('; break;
case 'cos': displayValue += 'cos('; break;
case 'tan': displayValue += 'tan('; break;
case 'log': displayValue += 'log10('; break;
case 'ln': displayValue += 'log('; break; // Math.log es ln en JS
case 'sqrt': displayValue += 'sqrt('; break;
case 'pow': displayValue += '^'; break;
}
updateDisplay();
}
function calculate() {
try {
let expression = displayValue;
historyDisplay.innerText = expression + ' =';
// Reemplazos para sintaxis de JavaScript
// 1. Manejo de potencias (^)
expression = expression.replace(/\^/g, '**');
// 2. Funciones trigonométricas con modo DEG/RAD
// Creamos una función wrapper temporal para inyectar conversión
const toRad = (angleMode === 'DEG') ? '* (Math.PI/180)' : '';
// Usamos regex para encontrar funciones trigonométricas y aplicar conversión si es DEG
// Nota: Esta es una implementación simplificada.
// Para una robustez total se necesitaría un parser real.
// Aquí, asumimos Math.sin(x) directo.
// Si es DEG, el usuario visualmente ve sin(90), pero JS ejecuta Math.sin(90 * PI/180)
// Vamos a inyectar las funciones matemáticas globales al eval
// Pero primero limpiamos tokens visuales
expression = expression.replace(/sin\(/g, `Math.sin(`);
expression = expression.replace(/cos\(/g, `Math.cos(`);
expression = expression.replace(/tan\(/g, `Math.tan(`);
expression = expression.replace(/log10\(/g, `Math.log10(`);
expression = expression.replace(/log\(/g, `Math.log(`); // ln
expression = expression.replace(/sqrt\(/g, `Math.sqrt(`);
// Ajuste muy básico para Grados: Si estamos en DEG, intentamos envolver argumentos numéricos
// NOTA: Modificar argumentos dentro de strings con regex es complejo.
// Para este demo, si está en DEG, aplicamos factor de conversión al resultado final si es trigonométrica pura es difícil.
// Solución: Advertencia visual o conversión manual es mejor para este alcance.
// Pero intentaremos un truco: Redefinir las funciones Math temporalmente si es necesario.
let result;
if (angleMode === 'DEG') {
// Sobreescribimos temporalmente para el eval
const originalSin = Math.sin;
const originalCos = Math.cos;
const originalTan = Math.tan;
Math.sin = (x) => originalSin(x * (Math.PI / 180));
Math.cos = (x) => originalCos(x * (Math.PI / 180));
Math.tan = (x) => originalTan(x * (Math.PI / 180));
result = eval(expression);
// Restaurar
Math.sin = originalSin;
Math.cos = originalCos;
Math.tan = originalTan;
} else {
result = eval(expression);
}
// Formateo del resultado
if (!isFinite(result) || isNaN(result)) {
throw new Error("Error Matemático");
}
// Redondear decimales excesivos
result = parseFloat(result.toFixed(8)).toString();
displayValue = result;
playSound('calc');
isResult = true;
} catch (e) {
displayValue = "Error";
playSound('error');
isResult = true;
setTimeout(() => {
if(displayValue === "Error") displayValue = "0";
updateDisplay();
}, 1500);
}
updateDisplay();
}
/* ----------------------------------------------------
3. FONDO ANIMADO (Canvas)
Red de partículas estilo "Ciencia/Neurológico"
---------------------------------------------------- */
const canvas = document.getElementById('bg-canvas');
const ctx = canvas.getContext('2d');
let width, height;
let particles = [];
function resize() {
width = canvas.width = window.innerWidth;
height = canvas.height = window.innerHeight;
}
class Particle {
constructor() {
this.x = Math.random() * width;
this.y = Math.random() * height;
this.vx = (Math.random() - 0.5) * 0.5;
this.vy = (Math.random() - 0.5) * 0.5;
this.size = Math.random() * 2 + 1;
this.color = Math.random() > 0.5 ? '#00f3ff' : '#bc13fe'; // Cian o Violeta
}
update() {
this.x += this.vx;
this.y += this.vy;
// Rebotar en bordes
if (this.x < 0 || this.x > width) this.vx *= -1;
if (this.y < 0 || this.y > height) this.vy *= -1;
}
draw() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fillStyle = this.color;
ctx.fill();
}
}
function initParticles() {
particles = [];
const particleCount = Math.floor(window.innerWidth / 10); // Densidad adaptable
for (let i = 0; i < particleCount; i++) {
particles.push(new Particle());
}
}
function animate() {
ctx.clearRect(0, 0, width, height);
// Dibujar líneas de conexión
for (let i = 0; i < particles.length; i++) {
particles[i].update();
particles[i].draw();
for (let j = i; j < particles.length; j++) {
const dx = particles[i].x - particles[j].x;
const dy = particles[i].y - particles[j].y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 100) {
ctx.beginPath();
ctx.strokeStyle = `rgba(100, 100, 255, ${1 - distance / 100})`;
ctx.lineWidth = 0.5;
ctx.moveTo(particles[i].x, particles[i].y);
ctx.lineTo(particles[j].x, particles[j].y);
ctx.stroke();
}
}
}
requestAnimationFrame(animate);
}
window.addEventListener('resize', () => {
resize();
initParticles();
});
// Inicialización
resize();
initParticles();
animate();
</script>
</body>
</html>