help@rskworld.in +91 93305 39277
RSK World
  • Home
  • Development
    • Web Development
    • Mobile Apps
    • Software
    • Games
    • Project
  • Technologies
    • Data Science
    • AI Development
    • Cloud Development
    • Blockchain
    • Cyber Security
    • Dev Tools
    • Testing Tools
  • About
  • Contact

Theme Settings

Color Scheme
Display Options
Font Size
100%
Back to Project
RSK World
js-calculator
RSK World
js-calculator
JavaScript Calculator - HTML5 + CSS3 + JavaScript + Voice Control + Scientific Functions + Modern UI + Glassmorphism Design
js-calculator
  • LICENSE1.1 KB
  • README.md1.3 KB
  • RELEASE_NOTES.md587 B
  • index.html6.3 KB
  • script.js23.4 KB
  • style.css6.9 KB
script.js
script.js
Raw Download
Find: Go to:
/*
    Project: JavaScript Calculator
    Author: Molla Samser (RSK World)
    Designer & Tester: Rima Khatun
    Website: https://rskworld.in
    Contact: info@rskworld.com, support@rskworld.com
    Phone: +91 93305 39277
    Location: Nutanhat, Mongolkote, Purba Burdwan, West Bengal, India, 713147
    Year: 2026
    Copyright: © 2026 RSK World. All rights reserved.
*/

class Calculator {
    constructor(previousOperandTextElement, currentOperandTextElement, historyListElement) {
        this.previousOperandTextElement = previousOperandTextElement;
        this.currentOperandTextElement = currentOperandTextElement;
        this.historyListElement = historyListElement;
        this.history = [];
        this.memory = 0;
        this.isMemorySet = false;
        this.soundEnabled = false; // Default off
        this.recognition = null;
        this.initVoice();
        this.clear();
    }

    initVoice() {
        if ('webkitSpeechRecognition' in window) {
            this.recognition = new webkitSpeechRecognition();
            this.recognition.continuous = false;
            this.recognition.lang = 'en-US';
            this.recognition.interimResults = false;

            this.recognition.onresult = (event) => {
                const command = event.results[0][0].transcript;
                console.log('Voice Command:', command);
                this.processVoiceCommand(command);
                document.getElementById('toggle-voice').classList.remove('voice-active');
            };

            this.recognition.onerror = (event) => {
                console.error('Voice Error:', event.error);
                document.getElementById('toggle-voice').classList.remove('voice-active');
            };

            this.recognition.onend = () => {
                document.getElementById('toggle-voice').classList.remove('voice-active');
            }
        }
    }

    processVoiceCommand(command) {
        // Simple parser
        const lowerCmd = command.toLowerCase();

        // Remove 'calculate' or 'what is'
        let cleanCmd = lowerCmd.replace(/calculate|what is/g, '').trim();

        // Map words to symbols
        cleanCmd = cleanCmd.replace(/plus|add/g, '+')
            .replace(/minus|subtract/g, '-')
            .replace(/times|multiply|multiplied by/g, '*')
            .replace(/divide|divided by/g, '÷') // Note: calculator uses ÷
            .replace(/point|dot/g, '.')
            .replace(/equals|equal/g, '=');

        // Handling numbers and operators is complex. 
        // For a basic demo, let's try to pass it to eval if it looks like math, 
        // OR simulate button presses.

        // Let's try to map the string to operations
        // This is a naive implementation for demo
        try {
            // Check if it's a simple expression like "50 + 5"
            // We need to parse this into calculator steps

            // Allow "clear"
            if (cleanCmd.includes('clear')) {
                this.clear();
                this.updateDisplay();
                return;
            }

            // Split by space?
            const parts = cleanCmd.split(' ');
            parts.forEach(part => {
                let num = parseFloat(part);
                if (!isNaN(num)) {
                    // It's a number
                    part.split('').forEach(digit => this.appendNumber(digit));
                } else {
                    // Operator?
                    if (['+', '-', '*', 'x', '/'].includes(part)) {
                        let op = part;
                        if (op === '/') op = '÷';
                        if (op === 'x') op = '×';
                        if (op === '*') op = '×';
                        this.chooseOperation(op);
                    }
                }
            });

            // If the command implies equals (e.g. just a math string), maybe hit equals?
            // Let's rely on user saying "equals" or just inputting.

        } catch (e) {
            console.log("Voice parse error", e);
        }
        this.updateDisplay();
    }

    toggleSound() {
        this.soundEnabled = !this.soundEnabled;
        const btn = document.getElementById('toggle-sound');
        if (this.soundEnabled) {
            btn.classList.add('active-state'); // Add a class to show it's on? Or just icon change
            btn.innerHTML = '<i class="fas fa-volume-up"></i>';
            // Play a test sound
            this.playSound();
        } else {
            btn.classList.remove('active-state');
            btn.innerHTML = '<i class="fas fa-volume-mute"></i>';
        }
    }

    playSound() {
        if (!this.soundEnabled) return;
        // Simple beep using AudioContext
        const AudioContext = window.AudioContext || window.webkitAudioContext;
        if (!AudioContext) return;

        const ctx = new AudioContext();
        const osc = ctx.createOscillator();
        const gain = ctx.createGain();

        osc.connect(gain);
        gain.connect(ctx.destination);

        osc.type = 'sine';
        osc.frequency.value = 800;
        gain.gain.value = 0.1;

        osc.start();
        osc.stop(ctx.currentTime + 0.05);
    }

    startVoice() {
        if (this.recognition) {
            try {
                this.recognition.start();
                document.getElementById('toggle-voice').classList.add('voice-active');
            } catch (e) {
                console.error("Voice start error", e);
            }
        } else {
            alert("Voice control not supported in this browser.");
        }
    }

    clear() {
        this.currentOperand = '0';
        this.previousOperand = '';
        this.operation = undefined;
    }

    // Memory Functions
    memoryClear() {
        this.memory = 0;
        this.isMemorySet = false;
        this.updateMemoryIndicator();
    }

    memoryRecall() {
        this.currentOperand = this.memory;
        this.updateDisplay();
    }

    memoryAdd() {
        const current = parseFloat(this.currentOperand);
        if (isNaN(current)) return;
        this.memory += current;
        this.isMemorySet = true;
        this.updateMemoryIndicator();
        this.currentOperand = '0'; // Optional: clear after add? Standard behavior varies. Let's keep input.
        // Actually standard behavior usually keeps display.
        // But some clear. Let's keep display.
    }

    memorySubtract() {
        const current = parseFloat(this.currentOperand);
        if (isNaN(current)) return;
        this.memory -= current;
        this.isMemorySet = true;
        this.updateMemoryIndicator();
    }

    updateMemoryIndicator() {
        const indicator = document.getElementById('memory-indicator');
        if (this.isMemorySet) {
            indicator.classList.remove('hidden');
        } else {
            indicator.classList.add('hidden');
        }
    }

    delete() {
        if (this.currentOperand === '0') return;
        if (this.currentOperand.length === 1) {
            this.currentOperand = '0';
        } else {
            this.currentOperand = this.currentOperand.toString().slice(0, -1);
        }
    }

    appendNumber(number) {
        if (number === '.' && this.currentOperand.includes('.')) return;
        if (this.currentOperand === '0' && number !== '.') {
            this.currentOperand = number.toString();
        } else {
            this.currentOperand = this.currentOperand.toString() + number.toString();
        }
    }

    chooseOperation(operation) {
        if (this.currentOperand === '') return;
        if (this.previousOperand !== '') {
            this.compute();
        }
        this.operation = operation;
        this.previousOperand = this.currentOperand;
        this.currentOperand = '';
    }

    compute() {
        let computation;
        const prev = parseFloat(this.previousOperand);
        const current = parseFloat(this.currentOperand);
        if (isNaN(prev) || isNaN(current)) return;

        switch (this.operation) {
            case '+':
                computation = prev + current;
                break;
            case '-':
                computation = prev - current;
                break;
            case '×':
            case '*':
                computation = prev * current;
                break;
            case '÷':
            case '/':
                if (current === 0) {
                    alert("Cannot divide by zero");
                    this.clear();
                    return;
                }
                computation = prev / current;
                break;
            case 'powY':
                computation = Math.pow(prev, current);
                break;
            default:
                return;
        }

        this.addToHistory(`${prev} ${this.operation === 'powY' ? '^' : this.operation} ${current} = ${computation}`);
        this.currentOperand = computation;
        this.operation = undefined;
        this.previousOperand = '';
    }

    scientificCompute(type) {
        const current = parseFloat(this.currentOperand);
        if (type !== 'rand' && type !== 'e' && isNaN(current)) return;
        let result;
        let expression;

        switch (type) {
            case 'sqrt':
                result = Math.sqrt(current);
                expression = `√(${current}) = ${result}`;
                break;
            case 'pow2':
                result = Math.pow(current, 2);
                expression = `${current}² = ${result}`;
                break;
            case 'pi':
                result = Math.PI;
                this.currentOperand = result;
                this.updateDisplay();
                return;
            case 'e':
                result = Math.E;
                this.currentOperand = result;
                this.updateDisplay();
                return;
            case 'sin':
                result = Math.sin(current * Math.PI / 180);
                expression = `sin(${current}°) = ${result}`;
                break;
            case 'cos':
                result = Math.cos(current * Math.PI / 180);
                expression = `cos(${current}°) = ${result}`;
                break;
            case 'tan':
                result = Math.tan(current * Math.PI / 180);
                expression = `tan(${current}°) = ${result}`;
                break;
            case 'log':
                result = Math.log10(current);
                expression = `log(${current}) = ${result}`;
                break;
            case 'ln':
                result = Math.log(current);
                expression = `ln(${current}) = ${result}`;
                break;
            case 'powY':
                this.chooseOperation('powY');
                return;
            case 'factorial':
                result = this.factorial(current);
                expression = `${current}! = ${result}`;
                break;
            case 'negate':
                result = current * -1;
                this.currentOperand = result;
                this.updateDisplay();
                return;
            case 'percent':
                result = current / 100;
                this.currentOperand = result;
                this.updateDisplay();
                return;
            case 'reciprocal':
                if (current === 0) {
                    alert("Cannot divide by zero");
                    return;
                }
                result = 1 / current;
                expression = `1/${current} = ${result}`;
                break;
            case 'abs':
                result = Math.abs(current);
                expression = `|${current}| = ${result}`;
                break;
            case 'rand':
                result = Math.random();
                this.currentOperand = result;
                this.updateDisplay();
                return;
            default:
                return;
        }

        this.addToHistory(expression);
        this.currentOperand = result;
        this.updateDisplay();
    }

    factorial(n) {
        if (n < 0) return NaN;
        if (n === 0 || n === 1) return 1;
        let result = 1;
        for (let i = 2; i <= n; i++) result *= i;
        return result;
    }

    addToHistory(item) {
        this.history.unshift(item);
        if (this.history.length > 10) this.history.pop();
        this.renderHistory();
    }

    renderHistory() {
        this.historyListElement.innerHTML = '';
        this.history.forEach(item => {
            const div = document.createElement('div');
            div.classList.add('history-item');
            div.innerText = item;
            div.addEventListener('click', () => {
                const parts = item.split(' = ');
                this.currentOperand = parts[1];
                this.updateDisplay();
            });
            this.historyListElement.appendChild(div);
        });
    }

    clearHistory() {
        this.history = [];
        this.renderHistory();
    }

    getDisplayNumber(number) {
        const stringNumber = number.toString();
        const integerDigits = parseFloat(stringNumber.split('.')[0]);
        const decimalDigits = stringNumber.split('.')[1];
        let integerDisplay;
        if (isNaN(integerDigits)) {
            integerDisplay = '';
        } else {
            integerDisplay = integerDigits.toLocaleString('en', { maximumFractionDigits: 0 });
        }
        if (decimalDigits != null) {
            return `${integerDisplay}.${decimalDigits}`;
        } else {
            return integerDisplay;
        }
    }

    updateDisplay() {
        this.currentOperandTextElement.innerText =
            this.getDisplayNumber(this.currentOperand);
        if (this.operation != null) {
            let opDisplay = this.operation;
            if (opDisplay === 'powY') opDisplay = '^';
            this.previousOperandTextElement.innerText =
                `${this.getDisplayNumber(this.previousOperand)} ${opDisplay}`;
        } else {
            this.previousOperandTextElement.innerText = '';
        }
        this.updateProgrammerDisplay();
    }

    updateProgrammerDisplay() {
        const current = parseFloat(this.currentOperand);
        if (isNaN(current) || !isFinite(current)) {
            document.getElementById('base-hex').innerText = '0';
            document.getElementById('base-dec').innerText = '0';
            document.getElementById('base-oct').innerText = '0';
            document.getElementById('base-bin').innerText = '0';
            return;
        }

        const integerPart = Math.floor(Math.abs(current));

        document.getElementById('base-hex').innerText = integerPart.toString(16).toUpperCase();
        document.getElementById('base-dec').innerText = integerPart.toString(10);
        document.getElementById('base-oct').innerText = integerPart.toString(8);
        document.getElementById('base-bin').innerText = integerPart.toString(2);
    }
}

const numberButtons = document.querySelectorAll('[data-number]');
const operationButtons = document.querySelectorAll('[data-operation]');
const sciButtons = document.querySelectorAll('[data-sci]');
const memoryButtons = document.querySelectorAll('[data-memory]');
const equalsButton = document.querySelector('[data-equals]');
const deleteButton = document.querySelector('[data-delete]');
const allClearButton = document.querySelector('[data-all-clear]');
const previousOperandTextElement = document.getElementById('previous-operand');
const currentOperandTextElement = document.getElementById('current-operand');
const historyListElement = document.getElementById('history-list');
const clearHistoryBtn = document.getElementById('clear-history');

const toggleHistoryBtn = document.getElementById('toggle-history');
const toggleSciBtn = document.getElementById('toggle-scientific');
const toggleThemeBtn = document.getElementById('toggle-theme');
const historyPanel = document.getElementById('history-panel');
const sciGrid = document.getElementById('scientific-grid');

const calculator = new Calculator(previousOperandTextElement, currentOperandTextElement, historyListElement);

// Theme Logic
const currentTheme = localStorage.getItem('theme') || 'dark';
document.documentElement.setAttribute('data-theme', currentTheme);
updateThemeIcon(currentTheme);

toggleThemeBtn.addEventListener('click', () => {
    let theme = document.documentElement.getAttribute('data-theme');
    let newTheme = theme === 'dark' ? 'light' : 'dark';
    document.documentElement.setAttribute('data-theme', newTheme);
    localStorage.setItem('theme', newTheme);
    updateThemeIcon(newTheme);
});

function updateThemeIcon(theme) {
    const icon = toggleThemeBtn.querySelector('i');
    if (theme === 'light') {
        icon.classList.replace('fa-moon', 'fa-sun');
    } else {
        icon.classList.replace('fa-sun', 'fa-moon');
    }
}

numberButtons.forEach(button => {
    button.addEventListener('click', () => {
        calculator.appendNumber(button.innerText);
        calculator.updateDisplay();
    });
});

operationButtons.forEach(button => {
    button.addEventListener('click', () => {
        calculator.chooseOperation(button.innerText);
        calculator.updateDisplay();
    });
});

sciButtons.forEach(button => {
    button.addEventListener('click', () => {
        calculator.scientificCompute(button.dataset.sci);
    });
});

memoryButtons.forEach(button => {
    button.addEventListener('click', () => {
        const action = button.dataset.memory;
        switch (action) {
            case 'MC': calculator.memoryClear(); break;
            case 'MR': calculator.memoryRecall(); break;
            case 'M+': calculator.memoryAdd(); break;
            case 'M-': calculator.memorySubtract(); break;
        }
    });
});

equalsButton.addEventListener('click', button => {
    calculator.compute();
    calculator.updateDisplay();
});

allClearButton.addEventListener('click', button => {
    calculator.clear();
    calculator.updateDisplay();
});

deleteButton.addEventListener('click', button => {
    calculator.delete();
    calculator.updateDisplay();
});

clearHistoryBtn.addEventListener('click', () => {
    calculator.clearHistory();
});

toggleHistoryBtn.addEventListener('click', () => {
    historyPanel.classList.toggle('active');
});

toggleSciBtn.addEventListener('click', () => {
    sciGrid.classList.toggle('hidden');
    document.getElementById('programmer-panel').classList.toggle('hidden');
});

// Ripple Effect & Sound
document.addEventListener('click', function (e) {
    // Sound
    if (e.target.matches('button') || e.target.closest('button')) {
        calculator.playSound();
    }

    // Ripple
    const button = e.target.closest('button');
    if (button) {
        const circle = document.createElement('span');
        const diameter = Math.max(button.clientWidth, button.clientHeight);
        const radius = diameter / 2;

        circle.style.width = circle.style.height = `${diameter}px`;
        circle.style.left = `${e.clientX - button.getBoundingClientRect().left - radius}px`;
        circle.style.top = `${e.clientY - button.getBoundingClientRect().top - radius}px`;
        circle.classList.add('ripple');

        const ripple = button.getElementsByClassName('ripple')[0];
        if (ripple) {
            ripple.remove();
        }

        button.appendChild(circle);
    }
});

// Sound and Voice Toggles
document.getElementById('toggle-sound').addEventListener('click', () => {
    calculator.toggleSound();
});

document.getElementById('toggle-voice').addEventListener('click', () => {
    calculator.startVoice();
});

window.addEventListener('keydown', e => {
    if (e.key >= 0 && e.key <= 9) calculator.appendNumber(e.key);
    if (e.key === '.') calculator.appendNumber(e.key);
    if (e.key === '=' || e.key === 'Enter') {
        e.preventDefault();
        calculator.compute();
    }
    if (e.key === 'Backspace') calculator.delete();
    if (e.key === 'Escape') calculator.clear();
    if (e.key === '+' || e.key === '-' || e.key === '*' || e.key === '/') {
        let op = e.key;
        if (op === '*') op = '×';
        if (op === '/') op = '÷';
        calculator.chooseOperation(op);
    }
    calculator.updateDisplay();
});

// Music Player Logic
class MusicPlayer {
    constructor() {
        this.ctx = null;
        this.isPlaying = false;
        this.loopId = null;
        this.tempo = 60; // BPM
        this.noteIndex = 0;
        this.notes = [
            // Simple Lo-fi chord progression (Cmaj7 - Am7 - Dm7 - G7)
            [60, 64, 67, 71], // Cmaj7
            [57, 60, 64, 69], // Am7
            [62, 65, 69, 72], // Dm7
            [55, 59, 62, 65]  // G7
        ];
    }

    init() {
        const AudioContext = window.AudioContext || window.webkitAudioContext;
        this.ctx = new AudioContext();
    }

    playNote(freq, time, duration, vol = 0.05) {
        if (!this.ctx) return;
        const osc = this.ctx.createOscillator();
        const gain = this.ctx.createGain();

        osc.connect(gain);
        gain.connect(this.ctx.destination);

        osc.type = 'sine'; // Sine for soft lo-fi feel
        osc.frequency.value = freq;

        gain.gain.setValueAtTime(0, time);
        gain.gain.linearRampToValueAtTime(vol, time + 0.1);
        gain.gain.exponentialRampToValueAtTime(0.001, time + duration); // Long release

        osc.start(time);
        osc.stop(time + duration + 1);
    }

    midiToFreq(midi) {
        return 440 * Math.pow(2, (midi - 69) / 12);
    }

    scheduleNextLoop(time) {
        const secondsPerBeat = 60 / this.tempo;
        // Play chord
        const chord = this.notes[this.noteIndex % this.notes.length];

        // Stagger notes slightly for "strum" effect
        chord.forEach((note, i) => {
            this.playNote(this.midiToFreq(note), time + (i * 0.05), 4);
        });

        // Simple Bass
        const root = chord[0] - 24; // 2 octaves down
        this.playNote(this.midiToFreq(root), time, 4, 0.08);

        this.noteIndex++;
    }

    start() {
        if (!this.ctx) this.init();
        if (this.ctx.state === 'suspended') {
            this.ctx.resume();
        }

        if (this.isPlaying) return;
        this.isPlaying = true;

        let nextNoteTime = this.ctx.currentTime;
        const schedule = () => {
            if (!this.isPlaying) return;
            // Schedule ahead
            while (nextNoteTime < this.ctx.currentTime + 0.1) {
                this.scheduleNextLoop(nextNoteTime);
                nextNoteTime += 4; // One chord every 4 seconds (roughly 1 bar at 60bpm)
            }
            this.loopId = requestAnimationFrame(schedule);
        };
        schedule();

        document.getElementById('toggle-music').classList.add('active-state');
    }

    stop() {
        this.isPlaying = false;
        if (this.loopId) cancelAnimationFrame(this.loopId);
        document.getElementById('toggle-music').classList.remove('active-state');
    }

    toggle() {
        if (this.isPlaying) this.stop();
        else this.start();
    }
}

const musicPlayer = new MusicPlayer();
document.getElementById('toggle-music').addEventListener('click', () => {
    musicPlayer.toggle();
});
704 lines•23.4 KB
javascript

About RSK World

Founded by Molla Samser, with Designer & Tester Rima Khatun, RSK World is your one-stop destination for free programming resources, source code, and development tools.

Founder: Molla Samser
Designer & Tester: Rima Khatun

Development

  • Game Development
  • Web Development
  • Mobile Development
  • AI Development
  • Development Tools

Legal

  • Terms & Conditions
  • Privacy Policy
  • Disclaimer

Contact Info

Nutanhat, Mongolkote
Purba Burdwan, West Bengal
India, 713147

+91 93305 39277

hello@rskworld.in
support@rskworld.in

© 2026 RSK World. All rights reserved.

Content used for educational purposes only. View Disclaimer